1. 引言
文件可以分为文本或者二进制的, 文本文件是由字符序列构成的,二进制文件是由位(bit)构成的。
Java提供了许多类实现文本I/O和二进制I/O。这些类可以分为文本I/O和二进制I/O。
二进制I/O不涉及编码和解码,因此二进制I/O比文本I/O更加高效。对于文本I/O而言,编码和解码时自动进行的。
在实际的开发过程中使用commons-io.jar包中封装好的类
2. 基本概念
字符流、字节流
字节流:字节流是按照字节去读写数据的,读取是以单个字节为单位,操作对象一般为二进制文件 根类是InputStream和OutputStream
字符流:按照字符去读取数据的,一般用于字符文件,字符流是由java虚拟机将单个字节转换为两个字节的unicode字符,操作对象一般为文本,根类为Reader和Writer
节点流和处理流
节点流:可以直接从数据源或者目的地读写数
处理流(包装流):不直接连接到数据源或者目的地,是其他流进行封装,他的构造方法主要是传入节点流的子类,处理流的子类必须传入节点流的子类。主要是简化操作和提高性能。
3. I/O类结构
图片来自网络:
4. 字节流
抽象类InputStream是读取二进制数据的根类,抽象类OutputStream是写入二进制数据的根类。
FileInputStream和FileOutputStream类用于从/向文件读取/写入字节。
5. ByteArrayInputStream和ByteArrayOutputStream
字节数组输入流ByteArrayInputStream:在内存中创建一个字节数组缓冲区,从输入流中读取的数据保存在字节数组缓冲区中,从字节数组中读取数据。
字节数组输出流ByteArrayOutputStream:在创建字节数组输出流的同时,内存会为该输出流创建一个默认大小的字节数组,用来存储写入的字节内容。
5.1. DateInputStream和DateOutputStream
DataInputStream从数据流中读取字节,并将其转换为指定的基本数据类型或字符串。
DataOutputStream将基本数据类型或者字符串转换为字节,并将字节输出到数据流。
5.2. BufferedInputStream和BufferedOutputStream
BufferedInputStream和BufferedOutputStream类可以通过减少磁盘读写次数来提高输入输出的速度。将磁盘上的数据一次性的读入到内存的缓冲区中,然后将缓冲区中的个别数据传递到程序中,反之,先将数据写入到缓冲区,当缓冲区满后,一次性将数据写入到磁盘中,减少磁盘的读写次数。
缓冲区的大小默认是512个字节。
public static void main(String[] args) {
//创建源
String path = "src/main/java/io/temp.txt";
//使用缓冲输入流
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path))) {
byte[] b = new byte[1024];
int len = -1;
//读取字节数据
while ((len = bufferedInputStream.read(b)) != -1) {
//将字节数组转换为字符串
String string = new String(b, 0, b.length);
System.out.println(string);
}
} catch (IOException e) {
e.printStackTrace();
}
}
5.3. 对象I/O
5.3.1. ObjectInputStream和ObjectOutputStream
ObjectInputStrem和ObjectOutputStream用于读写可序列化的对象,还可以实现基本数据类型与字符串的输入与输
出,可以代替DataOutputStream和DataInputStream。
public static void main(String[] args) {
//创建源
String path = "src/main/java/io/temp2.txt";
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path))) {
//输出字符串
objectOutputStream.writeUTF("hello world");
//输出对象
objectOutputStream.writeObject("hello world1");
//创建对象在输出
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("helloworld3");
objectOutputStream.writeObject(arrayList);
} catch (IOException e) {
e.printStackTrace();
}
//输入流,自动关闭流
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path))) {
//读字符串
System.out.println(objectInputStream.readUTF());
System.out.println(objectInputStream.readObject());
ArrayList<String> list = (ArrayList<String>) objectInputStream.readObject();
System.out.println(list.get(0));
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
5.3.2. 关于Serializable接口
Serializable接口是一种标记接口,实现这个接口可以启动java序列化机制,自动完成存储对象和数组的过程。
存储对象的过程称为对象序列化,是将对象转换为二进制存储到硬盘上;读取对象的过程称为对象的反序列化,是将硬盘上的对象二进制文件转换为对象读到程序中去。
要实现对象的序列化必须实现Serializable接口,该接口没有任何方法,可以理解为一个标记,表明这个类是可序列化的。
SerivalVersionUID字段的作用:这是版本号,保持版本号一直来进行序列化,防止序列化出错。
6. 字符流Reader和Writer
字符流的操作单位是字符,只能用来操作文本。
public static void main(String[] args) {
//创建源
String path = "src/main/java/io/temp3.txt";
//创建字符缓冲流,并指定缓冲流的大小
try (BufferedWriter writer = new BufferedWriter(new FileWriter(path), 1024)) {
//写入字符和字符串
writer.write('h');
//每个空格均为字符
writer.write("hello world");
} catch (IOException e) {
e.printStackTrace();
}
//读取字符
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
int size = 0;
//每次读取两个字符
char[] cbuf = new char[2];
while ((size = reader.read(cbuf, 0, cbuf.length)) != -1) {
System.out.println(new String(cbuf, 0, size));
}
//当没有字符时会读取到-1
System.out.println("此时字符已经读取完毕:" + reader.read());
} catch (IOException e) {
e.printStackTrace();
}
}
6.1. 转换流
InputStreamReader:是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
OutputStreamWriter:是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集讲字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
7. 关闭流
在实际开发中,经常会在程序中打开一些物理资源,如数据库的连接,网络连接,磁盘文件等,打开这些资源呢必须显示关闭,否则会引起的资源泄露。
JVM中的垃圾回收机制属于java内存管理的一部分,他只回收堆内存中分配出来的内存,至于程序中打开的物理资源垃圾回收机制是无能为力的。
7.1. 不关闭流的影响
如果不关闭资源或者不正确的关闭资源,会导致程序还在占用着资源,浪费资源,随着运行时间的加倍,一方面会内存泄露,另一方面会导致其他资源的代码获取不到资源,这样获取资源的线程会在获取资源上卡住。
7.2. 关于资源泄露
资源是存在于内存中的,资源泄露也叫做内存泄露。还有一种特指的内存泄露,指的是动态分配得到一个内存块,在使用完成后,忘记释放,这是单纯的内存泄露。
操作系统规定,进程申请的内存,只要没释放,且进程没有中止,那么这个内存就是属于进程的,这块内存就不能被其他进程所使用。如果你之前申请的内存,在使用完后没有释放,然后又重新申请,这样之前的内存没有任何指针指向,也就无法操作,对于你的进程来说,这个内存就丢失了