IO 即 Input / Output ,输入输出流。IO流在Java中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
Java IO 流的 40 多个类都是从如下 4 个 抽象类基类中派生出来的。
InputStream 用于从源头(通常是文件)读取数据(字节信息)到内存中,java.io.InputStream 抽象类是所有字节输入流的父类。
InputStream 常用方法:
public static void main(String[] args) {
try(InputStream fis = new FileInputStream("IO/input.txt")){
System.out.println("number of remaining bytes: " + fis.available());
int content;
long skip = fis.skip(2);
System.out.println("the actual number of bytes skipped: " +skip);
System.out.println("the content read from file: ");
while((content = fis.read()) != -1){
System.out.println((char) content);
}
}catch (IOException e){
e.printStackTrace();
}
}
input文件内容:
输出:
不过,一般不会单独使用 FileInputStream ,通常配合 BufferedInputStream (字符缓冲输入流)使用
public static void main(String[] args){
//新建一个 BufferedInputStream 对象
try(BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("IO/input.txt"))){
// 读取文件内容
byte[] content = new byte[bufferedInputStream.available()];
bufferedInputStream.read(content , 0 , bufferedInputStream.available());
String result = new String(content);
System.out.println(result);
}catch (IOException E){
E.printStackTrace();
}
}
输出:
DataInputStream 用于读取指定类型数据,不能单独使用,必须结合其它流,比如 FileInputStream。
FileInputStream fileInputStream = new FileInputStream("input.txt");
//必须将fileInputStream作为构造参数才能使用
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
//可以读取任意具体的类型数据
dataInputStream.readBoolean();
dataInputStream.readInt();
dataInputStream.readUTF();
ObjectInputStream 用于从输入流中读取 Java 对象(反序列化),ObjectOutputStream 用于将对象写入到输出流(序列化)。
ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) input.readObject();
input.close();
OutputStream 用于将数据(字节信息)写入到目的地(通常是文件),java.io.OutputStream 抽象类是所有字节输出流的父类。
OutputStream 常用方法:
FileOutputStream 是最常用的字节输出流对象,可直接指定文件路径,可以直接输出单字节数据,也可以输出指定的字节数组。
public static void main(String[] args) {
try(FileOutputStream outputStream = new FileOutputStream("IO/output.txt")){
byte[] array = "hello,world".getBytes();
outputStream.write(array); //将 array 写入到 output 文件,会覆盖 output 文件内容
}catch (IOException e){
e.printStackTrace();
}
}
结果:
类似于 FileInputStream ,FileOutputStream 通常也会配合 BufferedOutputStream(字节缓冲输出流)来使用。
public static void main(String[] args) {
try(BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("IO/output.txt"))){
byte[] out = "hello,world".getBytes();
bufferedOutputStream.write(out);
}catch (IOException e){
e.printStackTrace();
}
}
DataOutputStream 用于写入指定类型数据,不能单独使用,必须结合其它流,比如 FileOutputStream 。
// 输出流
FileOutputStream fileOutputStream = new FileOutputStream("out.txt");
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
// 输出任意数据类型
dataOutputStream.writeBoolean(true);
dataOutputStream.writeByte(1);
ObjectOutputStream 用于从输入流中读取 Java 对象(ObjectOutputStream ,反序列化),ObjectOutputStream 将对象写入到输出流(ObjectOutputStream ,序列化)
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("file.txt")
Person person = new Person("tom", 16);
output.writeObject(person);
虽然文件读写或者网络发送接收的信息最小存储单元是字节,但是字符流是由Java 虚拟机将字节转换得到的,这个过程比较耗时,如果不知道编码类型还很容易出现乱码。
将上面的 FileInputStream 代码示例中的 input.txt 文件内容改成中文:
输出:
因此,IO流提供了一个直接操作字符的接口,方便平时对字符的流操作。比如音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
Reader 用于从源头(通常是文件)读取数据(字符信息)到内存中,java.io.Reader 抽象类是所有字符输入流的父类。
Reader 用于读取文本,InputStream 用于读原始字节。
Reader 常用方法:
// 字节流转换为字符流的桥梁
public class InputStreamReader extends Reader {
}
// 用于读取字符文件
public class FileReader extends InputStreamReader {
}
FileReader 是继承于 InputStreamReader的
public static void main(String[] args) {
try (FileReader fileReader = new FileReader("IO/input.txt");) {
int content;
long skip = fileReader.skip(3);
System.out.println("The actual number of bytes skipped:" + skip);
System.out.print("The content read from file:");
while ((content = fileReader.read()) != -1) {
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
}
input文件:
输出:
Writer用于将数据(字符信息)写入到目的地(通常是文件),java.io.Writer 抽象类是所有字符输出流的父类
Writer常用方法:
// 字符流转换为字节流的桥梁
public class OutputStreamWriter extends Writer {
}
// 用于写入字符到文件
public class FileWriter extends OutputStreamWriter {
}
FileWriter 继承于 OutputStreamWriter.
public static void main(String[] args) {
try (Writer output = new FileWriter("IO/output.txt")) {
output.write("你好,世界");
} catch (IOException e) {
e.printStackTrace();
}
}
输出结果:
IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。
BufferedInputStream
从源头(通常是文件)读取数据(字节信息)到内存的过程中不会一个字节一个字节的读取,而是会先将读取到的字节存放在缓存区,并从内部缓冲区中单独读取字节。这样大幅减少了 IO 次数,提高了读取效率。
// 新建一个 BufferedInputStream 对象
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));
BufferedInputStream
内部维护了一个缓冲区,这个缓冲区实际就是一个字节数组。
BufferedOutputStream
将数据(字节信息)写入到目的地(通常是文件)的过程中不会一个字节一个字节的写入,而是会先将要写入的字节存放在缓存区,并从内部缓冲区中单独写入字节。这样大幅减少了 IO 次数,提高了读取效率
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
byte[] array = "JavaGuide".getBytes();
bos.write(array);
} catch (IOException e) {
e.printStackTrace();
}
类似于 BufferedInputStream
,BufferedOutputStream
内部也维护了一个缓冲区,并且,这个缓存区的大小也是 8192 字节。
BufferedReader
(字符缓冲输入流)和 BufferedWriter
(字符缓冲输出流)类似于 BufferedInputStream
(字节缓冲输入流)和BufferedOutputStream
(字节缓冲输入流),内部都维护了一个字节数组作为缓冲区。不过,前者主要是用来操作字符信息。
System.out.println("hello,world")
System.out 实际是用于获取一个 PrintStream 对象,print 方法实际调用的是 PrintStream 对象的 write 方法。
PrintStream 属于字节打印流,与之对应的是 PrintWriter(字符打印流)。PrintStream 是 OutputStream 的子类,PrintWriter 是 Writer 的子类。
public class PrintStream extends FilterOutputStream
implements Appendable, Closeable {
}
public class PrintWriter extends Writer {
}
随机访问流,指的是支持随意跳转到文件的任意位置进行读写的 RandomAccessFile。
RandomAccessFile 的构造方法如下,我们可以指定 mode (读写模式)。
// openAndDelete 参数默认为 false 表示打开文件并且这个文件不会被删除
public RandomAccessFile(File file, String mode)
throws FileNotFoundException {
this(file, mode, false);
}
// 私有方法
private RandomAccessFile(File file, String mode, boolean openAndDelete) throws FileNotFoundException{
// 省略大部分代码
}
读写模式主要有以下四种:
文件内容指的是文件中实际保存的数据,元数据则是用来描述文件属性:比如文件的大小信息、创建和修改时间。
output文件:
执行:
public static void main(String[] args) {
try(RandomAccessFile accessFile = new RandomAccessFile(new File("IO/output.txt"),"rw")){
System.out.println("当前文件的读写偏移量:" + accessFile.getFilePointer());
accessFile.seek(accessFile.length());
System.out.println("seek设置后的文件读写偏移量:" + accessFile.getFilePointer());
accessFile.write(new byte[]{'H','E','L','L','O'});
}catch (IOException e){
e.printStackTrace();
}
}
结果:
RandomAccessFile 的 write 方法在写入对象时,如果对应的位置已经有数据的话,会将其覆盖掉,如果没有的话则写入数据。
RandomAccessFile 比较常见的一个应用就是实现大文件的断点续传。简单来说就是上传文件中途暂停或失败(比如遇到网络问题)之后,不需要重新上传,只需要上传那些未成功上传的文件分片即可。