【JAVA基础之IO】字节流、字符流以及乱码问题

🔥作者主页小林同学的学习笔录

🔥mysql专栏小林同学的专栏

目录

1. IO概述

1.1  什么是IO

1.2  IO的分类

1.3  字节和字符流的顶级父类

2. 字节流

2.1  一切皆为字节

2.2  字节输出流【OutputStream】

2.3  FileOutputStream类

2.3.1  构造方法

2.3.2  数据追加续写

2.4  字节输入流【InputStream】

2.5 FileInputStream类

2.5.1  构造方法

2.6 字节流练习:图片复制

3. 字符流

3.1 字符输入流【Reader】

3.2 FileReader类

3.2.1  构造方法

3.3 字符输出流【Writer】

3.4 FileWriter类

3.4.1  构造方法

3.4.2  写出基本数据

3.4.3  关闭和刷新

4. IO异常的处理

5.缓冲流

5.1  原理

5.2  字节和字符缓冲流

5.3  字节缓冲流

5.3.1  构造方法

5.3.2  效率测试

5.4  字符缓冲流

5.4.1  构造方法

5.4.2  特有方法

6. 转换流

6.1 字符编码和字符集

6.1.1  字符编码

6.1.2  字符集

6.2  乱码问题

6.3 InputStreamReader类

6.3.1   构造方法

6.3.2   指定编码读取

6.4 OutputStreamWriter类

6.4.1  构造方法

6.4.2  指定编码写出

6.4.3  转换流理解图解

7. 序列化

7.1  概述

7.2 ObjectOutputStream类

7.2.1  构造方法

7.2.2  序列化操作

7.3 ObjectInputStream类

7.3.1  构造方法

7.3.2  反序列化操作1

7.3.3  反序列化操作2

8.打印流

8.1 概述

8.2 PrintStream类

8.2.1  构造方法

8.2.2  成员方法

8.2.3  改变打印流向

8.3  PrintWriter类

8.3.1  构造方法

8.3.2  成员方法

9. 工具包(Commons-io)

10. 工具包(hutool)


1. IO概述

1.1  什么是IO

生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。

我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input输出output ,即流向内存是输入流,流出内存的输出流。

1.2  IO的分类

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。

  • 输出流 :把数据从内存 中写出到其他设备上的流。

根据数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。

  • 字符流 :以字符为单位,读写数据的流。

1.3  字节和字符流的顶级父类

2. 字节流

2.1  一切皆为字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

2.2  字节输出流【OutputStream】

java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。

  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。

  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。

  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。

  • public abstract void write(int b) :将指定的字节输出流。

需要注意的是完成流的操作时,需要close()方法释放资源

流的关闭原则:先开后关,后开先关。

2.3  FileOutputStream类

2.3.1  构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。

  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

2.3.2  数据追加续写

经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?

  • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。

  • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 为默认值,表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:

2.4  字节输入流【InputStream】

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。

  • public abstract int read(): 从输入流读取数据的下一个字节。

  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

需要注意的是完成流的操作时,需要close()方法释放资源,

流的关闭原则:先开后关,后开先关。

2.5 FileInputStream类

2.5.1  构造方法

  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException

2.6 字节流练习:图片复制

代码演示

public class InputStream {
    public static void main(String[] args) throws IOException {
        String srcFile = "D:\\img\\R.jpg";
        String descFile = "R.jpg";

        byte[] bytes = new byte[1024];
        int len = 0;
        FileInputStream fileInputStream = new FileInputStream(srcFile);
        FileOutputStream fileOutputStream = new FileOutputStream(descFile);
        //循环读取
        while ((len = fileInputStream.read(bytes)) != -1){
            //边读边写
            fileOutputStream.write(bytes,0,len);
        }
        //流的关闭原则:先开后关,后开先关。
        fileInputStream.close();
        fileOutputStream.close();
    }
}

3. 字符流

3.1 字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。

  • public int read(): 从输入流读取一个字符。

  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。

3.2 FileReader类

java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

3.2.1  构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。

  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。

3.3 字符输出流【Writer】

java.io.Writer抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。

  • void write(int c) 写入单个字符。

  • void write(char[] cbuf)写入字符数组。

  • abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。

  • void write(String str)写入字符串。

  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。

  • void flush()刷新该流的缓冲。

  • void close() 关闭此流,但要先刷新它。

3.4 FileWriter类

3.4.1  构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。

  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

3.4.2  写出基本数据

write(int b) 方法,每次可以写出一个字符数据,代码使用演示:

class testClass{
    @Test
    public void test01() throws IOException {
        FileWriter fileWriter = new FileWriter("lhx.txt");
        fileWriter.write(98);
        fileWriter.write('b');

        	/*
        【注意】关闭资源时,与FileOutputStream不同。
      	 如果不关闭,数据只是保存到缓冲区,并未保存到文件。
        */
         fileWriter.close();
    }
}

注意:与fileOutputStream 不同,fileWriter 如果未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。

3.4.3  关闭和刷新

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

  • flush :刷新缓冲区,流对象可以继续使用。

  • close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

代码演示:

class testClass{
    @Test
    public  void test02() throws IOException {
        FileWriter fileWriter = new FileWriter("lhx.txt");
        fileWriter.write("你好");
        //刷新缓存,下面可以继续写入数据
        fileWriter.flush();

        fileWriter.write("帅");
        //刷新缓存并关闭资源
        fileWriter.close();
    }
}

输出结果:

你好帅

4. IO异常的处理

之前的入门练习,我们一直把异常抛出,而实际开发中并不能这样处理,建议使用try...catch...finally 代码块,处理异常部分,因为有可能close()上面的代码出异常,然后导致程序结束,导致close()方法并没有被执行

public class HandleException {
    public static void main(String[] args) {
      	// 声明变量(要初始化)
        FileWriter fw = null;
        try {
            //创建流对象
            fw = new FileWriter("lhx.txt");
            // 写出数据
            fw.write("你好"); 
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //做非null判断是因为有可能文件不存在,流对象没有被创建
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

如果流对象比较多的时候,关闭流的代码量还是会比较多,因此下面有解决方案:

5.缓冲流

5.1  原理

具体分为两个过程:

缓存过程:当使用缓冲流进行读写操作时,数据首先被读入或写入缓存区,这是一个字节数组,其大小由缓冲流的构造函数参数指定。在读取数据时,缓冲流会将磁盘上的数据分块读取到缓存区中,然后逐个字节(也可以是多个字节)地读取缓存区中的数据。当缓存区中的数据被读取完毕后,缓冲流会再次读取磁盘上的数据到缓存区中,直到读取到所需的数据。在写入数据时,缓冲流会将数据写入缓存区中,然后再将缓存区中的数据一次性地写入磁盘。当缓存区被写满时,缓冲流也会将缓存区中的数据一次性地写入磁盘。

刷新过程:缓冲流会在缓存区被写满时自动将缓存区中的数据写入磁盘。此外,缓冲流还提供了手动刷新缓存区的方法flush(),调用该方法会强制缓冲流将缓存区中的数据写入磁盘。在正常情况下,缓冲流会在关闭时自动刷新缓存区,将缓存区中的数据写入磁盘。

总的来说,缓冲流通过在内存中建立一个缓冲区来减少与磁盘或网络的IO次数,从而提高读写的效率。因为磁盘或网络IO操作是相对较慢的,而内存中的读写操作是相对较快的,所以通过缓冲区可以减少对磁盘或网络的IO操作,从而提高读写的速度。

5.2  字节和字符缓冲流

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream

  • 字符缓冲流BufferedReaderBufferedWriter

5.3  字节缓冲流

5.3.1  构造方法

  • public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。

  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。

5.3.2  效率测试

①.基本流,代码如下:

public class BufferStream {
    public static void main(String[] args)  {

        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            long begin = System.currentTimeMillis();
            String filePath = "D:\\java_project_source\\mysql\\资料-MySQL数据库\\进阶篇\\相关SQL脚本\\load_user_100w_sort.sql";
            String descFile = "d:\\lhx.sql";
            int len = 0;
            byte[] bytes = new byte[1024];
            fileInputStream = new FileInputStream(filePath);
            fileOutputStream = new FileOutputStream(descFile);
            while((len = fileInputStream.read(bytes)) != -1){
                fileOutputStream.write(bytes,0,len);
            }

            long end = System.currentTimeMillis();
            System.out.println("花费的时间(ms):" + (end - begin));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fileOutputStream != null){
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

输出结果:

花费的时间(ms):650

②.缓冲流,代码如下:

public class BufferStream02 {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            long begin = System.currentTimeMillis();
            String filePath = "D:\\java_project_source\\mysql\\资料-MySQL数据库\\进阶篇\\相关SQL脚本\\load_user_100w_sort.sql";
            String descFile = "d:\\lhx.sql";
            int len = 0;
            byte[] bytes = new byte[1024];
            bis = new BufferedInputStream(new FileInputStream(filePath));
            bos = new BufferedOutputStream(new FileOutputStream(descFile));
            while((len = bis.read(bytes)) != -1){
                bos.write(bytes,0,len);
            }
            long end = System.currentTimeMillis();
            System.out.println("花费的时间(ms):" + (end - begin));
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if(bis != null){
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(bos != null){
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

输出结果:

花费的时间(ms):142

细节:

缓冲流如果close()其底层也会调用基本流的close()方法,因此只需要关闭缓冲流的通道

缓冲流为什么能提高性能?

  • 缓冲流自带长度为8192的缓冲区
  • 可以显著的提高字节流的读写性能
  • 对于字符流提升不明显,对于字符缓冲流而言,关键点是有两个特有方法

5.4  字符缓冲流

5.4.1  构造方法

  • public BufferedReader(Reader in) :创建一个 新的缓冲输入流。

  • public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

5.4.2  特有方法

  • BufferedReader:public String readLine(): 读一行文字。

  • BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。

readLine方法演示,代码如下:

public class BufferStreamDemo {
    public static void main(String[] args) {
        BufferedReader bis = null;
        try {
            byte[] bytes = new byte[1024];
            String line = null;
            int len = 0;
            bis = new BufferedReader(new FileReader("lhx.txt"));
            while ((line =  bis.readLine()) != null){
                System.out.println((++len) + "  " + line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if(bis != null){
                    bis.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

输出结果:

1  package inputstream;
2  
3  import org.junit.jupiter.api.Test;

newLine方法演示,代码如下:

public class BufferStreamDemo2 {
    public static void main(String[] args) {
        BufferedWriter br = null;
        try {
            br = new BufferedWriter(new FileWriter("lhx2.txt"));
            br.write("哥哥好帅");
            br.newLine();
            br.write("消失的他");
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if(br != null){
                    br.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

输出结果:

在lhx2.txt看到内容:

哥哥好帅
消失的他

5.5  综合案例

方式一:

public class Test04 {
    public static void main(String[] args) throws IOException {
        /**
         * 3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施
         * 8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜
         * 4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和
         * 2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内
         * 1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝
         * 9.今当远离,临表涕零,不知所言。
         * 6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,
         * 7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,
         * 5.亲贤臣,远小人,此先
         *
         * 需求:用IO将上面文章恢复顺序
         */
        BufferedReader br = new BufferedReader(new FileReader("csb.txt"));
        String len;
        //key会自动进行排序处理
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        while((len = br.readLine()) != null){
            String[] split = len.split("\\.");
            treeMap.put(Integer.parseInt(split[0]),len);
        }
        br.close();

        //写出数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("csb.txt"));
        Set<Map.Entry<Integer, String>> entries = treeMap.entrySet();
        for (Map.Entry<Integer, String> entry : entries) {
            bw.write(entry.getValue());
            bw.newLine();
        }
        bw.close();
    }
}

方式二:

public class Test05 {
    public static void main(String[] args) throws IOException {
        /**
         * 3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施
         * 8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜
         * 4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和
         * 2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内
         * 1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝
         * 9.今当远离,临表涕零,不知所言。
         * 6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,
         * 7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,
         * 5.亲贤臣,远小人,此先
         *
         * 需求:用IO将上面文章恢复顺序
         */
        BufferedReader br = new BufferedReader(new FileReader("csb.txt"));
        String len;
        ArrayList<String> list = new ArrayList<>();
        while((len = br.readLine()) != null){
            list.add(len);
        }
        br.close();

        //排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                String[] split = o1.split("\\.");
                String[] split1 = o2.split("\\.");
                int i = Integer.parseInt(o1.split("\\.")[0]);
                int i1 = Integer.parseInt(o2.split("\\.")[0]);
                return i - i1;
            }
        });

        //写出数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("csb.txt"));
        for (String s : list) {
            bw.write(s);
            bw.newLine();
        }
        bw.close();
    }
}

6. 转换流

其实就是把字节流变成字符流,并且可以指定字符集的方法来解决乱码问题

InputStreamReader

OutputStreamWriter

6.1 字符编码和字符集

6.1.1  字符编码

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

编码:字符(能看懂的)--字节(看不懂的)

解码:字节(看不懂的)-->字符(能看懂的)

  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

6.1.2  字符集

  • 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。

  1. ASCII(美国标准信息交换码)

    • ASCII是一个7位字符集,定义了128个字符,包括控制字符(如换行符、回车符、制表符等)和可打印字符(如字母、数字、标点符号等)。
    • 每个ASCII字符占用1个字节(8位),即8个比特。
  2. Unicode

    • Unicode是一个字符集,旨在涵盖世界上所有的字符,无论是现代文本还是古代文本,无论是哪种语言或符号系统。
    • Unicode字符的编码范围从U+0000到U+10FFFF。
    • Unicode字符编码采用不同的编码方案来表示,其中最常见的是UTF-8、UTF-16和UTF-32。
  3. UTF-8(Unicode转换格式-8位)

    • UTF-8是一种变长字符编码,可以用来表示Unicode字符集中的所有字符。
    • UTF-8使用1到4个字节来表示一个字符,具体取决于字符的Unicode码点范围。
    • ASCII字符(U+0000到U+007F)在UTF-8中仍然占用1个字节,与标准ASCII编码兼容。
    • 英文占用一个字节,中文占用三个字节
  4. GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。GBK英文占用一个字节,中文占有两个字节

6.2  乱码问题

在IDEA中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。

代码演示:

public class ReaderDemo {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("D:\\File_GBK.txt");
        int read;
        while ((read = fileReader.read()) != -1) {
            System.out.print((char)read);
        }
        fileReader.close();
    }
}

输出结果:
���

为什么会出现乱码?

  • 读取数据是未读完整个汉字
  • 编码和解码的方式不统一

如何不产生乱码呢?

  • 不要用字节流读取文本文件
  • 编码解码时同时使用同一个码表,同一个编码方式

6.3 InputStreamReader类

转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

6.3.1   构造方法

InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

6.3.2   指定编码读取

public class ReaderDemo2 {
    public static void main(String[] args) throws IOException {
      	// 定义文件路径,文件为gbk编码
        String FileName = "D:\\file_gbk.txt";
      	// 创建流对象,默认UTF8编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
      	// 创建流对象,指定GBK编码
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
		// 定义变量,保存字符
        int read;
      	// 使用默认编码字符流读取,乱码
        while ((read = isr.read()) != -1) {
            System.out.print((char)read); // ��Һ�
        }
        isr.close();
      
      	// 使用指定编码字符流读取,正常解析
        while ((read = isr2.read()) != -1) {
            System.out.print((char)read);// 大家好
        }
        isr2.close();
    }
}

6.4 OutputStreamWriter类

转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

6.4.1  构造方法

OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

6.4.2  指定编码写出

public class OutputDemo {
    public static void main(String[] args) throws IOException {
      	// 定义文件路径
        String FileName = "D:\\out.txt";
      	// 创建流对象,默认UTF8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
        // 写出数据
      	osw.write("你好"); // 保存为6个字节
        osw.close();
      	
		// 定义文件路径
		String FileName2 = "D:\\out2.txt";
     	// 创建流对象,指定GBK编码
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
        // 写出数据
      	osw2.write("你好");// 保存为4个字节
        osw2.close();
    }
}

6.4.3  转换流理解图解

7. 序列化

7.1  概述

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:

需要注意的是序列化之后的数据,我们并看不懂,需要进行反序列化才能看懂

应用场景:就是不想让别人修改以及知道相应的信息

7.2 ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

ObjectOutputStream序列化流对象/对象操作输出流

7.2.1  构造方法

public ObjectOutputStream(OutputStream out): 创建一个指定OutputStream的ObjectOutputStream。

7.2.2  序列化操作

  ①.一个对象要想序列化,必须满足两个条件:

  • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,标记接口里面没有抽象方法,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰,transient瞬态修饰成员,不会被序列化。

②.写出对象方法

public final void writeObject (Object obj) : 将指定的对象写出。

代码演示:

@Data
@AllArgsConstructor
public class Student implements Serializable {
    private String name;
    private int age;
    //transient瞬态关键字
    //作用:不会把当前属性序列化到本地文件中
    private transient String address;

}

public class SerializableDemo {
    public static void main(String[] args) throws IOException {
        //准备序列化对象
        Student student = new Student("zhongjunjie",20,"珠海市");
        //创建流对象
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("lhx3.txt"));
        //写出对象
        objectOutputStream.writeObject(student);
        //关闭资源
        objectOutputStream.close();
    }
}

输出结果:
在lhx3.txt文件看到一下内容

�� sr serializable.Student�FN�;�|� I ageL namet Ljava/lang/String;xp   t zhongjunjie

7.3 ObjectInputStream类

ObjectInputStream反序列化流/对象操作输入流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

7.3.1  构造方法

public ObjectInputStream(InputStream in): 创建一个指定InputStream的ObjectInputStream。

7.3.2  反序列化操作1

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject () : 读取一个对象。
public class OpposeSerializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //创建流对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("lhx3.txt"));
        //读取数据
        Object o = objectInputStream.readObject();
        //打印数据
        System.out.println(o);
        //关闭资源
        objectInputStream.close();
    }
}

输出结果:

Student(name=zhongjunjie, age=20, address=null)
address = null是因为我们没有序列化该字段

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

7.3.3  反序列化操作2

另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配

  • 该类包含未知数据类型

  • 该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配,设置版本号之后,版本号就会共享,当你再次修改对象时,版本号还是一致,因此不会出现问题。

@Data
@AllArgsConstructor
public class Student implements Serializable {
    //自己生成一个版本号
    private static final long serialVersionUID = 1455412867956738563L;
    
    private String name;
    private int age;
    private transient String address;
    private String school;
    
}

8.打印流

8.1 概述

平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

8.2 PrintStream类

PrintStream字节打印流

8.2.1  构造方法

8.2.2  成员方法

8.2.3  改变打印流向

System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏",改变它的流向

public class Demo {
       public static void main(String[] args) throws IOException {
            // 调用系统的打印流,控制台直接输出97
            System.out.println(97);

            // 创建打印流,指定文件的名称
            PrintStream ps = new PrintStream("ps.txt", Charset.forName("UTF-8"));

            // ps.txt中输出97
            ps.println(97);

            //仿造系统也可以这样写,一般我们用链式表达式
            PrintStream out = System.out;
            out.println(97);
        }
  }

8.3  PrintWriter类

8.3.1  构造方法

8.3.2  成员方法

代码跟打印字节流很类似,就不演示了

9. 工具包(Commons-io)

介绍:

Commons是apache开源基金组织提供的工具包,里面有很多帮助我们提高开发效率的API

比如:

StringUtils   字符串工具类

NumberUtils   数字工具类

ArrayUtils   数组工具类

RandomUtils   随机数工具类

DateUtils   日期工具类

StopWatch   秒表工具类

ClassUtils   反射工具类

SystemUtils   系统工具类

MapUtils   集合工具类

Beanutils   bean工具类

Commons-io  io的工具类

等等.....

其中:Commons-io是apache开源基金组织提供的一组有关IO操作的开源工具包。

作用:提高IO流的开发效率。

10. 工具包(hutool)

介绍:

Commons是国人开发的开源工具包,里面有很多帮助我们提高开发效率的API

比如:

DateUtil  日期时间工具类

TimeInterval  计时器工具类

StrUtil  字符串工具类

HexUtil   16进制工具类

HashUtil   Hash算法类

ObjectUtil  对象工具类

ReflectUtil   反射工具类

TypeUtil  泛型类型工具类

PageUtil  分页工具类

NumberUtil  数字工具类

11.字节流和字符流的使用场景

字节流:拷贝任意类型的文件

字符流:读取纯文本文件的数据,或者往纯文本文件中写出数据

12.综合练习

12.1  拷贝一个文件夹,考虑子文件夹

public class Test01 {
    public static void main(String[] args) throws IOException {
        /**
         * 拷贝一个文件夹,考虑子文件夹
         */
        //创建数据源
        File src = new File("D:\\aaa");
        //复制的目的地
        File desc = new File("D:\\data");
        //拷贝的方法
        copyFile(src,desc);
    }

    private static void copyFile(File src, File desc) throws IOException {
        //不存在就创建,已经存在就创建失败,并不会报错
        desc.mkdirs();
        //1.遍历数据源的文件
        File[] files = src.listFiles();
        for (File file : files) {
            //2.判断为文件就拷贝
            if(file.isFile()){
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(desc,file.getName())));
                int len;
                byte[] bytes = new byte[1024];
                while ((len = bis.read(bytes)) != -1){
                    bos.write(bytes,0,len);
                }
                //后开的先关原则
                bos.close();
                bis.close();
            }else{
                //3.判断为文件夹,就进行递归
                copyFile(file,new File(desc,file.getName()));
            }
        }

    }
}

12.2  文件的加密和解密

异或加密是一种简单且常见的加密方法,它基于异或运算(XOR),其原理如下:

  1. 加密过程:

    • 将明文和密钥按位进行异或运算,得到密文。
    • 例如,对于明文 M 和密钥 K,加密过程为:C = M ^ K
  2. 解密过程:

    • 将密文和密钥按位进行异或运算,得到明文。
    • 由于异或运算具有逆运算性质,因此解密过程为:M = C ^ K。

虽然异或加密是一种简单的加密方法,但它具有一定的安全性,尤其适合用于简单的数据加密和解密。然而,由于异或加密的安全性较低,容易受到攻击,因此在实际应用中应该慎重选择加密方法,特别是对于需要更高安全性的场景

public class Test02 {
    public static void main(String[] args) throws IOException {
        /**
         * 文件加密:
         * 为了保证数据的安全性,就需要对原始的文件进行加密,要使用的时候再对机密过的文件进行解密
         * 这里的加密解密用到了 (^异或操作)
         * 异或:两边都相同,返回false
         *      两边不相同,返回true
         *      原理就是异或后的表达式,下一次再异或这个
         */
        //加密
//        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("R.jpg"));
//        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A.jpg"));

        //解密
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("A.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("B.jpg"));
        int len;
        while ((len = bis.read()) != -1){
            //(加密/解密)操作
            bos.write(len ^ 10);
        }
        //释放资源
        bos.close();
        bis.close();
    }
}

12.3  排序

文本文件中有以下的数据: 2-1-9-4-7-8 将文件中的数据进行排序,变成以下的数据: 1-2-4-7-8-9

实现方式一:

public class Test03{
    public static void main(String[] args) throws IOException {
        /*
            文本文件中有以下的数据:
                2-1-9-4-7-8
            将文件中的数据进行排序,变成以下的数据:
                1-2-4-7-8-9
        */


        //1.读取数据
        FileReader fr = new FileReader("aaa.txt");
        StringBuilder sb = new StringBuilder();
        int ch;
        while((ch = fr.read()) != -1){
            sb.append((char)ch);
        }
        fr.close();
        System.out.println(sb);
        //2.排序
        String str = sb.toString();
        String[] arrStr = str.split("-");//2-1-9-4-7-8

        ArrayList<Integer> list = new ArrayList<>();
        for (String s : arrStr) {
            int i = Integer.parseInt(s);
            list.add(i);
        }
        Collections.sort(list);
        System.out.println(list);
        //3.写出
        FileWriter fw = new FileWriter("aaa.txt");
        for (int i = 0; i < list.size(); i++) {
            if(i == list.size() - 1){
                fw.write(list.get(i) + "");
            }else{
                fw.write(list.get(i) + "-");
            }
        }
        fw.close();
    }
}

实现方式二:

public class Test04 {
    public static void main(String[] args) throws IOException {
        /*
            文本文件中有以下的数据:
                2-1-9-4-7-8
            将文件中的数据进行排序,变成以下的数据:
                1-2-4-7-8-9

           细节1:
                文件中的数据不要换行

            细节2:
                bom头
        */
        //1.读取数据
        FileReader fr = new FileReader("myio\\a.txt");
        StringBuilder sb = new StringBuilder();
        int ch;
        while((ch = fr.read()) != -1){
            sb.append((char)ch);
        }
        fr.close();
        System.out.println(sb);
        //2.排序
        Integer[] arr = Arrays.stream(sb.toString()
                                      .split("-"))
            .map(Integer::parseInt)
            .sorted()
            .toArray(Integer[]::new);
        //3.写出
        FileWriter fw = new FileWriter("myio\\a.txt");
        String s = Arrays.toString(arr).replace(", ","-");
        String result = s.substring(1, s.length() - 1);
        fw.write(result);
        fw.close();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值