2MUCH

Java基础之IO流

2023-05-03


参考JAVAschool基础教程的IO流学习:http://www.51gjie.com/java/686.html

以及chatgpt

概念

IO流(stream)

流的源端和目的端可简单地看成是字节的生产者和消费者,对输入流,可不必关心它的源端是什么,只要简单地从流中读数据,而对输出流,也可不知道它的目的端,只是简单地往流中写数据。

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

http://www.51gjie.com/java/686.html

Java IO流类图

分类

有一些类名容易搞混,比如:InputStreamReader和FileInputStream,两者都含InputStream,但是前者是继承Reader类的字符流,后者是继承InputStream类的字节流。因此,要识别类是属于字符or字节流的话,看【后缀】即可:

按用法分类:

(1) 按数据来源(去向)使用
是文件: FileInputStream, FileOutputStream, FileReader, FileWriter
是byte[]:ByteArrayInputStream, ByteArrayOutputStream
是Char[]: CharArrayReader, CharArrayWriter
是String: StringBufferInputStream, StringReader, StringWriter
网络数据流:InputStream, OutputStream, Reader, Writer 

(2) 按是否格式化输出使用
要格式化输出:PrintStream, PrintWriter 

(3) 按是否要缓冲使用
要缓冲:BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter。

(4) 按数据格式使用
二进制格式(只要不能确定是纯文本的): InputStream, OutputStream及其所有带Stream结束的子类
纯文本格式(含纯英文与汉字或其他编码方式);Reader, Writer及其所有带Reader, Writer的子类 

(5) 按输入输出使用
输入:Reader, InputStream类型的子类;输出:Writer, OutputStream类型的子类 

(6) 特殊需要
从Stream到Reader,Writer的转换类:InputStreamReader, OutputStreamWriter
对象输入输出:ObjectInputStream, ObjectOutputStream
进程间通信:PipeInputStream, PipeOutputStream, PipeReader, PipeWriter 
合并输入:SequenceInputStream
更特殊的需要:PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader

(7) 决定使用哪个类以及它的构造进程的一般准则如下(不考虑特殊需要): 
考虑最原始的数据格式是什么:是否为文本?是输入还是输出?是否需要转换流:InputStreamReader, OutputStreamWriter?数据来源(去向)是什么:文件?内存?网络?是否要缓冲:bufferedReader (特别注明:一定要注意的是readLine()是否有定义,有什么比read, write更特殊的输入或输出方法)是否要格式化输出:print。

http://www.51gjie.com/java/686.html

字符流和字节流的转换

转换流:是两者之间的桥梁

File类

java.io.File类实现对文件进行基本操作。常用方法如下:

其中,listFiles返回的是File[]类型,list方法返回的是String[]类型

InputStream类

是字节输入流的所有类的超类。常见子类:

以下仅介绍FileInputStream的常见用法。需要注意的是,因为这些字类都实现了InputStream定义的基本方法,故基本的使用方法是差不多的。但是各自有自己的特色方法,我们可以按需使用。

FileInputStream

用法:读取文件内容,用byte[]存储。主要方法如下:

FileInputStream(String fileName) // 构造函数
int read() // 从输入流中读取一个字节的数据(输出时可用(char)转换为字符)。如果已经到达文件的末尾,则返回-1
int read(byte[] b) // 从输入流中读取若干个字节的数据,并将其存储在缓冲区`b`中。返回实际读取的字节数。
int read(byte[] b, int offset, int len) // 从输入流中读取最多`len`个字节的数据,并将其存储在缓冲区`b`中,从偏移量`off`开始存储。返回实际读取的字节数。
void close() // 关闭并释放流

使用实例:

public static void fileRead() {  
    try {  
        File file = new File("IOStreamLearning/file.txt");  
        // 如果不确定文件路径是否写对,可以在失败时输出文件路径进行调整  
        if (!file.exists()) {  
            throw new FileNotFoundException("File not found: " + file.getAbsolutePath());  
        }  
        // 1.使用read()方法  
        FileInputStream fis1 = new FileInputStream(file);  
        // 也可以直接将文件路径写在参数里  
        // FileInputStream fis1 = new FileInputStream("IOStreamLearning/file.txt");  
        int data;  
        // 读取结果转换为int赋值给data  
        while ( (data = fis1.read()) != -1) {  
            // System.out.print((char)data);  
        }  
        fis1.close();  
        // 2.使用read(byte[] b)方法  
        FileInputStream fis2 = new FileInputStream(file);  
        byte[] b2 = new byte[2];  
        int byteArray = fis2.read(b2);  
        // 依次读取b2所定义的字节数量,直到读取结束  
        while (-1 != byteArray) {  
            // System.out.print(new String(b2)); // byte[]->String用的是new String(byte[])哦!  
            byteArray = fis2.read(b2);  
        }  
        fis2.close();  
        // 3.使用read(byte[], int offset, int len)  
        FileInputStream fis3 = new FileInputStream(file);  
        byte[] b3 = new byte[1024];  
        while (-1 != fis3.read(b3, 0, 1024)) {  
            System.out.println(new String(b3));  
        }  
        fis3.close();  
    } catch (IOException e) {  
        throw new RuntimeException(e);  
    }

OutputStream类

是字节输出流的所有类的超类。常见子类:

image.png

以下仅介绍FileOutputStream的常见用法。

FileOutputStream

用于将数据写入文件,常用方法:

void write(int b) // 写入一个字节到输出流
void write(byte[] b) // 写入一个字节数组
void write(byte[]b, int offset, int len)
void flush() // 刷新输出流,将缓冲区的内容写入目标文件。虽然大部分情况下,程序正常结束时数据都会自动写入文件中,但是保险起见需要在适当的时候及时刷新写入数据
void close()

关于为何第一个方法的参数是int类型:

虽然这个方法接受一个int作为参数,但它实际上会写入一个无符号字节。Java没有无符号字节数据类型,所以这里要使用int来代替。

by https://www.jianshu.com/p/c47c79882607

应用实例:

public static void writeFile() {  
    try {  
        // 1.使用write(int b)方法  
        // FileOutputStream第二个参数为可选,表示是否追加写。默认为false  
        FileOutputStream fos1 = new FileOutputStream("IOStreamLearning/file1.txt", true);  
        byte b = 66;  
        fos1.write(b);  
        fos1.flush();  
        fos1.close();  
        // 2.使用write(byte[])方法  
        FileOutputStream fos2 = new FileOutputStream("IOStreamLearning/file2.txt");  
        String content = "hello";  
        fos2.write(content.getBytes()); // 通过getBytes方法将要写入的String转换为byte[]  
        fos2.flush();  
        fos2.close();  
        // 3.使用write(byte[], int offset, int len)方法  
        FileOutputStream fos3 = new FileOutputStream("IOStreamLearning/file3.txt");  
        String content1 = "bye";  
        byte[] b3 = content1.getBytes("UTF-8"); // 将String对象转换为UTF-8编码的byte数组  
        // 如果要写入全部,len参数取数组的长度。注意len参数不能指定为超出实际数组的长度  
        fos3.write(b3, 0, b3.length);   
fos3.flush();  
        fos3.close();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}

Reader类

是字符输入流的所有类的超类。常见子类:

三者的区别: BufferedReader是一个缓冲字符输入流,它可以减少了读取文件的次数,提高读取文件的效率。InputStreamReader是用来读取字节流并将其转换为字符流的类,可以将任何字节流转换为字符流。更重要的是:InputStreamReader可以接收第二个参数,指定字节流使用的编码格式。FileReader继承自InputStreamReader类,读取文本文件时可以直接使用该类。但是,FileReader是使用系统默认的字符编码读取文件(无法指定的编码),如果文件的编码不是系统默认的编码,那么就不能正确地读取文件内容(乱码)。

需要注意的是:这些类都是用于读取文本文件的,如果要读取二进制文件(例如图片、音频、视频等),则应该使用InputStream类及其子类,而不是使用这些类。

下面介绍仅BufferedReader的用法。

BufferedReader

在开始之前,先搞清楚一件事:前面说到,InputStreamReader可以通过第二个参数指定读取的编码,但是BufferedReader并不能这样做。那么如果实现缓存+指定编码的效果呢?答案是:将InputStreamReader对象传递给BufferedReader构造函数(下面会给出例子)。

BufferedReader的常用方法:

int read() // 与InputStream类似
int read(byte[] b) // 与InputStream类似
String readLine() // 读取一行文本数据,如到达文件结尾返回null
close() // 与InputStream类似

具体实例:

public static void readFile() {  
    String filePath = "IOStreamLearning/file.txt";  
    try {  
        // 1.使用FileReader构建BufferedReader对象  
        BufferedReader br1 = new BufferedReader(new FileReader(filePath));  
        String line;  
        while(null != (line = br1.readLine())) {  
            // System.out.println(line);  
        }  
        // 2.使用InputStreamReader构建BufferedReader对象  
        // FileReader无法指定读取编码,但是可以借助FileInputStream+InputStreamReader来指定编码  
        BufferedReader br2 = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));  
        while(null != (line = br2.readLine())) {  
            System.out.println(line);  
        }  
    } catch(IOException e) {  
        System.out.println("Error reading file: " + e.getMessage());  
    }  
}

BufferedReader的构造函数不能直接使用文件路径作为参数,而是必须使用字符输入流传递进去(例如借助桥梁InputStreamReader或它的子类FileReader)。即,需要使用这些方法作为字节流和字符流的连接桥梁。

分析方法2:BufferedReader br2 = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8")); 。FileInputStream提供文件的字节流,InputStreamReader将字节流转换为字符流,BufferedReader接收字符流生成对象。此外,读取的编码集在InputStreamReader的第二个参数指定。如果不指定编码方式,则会使用默认的编码方式(据说Windows下默认编码是GBK,Linux下默认编码是UTF-8)。

Writer类

是字符输出流的所有类的超类,常用子类:

以下仅介绍OutputStreamWriter的常见用法。

OutputStreamWriter

将字符流转换为字节流。常见用法:

void write(int c) // 写入单个字符
void write(char[] c, int offset, int len) // 写入字符数组
void write(String str, int offset, int len) // 写入字符串
void flush()
void close()

具体实例:

public static void writeFile() {  
    try{  
        String filePath = "IOStreamLearning/file4.txt";  
        // 1.使用write(int c)方法  
        OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8");  
        int c = 66;  
        osw1.write(c);  
        osw1.flush();  
        osw1.close();  
        // 2。使用write(char[] c, int offset, int len)方法  
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8");  
        char[] c2 = new char[3];  
        c2[0] = 'a';  
        c2[1] = 'a';  
        osw2.write(c2, 0, c2.length);  
        osw2.flush();  
        osw2.close();  
        // 2。使用write(String str, int offset, int len)方法  
        OutputStreamWriter osw3 = new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8");  
        String content = "hello world";  
        osw3.write(content, 0, content.length());  
        osw3.flush();  
        osw3.close();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}