Java基础(下)之IO流

IO流-存储和读取数据的解决方案

image
image

IO流的体系

image
image

字节流 以FileOutputStream/FileInputStream为例

FileOutputStream

基本流程

class Main {
    public void main(String[] args) throws IOException {
        //1.创建字节输出流对象
        FileOutputStream fos = new FileOutputStream("data\\a.txt",false);
        //2.写入数据
        String str = "kankelaoyezuishuai";
        fos.write(str.getBytes());
        //3.释放资源
        fos.close();
    }
}
创建字节输出流 new FileOutputStream

注意:

  1. 参数是字符串表示的路径或File对象均可
  2. 若文件不存在则会创建该文件,但必须保证父级路径是存在的
  3. 若文件中已有数据,是否会覆盖文件中的数据,取决于第二个名为append的boolean参数
写数据 write

image
注意:

  1. 在不同系统中对换行的字符规定不同,win:\r\n,Linux:\n,Mac:\r。在Windows OS中,Java对换行进行了优化,程序中写\r或\n可实现换行,因为Java在底层会补全。开发中建议写全。
释放资源 close

每次使用完流之后务必释放资源

FileInputStream

基本流程

class Main {
    public void main(String[] args) throws IOException {
        File f1 = new File("data\\a.txt");

        //1.创建输入流对象
        FileInputStream fis = new FileInputStream(f1);
        StringBuilder sb = new StringBuilder();

        //2.循环读取数据,直到文件末尾
        int c;
        while((c = fis.read()) != -1){
            sb.append((char)c);
        }
        System.out.println(sb);

        //3.释放资源
        fis.close();
    }
}
创建字节输入流 new FileInputStream

注意:

  1. 若文件不存在,则直接报错
读数据 read

image

注意:

  1. 若读到文件末尾,read方法将返回-1
  2. read()一次读取一个字节,速度较慢,返回的是读取数据本身。read(byte[])一次读取多个字节并放入数组,具体与数组长度有关,返回的是读取的字节数据个数。
释放资源 close

每次使用完流之后务必释放资源

字节流相关的异常处理

image

class Main {
    public static void main(String[] args) throws IOException {
        File f1 = new File("data\\video.mp4");
        File f2 = new File("data\\video_copy.mp4");

        //1.创建输入流对象
        long start = System.currentTimeMillis();
        try(FileInputStream fis = new FileInputStream(f1);
        FileOutputStream fos = new FileOutputStream(f2);){

            //2.循环读取数据,直到文件末尾
            byte[] buf = new byte[1024 * 1024];
            int len;
            while((len = fis.read(buf)) != -1){
                fos.write(buf,0,len);
            }
        }catch (IOException e){
            System.out.println(e.getMessage());
        }
        long end = System.currentTimeMillis();
        long dur = end - start;
        System.out.println("文件拷贝耗时:"+ dur + " ms");
    }
}

示例代码:拷贝一个大文件并测量用时

class Main {
    public static void main(String[] args) throws IOException {
        File f1 = new File("data\\video.mp4");
        File f2 = new File("data\\video_copy.mp4");

        //1.创建输入流对象
        FileInputStream fis = new FileInputStream(f1);
        FileOutputStream fos = new FileOutputStream(f2);

        long start = System.currentTimeMillis();
        //2.循环读取数据,直到文件末尾
        byte[] buf = new byte[1024 * 1024];
        int len;
        while((len = fis.read(buf)) != -1){
            fos.write(buf,0,len);
        }

        //3.释放资源
        fis.close();
        fos.close();
        long end = System.currentTimeMillis();
        long dur = end - start;
        System.out.println("文件拷贝耗时:"+ dur + " ms");
    }
}

字符集

image

GBK

image
image

Unicode 万国码

image
UTF-16编码规则:用2-4个字节保存
UTF-32编码规则:固定使用4个字节保存
UTF-8编码规则:用1-4个字节保存
image
注意:UTF-8不是一个字符集,而是Unicode字符集的一种编码方式。

为什么会产生乱码

原因1:读取数据时未读完整个汉字
例如,使用字节流读取数据时一次只能读取一个字节,而汉字一般由多个字节表示。因此不要用字节流读取文本。
原因2:编码和解码的方式不统一
image

Java中编解码的实现

image

class Main {
    public static void main(String[] args) throws IOException {
        File f1 = new File("data\\video.mp4");
        File f2 = new File("data\\video_copy.mp4");
        String str = "你好,Java!";
        byte[] code_utf8 = str.getBytes();//默认使用UTF-8编码
        byte[] code_gbk = str.getBytes("GBK");//GBK
        System.out.println(Arrays.toString(code_utf8));
        System.out.println(Arrays.toString(code_gbk));
        System.out.println(new String(code_utf8));//默认使用UTF-8解码
        System.out.println(new String(code_gbk));//编解码方式不一致则会乱码
        System.out.println(new String(code_gbk,"GBK"));//正确的编解码不会乱码
    }
}

字符流 以FileReader和FileWriter为例

image
image

FileReader

class Main {
    public static void main(String[] args) throws IOException {
        // 1.创建字符输入流对象
        File f1 = new File("data\\a.txt");
        FileReader fr =new FileReader(f1);
        
        // 2.读取数据(逐个读取)
        int ch;
        while((ch = fr.read()) != -1){
            System.out.print((char)ch);
        }
		
		//2.读取数据(连续读取)
		char[] buf = new char[1 * 1024 * 1024];
        int len;
        while((len = fr.read(buf)) != -1){
            System.out.println(new String(buf,0,len));
        }
        
        //3.释放资源
        fr.close();
    }
}

基本流程

创建字符输入流对象

image
注意:路径不存在会报错。

读取数据 read

image
问题:UTF-8中一个汉字占用了3个字节,而Java中char类型变量占用2个字节,为什么1个char变量接收并存储read返回的1个汉字字符?
解释:在 Java 的体系结构中,文件存储的编码格式与 JVM 堆内存中的字符编码格式是完全解耦的。FileReader 之所以能将 3 字节的 UTF-8 汉字放入 2 字节的 char 中,是因为在这个过程中发生了一次解码与重新编码

  • 外部存储:(UTF-8):这是一种变长编码方案,主要用于磁盘存储和网络传输以节省空间。在 UTF-8 中,常用的汉字确实被编码为 3 个字节。
  • 内部内存(UTF-16):Java 语言规范规定,JVM 内存中的 char 类型固定采用 UTF-16 编码表示。在 UTF-16 中,基本多语言平面(BMP,包含了绝大多数常用汉字)内的所有字符,都统一使用 2 个字节(16 bits)来表示。

当调用 FileReader.read() 读取一个汉字时,数据流转经历了以下三个关键阶段:

  1. 字节读取:FileInputStream 从底层操作系统的文件描述符中读取出 3 个连续的字节。
  2. 查表与解码(关键步骤):FileReader 内部封装的 StreamDecoder 会根据指定的字符集(此处为 UTF-8),识别出这 3 个字节属于同一个逻辑字符,并计算出它的 Unicode 代码点(Code Point)。
  3. 内存重塑:获取到代码点后,JVM 会将其转换为内部的 UTF-16 格式。对于常用汉字,该代码点的值恰好落在 0x0000 到 0xFFFF 之间,因此完全可以装入一个 16 位的 char 变量中。
释放资源

image

FileWriter

基本流程

创建字符输出流

image
注意:

  1. 参数是字符串表示的路径或File对象均可
  2. 若文件不存在则会创建该文件,但必须保证父级路径是存在的
  3. 若文件中已有数据,是否会覆盖文件中的数据,取决于第二个名为append的boolean参数
写入数据

image
如果write方法的参数是整数(字符),实际上写到文件中的是经过字符集编码后的整数(字符)

释放资源

每次使用完成后务必释放资源

字符输入流底层原理

  1. 创建字符输入流对象
    底层:关联文件,并创建缓冲区(长度为8192的字节数组)
  2. 读取数据
    底层:先判断缓冲区中是否有数据可读取,若有数据,直接从缓冲区读取;若没有数据,则从文件中读取数据到缓冲区,每次尽可能装满缓冲区,若文件中也无数据可读,返回-1
    问题:文件中含有8192个a和bcdefg,下列代码打印的结果是什么?
class Main {
    public static void main(String[] args) throws IOException {
        File f1 = new File("data\\a.txt");
        FileReader fr = new FileReader(f1);
        int c = fr.read();
        FileWriter fw = new FileWriter(f1);
        while((c = fr.read()) != -1){
            System.out.print((char)c);
        }
        fw.close();
        fr.close();
    }
}

解答:打印的结果为8192个a,因为int c = fr.read()会读取文件内容直到8192个字节的缓冲区满,接着fw = new FileWriter(f1)清空了文件,while循环中fr.read()一直从缓冲区中读数据,直到缓冲区为空,再从文件中读取时发现无数据可读,返回-1结束

字符输出流底层原理

  1. 创建字符输出流对象
    底层:关联文件,并创建缓冲区(长度为8192的字节数组)
  2. 写入数据
    一般情况下,FileWriter的write方法不会立刻将数据写入文件,而是写入缓冲区中,当缓冲区满 or flush手动刷新 or close方法关闭时,才会将缓冲区数据写入文件中。
    image

高级流

image

缓冲流

image

字节缓冲流 提高读写数据的性能

image

基本流程

同基本字节流

public class HelloWorld {
    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("data\\a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("data\\b.txt"));
//        FileInputStream bis = new FileInputStream("data\\a.txt");
//        FileOutputStream bos = new FileOutputStream("data\\b.txt");
        int b;
        while((b = bis.read()) != -1){
            bos.write(b);
        }
        bis.close();
        bos.close();
        long end = System.currentTimeMillis();
        System.out.printf("用时:%d ms",end - start);
    }
}
原理(缓冲流与基本流的本质区别)

提速的原因在于缓冲流实例内部持有一个名为 buf 的字节数组(默认大小 8192 字节),以及用于记录读取状态的游标变量,极大地减少了硬盘IO操作导致的系统调用的次数,而基本字节流本身不主动持有应用层缓冲区。这也是缓冲流与基本流的本质区别。
image
但是缓冲流并不总是比基本流快,特别是在代码中已经手动定义了足够大的缓冲区

FileInputStream fis = new FileInputStream("data.mp4");
byte[] myBuf = new byte[8192];
fis.read(myBuf);

在这种情况下,FileInputStream 同样只产生 1 次系统调用。
此时如果你再套上一层 BufferedInputStream,不仅不会提升速度,反而会降低性能。因为数据会经历两次内存拷贝:操作系统 \(\rightarrow\) 缓冲流的 buf 数组 \(\rightarrow\) 你的 myBuf 数组。

基本流VS缓冲流的拷贝速度对比
public class HelloWorld {
    public static void main(String[] args) throws IOException {
        File f1 = new File("data\\a.txt");
        File f2 = new File("data\\b.txt");
        FileInputStream fis = new FileInputStream(f1);
        FileOutputStream fos = new FileOutputStream(f2);
        long start = System.currentTimeMillis();
        int data;
        while((data = fis.read()) != -1){
            fos.write(data);
        }
        long end = System.currentTimeMillis();
        System.out.printf("基本流逐字节读取%d大小的文件,用时:%d\n",f1.length(),end - start);
        fis.close();
        fos.close();

        fis = new FileInputStream(f1);
        fos = new FileOutputStream(f2);
        start = System.currentTimeMillis();
        byte[] buf = new byte[1024 * 1024];
        int len;
        while((len = fis.read(buf)) != -1){
            fos.write(buf,0,len);
        }
        end = System.currentTimeMillis();
        System.out.printf("基本流批量读取%d大小的文件,用时:%d\n",f1.length(),end - start);
        fis.close();
        fos.close();

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f1));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(f2));
        start = System.currentTimeMillis();
        while((data = bis.read()) != -1){
            bos.write(data);
        }
        end = System.currentTimeMillis();
        System.out.printf("缓冲流逐字节读取%d大小的文件,用时:%d\n",f1.length(),end - start);
        bis.close();
        bos.close();

        bis = new BufferedInputStream(new FileInputStream(f1));
        bos = new BufferedOutputStream(new FileOutputStream(f2));
        start = System.currentTimeMillis();
        while((len = bis.read(buf)) != -1){
            bos.write(buf,0,len);
        }
        end = System.currentTimeMillis();
        System.out.printf("缓冲流批量读取%d大小的文件,用时:%d\n",f1.length(),end - start);
        bis.close();
        bos.close();
    }
}

image

字符缓冲流

image
注意:readLine不会将行末的换行符\n读入字符串。

两个特有方法

image

原理

这两个类在基础字符流之上,额外开辟了一个字符缓冲区(Character Buffer,即 char[] cb,默认 8192 个字符)。
性能差异(不明显)的本质:
在字符流的场景下,BufferedReader 提升的不再仅仅是减少“系统调用”的次数(这部分已经被 StreamDecoder 做了很大一部分),而是极大地减少了“解码操作”的频率与跨类方法调用的开销。它将高频的单字符解码,转换为了低频的批量解码。

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

image
image

作用1:使用特定字符集编码读写文本文件(现已淘汰)

FileReader 在早期的 API 设计中存在一个广受诟病的缺陷:它没有提供可以传入字符集参数的构造方法。
Java 11 之前:为了强制使用特定编码读取文件,开发者无法使用 FileReader,只能手动组合转换流与字节流:

// 经典且繁琐的写法
InputStreamReader isr = new InputStreamReader(new FileInputStream("data.txt"), "GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f1),"GBK");

Java 11 及以后:官方修正了这一缺陷,为 FileReader 补充了重载的构造方法。
在严谨的系统级开发中,隐式依赖默认环境配置是产生诡异 BUG 的根源。无论当前的 JDK 版本如何,符合工程规范的做法是永远显式声明字符集:

// Java 11+ 推荐写法
FileReader fr = new FileReader(file, Charset.forName("GBK"));
FileWriter fw = new FileWriter(file,Charset.forName("GBK"));

作用2:字节流使用字符流的特定方法

利用字节流读取文件中数据,每次读一行,且不能出现乱码

public class HelloWorld {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("data\\a.txt");

        InputStreamReader isr = new InputStreamReader(fis,"GBK");//字节流读取中文会乱码,因此需要通过转换流包装为Reader字符流
        BufferedReader br = new BufferedReader(isr);//为了使用缓冲流特有的读写整行的方法,因此需要包装为字符缓冲流
        String line;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }
        fis.close();
    }
}

练习:将GBK文本文件转换UTF-8

public class HelloWorld {
    public static void main(String[] args) throws IOException {
        File f1 = new File("data\\b.txt");
        File f2 = new File("data\\c.txt");

        BufferedReader br = new BufferedReader(new FileReader(f1,Charset.forName("GBK")));
        BufferedWriter bw = new BufferedWriter(new FileWriter(f2,Charset.forName("utf-8")));

        String line;
        while((line = br.readLine()) != null){
            bw.write(line);
            bw.newLine();
        }
        br.close();
        bw.close();
    }
}

序列化流/对象操作输出流:持久化写Java中的对象

序列化流可以把Java的对象到文件中,属于字节流的一种。
注意:

  1. 使用序列化流操作对象时,需要让JavaBean类实现Serializable接口。否则会出现NotSerializableException异常
  2. 在序列化对象时,若不想保存某个属性,可使用transient关键字来修饰该属性
    image
package com.example.helloworld;
import java.io.*;
import java.util.*;

/// Serializable接口中没有抽象方法,属于标记型接口
/// 一旦实现了这个接口,表明当前类可以被序列化
/// 可序列化是类的对象可以使用序列化流的前提
class Student implements Serializable{
    String name;
    int age;
    public Student(String name,int age){
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) throws IllegalArgumentException{
        if (age <= 0 || age > 150) {
            throw new IllegalArgumentException();
        }
        this.age = age;
    }

    public String getName() {

        return name;
    }

    public void setName(String name) throws IllegalArgumentException{
        if (name.length() < 3 || name.length() > 10) {
            throw new IllegalArgumentException();
        }
        this.name = name;
    }
}

class Main {
    public static void main(String[] args) throws IOException {
        //1.创建对象
        Student s1 = new Student("yxc",18);
        
        //2.创建序列化流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data\\stu.txt"));

        //3.写入对象到文件
        oos.writeObject(s1);

        //4.关闭流
        oos.close();
    }
}

反序列化流/对象操作输入流

反序列化流可以把Java的对象从文件中读,属于字节流的一种。
image

   //1.创建序列化流
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data\\stu.txt"));
   //2.从文件中读取对象
   Student stu = (Student)ois.readObject();
   //3.访问对象
   System.out.println(stu);
   //4.关闭流
   ois.close();

注意:当执行 ois.readObject() 时,ObjectInputStream 会读取流中记录的版本号,并与本地 JVM 内存中加载的类的版本号进行严格的相等性校验。一旦发现两者不匹配,为了防止由于结构不一致导致的内存越界或状态损坏(例如试图将数据反序列化到已被删除的字段中),JVM 会立即阻断并抛出 InvalidClassException。反序列化对象时需要保证当前对象的类拓扑结构没有发生变化。若要强制读取对象,则需要手动设置类的版本号serialVersionUID,示例如下:
image

/// Serializable接口中没有抽象方法,属于标记型接口
/// 一旦实现了这个接口,表明当前类可以被序列化
/// 可序列化是类的对象可以使用序列化流的前提
class Student implements Serializable{

    @Serial
    private static final long serialVersionUID = 1L;//手动设置类的版本号

    private String name;
    private int age;
    private transient String address;//使用transient修饰的属性不会被序列化

    public Student(String name,int age,String address){
        this.age = age;
        this.name = name;
        this.address = address;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) throws IllegalArgumentException{
        if (age <= 0 || age > 150) {
            throw new IllegalArgumentException();
        }
        this.age = age;
    }

    public String getName() {

        return name;
    }

    public void setName(String name) throws IllegalArgumentException{
        if (name.length() < 3 || name.length() > 10) {
            throw new IllegalArgumentException();
        }
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return this.name + " " + Integer.toString(this.age) + " " + this.address;
    }
}

建议:
Java 原生的序列化机制由于存在严重的安全漏洞(如反序列化任意代码执行攻击)且性能低下,在现代分布式系统架构中已不被推荐使用。

练习:批量化序列化和反序列化对象

package com.example.helloworld;
import java.io.*;
import java.util.*;

/// Serializable接口中没有抽象方法,属于标记型接口
/// 一旦实现了这个接口,表明当前类可以被序列化
/// 可序列化是类的对象可以使用序列化流的前提
class Student implements Serializable{

    @Serial
    private static final long serialVersionUID = -7885932473776034884L;
    private String name;
    private int age;
    private String address;//使用transient修饰的属性不会被序列化

    public Student(String name,int age,String address){
        this.age = age;
        this.name = name;
        this.address = address;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) throws IllegalArgumentException{
        if (age <= 0 || age > 150) {
            throw new IllegalArgumentException();
        }
        this.age = age;
    }

    public String getName() {

        return name;
    }

    public void setName(String name) throws IllegalArgumentException{
        if (name.length() < 3 || name.length() > 10) {
            throw new IllegalArgumentException();
        }
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return this.name + " " + Integer.toString(this.age) + " " + this.address;
    }

    @Override
    public boolean equals(Object obj) {
        // 1. 检查是否是同一个对象的引用
        if(obj == this) return true;

        // 2. 检查是否为空以及类类型是否相同
        if(obj == null || this.getClass() != obj.getClass()) return false;

        // 3. 强转并比较各个属性的值
        Student stu = (Student) obj;
        return age == stu.age && address.equals(stu.address) && name.equals(stu.name);
    }
}

class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student s1 = new Student("ljm",21,"引镇");
        Student s2 = new Student("zld",23,"纺织城");
        Student s3 = new Student("王二",25,"Singapore");

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data\\stu.txt"));
//        oos.writeObject(s1);
//        oos.writeObject(s2);
//        oos.writeObject(s3);
        List<Student>list = new ArrayList<>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        oos.writeObject(list);

        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data\\stu.txt"));
        List<Student>list1 = (ArrayList<Student>)ois.readObject();
        ois.close();

        System.out.println(list1);
        System.out.println(list1.equals(list));//必须重写类的equals方法,否则一直返回false
    }
}

打印流

image
打印流分为PrintStream(字节打印流)和PrintWriter(字符打印流)组成。
特点:

  1. 打印流只操作文件目的地,不操作数据源
  2. 其特有的写出方法可以实现,数据原样写出,例如打印97,true到文件中
  3. 其特有的写出方法可以实现,自动刷新,自动换行,打印一次数据 = 写出 + 换行 + 刷新

字节打印流 System.out的类

image
image

class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.创建字节打印流
        PrintStream ps = new PrintStream(new FileOutputStream("data\\a.txt"));
        PrintStream ps1 = System.out;//特殊的字节打印流,由虚拟机创建,指向控制台,在系统中是唯一的,无法关闭
        //2.输出
        ps.println(97);//数据原样写出
        ps.println(true);
        ps.printf("%s和%s共有%d人\n","zld","ljm",2);
        //3.释放资源
        ps.close();
		ps1.close();
		ps1.println("猜猜我能执行吗?");
    }
}

字符打印流

image
image

class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        PrintWriter pw = new PrintWriter(new FileOutputStream("data\\a.txt"),true);

        pw.println(108);
        pw.print("你好Javaweb");
        pw.printf("你好%s,%d","zld",123);
        pw.close();
    }
}

解压缩流/压缩流

image

解压缩流

/// 解压zip文件到指定目录
public static void unzip(String zipfile,String dst) throws IOException{
	File f1 = new File(zipfile);
	ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(f1)));
	ZipEntry entry;
	while((entry = zis.getNextEntry()) != null){
		System.out.println(entry);
		if(entry.isDirectory()){
			File target_dir = new File(dst,entry.getName());
			if(!target_dir.mkdirs()){
				throw new ZipException("Failed to unzip" + zipfile);
			}
		}else{
			File target_file = new File(dst,entry.getName());
			BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target_file));
			byte[] data = new byte[1024];
			int len;
			while((len = zis.read(data)) != -1){
				bos.write(data,0,len);
			}
			bos.close();
			zis.closeEntry();//表示压缩包中的一个文件处理完成
		}
	}
}

压缩流

/// zos:压缩包的输出流
/// src:当前要加入压缩包的文件或目录
/// src_root:要加入压缩包的源文件或目录
public static void zip(ZipOutputStream zos, String src_root,String src) throws IOException {

	File src_path = new File(src);
	String root_path = new File(src_root).getAbsolutePath();
	if (src_path.isFile()) {
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src_path));
		ZipEntry entry = new ZipEntry(src_path.getName());
		zos.putNextEntry(entry);
		byte[] data = new byte[1024];
		int len;
		while ((len = bis.read(data)) != -1) {
			zos.write(data, 0, len);
		}
		bis.close();
		zos.closeEntry();
		return;
	}
	for (File file : src_path.listFiles()) {
		if (file.isFile()) {
			String relativePath = file.getAbsolutePath().substring(root_path.length() + 1);
			ZipEntry entry = new ZipEntry(relativePath);
			zos.putNextEntry(entry);
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
			byte[] data = new byte[1024];
			int len;
			while ((len = bis.read(data)) != -1) {
				zos.write(data, 0, len);
			}
			bis.close();
			zos.closeEntry();
		}else{
			zip(zos,root_path, file.getAbsolutePath());
		}
	}
}

commons-io

image
image
image
image

Hutool工具包

posted @ 2026-03-03 16:06  安河桥北i  阅读(3)  评论(0)    收藏  举报