1、基本的输入流和输出流
流是 Java 中最重要的基本概念之一。文件读写、网络收发、进程通信,几乎所有需要输入输出的地方,都要用到流。
流是做什么用的呢?就是做输入输出用的。为什么输入输出要用“流”这种方式呢?因为程序输入输出的基本单位是字节,输入就是获取一串字节,输出就是发送一串字节。但是很多情况下,程序不可能接收所有的字节之后再进行处理,而是接收一点处理一点。比方你下载魔兽世界,不可能全部下载到内存里再保存到硬盘上,而是下载一点就保存一点。这时,流这种方式就非常适合。
在 Java 中,每个流都是一个对象。流分为两种:输入流(InputStream)和输出流(OutputStream)。对于输入流,你只要从流当中不停地把字节取出来就是了;而对于输出流,你只要把准备好的字节串传给它就行。
流对象是怎么获得的呢?不同的外部系统,获取流的方式也不同。例如,文件读写就要创建 FileInputStream/FileOutputStream 对象,而网络通信是通过 Socket 对象来获取输入输出流的。一般来说,如果一个类有 getInputStream() 或 getOutputStream() 这样的方法,就表明它是通过流对象来进行输入输出的。
InputStream 是输入流,下面是一个通过 InputStream 读取文件的例子:
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.FileNotFoundException; import java.util.Arrays; /** * 通过流读取文件 */ public class ReadFileDemo { // 程序入口 public static void main(String[] args) { String path = "c:/boot.ini" ; File file = new File(path); // 创建输入流 InputStream is; try { is = new FileInputStream(file); } catch (FileNotFoundException e) { System.err.println( "文件 " + path + " 不存在。" ); return ; } // 开始读取 byte [] content = new byte [ 0 ]; // 保存读取出来的文件内容 byte [] buffer = new byte [ 10240 ]; // 定义缓存 try { int eachTime = is.read(buffer); // 第一次读取。如果返回值为 -1 就表示没有内容可读了。 while (eachTime != - 1 ) { // 读取出来的内容放在 buffer 中,现在将其合并到 content。 content = concatByteArrays(content, buffer, eachTime); eachTime = is.read(buffer); // 继续读取 } } catch (IOException e) { System.err.println( "读取文件内容失败。" ); e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { // 这里的异常可以忽略不处理 } } // 输出文件内容 String contentStr = new String(content); System.out.println(contentStr); } /** * 合并两个字节串 * * @param bytes1 字节串1 * @param bytes2 字节串2 * @param sizeOfBytes2 需要从 bytes2 中取出的长度 * * @return bytes1 和 bytes2 中的前 sizeOfBytes2 个字节合并后的结果 */ private static byte [] concatByteArrays( byte [] bytes1, byte [] bytes2, int sizeOfBytes2) { byte [] result = Arrays.copyOf(bytes1, (bytes1.length + sizeOfBytes2)); System.arraycopy(bytes2, 0 , result, bytes1.length, sizeOfBytes2); return result; } } |
虽然写得很啰嗦,但这确实是 InputStream 的基本用法。注意,这只是一个例子,说明如何从输入流中读取字节串。实际上,Java 提供更简单的方式来读取文本文件。以后将会介绍。
相比从流中读取,使用 OutputStream 输出则是非常简单的事情。下面是一个例子:
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
|
import java.io.OutputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; import java.util.Date; /** * 将当前日期保存到文件 */ public class SaveFileDemo { public static void main(String[] args) throws IOException { String path = "c:/now.txt" ; File file = new File(path); if (!file.exists() && !file.createNewFile()) { System.err.println( "无法创建文件。" ); return ; } OutputStream os = new FileOutputStream(file); // 创建输出流(前提是文件存在) os.write( new Date().toString().getBytes()); // 将当前时间写入文件 os.close(); // 必须关闭流,内容才会写入文件。 System.out.println( "文件写入完成。" ); } } |
Java 还提供其它的流操作方式,但它们都是对 InputStream 和 OutputStream 进行扩展或包装。所以这两个类是基础,必须要理解它们的使用。
2、Reader 和 Writer
介绍了 InputStream 和 OutputStream,接下来介绍 Reader 和 Writer。这两个类其实就是将 InputStream 和 OutputStream 包装了一下。不过它们处理的不是字节(byte),而是字符(char)。如果一个流当中的内容都是文本,那么用 Reader/Writer 处理起来会简单些。下面是一个用 Reader 读取文本文件的例子:
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
27
28
29
30
31
|
import java.io.FileReader; import java.io.IOException; import java.io.Reader; /** * 读取文本文件 */ public class ReadTextFileDemo { // 程序入口 public static void main(String[] args) { String path = "c:/boot.ini" ; String content = "" ; try { Reader reader = new FileReader(path); char [] buffer = new char [10240]; int count; while ((count = reader.read(buffer)) != -1) { content += new String(buffer, 0, count); } } catch (IOException e) { System.err.println( "读取文件失败。" ); e.printStackTrace(); } System.out.println(content); } } |
至于如何用 Writer 将文本内容写入文件,这里就不给出例子了,看官自己试着写一下吧。
上面这个例子,仍然不是读取文本文件最常用的方式。Java 提供 BufferedReader,我们通常用它来读取文本文件。下面是一个例子:
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
27
|
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * 用 BufferedReader 读取文本文件 */ public class ReadTextDemo2 { public static void main(String[] args) { String path = "c:/boot.ini" ; String content = "" ; try { BufferedReader reader = new BufferedReader( new FileReader(path)); String line; while ((line = reader.readLine()) != null) { content += line + "/n" ; } } catch (IOException e) { System.err.println( "读取文件失败。" ); e.printStackTrace(); } System.out.println(content); } } |
3、对象序列化
对象序列化也是流应用的一个重要方面。序列化就是把一个对象转换成一串字节,既可以保存起来,也可以传给另外的 Java 程序使用。ObjectOutputStream 和 ObjectInputStream 就是专门用来进行序列化和反序列化的。下面就是一个简单的例子:
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
import java.io.ObjectOutputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.io.ObjectInputStream; import java.io.FileInputStream; import java.io.EOFException; /** * ObjectOutputStream/ObjectInputStream 示例。 * 这两个类分别用于序列化和反序列化。 */ public class SerializationDemo { public static void main(String[] args) throws Exception { String path = "c:/persons.data" ; File f = new File(path); if (!f.exists()) { f.createNewFile(); } writePersons(path); readPersons(path); } // 从指定的文件中读取 Person 对象 private static void readPersons(String path) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream( new FileInputStream(path)); Person p; while ( true ) { try { p = (Person) ois.readObject(); System.out.println(p); } catch (EOFException e) { break ; } } } // 将 Person 对象保存到指定的文件中 private static void writePersons(String path) throws IOException { Person[] persons = new Person[]{ new Person( "张三" , 23 ), new Person( "李四" , 24 ) }; ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(path)); for (Person person : persons) { oos.writeObject(person); } oos.close(); } /////////////////////////////////////////////////////////// private static class Person implements Serializable { private String name; private int age; public Person() { } public Person(String name, int age) { this .name = name; this .age = age; } public String getName() { return name; } public void setName(String name) { this .name = name; } public int getAge() { return age; } public void setAge( int age) { this .age = age; } @Override public String toString() { return "Person{" + "name='" + name + '/ '' + ", age=" + age + '}' ; } } } |
这个例子里面看不到字节和字符的读写,因为这两个类都包装好了。上面只是一个简单的例子,序列化要写好的话还是有不少讲究的。想深入了解序列化,可以看看这里。本文只关注跟流有关的部分。其实序列化用的很少,因为序列化降低了灵活性,所以可以不用的话一般都不会用。