有关输入输出流(IOStream)的内容,分成两个文档。此篇文档主要介绍非节点流。
非节点流不能直接从一个节点(设备)读或写数据,而是通过节点流将数据保存到一个数组中,然后再通过节点流将数据写出去。它们每一种都有一些特殊的功能:
- 数据流:将基本类型数据和字节之间进行相互转换
- 缓冲流:可以提高其他流的读写效率
- 快速输出流:对流进行快速输出
- 转换流:将字节流转为字符流
- 对象流:将对象序列化为字节,或反序列化为对象
- 随机访问流:对文件进行随机访问
数据流
字节流是以字节文件单位进行读写,但很多时候这样并不方便。使用数据流能够对字节流进行增强。
数据流包括DataOutputStream和DataInputStream,可以将若干个字节自动转换为指定基本数据类型的数据,例如int、float、char等。也可以直接把一个数据自动转成指定基本数据类型再写出去。一般怎么读就怎么写,如果读和写的类型长度不一致——写比读长时会出现EOF异常;写比读短会出现数据错误。
数据流在使用时必须“包裹”一个字节流,能够增强这个字节流的功能,使得可以一次读取或写出一个具体类型的数据。数据流原理是通过移位操作来拿到每一个字节的值,放到数组中,再写出去。
public class Test {
public static void main(String[] args) throws Exception {
DataInputStream dis = new DataInputStream(
new FileInputStream("c.txt")); // 将从文件读取
System.out.println(dis.readInt()); // 从文件读取一个int并打印
DataOutputStream out = new DataOutputStream(
new ByteArrayOutputStream()); // 将向ByteArray输出流写入
out.writeLong(1000L); // 写入一个long类型数据
out.flush();
}
}
缓冲流
缓冲流包括BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter,可以为构造时包裹的流对象设置一个默认大小的缓冲区数组,通过缓冲区进行读写,减少系统磁盘的IO次数,从而提高读写的效率。一般情况下,是读取效率最快的流。**
字节缓冲流
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("src/背影.txt")); // 增强文件输入流
bis.skip(3); // 跳过3个字节不读
int i = bis.read(); // 读一个字节
byte[] bytes = new byte[200];
bis.read(bytes); // 读200个字节
System.out.println(Arrays.toString(bytes));
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream("kk.txt")); // 增强文件输出流
bos.write("ABCBoy".getBytes()); // 向文件写入
bos.flush();
bos.close();
字节缓冲流有可能会拆分字符,故一般用于读取多媒体文件,如音频、视频等。
字符缓冲流
BufferedReader br = new BufferedReader(
new FileReader("src/背影.txt"));
br.skip(br.readLine().length() + 2); // 跳过一些字符,换行和回车各是一个字符
String line = null;
while((line = br.readLine()) != null) {
// 一次读取一行,读到换行或回车表示一行结束
// 如果整个文件读完,返回null
System.out.println(line);
}
BufferedWriter bw = new BufferedWriter(
new FileWriter("src/ll.txt"));
bw.write("ABCBoy");
bw.flush();
bw.close();
字符缓冲流主要使用其中的字符输入流,每次一般读取一行。而输出字符一般使用输出流。
输出流
输出流包括PrintOutputStream和PrintWriter。一般主要使用PrintWriter作为字符输出的首选,速度快。输出流增强字符流、字节流和文件。
PrintWriter pw = new PrintWriter(
new FileOutputStream("src/ll.txt", true)); // 准备写入一个文件
for(int i = 0; i < 1000; i++) {
pw.println("ABCBoy"); // 一次写入一行,并加上回车换行(\r\n,Windows中)/换行(\n,Linux中)
}
pw.flush();
pw.close(); // PrintWriter的close方法没有抛出异常
转换流
转换流包括InputStreamReader和OutputStreamWriter,能够将字节流转换为字符流的同时,指定字符流的编码。
FileInputStream fis = new FileInputStream("src/1.txt");
InputStreamReader isr = new InputStreamReader(fis); // 默认编码为UTF-8
InputStreamReader isr2 = new InputStreamReader(fis, "GBK"); // 指定编码GBK
BufferedReader br = new BufferedReader(isr); // 套上缓冲流方便读一行
System.out.println(br.readLine());
br.close(); // 关闭最外层即可将内层的流关闭
FileOutputStream fos = new FileOutputStream("2.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos); // 默认编码为UTF-8
OutputStreamWriter osw2 = new OutputStreamWriter(fos, "GBK"); // 指定编码GBK
PrintWriter pw = new PrintWriter(osw, true); // 在文件中追加
pw.println("怎么费事"); // 按行输出
pw.flush();
pw.close(); // 关闭最外层即可将内层的流关闭
对象流
对象流又称序列化流/串行化流(Serializable Stream),可以将对象转化为可传输到文件、网络、数组、管道等节点的字节序列(序列化),或者序列转化回对象(反序列化)。
- 要序列化一个对象,该对象的类须要实现Serializable接口(但并不需要实现任何方法)。如果是集合或数组,则内部元素需要实现序列化接口。
- 如果要避免对对象中的一些属性进行序列化,可以对这些属性添加transient修饰符,使得对象在进行序列化的时忽略该属性。在反序列化后,该属性为默认值。
- 反序列化重新生成的对象,其类型和数据等都和之前一致,但内存地址有可能不同。从文件中反序列化时,只能从一个文件中读取一个对象(包括集合和数组)。反序列化不会调用构造方法。(克隆对象也不会调用构造方法)
- 如果对一个对象序列化后,如果没有指定版本号,并对这个对象的类进行了修改(版本号也会被同步修改),再反序列化时会报错。故实现序列化时需提供一个版本号。在反序列化前后若版本号一致,则不会报错。 ```java class Student implements Serializable { // 能够序列化的类需要实现这个接口 private static final long serialVersionUID = 1L; // 指定版本号 private int id = 2; private String name = “ABCBoy”; private transient String password = “123456”; // 被transient修饰的属性不会被序列化 // 构造方法等略 }
public class SerializableTest { public void test1() throws Exception { Student student = new Student(); // 序列化 将对象转换成一个可传输的字节序列 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(“3.txt”)); // 将对象序列化到一个文件中 oos.writeObject(student); oos.flush(); oos.close(); }
public void test2() throws Exception {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("3.txt")); // 将文件中的数据反序列化成对象
// 反序列化创建对象不会调用构造方法
Student student = (Student)ois.readObject();
}
public void test3() throws Exception {
List<Student> students = new ArrayList<>();
students.add(new Student());
students.add(new Student());
ObjectOutputStream ois = new ObjectOutputStream(
new FileOutputStream("4.txt")); // 可以通过集合批量序列化对象到文件中
ois.writeObject(students);
ois.flush();
ois.close();
}
public void test4() throws Exception {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("4.txt"));
Object obj = ois.readObject();
Object obj2 = ois.readObject(); // 一个文件只能反序列化一次,报错
}
}
<a name="6EyV6"></a>
## 随机访问流
随机访问流只继承了Object类,没有继承之前IO流的类。它用于读写文件,可以定位到任意位置,也可以前后反复定位。它具有各种模式,且不需要进行刷新(flush)。<br />具有的模式包括:
- r:只读模式。
- rw:读写模式,若文件不存在则创建。
- rws:实时读写模式,实时写入。
- rwd:缓存读写模式,写入缓存,缓存满后再写入文件。
```java
RandomAccessFile raf = new RandomAccessFile("src/1.txt", "rw");
raf.seek(4); // 跳过4个字符
raf.readLine(); // 读一行,读完返回null
raf.write("aaaa".getBytes()); // 写并覆盖aaaa(4字节)
raf.close();