likes
comments
collection
share

Java —— IO流详解

作者站长头像
站长
· 阅读数 5

1. IO流概述

1.1 IO流与File类的区别

1.2 IO流的分类

  1. 流的方向分:输入流(读取)和输出流(写出)
  2. 操作文件类型分: 字节流(可操作所有类型文件)和字符流(只操作纯文本文件)

纯文本文件:Windows自带记事本打开能读懂的文件。

1.3 IO流体系

Java —— IO流详解 注:这里我们要明确一个概念,输入输出都是针对内存而言,输入代表本地(磁盘)输入到内存。输出代表从内存输出到本地。

2. 字节流

2.1 FileOutputStream

操作本地文件的字节输出流,可以把程序(内存)中的数据写到本地文件中。

2.1.1 使用步骤

FileOutputStream 的使用步骤:

  1. 创建字节输出流对象(创建通道)

    FileOutputStream fos = new FileOutputStream("F:\\学习\\java 练习");
    

    参数是字符串路径,也可以是File对象。

    若文件不存在则会创建一个新的文件,但需要保证父级路径是存在的,如果文件已经存在,则会清空文件。

  2. 写数据

    fos.write(18);
    

    参数是整数,实际上写到本地文件中的是整数在ASCII上对应的字符

  3. 释放资源(关闭通道)

    fos.close();
    

    每次使用完之后都要释放资源,解除资源的占用。

    Q:为什么要自行关闭流呢?

    A:关闭流是一种资源释放机制,避免资源长时间占用。当我们用了虚拟机以外的资源时,比如端口、显存、文件的时候(访问本地资源),超出虚拟机能够释放资源的极限,这时虚拟机不能通过GC对占用资源进行释放。如果再次对未关闭流的文件进行读写,会报错,告诉你这个文件被占用。

    举例:如果你再堆区new一个对象的时候,如果等号左边是对端口、显存、文件进行操作的时候,虚拟机就无法利用垃圾回收机制对堆区占用的资源进行释放。

public void output() throws FileNotFoundException {
    FileOutputStream fos = new FileOutputStream("F:\学习\java 练习");
}
//FileOutputStream构造方法,抛出FileNotFoundException异常
public FileOutputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null, false);
}

public void output() throws IOException {
    //第一步:创建字节输出流对象(创建通道)。
    FileOutputStream fos = new FileOutputStream("F:\学习\java 练习");
    //第二步:写数据。
    fos.write(18);
    //第三步:释放资源(关闭通道)。
    fos.close();
}

2.1.2 写数据的三种方式

FileOutputStream 写数据的三种方式:

方法名称说明
write(int b)一次写 一个字节 数据
write(byte[] b)一次写 一个字节数组 数据
write(byte[] b, int off, int len)一次写 一个字节数组的部分 数据
//byte[] bytes = {97, 98, 99};
String str = "abc";
byte[] bytes = str.getBytes();
fos.write(bytes);
//每次写出从off索引开始,len个字节
fos.write(bytes, 0, 1);

换行写

写出一个换行符就行。

Windows: \r\n(回车换行)

Linux: \n

Mac: \r

注: 在Windows中,Java对回车进行了优化,写\r和\n其中一个就可以了,Java在底层会补全。(最好不要省略)。

续写

FileOutputStream fos = new FileOutputStream("F:\学习\java 练习", true);

2.2 FileInputStream

操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来

2.2.1 使用步骤

FileInputStream 的使用步骤:

//ab
//如果文件不存在直接报错
FileInputStream fis = new FileInputStream("F:\学习\java 练习\a.txt");
//一次读一个字节,读出来的是数据在ASCII上对应的数字。
//读到文件末尾返回-1
int b1 = fis.read();//97 -> a,如果想打印a,强转成char
int b2 = fis.read();//98 -> b
int b3 = fis.read();//-1
fis.close();

2.2.2 循环读取

FileInputStream 进行循环读取:

read() 方法读到文件末尾返回-1

FileInputStream fis = new FileInputStream("F:\学习\java 练习\a.txt");
int a;
while (( a = fis.read()) != -1){
    System.out.println(a);
}
fis.close();

2.3 字节流应用 —— 文件拷贝

核心思想:边读边写

2.3.1 一次读一个字节

//数据源
FileInputStream fis = new FileInputStream("D:\test.mp4");
//目的地
FileOutputStream fos = new FileOutputStream("test_copy.mp4");
//核心思想:边读边写
int b;
while ((b = fis.read()) != -1) {
    fos.write(b);
}
// 关闭资源,先开的后关闭
fos.close();
fis.close();

2.3.2 一次读多个字节


// 数据源路径
FileInputStream fis = new FileInputStream("D:\test.jpg");
// 目的地路径
FileOutputStream fos = new FileOutputStream("test_copy.jpg");

// 定义数组
byte[] b = new byte[1024];
// 定义长度
int len;
// 进行循环读取,读取到的数据写入数组b中
while ((len = fis.read(b))!=-1) {
    // 写出数据
    fos.write(b, 0 , len);//改进,为了最后一次
}

// 关闭资源
fos.close();
fis.close();

为什么第15行要使用write(byte[] b, int off, int len) 这个方法,而不使用fos.write(b)方法呢?

让我们通过下面这张图看一下究竟是为什么:

Java —— IO流详解 为了防止写入之前残留的数据,因此使用了write(byte[] b, int off, int len)

3. 字符流

字符流: 字符流的底层就是字节流。

字符流 = 字节流 + 字符集

字符流特点

  1. 输入流: 一次读一个字节,遇到中文时,一次读多个字节(与字符集有关)。
  2. 输出流: 底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。

使用场景: 对于纯文本文件进行读写操作。

3.1 FileReader

3.1.1 构造方法

创建字符输入流 FileReader 对象

构造方法说明
FileReader(File file)创建字符输入流关联本地文件
FileReader(String fileName)创建字符输入流关联本地文件

若读取文件不存在,直接报错。

3.1.2 成员方法

FileReader 读取数据方法

成员方法说明
public int read()读取数据,读取到末尾,返回-1
public int read(char[] cbuf)读取多个数据,读取到末尾,返回-1

注:按字节进行读取,遇到中文,一次读取多个字节。

FileReader 释放资源方法

成员方法说明
Public int close()释放资源/关流

3.1.3 使用步骤

FileReader 的使用步骤:

// 创建字符输入流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存数据
int b ;
// 进行循环读取
// 在读取之后,方法的底层会进行解码并转成十进制,将十进制数据作为返回值。
// 如果想看到汉字,进行强转就可以了。
while ((b = fr.read())!=-1) {
    System.out.println((char)b);
}
// 关闭资源
fr.close();
// 创建字符输入流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 进行循环读取
// 读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中。
while ((len = fr.read(cbuf))!=-1) {
     System.out.println(new String(cbuf,0,len));
}
// 关闭资源
fr.close();

3.1.4 字符输入流原理

创建字符输入流对象时,关联文件,并创建缓冲区(长度为8192的字节数组)。

Java —— IO流详解

读取数据时

  1. 判断缓冲区是否有数据可以读取。
  2. 若缓冲区有数据:从缓冲区中读取。
  3. 若缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区,如果文件中没有数据了,就返回 -1。

3.2 FileWriter

3.2.1 构造方法

FileWriter 的构造方法:

构造方法说明
FileWriter(File file)创建字符输出流关联本地文件
FileWriter(String fileName)创建字符输出流关联本地文件
FileWriter(File file, boolean append)创建字符输出流关联本地文件,续写
FileWriter(String fileName, boolean append)创建字符输出流关联本地文件,续写

3.2.2 成员方法

FileWriter 的成员方法:

成员方法说明
void write(int c)写出一个字符
void write(char[] cbuf)写出一个字符串
abstract void write(char[] cbuf, int off, int len)写出一个字符串的一部分
void write(String str)写出一个字符数组
void write(String str, int off, int len)写出字符数组的一部分

3.2.3 使用步骤

FileWriter 的使用步骤:

// 创建字符输出流对象
FileWriter fw = new FileWriter("a.txt");     
// 写出数据
fw.write(25105);// 我
fw.write("你好");// 你好

fw.write(97); // 写出第1个字符
fw.write('b'); // 写出第2个字符
fw.write('C'); // 写出第3个字符
fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。

fw.close();

注:关闭资源时,与 FileOutputStream 不同。如果不关闭,数据只是保存到缓冲区,并未保存到文件。

3.2.4 字符输出流原理

创建字符输出流对象时,关联文件,并创建缓冲区(长度为8192的字节数组)。

Java —— IO流详解 写数据先写到缓冲区。

从缓冲区写到文件中的几种情况:

  1. 缓冲区装满了,直接写出到文件。
  2. 手动刷新。flush()方法,刷新之后,还可以继续往文件里写出数据。
  3. 释放资源,关流。close()方法。断开通道,无法继续写出。

4. 缓冲流

Java —— IO流详解 缓冲流提高了性能:缓冲区自带长度8192的缓冲区。

由于字符流自带缓冲区,所以对于字符流提升不明显

对于字符流而言关键点是字符缓冲流中有两个特有的方法,这是字符流不具有的。

4.1 字节缓冲流

原理:底层自带了长度为8192的缓冲区提高性能。

字节缓冲流提高效率的原理:节省与硬盘打交道的时间,内存操作比磁盘操作快很多

Java —— IO流详解

构造方法

方法名称说明
public BufferedInputStream(InputStream in)把基本流包装成高级流,提高读取数据的性能。
public BufferedOutputStream(OutputStream out)把基本流包装成高级流,提高写出数据的性能。

使用步骤:以拷贝文件为例

一次一个字节

public static void main(String[] args) throws IOException {
    // 创建缓冲流对象
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\a.txt"));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.txt"));
    int b;
    while ((b = bis.read()) != -1){
        bos.write(b);
    }
    // 释放资源,直接调用缓冲流close(),会先关闭基本流。
    bos.close();
    bis.close();
}

一次多个字节

// 创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.txt"));
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
    bos.write(bytes, 0, len);
}
// 释放资源,直接调用缓冲流close(),会先关闭基本流。
bos.close();
bis.close();

4.2 字符缓冲流

原理:底层自带缓冲区提高性能

构造方法:

方法名称说明
public BufferedReader(Reader in)把基本流变成高级流
public BufferedWriter(Writer out)把基本流变成高级流

字符缓冲流的特有方法(字符流不具有):

方法名称说明
public String readLine()输入流方法,读取一行数据,如果没有数据可读了,会返回null
public void newLine()输出流方法,跨平台换行

5. 转换流

转换流:字符流和字节流之间的桥梁。

使用场景:字节流想使用字符流中的方法。

Java —— IO流详解

Java —— IO流详解

6. 序列化流

Java —— IO流详解

6.1 序列化流

序列化流/对象操作输出流:可以把Java中的对象写到本地文件中。

构造方法public ObjectOutputStream(OutputStream out),把基本流包装成高级流。

成员方法public final void writeObject (Object obj),把对象序列化(写出)到文件中去。

public static void test() throws IOException {

    Student student = new Student("zhang", 18);
    
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
    
    oos.writeObject(student);
    
    oos.close();
}

使用对象输出流将对象保存到文件时可能会出现java.io.NotSerializableException异常,需要实现序列化接口:

//让对象实现接口,使其可以序列化
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;//版本号
    private String name;
    private int age;

    public Student(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;
    }
}

瞬态关键字 transientprivate transient String name;,不会将当前属性序列化到本地文件中

6.2 反序列化流

反序列化流/对象操作输入流:可以把序列化到本地文件中的对象,读取到程序中来。

构造方法public ObjectInputStream(InputStream in),把基本流包装成高级流

成员方法public final Object readObject (),将之前使用 ObjectOutputStream 序列化的原始数据恢复为对象。

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));

Object o = ois.readObject();

System.out.println(o);//Student{name='zhang', age=18}

ois.close();

6.3 序列化流可能发生的问题

序列化/反序列化版本号问题:对象类实现序列化接口,这个对象可以被序列化,根据这个类中的内容计算出一个序列号(可以理解为版本号),创建对象时会将版本号一同写入本地文件中,若此时修改类中代码,会重新计算版本号。

这时,反序列化将对象读取到内存中,两个版本号不同,代码报错。文件中版本号和javabean的版本号不匹配。

解决方法:在类中添加版本号 serialVersionUID,这样编译器就不会自动为其设置版本号,而是在编译时直接写入指定的序列化版本号。

7. 打印流

打印流:不能输入,只能写出Java —— IO流详解

打印流的几个特点:

  1. 打印流只能操作文件目的地,不能操作数据源。

  2. 特有的写出方法可以实现数据原样写出。(98 -> 98)

  3. 特有的写出方法,可以实现自动刷新,自动换行。

    打印一次数据 = 写出 + 换行 + 刷新

7.1 字节打印流

7.1.1 构造方法

构造方法说明
public PrintStream(OutputStream/File/String)关联字节输出流/文件/文件路径(底层都实现字节流)
public PrintStream(String fileName, CharSet charSet)指定字符编码
public PrintStream(OutputStream out, boolean autoFlush)自动刷新(字节流底层无缓存区,开不开都一样)
public PrintStream(OutputStream out, boolean autoFlush, String encoding)指定字符编码且自动刷新

7.1.2 成员方法

成员方法说明
public void write(int b)常规方法:规则跟之前一样,将指定的字节读出。(97 -> a)
public void println(XXX xx)特有方法:打印任意数据,自动刷新,自动换行。(原样输出)
public void print(XXX xx)特有方法:打印任意数据,不换行。(原样输出)
public void print(String format, Object...args)特有方法:带有占位符的打印语句,不换行。(原样输出)

7.2 字符打印流

字符流,底层有缓冲区,想要自动刷新需要开启。

7.2.1 构造方法

构造方法说明
public PrintWriter(Writer/File/String)关联字节输出流/文件/文件路径
public PrintWriter(String fileName, CharSet charSet)指定字符编码
public PrintWriter(Writer out, boolean autoFlush)自动刷新
public PrintWriter(Writer out, boolean autoFlush, String encoding)指定字符编码且自动刷新

7.2.2 成员方法

成员方法说明
public void write(int b)常规方法:规则跟之前一样,将指定的字节读出。(97 -> a)
public void println(XXX xx)特有方法:打印任意数据,自动刷新,自动换行。(原样输出)
public void print(XXX xx)特有方法:打印任意数据,不换行。(原样输出)
public void print(String format, Object...args)特有方法:带有占位符的打印语句,不换行。(原样输出)

获取打印流对象,此打印流在虚拟机启动时,由虚拟机创建,默认指向控制台。

特殊的打印流,系统中的标准输出流,是不能关闭的,在系统中是唯一的:

PrintStream ps = System.out;
ps.println("123");

8. 压缩流/解压缩流

Java —— IO流详解

压缩包里面每一个文件都是一个ZipEntry。

8.1 解压缩流

解压本质:把每一个ZipEntry按照层级拷贝到本地另一个文件夹中。

直接上代码:

获取解压的源路径和目标路径:

public static void main(String[] args) throws IOException {
    //待解压的压缩包路径
    File src = new File("C:\Users\Administrator\Desktop\a.zip");
    //解压目的地
    File dest = new File("C:\Users\Administrator\Desktop\");
    unzip(src, dest);

}

传入地址,进行解压:

public static void unzip(File src, File dest) throws IOException {
    //创建一个解压缩流。
    ZipInputStream zis = new ZipInputStream(new FileInputStream(src));
    //获取压缩包中每一个zipEntry对象
    ZipEntry entry;
    while((entry = zis.getNextEntry()) != null){
        //打印测试
        System.out.println(entry);
        if(entry.isDirectory()){
            //文件夹,在目的地dest处创建一个同样的文件夹
            File file = new File(dest, entry.toString());
            file.mkdirs();
        }else {
            //文件,读取压缩包中的文件,并把它存到目的地dest文件夹中
            FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
            int b;
            while ((b = zis.read()) != -1){
                fos.write(b);
            }
            fos.close();
            //表示在压缩包中的一个文件处理完了
            zis.closeEntry();
        }
    }
    zis.close();
}

8.2 压缩流

  1. 将文件D:\a.txt压缩成一个压缩包:
public static void main(String[] args) throws IOException {
    //需压缩文件的路径
    File src = new File("D:\a.txt");
    //压缩包的路径
    File dest = new File("D:\ ");
    //压缩
    toZip(src,dest);
}


public static void toZip(File src, File dest) throws IOException {
    // 创建压缩流关联压缩包
    ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
    // 创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
    // 压缩包里面的路径
    ZipEntry entry = new ZipEntry("aaa\bbb\a.txt");
    // 把ZipEntry对象放到压缩包当中
    zos.putNextEntry(entry);
    // 把src文件中的数据写到压缩包当中
    FileInputStream fis = new FileInputStream(src);
    int b;
    while((b = fis.read()) != -1){
        zos.write(b);
    }
    zos.closeEntry();
    zos.close();
}
  1. D:\aaa文件夹 压缩成一个压缩包:
public static void main(String[] args) throws IOException {
    // 要压缩的文件夹路径
    File src = new File("D:\aaa");
    // 压缩包的父级路径
    File destParent = src.getParentFile();//D:\
    // 压缩包的路径
    File dest = new File(destParent, src.getName() + ".zip");
    // 创建压缩流关联压缩包
    ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
    // 获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
    toZip(src, zos, src.getName());//aaa
    // 释放资源
    zos.close();
}


public static void toZip(File src, ZipOutputStream zos, String name) throws IOException {
    // 进入src文件夹
    File[] files = src.listFiles();
    // 遍历
    for (File file : files) {
        if(file.isFile()){
            // 文件,变成ZipEntry对象,放入到压缩包当中
            ZipEntry entry = new ZipEntry(name + "\ " + file.getName());//aaa\a\a.txt
            zos.putNextEntry(entry);
            //读取文件中的数据,写到压缩包
            FileInputStream fis = new FileInputStream(file);
            int b;
            while((b = fis.read()) != -1){
                zos.write(b);
            }
            fis.close();
            zos.closeEntry();
        }else{
            // 文件夹,递归
            toZip(file, zos, name + "\ " + file.getName());
            //     a         aaa     \       a
        }
    }
}
转载自:https://juejin.cn/post/7313758022485065767
评论
请登录