理解本章就想象着总有一根管子怼来怼去,怼到水桶上…
目录
输入/输出流的分类
java.io
包中定义了多个流类型(类或抽象类)来实现输入/输出功能:可以从不同的角度对其进行分类:
- 按数据流的放行不同可以分为输入流和输出流。
- 按处理数据单位不同可以分为字节流和字符流。
- 按功能不同可以分为字节流和处理流。
J2SDK
所提供的所有流类型位于包java.io
内都分别继承自以下四种抽象流类型。
1 | 字节流 字符流 |
输入/输出流
描述输入/输出流都是站在程序的角度上来说:如
一根管道怼到文件上读数据,叫输入流
一根管道怼到文件上往外写数据,叫输出流
字节/字符流
- 字节流 一个字节一个字节的往外读,
01010011110...
- 字符流 一个字符一个字符往外读。字符为两个字节,
utf16
节点/处理流
- 节点流 可以从一个特定的数据源(节点)读写数据(如:文件、内存)。
即一根管道直接怼到数据源上。
2.处理流 是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
即套在其他管道之上的这些流。
InputStream
继承自InputStream
的流都是用于向程序中输入数据,且数据的单位为字节(8 bit)
FileInputStream
即这根管道怼到文件上往里读 数据。
InputStream
的基本方法:
1 | //读取一个字节并以整数的形式返回(0~255),如果返回-1则已到输入流的末尾。 |
1 | public class TestFileInputStream { |
OutputStream
继承自OutputStream
的流是用于程序中输出数据,且数据的单位为字节(8 bit)
FileOutputStream
即这根管道怼到文件上往外写数据。
OutputStream
的基本方法:
1 | //向输出流中写入一个字节数据,该字节数据为参数b的低8位。 |
良好的编程方式为先写flush()
再写close()
1 | public class TestFileOutputStream { |
Reader
继承自Reader
的流都是用于向程序中输入数据,且数据的单位为字节(16 bit即两个字节)
注意:数据单位与InputStream
的区别。
Reader
的基本方法:
1 | //读取一个字符并以整数的形式返回(0~255),如果返回-1则已到输入流的末尾。 |
1 | public class TestFileReader { |
Writer
继承自Writer
的流是用于程序中输出数据,且数据的单位为字节(16 bit即两个字节)
Writer
的基本方法:
1 | //向输出流中写入一个字符数据,该字节数据为参数b的低16位。 |
1 | public class TestFileWriter { |
节点流类型
1 | 类型 字符流 字节流 |
处理流类型
- 缓冲流
缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率。
带缓冲区(小桶)的。可以显著的减少对于IO
的读写的次数,保护硬盘。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class TestBufferStream1 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream(
"E:\\EclipseWorkspace\\mashibingProject\\src\\IO\\TestFileReader.java");
BufferedInputStream bis = new BufferedInputStream(fis);
int c = 0;
System.out.println(bis.read());
System.out.println(bis.read());
bis.mark(100);
for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
System.out.print((char) c + " ");
}
System.out.println();
bis.reset();
for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
System.out.print((char) c + " ");
}
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1 | public class TestBufferStream2 { |
转换流
把字节流转换为字符流。OutputStreamWriter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class TestOutputStreamWriter {
public static void main(String[] args) {
try {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:/a.txt"));
osw.write("liuandcolin630.github.io");
System.out.println(osw.getEncoding());
osw.close();
osw = new OutputStreamWriter(new FileOutputStream("E:/a.txt", true), "ISO8859_1");
osw.write("liuandcolin630.github.io\"");
System.out.println(osw.getEncoding());
osw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}InputStreamReader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class TestInputStreamReader {
public static void main(String[] args) {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = null;
try {
s = br.readLine();
while (s != null) {
if (s.equalsIgnoreCase("exit")) {
break;
}
System.out.println(s.toUpperCase());
s = br.readLine();
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}这是一个阻塞式、同步式的方法。只要键盘不输入数据,摁回车换行,这个方法就会一直等待着,不运行。
System.in
:The "standard" input stream
数据流
DataInputStream
和DataOutputStream
分别继承自InputStream
和OutputStream
,它属于处理流,需要分别“套接”在InputStream
和OutputStream
类型的节点流上。
DataInputStream
和DataOutputStream
提供了可以存取与机器无关的Java原始类型数据(如:int
、double
等)的方法。
DataInputStream
和DataOutputStream
的构造方法为:- `DataInputStream( InputStream in)` - `DataOutputStream( OutputStream out)`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class TestDataStream {
public static void main(String[] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
dos.writeDouble(Math.random());
dos.writeBoolean(true);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
System.out.println(bais.available());
DataInputStream dis = new DataInputStream(bais);
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
dos.close();
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Print流
PrintWriter
和PrintStream
都属于
输出流,分别针对于字符与字节。PrintWriter
和PrintStream
提供了重载的print
。Println
方法用于多种数据类型的输出。PrintWriter
和PrintStream
的输出操作不会抛出异常,用户通过检测错误状态获取错误信息。PrintWriter
和PrintStream
都有自动flush
功能。
举例1:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class TestPrintStream {
public static void main(String[] args) {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream("E:/log.dat");
ps = new PrintStream(fos);
} catch (IOException e) {
e.printStackTrace();
}
if (ps != null) {
System.setOut(ps);
}
int ln = 0;
for (char c = 0; c < 60000; c++) {
System.out.print(c + " ");
if (ln++ >= 100) {
System.out.println();
ln = 0;
}
}
}
}举例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class TestPrintStream2 {
public static void main(String[] args) {
// String filename = args[0];
String filename = "E:\\EclipseWorkspace\\mashibingProject\\src\\IO\\TestPrintStream.java";
if (filename != null) {
list(filename, System.out);
}
}
private static void list(String filename, PrintStream ps) {
try {
BufferedReader br = new BufferedReader(new FileReader(filename));
String s = null;
while ((s = br.readLine()) != null) {
ps.println(s);
}
br.close();
} catch (IOException e) {
ps.println("无法读取文件!");
}
}
}args[0]:为命令行参数。在Eclipse运行需要找:菜单Run -> Run Configurations -> Arguments -> Program arguments 里填写参数。
举例3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public static void main(String[] args) {
String s = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
FileWriter fw = new FileWriter("E:/logFile.log", true);
PrintWriter log = new PrintWriter(fw);
while ((s = br.readLine()) != null) {
if (s.equalsIgnoreCase("exit")) {
break;
}
System.out.println(s.toUpperCase());
log.println("-------");
log.println(s.toUpperCase());
log.flush();
}
log.println("=====" + new Date() + "======");
log.flush();
log.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Object流
直接将Object
写入或读出
比如:画图程序,画方块或三角,一存盘,就把方块和三角形存到硬盘上了,下次再打开这个文件的时候,方块和三角形还在原来的那个位置上,如果用面向对象的思维来说,方块和三角形都有自己的成员变量,x
、y
、宽度、高度等,这些都有。存盘的时候,是把这些内容都存在硬盘上,写到文件里。如果拿一个方块来存盘,那么该如何存盘呢?先存方块的左上角坐标,把长、宽、高存入,下次再打开文件的时候,在原地把它再画出来就行了。另外,还要存颜色、线条的宽度、线条的属性(点线、折线、实线、虚线等都不一样)。实际当中,会在内存里new
出一个方块的对象来,对象里存了x
值、y
值等属性值,存盘的时候就把这些属性一个个的存入硬盘即可,使用DataOutputStream
。既然你把Object
里的各个属性写入,何不把整个Object
写入呢?这就是Object
的初衷。其实这也叫序列化即一个Object
直接的转化成为字节流直接写到硬盘上或者网络上去。比如:游戏里的存档功能,再登入游戏后,你的角色、属性、怪物、景观等一系列都恢复到之前退出时的样子。存档时就是一个个的把全屏幕里的这些属性都序列化入硬盘里,然后再读出来。Serializable
关键字
如果你想把某个类的对象序列化,需要实现Serializable
这个接口,打上标记(因为这个接口是个空接口,无任何方法),这样编译器才能知道,这个类的对象是可序列化的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public class TestObjectStream {
public static void main(String[] args) {
T t = new T();
t.b = 8;
try {
FileOutputStream fos = new FileOutputStream("E:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(t);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("E:/a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
T tReaded = (T) ois.readObject();
System.out.println(tReaded.i + " " + tReaded.j + " " + tReaded.a + " " + tReaded.b);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class T implements Serializable {
int i = 5;
int j = 10;
double a = 10.5;
int b = 10;
}transient
关键字
使成员变量变为透明。即它修饰的成员变量在序列化时不予考虑。也就是说,往硬盘里写入的时候,只写入除transient
修饰的其他的值,所以读出时transient
修饰的值为默认值。externalizable
关键字
它是Serializable
的子接口。Serializable
关键字是将某个类的对象来序列化,由JDK
完成并控制到底如何进行序列化的过程,你自己无法控制。如果你想控制你自己的这个对象是如何写出去的,那么就去实现externalizable
接口。通过readExternal
和writeExtenal
来自己控制自己的序列化过程。
总结
- InputStream/OutputStream
- Reader/Writer
- FileInputStream/FileOutputStream
- FileReader/FileWriter
- BufferedInputStream/BufferedOutputStream
- BufferedReader/BufferedWriter
- ByteArrayInputStream/ByteArrayOutputStream
- InputStreamReader/OutputStreamWriter
- DataInputStream/DataOutputStream
- PrintStream/PrintWriter
- ObjectInputStream/ObjectOutputStream