IO流(二)

高级流

在IO流(一)中我们学习了IO流的基本输入输出流,以及一个高级流——缓冲流,这篇文章我们继续学习IO高级流。

常见错误===========

Exception in thread “main” java.io.FileNotFoundException: D:\bbb (拒绝访问。)
这可能是因为你的代码中的某处将D:\bbb这个文件打开了,正在操作它,但是在另一处地方又试图打开它操作它,所以在另一处代码就会报这个错误。

一、转换流

转换流字符流的一种
在这里插入图片描述
转换流的作用:
它可以将内容以字节读入内存,然后按照指定的字符集转换为文本。
在这里插入图片描述

练习

做两个练习来认识了解转换流:

练习一

在这里插入图片描述需求1代码: 手动创建一个GBK的文件,把文件中的中文读取到内存中,不能出现乱码

/**
 * @author Watching
 * * @date 2023/5/23
 * * Describe:
 * 使用转换流指定字符集读取文件内容
 * 读取d.txt的文件内容,d.txt的编码格式为GBK
 */
public class ConvertStreamDemo {
    public static void main(String[] args) throws IOException {

        /**
         * 但是这种方式在jdk11的时候被替代了
         * 在JDK11中,FileReader有了一个新的构造方法
         * new FileReader(File file,Charset charset);
         * 这个构造方法可以直接指定读取时所用的字符集,就不需要转换流了
         */
        //指定读取的字符集为GBK
//        InputStreamReader isr = new InputStreamReader(new FileInputStream("d.txt"),"GBK");
//        int read;
//        while ((read = isr.read()) != -1){
//            System.out.print((char)read);
//        }
//        isr.close();

    }
}

需求2代码:把一段中文按照GBK的方式写到本地文件

转换流可以指定读入和输出的字符集

/**
 * @author Watching
 * * @date 2023/5/23
 * * Describe:
 * 使用转换流以指定的字符集输出数据
 */
public class ConvertStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /**
         * 这种写法在JDK11同样被代替了,我们可以使用FileWriter的新构造方法
         * new FileWriter(File file,Charset charset)
         */
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("e.txt"),"GBK");
        osw.write("你好,这段文字的编码为GBK");
        osw.close();
    }
}

需求3代码:将本地文件中的GBK文件,转成UTF-8
转换流可以指定读入和输出的字符集

/**
 * @author Watching
 * * @date 2023/5/23
 * * Describe:
 * 使用字节转换流将编码为GBK的文件内容读取,并以UTF-8的编码方式写出到另一个文件
 */
public class ConvertStreamDemo2 {
    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("d.txt"),"GBK");
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("f.txt"), StandardCharsets.UTF_8);
        char[] chars = new char[1024];
        int len;
        while((len = isr.read(chars)) != -1){
            osw.write(chars,0,len);
        }
        osw.close();
        isr.close();
        /**
         * 当然也可以写jdk11的替代方案
         */
    }
}

练习二

在这里插入图片描述
利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
1.字节流在读中文的时候会出现乱码,所以需要字符流来处理
2.字节流是没有读取一整行数据的方法的,自由字符缓冲流才能处理

/**
 * @author Watching
 * * @date 2023/5/23
 * * Describe:
 * 利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
 * //1.字节流在读中文的时候会出现乱码,所以需要字符流来处理
 * //2.字节流是没有读取一整行数据的方法的,自由字符缓冲流才能处理
 */
public class ConvertStreamDemo3 {
    public static void main(String[] args) throws IOException {
        /**
         * 使用转换流包装字节输入流,获得可以指定读取字符集的方法
         * 再用字符缓冲流包装转换流,获得readLine()方法
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("d.txt"), "GBK"));
        String s ;
        while((s = br.readLine())!= null){
            System.out.println(s);
        }
    }
}

总结

在这里插入图片描述

二、【反】序列化流

序列化流是字节流的一种。
在这里插入图片描述

序列化流

序列化流的作用是可以将java对象写到本地文件中。
需要注意的是,被序列化的java对象所属的类必须实现了Serilizable接口
在这里插入图片描述
测试,将一个java对象写到本地文件中:

/**
 * @author Watching
 * * @date 2023/5/23
 * * Describe:
 * 将一个java对象写到本地文件中
 */
public class ObjectStreamDemo1 {
    public static void main(String[] args) throws IOException {
        Student student = new Student("tom","123");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("g.txt"));
        oos.writeObject(student);
    }
    public static class Student implements Serializable {
        public Student(){}
        public Student(String name,String id){
            this.id = id;
            this.name = name;
        }
        private String name;
        private String id;
    }
}

结果展示:
在这里插入图片描述

反序列化

在这里插入图片描述
将上面序列化的对象反序列化回来

/**
 * @author Watching
 * * @date 2023/5/24
 * * Describe:将序列化的对象反序列化
 */
public class ObjectStreamDemo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("g.txt"));
        Student student = (Student) ois.readObject();
        System.out.println(student);
        ois.close();
    }
}

结果展示:
在这里插入图片描述

序列化和反序列化需要注意的细节

1.当反序列化的目标类被修改后,反序列化会报错

Exception in thread “main” java.io.InvalidClassException: ObjectStream.Student; local class incompatible: stream classdesc serialVersionUID = -4039190925851938632, local class serialVersionUID = -1958645621702799953

这个错误的原因是反序列化的目标类被修改了,而这个类的对象在序列化前会根据类的属性方法等计算出一个Long型的序列号,在反序列化时会重新计算一边,如果序列化号发生了改变,就会出现上面的错误。
在开发中,很难保证javabean不被修改,我们为了保证可以成功反序列化,可以在javabean中添加一个属性

private static final Long serializableUID = 1L;//序列化号尽量用随机long型

这样的话在反序列化时,就算类被修改了,也能成功反序列化
2.如果存在不想序列化的属性
可以在属性前面添加一个transient关键字,这样的话这个属性就不会被序列化到本地文件了

    private transient String address;

练习

在这里插入图片描述
思路,通常情况下,我们不知道文件中会有几个序列化对象,所以在反序列化时并不好编写代码,所以我们默认在序列化时,将对象都放在集合中,反序列化时直接获得集合,再从集合中获取对象
序列化:

/**
 * @author Watching
 * * @date 2023/5/24
 * * Describe:
 * 一次性序列化多个对象
 */
public class ObjectStreamDemo3 {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("h.txt"));
        ArrayList<Student> arrayList = new ArrayList<>();
        arrayList.add(new Student("tom","1"));
        arrayList.add(new Student("jack","2"));
        arrayList.add(new Student("marry","3"));
        oos.writeObject(arrayList);
        oos.close();
    }
}

反序列化:

/**
 * @author Watching
 * * @date 2023/5/24
 * * Describe:
 */
public class ObjectStreamDemo4 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("h.txt"));
        ArrayList<Student> arrayList = (ArrayList<Student>) ois.readObject();
        ois.close();
        for (Object o : arrayList) {
            System.out.println(o);
        }

    }
}

三、打印流

字节打印流只有输出,没有输入。
在这里插入图片描述
打印流的分类和特点:
在这里插入图片描述

字节打印流

字节打印流的构造器:
字节流底层没有缓冲数组,所以flush也就没有意义,当然写了也不报错
在这里插入图片描述
字节打印流的成员方法:
1.字节打印流的特有方法可以实现数据原样输出,比如97输出就是97,而不是字符集对应的字符
2.printf方法的占位符有很多种,功能也不尽相同,可以自行了解
在这里插入图片描述

字符打印流

字符打印流的底层有缓冲区,所以效率要比字节打印流高,所以可以开启自动刷新flush
字符打印流的构造方法:
在这里插入图片描述
字符打印流的成员方法:
字符打印流的成员方法与字节打印流的成员方法基本相同
在这里插入图片描述

总结

在这里插入图片描述

四、压缩流

在这里插入图片描述

压缩流应用场景:
1.比如在开发种,要传输的数据很大,所以需要先压缩再传输
2.获取到一个压缩后的文件,我们需要先解压,才能获取到文件内容
3.在java中,只能识别zip格式的压缩文件。

解压缩

我们来编写代码测试以下解压缩流:
首先在磁盘中有一个文件,下面是它的文件结构:
在这里插入图片描述
文件夹aaa包含了文件夹bbb,文件夹ccc,以及3个txt文件
在这里插入图片描述
文件夹bbb包含了3个txt文件
在这里插入图片描述
文件夹ccc包含了3个txt文件
在这里插入图片描述
压缩流 ZipInputStreamgetNextEntry() 方法可以获取到压缩文件中的每一个文件,包括文件夹,返回文件是一个 ZipEntry对象 ,在读取到所有文件后将返回 null 而且在遇到文件夹时不需要我们进行递归处理,在底层它已经帮我们递归处理了,我们只需要循环遍历直到 getNextEntry() 方法返回 null

通过下面这段代码,我们可以了解到getNextEntry()方法的遍历方式。
它获取了所有文件。

/**
 * @author Watching
 * * @date 2023/5/25
 * * Describe:
 */
public class test3 {
    public static void main(String[] args) throws IOException {
        ZipInputStream zis = new ZipInputStream(new FileInputStream("aaa.zip"));
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null){
            System.out.println(entry.getName());
        }

    }
}

在这里插入图片描述
ZipEntry中还有很多方法,其中的isDirectory()是我们接下来要用到的。
在这里插入图片描述
注意,在每使用完一个ZipEntry之后需要将其关闭。

下面是解压缩的完整代码(记得在finally中关闭流)。
1.遍历ZipEntry,判断是目录还是文件
2.目录则在目的地址创建目录,
3.文件则使用FileoutputStream拷贝输出,zis是FileInputStream的包装类,可以用于读取压缩包中的文件的内容。

/**
 * @author Watching
 * * @date 2023/5/24
 * * Describe:解压缩,java只能识别zip格式的压缩文件
 */
public class ZipStreamDemo1 {
    public static void main(String[] args) throws IOException {
        //要解压的压缩文件
        File src = new File("aaa.zip");
        //解压到目的文件
        File dest = new File("aaa");
        unzip(src, dest);
    }

    //解压
    public static void unzip(File src, File dest) throws IOException {
        ZipInputStream zis = new ZipInputStream(new FileInputStream(src));
        ZipEntry entry;
        /**
         * 如果遇到文件夹,是不需要递归操作的,因为解压缩流底层做了递归操作,我们只需要循环遍历
         * 直到读到的entry为null就代表读完了所有文件。
         */
        while ((entry = zis.getNextEntry()) != null) {//获取压缩文件中的每一个ZipEntry对象
            if (entry.isDirectory()) {//如果是文件夹,需要在目标地址创建一个同名文件夹
                new File(dest,entry.getName()).mkdirs();
                zis.closeEntry();//表示压缩包里面的一个文件处理完毕了
            } else {//如果是文件,则先使用压缩流读取,再使用文件输出流输出到目标地址
                FileOutputStream fos = new FileOutputStream(new File(dest, entry.getName()));
                byte[] bytes = new byte[1024];
                int len;
                while ((len = zis.read(bytes)) != -1) {
                    fos.write(bytes, 0, len);
                }
                fos.close();
                zis.closeEntry();//表示压缩包里面的一个文件处理完毕了
            }
        }
        zis.close();
    }
}

压缩单个文件

1.创建压缩流关联压缩包
2.创建ZipEntry对象,表示要压缩进压缩包的文件的文件名
3.使用字节输入流读取要压缩的文件
4.使用压缩流将文件写入压缩包
最后记得关流

/**
 * @author Watching
 * * @date 2023/5/25
 * * Describe:
 * 压缩单个文件
 */
public class ZipStreamDemo2 {
    public static void main(String[] args) throws IOException {
        //要压缩的文件的地址
        File src = new File("a.txt");
        //目的地址
        File dest = new File("E:\\ideaFile\\ioStream\\myio");
        toZip(src, dest);
    }

    private static void toZip(File src, File dest) throws IOException {
        //1.创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "a.zip")));
        //2.创建ZipEntry对象,表示压缩包里的每一个文件和文件夹
        ZipEntry zipEntry = new ZipEntry("a.txt");
        //3.把zipEntry对象放进压缩包中
        zos.putNextEntry(zipEntry);
        //4.把src中的数据写入压缩包中
        FileInputStream fis = new FileInputStream(new File("a.txt"));
        int len;
        byte[] bytes = new byte[1024];
        while ((len = fis.read(bytes)) != -1) {
            zos.write(bytes,0,len);
        }
        fis.close();
        zos.closeEntry();
        zos.close();
    }
}

压缩文件夹

注意区分ZipInputStream和ZipOutputStream

/**
 * @author Watching
 * * @date 2023/5/25
 * * Describe:压缩文件夹
 * 压缩文件夹 E:\ideaFile\ioStream\myio\bbb
 */
public class ZipStreamDemo3 {
    public static void main(String[] args) throws IOException {
        //1.创建File对象表示要压缩的文件夹
        File src = new File("E:\\ideaFile\\ioStream\\myio\\bbb");
        //2.创建File对象表示生成的压缩文件放在哪里(压缩文件的父级路径
        File destParent = src.getParentFile();
        //3.创建File对象表示压缩包的路径
        File dest = new File(destParent, src.getName() + ".zip");
        //4.创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
        //5.获取src下的每一个文件,变成ZipEntry对象,放入压缩包
        toZip(src, zos, src.getName());
        //6.释放资源
        zos.close();

    }

    /**这个方法的难点就在于文件路径的填写
     * @param src  数据源
     * @param zos  压缩流
     * @param name 压缩包内部的路径
     *             作用:获取src下的每一个文件,变成ZipEntry对象,放入到压缩包中
     */
    public static void toZip(File src, ZipOutputStream zos, String name) throws IOException {
        File[] files = src.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                ZipEntry zipEntry = new ZipEntry(name + "\\" + file.getName());
                zos.putNextEntry(zipEntry);
                FileInputStream fis = new FileInputStream(file);
                int len;
                byte[] bytes = new byte[1024];
                while ((len = fis.read(bytes)) != -1) {
                    zos.write(bytes, 0, len);
                }
                zos.closeEntry();
            } else {
                //文件夹,递归
                toZip(file, zos, name + "\\" + file.getName());
            }
        }
    }
}

五、commons-io

commons-io是apache开源组织开源的一套关于io流的工具包,其中包含了很多实用的方法
在这里插入图片描述

在这里插入图片描述
自行了解commons-io下的工具类

六、hutool

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值