九, IO流

1. 初识IO流

  1. IO流: 存储和读取数据的解决方案, I: Input, O: Output, 流: 像水流一样传输数据

  2. IO流的作用: 用于读写数据(本地文件, 网络)

  3. IO流按照流向可以分为两类:
    (1) 输出流: 程序->文件 (2) 输入流: 文件->程序

  4. IO流按照操作文件的类型可以分为两类:
    (1) 字节流: 可以操作所有类型的文件 (2) 字符流: 只能操作纯文本文件(用Windows系统自带的记事本打开并且能读懂的文件, .txt, .xml, .md, .lrc)
    image
    image
    image

2. 字节输出流

2.1 基本用法

  1. FileOutputStream: 操作本地文件的字节输出流, 可以把程序中的数据写到本地文件中
        // 写出一段文字到本地文件中(暂时不写中文)
        // 1. 创建字节输出流对象
        /*
            细节一: 参数是字符串表示的路径或者是File对象都是可以的
            细节二: 如果写出的文件不存在, 会自动创建, 但是要保证父级文件夹存在
            细节三: 如果文件已经存在, 会覆盖
         */
        FileOutputStream fos = new FileOutputStream("D:\\Java\\a.txt");
        // 2. 写出数据
        /*
            细节: write方法的参数是整数, 但是实际上写到本地文件中的是整数在ASCII表中对应的字符
            97 对应的字符是a
         */
        fos.write(97);
        // 3. 释放资源: 每次使用完流之后都要释放
        fos.close();

image

2.2 换行写

        /*
            换行写: 再次写出一个换行符即可
            Windows: \r\n
            Linux: \n
            Mac: \r
            细节:
                在Windows操作系统, java对回车换行进行了优化
                虽然完整的是\r\n, 但是可以只写其中一个, java就能识别, 因为java在底层会补全
            建议: 不要省略. 全写了
         */
        FileOutputStream fos = new FileOutputStream("D:\\java\\awei.txt");
        String str = "asndbanf";
        fos.write(str.getBytes());

        String wrap = "\r\n";
        fos.write(wrap.getBytes());

        String str2 = "asndbanf";
        fos.write(str2.getBytes());

        fos.close();

2.3 续写

        /*
            续写:
                如果想要续写, 打开续写开关即可
                开关位置: 创建对象的第二个参数
                默认false: 表示管必续写, 此时创建对象会清空文件
                手动传递true: 表示打开续写, 此时创建对象不会清空文件
         */
        FileOutputStream fos = new FileOutputStream("D:\\java\\awei.txt", true);
        String str = "asndbanf";
        fos.write(str.getBytes());

        String wrap = "\r\n";
        fos.write(wrap.getBytes());

        String str2 = "asndbanf";
        fos.write(str2.getBytes());

        fos.close();

3. 字节输入流

3.1 字节输入流基本用法

  1. FileInputStream: 操作本地文件中的字节输入流, 可以把本地文件中的数据读取到程序中来
        // 文件里暂时不写中文
        /*
            1. 创建字节输入流对象:
                细节1: 如果文件不存在, 就直接报错
            2. 读取数据:
                细节1: 一次读一个字节, 读出来的是在ASCII码对应的int值
                细节2: 如果文件已经读完, 就返回-1
            3. 释放资源: 每次使用完必须释放资源
         */
        FileInputStream fis = new FileInputStream("D:\\Java\\awei.txt");
        int b;
        while((b = fis.read()) != -1){
            System.out.print((char)b);
        }
    
        fis.close();

3.2 文件拷贝(使用FileInputStream一次读取多个字节)

image

  1. public int read(byte[] buffer) 一次读取一个字节数组数据
    image
    image
        FileInputStream fis = new FileInputStream("D:\\Java\\awei.txt");
        FileOutputStream fos = new FileOutputStream("D:\\Java\\a.txt");
        byte[] bytes = new byte[1024];
        int len;
        while((len = fis.read(bytes)) != -1){
            fos.write(bytes, 0, len);
        }
        fos.close();
        fis.close();

4. 字符集(读中文)

4.1 ASCII和GBK

  1. 计算机最小的存储单元是一个字节

  2. ASCII字符集中, 一个英文占一个字节, 二进制第一位是0

  3. 简体中文版Windows, 默认使用GBK字符集, GBK字符集完全兼容ASCII字符集

  4. GBK: 规则1: 汉字用两个字节存储, 规则2: 高位字节二进制一定以1开头, 转成十进制之后是负数, 是为了和英文区分开
    image

image
image

4.2 Unicode(万国码)

  1. UTF: Unicode Transfer Format
    image

  2. UTF-8编码规则: 使用1-4个字节进行保存(ASCII:1个字节表示, 中文:3个字节表示)

  3. UTF-8: 不是字符集, 是Unicode字符集中的一种编码方式

4.3 乱码

  1. 出现的原因:

    • 读取数据时未读完整个汉字
    • 编码和解码方式不统一
  2. 如何不产生乱码?

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

4.4 编码和解码

image

        /*
        Java中编码的方式:
            public byte[] getBytes()  使用默认方式进行编码
            public byte[] getBytes(String charsetName) 使用指定的编码进行编码

        Java中解码的方式:
            String (byte[] bytes)  使用默认方式进行解码
            String (byte[] bytes, String charsetName) 使用指定的编码进行解码
         */

        // 1. 编码
        String str = "ai你哟";
        byte[] bytes1 = str.getBytes();
        System.out.println(Arrays.toString(bytes1));

        byte[] bytes2 = str.getBytes("GBK");
        System.out.println(Arrays.toString(bytes2));

        // 2. 解码
        String str1 = new String(bytes1);
        System.out.println(str1);
        String str2 = new String(bytes1, "GBK");
        System.out.println(str2);

5. 字符流

5.1 初识字符流

  1. 字符流的底层其实就是字节流, 字符流 = 字节流 + 字符集
  2. 字符流的特点:
    • 输入流: 一次读一个字节, 遇到中文时, 一次读多个字节
    • 输出流: 底层会把数据按照指定的编码方式进行编码, 变成字节再写到文件中

5.2 读取数据

        /*
            1. 创建对象
            public FileReader(File file) 创建字符输入流关联本地文件
            public FileReader(String fileName) 创建字符输入流关联本地文件

            2. 读取数据
            public int read() 读取数据, 读到末尾返回-1
            public int read(char[] buffer) 读取duoge数据, 读到末尾返回-1
            3. 关闭流
            public void close() 释放资源/关流
         */
        FileReader fr = new FileReader("D:\\Java\\awei.txt");
        // 字符流的底层也是字节流, 默认也是一个一个字节读取
        // 遇到中文读取多个, GBK一次读取两个, Unicode一次读取三个字节
//        int ch;
//        while((ch = fr.read()) != -1){
//            // 读取之后, 方法的底层还会解码并转成十进制, 所以我们强制转换为char类型
//            System.out.print((char)ch);
//        }
        int len;
        char[] chars = new char[1024];
        while((len = fr.read(chars)) != -1){
            //空参的read + 强制转换类型
            System.out.print(new String(chars, 0, len));
        }
        fr.close();

5.3 写数据

image
image

        FileWriter fw = new FileWriter("D:\\Java\\awei.txt");

//        fw.write(25105);
//        fw.write("你好?");
        char[] chs = {'a', 'b', 'c'};
//        fw.write(chs);
        fw.write(chs, 1, 2);
        fw.close();

5.4 字符流原理解析

  1. 字节流没有缓冲区
    字符输入流
    image
    image

  2. 字符输出流

  3. 何时写入目的地
    image

  4. image

6. 缓冲流

image

6.1 字节缓冲流

  1. 原理: 底层自带了长度为8192的字节数组(8KB)缓冲区提高性能
    image
        /*
            利用字节缓冲流拷贝文件
            字节缓冲输入流的构造方法:
                public BufferedInputStream(InputStream is)
            字节缓冲输出流的构造方法:
                public BufferedOutputStream(OutputStream os)
         */
        // 1. 创建字节缓冲流对象
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Java\\awei.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\Java\\awei_copy2.txt"));
        // 2. 读取数据并写入
//        int b;
        int len;
        byte[] bytes = new byte[1024];
        while((len = bis.read(bytes)) != -1){
            bos.write(bytes, 0, len);
        }
        // 3. 释放资源
        bos.close();
        bis.close();

6.2 字符缓冲流

  1. 字符基本流其实已经自带了缓冲区(8192的字符数组, java当中, 一个字符是2B, 所有是16KB), 所以字符缓冲流的效率并没有很大的提升, 但还是需要学习, 因为有很好用的方法
    image
    image
        /*
            字符缓冲输入流的:
                构造方法:
                        public BufferedReader(Reader r)
                特有方法:
                        public String readLine() 读取一整行, 但是不会把回车换行符号读到内存当中
            字符缓冲输出流的:
                构造方法:
                        public BufferedWriter(Writer w)
                特有方法:
                        public void newLine()  跨平台的换行
         */
        // 1. 创建字符缓冲流对象
        BufferedReader br = new BufferedReader(new FileReader("D:\\Java\\awei.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Java\\awei_copy.txt"));
        // 2. 读取数据, 写出对象
        bw.write("hello world!");
//        bw.write("\r\n"); // 平台不一样可能导致不能换行
        bw.newLine();
        bw.write("hello world!");
        bw.newLine();
        String line;
//        System.out.println(line);
        while((line = br.readLine()) != null){
            System.out.println(line);
        }
        // 3. 释放资源
        bw.close();
        br.close();

7. 转换流

  1. 字节流没有读取一整行的方法

  2. 转换流是字符流和字节流之间的桥梁, 作用在于

    • 指定字符集读写数据(JDK11后淘汰)
    • 字节流想要使用字符流中的方法
  3. 字符转换输入流: InputStreamReader, 字符转换输出流: OutputStreamWriter

        /*
            将本地文件中的GBK文件, 转换成UTF-8
         */
        // 1. JDK11以前的方案
//        InputStreamReader isr = new InputStreamReader(new FileInputStream("hello.txt"), "GBK");
//        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hello1.txt"), "UTF-8");
//
//        int b;
//        while((b = isr.read()) != -1){
//            osw.write(b);
//        }
//        osw.close();
//        isr.close();

        // 2. 现在的替代方案
        FileReader fr = new FileReader("hello.txt", Charset.forName("GBK"));
        FileWriter fw = new FileWriter("hello1.txt", Charset.forName("UTF-8"));
        int b;
        while((b = fr.read()) != -1){
            fw.write(b);
        }
        fw.close();
        fr.close();

image

8. 序列化流和反序列化流(字节流)

image

8.1 序列化流(对象操作输出流)

  1. 序列化流: 可以把Java中的对象写到文件中
    image
        /*
            需求: 利用序列化流/对象操作输出流, 把一个对象写到本地文件中
            要序列化的类需要实现Serializable接口, 否则会报NotSerializableException
            接口中其里面没有抽象方法, 是一个标记型接口, 一旦实现了这个接口, 该类对象就可以被序列化(类似于合格证)
            构造方法:
                public ObjectOutputStream(OutputStream out) throws IOException 把基本流变成高级流
            成员方法:
                public final void writeObject(Object obj) throws IOException
         */
        // 1. 创建对象
        Dog dog = new Dog("旺财", 10);
        // 2. 创建序列化流的对象/对象操作输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Java\\dog.txt"));
        // 3. 写出数据
        oos.writeObject(dog);
        // 4. 释放资源
        oos.close();
  1. 如果不想把属性序列化到本地文件中, 可以使用transient(瞬态关键字), 该关键字标记的成员变量不参与序列化过程
  2. 序列化流写到文件中的数据不能修改, 一旦修改, 就不能读取回来

8.2 反序列化流(对象操作输入流)

  1. 反序列化流: 可以把序列化到本地文件中的对象, 读取到程序中来
    image
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Java\\dog.txt"));
        Object o = ois.readObject();
        System.out.println(o);
        ois.close();
  1. 序列化对象后, 修改了Javabean类, 再次反序列化, 会出问题, 会抛出InvalidclassException异常
    解决方案:给Javabean类添加serialVersionUID(序列号、版本号)
    image

  2. 使用IDEA中的自动生成UID,设置中调整
    image
    image
    image

9. 打印流

  1. 打印流不能操作数据源, 只能操作目的地(不能读, 只能写)
    image

9.1 字节打印流

  1. 字节打印流: 默认自动刷新, 特有的println自动换行
    image
    image
    image

9.2 字符打印流

  1. 字符流底层有缓冲区, 想要自动刷新需要开启, 特有的println自动换行
    image
    image
    image

10. 压缩流和解压缩流

image

10.1 解压缩流

    public static void main(String[] args) throws ParseException, IOException, ClassNotFoundException {
        // 1. 创建一个File表示要解压的压缩包
        File src = new File("D:\\Java\\zip.zip");
        // 2. 创建一个File表示要解压到的目录
        File dest = new File("D:\\Java\\");

        // 调用方法
        unzip(src, dest);

    }
    // 定义一个方法来解压
    public static void unzip(File src, File dest) throws IOException {
        // 解压的本质: 把压缩包里面的每一个文件夹或者文件读取出来, 按照层级拷贝到目的地当中

        // 创建一个解压缩流来读取压缩包中的数据
        ZipInputStream zis = new ZipInputStream(new FileInputStream(src));
        // 要先获取到压缩包里面的每一个ZipEntry对象, 才能读取压缩包里面的内容
//        for(int i = 0; i < 100; ++i){
//            System.out.println(zis.getNextEntry());
//        }
        // 获取压缩包里面的下一个ZipEntry对象, 表示当前在压缩包中获取到的文件或者文件夹
        ZipEntry entry;
        while((entry = zis.getNextEntry()) != null){
//            System.out.println(entry);
            // 文件夹: 需要在目的地dest创建一个同样的文件夹
            if(entry.isDirectory()){
                File file = new File(dest, entry.toString());
                file.mkdirs();
            }else{
            // 文件: 需要读取压缩包中的文件, 并把它存取到目的地dest文件夹中(按照层级目录进行存放)
                FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
                byte[] buf = new byte[1024];
                int len;
                while((len = zis.read(buf)) != -1){
                    fos.write(buf, 0, len);
                }
                fos.close();
                zis.closeEntry();
            }

        }
        zis.close();

    }

10.2 压缩流

  1. 压缩的本质: 把每一个(文件/文件夹)看成ZipEntry对象放到压缩包中
    image
    image

11. 常用工具包

11.1 Commons-io

  1. Commons-io: 是apache开源基金组织提供的一组有关IO操作的工具包
  2. Commons-io使用步骤:
  • (1) 在项目中创建一个文件夹lib
  • (2) 将jar包复制粘贴到lib文件夹
  • (3) 右键点击jar包,选择Add as Library -> 点击OK
  • (4) 在类中导包使用
    image
    image
    Commons-io下载

11.2 Hutool工具包

image

12. 网络爬虫

12.1 制造假数据(姓名)

    /*
        制造假数据:
            获取姓氏: https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0
            获取男生名字: http://www.haoming8.cn/baobao/10881.html
            获取女生名字: http://www.haoming8.cn/baobao/7641.html
    */
        // 1. 定义变量记录网址
        String familyNameNet = "https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0";
        String boyNameNet = "http://www.haoming8.cn/baobao/10881.html";
        String girlNameNet = "http://www.haoming8.cn/baobao/7641.html";
        // 2. 爬取数据, 把网址上所有的数据拼接成一个字符串

        String s1 = webCrawler(familyNameNet);
        String s2 = webCrawler(boyNameNet);
        String s3 = webCrawler(girlNameNet);
//        System.out.println(s2);
        // 3. 通过正则表达式, 把其中符合要求的数据获取出来
        // 0表示获取所有, 1表示第一组
        ArrayList<String> familyNameList = getData(s1, "([^\\w]{4})(,|。)", 1);
        ArrayList<String> boyNameList = getData(s2, "([\\u4E00-\\u9FA5]{2})(、|。)", 1);
        ArrayList<String> girlNameList = getData(s3, "(.. ){4}..", 0);
//        System.out.println(familyNameList);
//        System.out.println(boyNameList);
//        System.out.println(girlNameList);
        // 4. 处理数据
        ArrayList<String> familyNameList1 = new ArrayList<>();
        for(String s : familyNameList){
            for(int i = 0; i < s.length(); ++i){
                familyNameList1.add(s.charAt(i) + "");
            }
        }
//        System.out.println(familyNameList1);
        ArrayList<String> boyNameList1 = new ArrayList<>();
        for(String s : boyNameList){
            if(boyNameList1.contains(s)) continue;
            boyNameList1.add(s);
        }
//        System.out.println(boyNameList1);
        ArrayList<String> girlNameList1 = new ArrayList<>();
        for(String s : girlNameList){
            String[] arr = s.split(" ");
            for(int i = 0; i < arr.length; ++i){
                girlNameList1.add(arr[i]);
            }
        }
//        System.out.println(girlNameList1);

        // 5. 生成姓名(唯一) - 性别 - 年龄数据
        ArrayList<String> infos = getInfos(familyNameList1, boyNameList1, girlNameList1, 70, 50);
        System.out.println(infos);

        // 6. 保存数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Java\\fakeData.txt"));
        for(String info : infos){
            bw.write(info);
            bw.newLine();
        }
        bw.close();
    }


    // 获取男生和女生的信息
    /*
        参数一: 装着姓氏的集合
        参数二: 装着男生名字的集合
        参数三: 装着女生名字的集合
        参数四: 男生的个数
        参数五: 女生的个数
     */
    private static ArrayList<String> getInfos(ArrayList<String> familyNameList1, ArrayList<String> boyNameList1, ArrayList<String> girlNameList1, int boyCount, int girlCount) {
        // 1. 生成男生不重复的名字
        HashSet<String> boys = new HashSet<>();
        while(true){
            if(boyCount == boys.size()) break;
            // 随机
            Collections.shuffle(familyNameList1);
            Collections.shuffle(boyNameList1);
            boys.add(familyNameList1.get(0) + boyNameList1.get(0));
        }
        // 2. 生成女生不重复的名字
        HashSet<String> girls = new HashSet<>();
        while(true) {
            if (girlCount == girls.size()) break;
            // 随机
            Collections.shuffle(familyNameList1);
            Collections.shuffle(girlNameList1);
            girls.add(familyNameList1.get(0) + girlNameList1.get(0));
        }
//        System.out.println(girls);
        // 3. 张三-男-23
        Random r = new Random();
        ArrayList<String> res = new ArrayList<>();
        for(String boy : boys){
            int age = r.nextInt(9) + 18;
            res.add(boy + "-男-" + age);
        }
        for(String girl : girls){
            int age = r.nextInt(9) + 18;
            res.add(girl + "-女-" + age);
        }
        return res;
    }

    private static ArrayList<String> getData(String s1, String regex, int index) {
        ArrayList<String> list = new ArrayList<>();
        Pattern p = Pattern.compile(regex);
        // 按照pattern的规则, 到s1中去获取数据
        Matcher matcher = p.matcher(s1);
        while(matcher.find()){
            String group = matcher.group(index);
            list.add(group);
//            System.out.println(group);
        }
        return list;
    }

    public static String webCrawler(String net) throws IOException {
        // 1. 定义StringBuilder拼接爬取到的数据
        StringBuilder sb = new StringBuilder();
        // 2. 创建一个URL对象
        URL url = new URL( net);
        // 3. 连接上该网络
        URLConnection urlConnection = url.openConnection();
        // 4. 读取数据
        // 有中文要转变成字符流
        InputStreamReader isr = new InputStreamReader(urlConnection.getInputStream());
        int ch;
        while((ch=isr.read()) != -1){
            sb.append((char)ch);
        }
        // 5. 释放资源
        isr.close();
        // 6. 把读取到的数据返回
        return sb.toString();

    }
  1. 利用糊涂包生成假数据
posted @ 2025-08-11 11:16  awei040519  阅读(20)  评论(0)    收藏  举报