# IO流

Io流是数据存储和读取的解决方案,用于读写文件中的数据,或网络中的数据

注意

  • File类:表示系统文件中的文件或者文件夹的路径
  • File类:只能对文件本身进行操作,不能读写文件里面存储的数据

# Io流的分类

  • 流的方向:IO流->输入、输出流
  • 操作文件类型:IO流->字节流(能操作所有类型的文件)、字符流(纯文本文件)
  • 输入流负责读取数据,输出流负责写入数据

# 基本IO流:字节流、字符流

# 高级流

# 缓冲流

  • 字节流:字节缓冲输入流、字节缓冲输出流
  • 字符流:字符缓冲输入流、字符缓冲输出流

基本流与高级流相比:高级流全部带有缓冲区(字符流自带缓冲区),并且这四种流封装了一些方法

# 字节输出流FileOutputStream

package Io;

import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //Io流
        //演示:使用字节流进行操作文件
        //写一段文字到本地文件夹中(暂时不写中文)
        //实现步骤:
        // 1.创建对象
        //2.写出数据
        //3.释放资源


        //1.创建对象
        //写出 输出流 OutputStream
        //本地文件 File
        FileOutputStream fos = new FileOutputStream("attrs\\file\\a.txt");
        //2.写入数据 要求Int 类型
        fos.write("hello Java".getBytes());
        //3.释放资源
        fos.close();
    }
}

注意

字节输出流的细节

  1. 创建字节输出流的对象
  • 参数是字符串表示的路径或者是File对象都是可以的
  • 如果文件不存在,则会创建一个新的文件,但是要保证父级路径是存在的
  • 如果文件已经存在,构造则会清空并覆盖文件
  1. 写数据
  • write方法的参数是整数,但实际上写到本地文件中的是整数在ASCII上对应的字符
  • 97->a
  1. 释放资源
  • 每次使用完流之后都要释放资源,否则会占用着文件

# FileOutputStream写数据的三种方式

方法名称 说明
void write(int b) 写入一个字节到文件中(一次写一个字节)
void write(byte[] b) 写入一个字节数组到文件中(一次写一个字节数组)
void write(byte[] b, int off, int len) 写入一个字节数组到文件中(一次写一个字节数组的一部分)
package Io;

import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //1. void write(byte[] b)
        FileOutputStream fos1 = new FileOutputStream("attrs\\file\\a.txt");
        fos1.write("hello Java123".getBytes());
        fos1.close();
        //字节数组
        System.out.println(Arrays.toString("hello Java123".getBytes()));//[104, 101, 108, 108, 111, 32, 74, 97, 118, 97, 49, 50, 51]

        //2. void write(byte[] b, int off, int len)
        FileOutputStream fos2 = new FileOutputStream("attrs\\file\\a.txt");
        //参数一:off起始位置 len长度(写多少个)
        fos2.write("hello Java123".getBytes(), 0, 7);
        fos2.close();
    }
}

# 换行和续写

package Io;

import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //2. void write(byte[] b, int off, int len)
        FileOutputStream fos2 = new FileOutputStream("attrs\\file\\a.txt");
        //参数一:off起始位置 len长度(写多少个)
        fos2.write("hello Java123".getBytes(), 0, 7);


        //换行和续写
        //换行写 (就是在中间写一个换行符就可以)
        //win系统 \r\n
        //linux系统 \n
        //mac \r
        fos2.write("\r\n".getBytes());
        fos2.write("\n".getBytes());
        fos2.write("\r".getBytes());
        //在win系统中java对换行进行了优化 虽然完整的是\r\n,但是我们写其中一个\r或\n java在底层会进行优化
        //建议写\r\n
        //续写
        //在创建对象时 如果文件已经存在,则会清空文件内容
        //FileOutputStream 第二个参数append为是否开启续写 默认值为false
        //开启后创建对象则会开启续写
        FileOutputStream fos3 = new FileOutputStream("attrs\\file\\a.txt", true);
        fos2.write("my home".getBytes());
        fos2.close();
    }
}

# 字节输入流FileInputStream

字节输入流FileInputStream用于读取文件

package Io;

import java.io.FileInputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //字节输入流`FileInputStream`
        //1.创建字节输入流的对象
        //如果文件不存在则直接报错
        FileInputStream fis = new FileInputStream("attrs\\file\\a.txt");
        //2.读取数据
        //一个一个的读取
        //如果读不到则返回-1
        int read = fis.read();
        //打印数据
        System.out.println((char) read);
    }
}

# 字节流循环读取

 package Io;

import java.io.FileInputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //字节流循环读取
        //创建对象
        FileInputStream fis1 = new FileInputStream("attrs\\file\\a.txt");
        //循环读取数据
        int b;
        while ((b = fis1.read()) != -1) {
            System.out.println((char) b);
        }
        //读取完毕释放资源
        fis1.close();
    }
}

# 练习 文件拷贝

package Io;

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        //练习 文件拷贝
        //读取文件对象
        FileInputStream fis2 = new FileInputStream("attrs\\file\\a.txt");
        //拷贝文件对象
        FileOutputStream fos4 = new FileOutputStream("attrs\\file\\b.txt");
        int c;
        while ((c = fis2.read()) != -1) {
            //边读边写
            fos4.write(c);
        }
        fos4.close();
        //先开的流 最后再关闭
        fis2.close();
    }
}

# FileOutputStream一次读取多个字节

方法名称 说明
int read() 一次读取一个字节
int read(byte[] buffer) 一次读取多个字节数组
package Io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //一次读取多个数据
        FileInputStream fis3 = new FileInputStream("attrs\\file\\a.txt");
        //创建一个字节数组
        byte[] bytes = new byte[2];
        //read返回读取了几个数据
        int read1 = fis3.read(bytes);
        System.out.println(new String(bytes, 0, read1));
    }
}

# 字节流异常处理

使用try-with-resources实现特定情况自动释放资源

public class Main {
    public static void main(String[] args) throws IOException {
        //io 字节流异常处理
        //只有实现了AutoCloseable接口的流才可以使用try-with-resources
        //功能:特定情况下自动释放资源
        try (FileInputStream fis7 = new FileInputStream("attrs\\file\\e.txt");
             FileOutputStream fos8 = new FileOutputStream("attrs\\file\\c.txt")) {
            //拷贝文件对象
            int e;
            byte[] bytes2 = new byte[1024 * 1024];
            while ((e = fis7.read(bytes2)) != -1) {
                //边读边写
                fos8.write(bytes1, 0, d);
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }
}

# 字符集详解

计算机存储规则

  • GB2312:1980年发布 中文字符编码规则
  • BIG5:1984年发布 台湾地区繁体字符编码规则
  • GBK:2000年发布 兼容GB2312和BIG5 包含日文、韩文、中文和繁体
  • 简体中文版windows系统默认的字符集就是GBK。系统显示:ANSI(兼容大部分国家字符集)

# 汉字的存储规则(GBK)

  • 一个汉字使用两个字节存储

# uniCode字符集(万国码)

utf-8:是一种uniCode的编码方式 utf-8:用1~4的字节表示文字

  • 英文:1个字节
  • 汉字:3个字节

# 为什么会有乱码

  • 字符集不匹配
  • 读取数据是未读完整个汉字
  • 编码和解码的方式不匹配

# 如何不产生乱码

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

# 编码和解码的方式

编码方式

String类中的方法 说明
public byte[] getBytes() 使用默认的方式进行编码
public byte[] getBytes(String charsetName) 使用指定的编码的方式进行编码

解码方式

String类中的方法 说明
String(byte[] bytes) 使用默认的方式进行编码
String(byte[] bytes,String charsetName) 使用指定的方式进行解码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class Main {
    public static void main(String[] args) {
        //使用指定的编码和解码方式读取和写入中文
        //写入/读取中文
        //创建输出/输入流对象
        try (FileOutputStream fileOutputStream = new FileOutputStream("attrs\\file\\a.txt");
             FileInputStream fileInputStream = new FileInputStream("attrs\\file\\a.txt");
        ) {
            //写入中文
            fileOutputStream.write("你好,我是java".getBytes(StandardCharsets.UTF_8));
            //读取中文
            byte[] bytes3 = new byte[1024 * 1024];
            int read2 = fileInputStream.read(bytes3);
            System.out.print(new String(bytes3, 0, read2, StandardCharsets.UTF_8));

        } catch (IOException e) {
            System.err.println("中文读取或写入失败");
        }
    }
}

# 字符输入流FileReader

字符流的使用方式与字节流基本一致

# 空参read方法详解

public class Main {
    public static void main(String[] args) throws IOException {
        try (
                //创建字符流输入对象
                FileReader fileReader = new FileReader("attrs\\file\\a.txt")
        ) {
            int read3;
            //读取数据read()
            //字符流的底层是字节流,默认也是一个字节一个字节读取
            //如果遇到中文就会一次读取多个,CBK一次一次读两个字节,UTF-8一次读取多个
            //在读取之后,方法的底层还是会进行解码并转成十进制
            //最终把这个十进制作为返回值
            //这个十进制的数据也表示在字符集上的数子
            while ((read3 = fileReader.read()) != -1) {
                //字符集输入流返回十进制数
                System.out.println(read3);//25105
                //如果想看到中文,就需要把这些十进制数据进行强转

                System.out.print((char) read3);
            }
        } catch (IOException e) {
            System.err.println("字符流读取失败");
        }
    }
}

# 带参read方法详解

public class Main {
    public static void main(String[] args) {
        //字符流带参输入
        FileReader fileReader1 = new FileReader("attrs\\file\\a.txt");
        char[] chars = new char[1024 * 1024];
        //read(chars):读取数据,解码、强转三步合并了,把强制转换后的字符放到数组中
        //空参read+强制类型转换
        int read2 = fileReader1.read(chars);
        System.out.println(Arrays.toString(chars));
        System.out.println(new String(chars, 0, read2));
        fileReader1.close()
    }
}

# 字符流输出流FileWriter

# FileWriter的构造方法

构造方法 说明
public FileWriter(File file) 创建字符输出流关联本地文件
public FileWriter(String file) 创建字符输出流关联本地文件
public FileWriter(File file,boolean append) 创建字符输出流关联本地文件,续写
public FileWriter(String file,boolean append) 创建字符输出流关联本地文件,续写
public class Main {
    public static void main(String[] args) throws IOException {
        //字符流输出FileWriter
        FileWriter fileWriter = new FileWriter("attrs\\file\\a.txt");
        fileWriter.write(Content.article1);
        fileWriter.close();
        //续写
        FileWriter fileWriter1 = new FileWriter("attrs\\file\\a.txt", true);
        fileWriter1.write("\r\njava续写");
        fileWriter1.close();
    }
}

# FileWriterwrite成员方法

成员方法 说明
public void write(int c) 写出一个字符
public void write(String str) 写出字符串
public void write(char[] chars) 写出字符数组
public void write(String str,int start,int end) 写出字符串的指定部分
public void write(char[] chars,int start,int end) 写出字符数组的指定部分
public class Main {
    public static void main(String[] args) throws IOException {
        //字符输出流的成员方法
        FileWriter fileWriter2 = new FileWriter("attrs\\file\\a.txt", true);
        //当调用write方法时:根据字符集的编码方式进行编码,把编码之后的数据写到文件中去
        //写出一个字符
        fileWriter2.write(97);
        //写出一个字符串
        fileWriter2.write("\r\n");
        //写出字符串的一部分
        fileWriter2.write("你好java", 0, 3);
        //写出一个字符数组
        fileWriter2.write(new char[]{'我', '是', 'c'});
        //写出字符数组的一部分
        fileWriter2.write(new char[]{'我', 'b', 'c'}, 0, 2);
        fileWriter2.close();
    }
}

# 字符流原理详解

  1. 创建字符输入流对象 底层:关联文件,并创建缓冲区(长度为8192的字节数组)
  2. 读取数据 底层:
  • 判断缓冲区中是否有数据可以读取
  • 如果缓冲区中有数据:就从缓冲区中读取
  • 如果缓冲区中没有数据,就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区,如果文件中也没有数据了,则返回-1
  1. read方法
  • 空参的read方法:一次读取一个字节,遇到中文一次读多个字节,把字节编码并转成十进制返回
  • 有参的read方法:把读取字节、解码、强转三步合并,强转之后的字符放到数组中

# 字符流与字节流的使用场景

字节流:

  • 拷贝任意类型的文件

字符流:

  • 读取纯文本文件中的数据
  • 往纯文本文件中写出数据

# 字符与字节流综合练习

# 文件拷贝

public class Main {
    public static void main(String[] args) throws IOException {
        //1.拷贝一个文件夹,考虑子文件夹
        File file = new File("attrs\\file");
        //拷贝的文件夹
        File file1 = new File("attrs\\file1");
        //文件拷贝
        copyFile(file, file1);
    }
    //拷贝文件夹 包含子文件夹

    /**
     * 作用:拷贝文件夹
     *
     * @param src     File      原数据
     * @param copySrc File      目标数据
     * @throws IOException 可能出现的错误
     */
    public static void copyFile(File src, File copySrc) throws IOException {
        //创建父级路径
        copySrc.mkdir();
        File[] files = src.listFiles();
        if (files == null) return;
        for (File file : files) {
            if (file.isFile()) {
                //如果是文件,直接拷贝
                FileInputStream fileInputStream = new FileInputStream(file);
                //创建文件
                FileOutputStream fileOutputStream = new FileOutputStream(new File(copySrc, file.getName()));
                int readLength;
                byte[] bytes = new byte[1024 * 1024];
                while ((readLength = fileInputStream.read(bytes)) != -1) {
                    fileOutputStream.write(bytes, 0, readLength);
                }
                fileOutputStream.close();
                fileInputStream.close();
            } else copyFile(file, new File(copySrc, file.getName()));
        }

    }
}

# 加密解密文件

public class Main {
    public static void main(String[] args) throws IOException {
        //2.加密和解密文件
        //数据源
        File file2 = new File("attrs\\file\\a.txt");
        //加密文件位置
        File file3 = new File("attrs\\file\\a-加密文件.txt");
        encryptDecryptionFile(file2, 1, file3);
        //解密文件位置
        File file4 = new File("attrs\\file\\a-解密文件.txt");
        //解密文件
        encryptDecryptionFile(file3, 1, file4);

    }

    /**
     * 作用:加密解密文件
     *
     * @param src     File      源文件
     * @param key     int    加密解密密钥
     * @param copySrc File  目标文件
     */
    public static void encryptDecryptionFile(File src, int key, File copySrc) throws IOException {
        if (!src.isFile()) throw new IOException("src不是文件");
        //读取文件对象
        FileInputStream fileInputStream = new FileInputStream(src);
        //写入文件对象
        FileOutputStream fileOutputStream = new FileOutputStream(copySrc);
        int b;
        while ((b = fileInputStream.read()) != -1) {
            //^ 异或 相同返回false 不同返回true
            //当运算符两边为数字时,会将两边的数字转为2进制进行与运算得到的结果再转为十进制进行输出
            //当进行两次与运算时,会得到原始的数字
            fileOutputStream.write(b ^ key);
        }
        fileOutputStream.close();
        fileInputStream.close();
    }
}

# 文件排序

public class Main {
    public static void main(String[] args) throws IOException {
    /*
        3.文本文件中有以下数据
        2-1-5-4-3
        将文件中的数据进行排序,并输出到新文件中
        1-2-3-4-5
         */
        //源文件
        File file5 = new File("attrs\\file\\d.txt");
        //目标文件
        File file6 = new File("attrs\\file\\d-排序后.txt");
        sortFile(file5, file6);
    }

    /**
     * 作用:排序文件
     *
     * @param src     File      源文件
     * @param copySrc File 目标文件
     */
    public static void sortFile(File src, File copySrc) throws IOException {
        if (!src.isFile()) throw new IOException("src不是文件");
        //创建读取文件对象
        FileReader fileReader = new FileReader(src);
        int b;
        StringBuilder sb = new StringBuilder();
        while ((b = fileReader.read()) != -1) sb.append((char) b);
        fileReader.close();
        Integer[] split = Arrays.stream(sb.toString().split("-")).map(Integer::parseInt).sorted().toArray(Integer[]::new);
        //处理字符串
        String str = Arrays.toString(split);
        String finallyStr = str.substring(1, str.length() - 1).replace(", ", "-");
        //创建输出流对象
        FileWriter fileWriter = new FileWriter(copySrc);
        fileWriter.write(finallyStr);
        fileWriter.close();
    }
}

# 字节缓冲流

  • 原理:底层自带了长度为8192的缓冲区提高性能
  • 缓冲流是高级流,他对基本流做了一个包装
  • 在创建高级流时需要创建基本流,高级流不能单独工作
方法名称 说明
public BufferedInputStream(InputStream is) 把基本流包装成高级流,提高读写数据的性能
public BufferedOutputStream(OutputStream os) 把基本流包装成高级流,提高读写数据的性能
public class Main {
    public static void main(String[] args) throws IOException {
        //字节缓冲流
        //利用字节缓冲流来拷贝文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("attrs\\file\\a.txt"));
        //读取数据 readAllBytes()获取全部的字符数据并保存到数组中
        byte[] bytes2 = bis.readAllBytes();
        //拷贝文件对象
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("attrs\\file\\a-字节缓冲流拷贝.txt"));
        bos.write(bytes2);
        bos.close();
        bis.close();
    }
}

# 字符缓冲流

  • 字符缓冲流本身自带8192的缓冲区
  • 字符流本身自带缓冲区,因此性能几乎没有提升,但字符缓冲流封装了一些方法,是的操作更加简单
方法名称 说明
public BufferedReader(Reader r) 把基本流包装成高级流,提高读写数据的性能
public BufferedWriter(Writer w) 把基本流包装成高级流,提高读写数据的性能

# 字符缓冲输入流特有的方法BufferedReader

方法名称 说明
public String readLine() 读取一行数据,如果没有数据可读了,会返回null

# 字符缓冲输出流特有的方法BufferedWriter

方法名称 说明
public void newLine() 写入一个换行符,兼容全平台

# 实现

public class Main {
    public static void main(String[] args) throws IOException {

        //字符缓冲流
        //利用字符缓冲流来拷贝文件
        BufferedReader bufferedReader = new BufferedReader(new FileReader("attrs\\file\\a.txt"));
        //拷贝文件对象
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("attrs\\file\\a-字符缓冲流拷贝.txt"));
        //读取数据 一行一行的读取,遇到回车换行就会结束(不会把回车换行读到内存中,会清除原来的换行)
        //
        String s;
        //如果没有数据可读了 则会返回null
        while ((s = bufferedReader.readLine()) != null) {
            bufferedWriter.write(s);
            //换行
            bufferedWriter.newLine();
        }
        bufferedWriter.close();
        bufferedReader.close();
    }
}

# 文件缓冲流练习

public class Main {
    public static void main(String[] args) throws IOException {
        //记录软件运行次数
        //将软件运行次数写入本地文件中存储
        String fileUrl = "attrs\\file\\软件运行次数.txt";
        File file7 = new File(fileUrl);
        //如果文件不存在,则创建文件 防止BufferedReader报错
        file7.createNewFile();
        BufferedReader bufferedReader1 = new BufferedReader(new FileReader(file7));
        String read3 = bufferedReader1.readLine();
        //如果文件为空 则将read3手动赋值为0 避免重复写入文件
        if (read3 == null) read3 = "0";
//        if (read3 == null) {
//            BufferedWriter bufferedWriter1 = new BufferedWriter(new FileWriter(file7));
//            bufferedWriter1.write("1");
//            bufferedWriter1.close();
//            read3 = bufferedReader1.readLine();
//            //注意只有关闭了流重新读取文件才会更新
//            //读取的是缓冲区的数据
//            bufferedReader1.close();
//        }
        //使用try-with-resource 在报错时关闭流
        try (BufferedWriter bufferedWriter1 = new BufferedWriter(new FileWriter(file7))) {
            bufferedWriter1.write(String.valueOf(Integer.parseInt(read3) + 1));
            if (Integer.parseInt(read3) > 3) throw new Error("本软件只能免费3次,欢迎您注册会员后使用");
        }
    }
}

# 字符流-转换流

  • 转换流是字符流和字节流之间的桥梁
  • 将字节流转换为字符流,拥有字节流特性的同时,又可以使用字符流中的方法
public class Main {
    public static void main(String[] args) throws IOException {
        //字符转换流 在jdk11时这种方式被淘汰了
        //1.利用转换流按照指定的字符编码读取数据
//        InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("attrs\\file\\字符转换流.txt"), "GBK");
//        char[] chars4 = new char[1024 * 1024];
//        int read4;
//        while ((read4 = inputStreamReader.read(chars4)) != -1) {
//            System.out.print(new String(chars4, 0, read4));
//        }
//        inputStreamReader.close();

        FileReader fileReader = new FileReader("attrs\\file\\字符转换流.txt", Charset.forName("GBK"));
        char[] chars4 = new char[1024 * 1024];
        int read4;
        //创建输出流对象
        FileWriter outputStreamWriter = new FileWriter("attrs\\file\\字符转换流1.txt", Charset.forName("GBK"));
        //在jdk11时已经淘汰
//        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("attrs\\file\\字符转换流1.txt"), Charset.forName("GBK"));
        while ((read4 = fileReader.read(chars4)) != -1) {
            outputStreamWriter.write(chars4, 0, read4);
        }
        outputStreamWriter.close();
        fileReader.close();

        //2.文件字符类型转换
        InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("attrs\\file\\字符转换流.txt"), Charset.forName("GBK"));
        OutputStreamWriter outputStreamWriter1 = new OutputStreamWriter(new FileOutputStream("attrs\\file\\字符转换流-utf-转换.txt"), StandardCharsets.UTF_8);
        char[] chars5 = new char[1024 * 1024];
        int read5;
        while ((read5 = inputStreamReader.read(chars5)) != -1) {
            outputStreamWriter1.write(chars5, 0, read5);
        }
        outputStreamWriter1.close();
        inputStreamReader.close();

        //替代方案直接创建FireReader和FireWriter
        FileReader fireReader = new FileReader("attrs\\file\\字符转换流.txt", Charset.forName("GBK"));
        FileWriter fireWriter = new FileWriter("attrs\\file\\字符转换流-utf-转换.txt", StandardCharsets.UTF_8);
        char[] chars6 = new char[1024 * 1024];
        int read6;
        while ((read6 = fireReader.read(chars6)) != -1) {
            fireWriter.write(chars6, 0, read6);
        }
        fireWriter.close();
        fireReader.close();

        //3.利用字节流读取数据,每次读一整行,而且不能出现乱码
        //一次读一整行时字符缓冲流特有的
        String fileUrl = "attrs\\file\\字符转换流1.txt";
        //创建字节流对象
        FileInputStream fileInputStream = new FileInputStream(fileUrl);
        //将字节流包装成字符流(利用字符转换流)
        InputStreamReader inputStreamReader2 = new InputStreamReader(fileInputStream, "GBK");
        //将字符流包装成字符缓冲流
        BufferedReader bufferedReader1 = new BufferedReader(inputStreamReader2);
        //读取一整行
        System.out.println(bufferedReader1.readLine());
        //释放资源
        bufferedReader1.close();
    }
}

# 字节流-序列化流(对象操作输出流)

  • 可以吧java中的对象写到文件当中

# 构造方法

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

# 成员方法

成员方法 说明
public void writeObject(Object obj) 把对象写入到文件中

注意

使用序列化流将对象直接保存到文件时会报NotSerializableException异常

  • 解决方案:需要让javaBean类实现Serializable接口
  • Serializable接口中没有抽象方法,属于标记型接口
  • 一旦实现了这个接口,那么表示当前类可以被序列化
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //序列化流
        //创建序列化流(序列化流是由字节流包装而来)
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("attrs\\file\\序列化流.txt"));
        People people = new People("张三", 18, '男');
        objectOutputStream.writeObject(people);
        objectOutputStream.close();
    }
}



package Io;

import java.io.Serializable;

public class People implements Serializable {
    /*
     *Serializable接口中没有抽象方法,属于标记型接口
     * 表示:一旦实现了这个接口,那么表示当前类可以被序列化
     * */
    private String name;
    private int blod;
    private char sex;

    public People(String name, int blod, char sex) {
        this.name = name;
        this.blod = blod;
        this.sex = sex;
    }

    public People() {
    }

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return blod
     */
    public int getBlod() {
        return blod;
    }

    /**
     * 设置
     *
     * @param blod
     */
    public void setBlod(int blod) {
        this.blod = blod;
    }

    /**
     * 获取
     *
     * @return sex
     */
    public char getSex() {
        return sex;
    }

    /**
     * 设置
     *
     * @param sex
     */
    public void setSex(char sex) {
        this.sex = sex;
    }

    public String toString() {
        return "People{name = " + name + ", blod = " + blod + ", sex = " + sex + "}";
    }
}

# 字节流-反序列化流(对象操作输入流)

可以把序列化到本地文件中的对象,读取到程序中来

# 构造方法

构造方法 说明
public ObjectInputStream(InputStream in) 把字节流包装成高级流

# 成员方法

成员方法 说明
public Object readObject() 从文件中读取对象,返回读取的对象,读取的对象必须是序列化流所序列化的对象
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //反序列化流
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("attrs\\file\\序列化流.txt"));
        People people1 = (People) objectInputStream.readObject();
        System.out.println(people1.toString());
    }
}
package Io;

import java.io.Serial;
import java.io.Serializable;

public class People implements Serializable {
    //固定版本号
    @Serial
    private static final long serialVersionUID = 6598053267419231310L;
    /*
     *Serializable接口中没有抽象方法,属于标记型接口
     * 表示:一旦实现了这个接口,那么表示当前类可以被序列化
     * */


    private String name;
    //瞬态关键字  transient
    //表示不会把该成员变量序列化到文件中

    private transient int blod;
    private char sex;
    private int age;


    public People() {
    }

    public People(String name, int blod, char sex, int age) {
        this.name = name;
        this.blod = blod;
        this.sex = sex;
        this.age = age;
    }

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return blod
     */
    public int getBlod() {
        return blod;
    }

    /**
     * 设置
     *
     * @param blod
     */
    public void setBlod(int blod) {
        this.blod = blod;
    }

    /**
     * 获取
     *
     * @return sex
     */
    public char getSex() {
        return sex;
    }

    /**
     * 设置
     *
     * @param sex
     */
    public void setSex(char sex) {
        this.sex = sex;
    }


    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "People{name = " + name + ", blod = " + blod + ", sex = " + sex + ", age = " + age + "}";
    }
}

注意

  • 序列化流写到文件中的数据是不能改的,一旦修改了 就无法再读出来
  • 序列化对象后,修改了javaBean类,再反序列化就会报错
  • 解决方案:给javaBean类添加一个固定版本号,当反序列化时,会比较版本号,如果版本号不一致,就会报错
  • 如果说成员变量某一个的值不想被序列化,可以使用transient关键字修饰(瞬态关键字)
  • 反序列化如果读到文件末尾则会报异常

# 练习

public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //练习:
        //1.将多个自定义对象序列化到文件中,但是对象的个数不确定
        People people2 = new People("张三", 18, '男', 18);
        People people3 = new People("李四", 18, '女', 18);
        People people4 = new People("王五", 18, '男', 18);
        ObjectOutputStream objectOutputStream1 = new ObjectOutputStream(new FileOutputStream("attrs\\file\\序列化流-存储多个对象.txt"));
        ArrayList<People> peoples = new ArrayList<>();
        Collections.addAll(peoples, people2, people3, people4);
        objectOutputStream1.writeObject(peoples);
        objectOutputStream1.close();

        //读取
        ObjectInputStream objectInputStream1 = new ObjectInputStream(new FileInputStream("attrs\\file\\序列化流-存储多个对象.txt"));
        ArrayList<People> object = (ArrayList<People>) objectInputStream1.readObject();
        objectInputStream1.close();
        for (People people5 : object) {
            System.out.println(people5.toString());
        }
    }
}

# 打印流

  • 打印流不能读只能写,打印流只有输出流,是字节流和字符流的高级流

# 分类

打印流一般是指:PrintStream(字节打印流)和PrintWriter(字符打印流)

# 特点

  • 打印流只能操作文件的目的地,不能操作数据源
  • 特有的写出方法可以实现原样写出
  • 特有的写出方法,可以实现自动刷新,自动换行。打印一次数据=写出+换行+刷新

# 构造方法

构造方法 说明
public PrintStream(OutputStream/File/String out) 关联字节输出流、文件、文件路径
piblic PrintStream(String fileName,Charset charset) 指定字符编码
public PrintStream(OutputStream out,boolean autoFlush) 自动刷新
public PrintStream(OutputStream out,boolean autoFlush,String encoding) 指定字符编码且自动刷新

注意

字节流底层无缓冲区,因此开不开自动刷新都一样

# 成员方法

成员方法 说明
public void write(int b) 规则跟之前的一样,将指定字节写出
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String format,Object...args) 特有方法:带有占位符的打印语句,不换行
public class Main {
    public static void main(String[] args) throws IOException {
        //字节打印流
        PrintStream printStream = new PrintStream(new FileOutputStream("attrs\\file\\字节打印流.txt"), true, "GBK");
        //字节方式写出
        printStream.write(97);//a
        //换行 原样写出
        printStream.println(97);//97
        //不换行 原样写出
        printStream.println(true);//true
        //使用占位符写出
        printStream.printf("%s爱上了%s", "阿珍", "阿强");//阿珍爱上了阿强
        printStream.close();
    }
}

# 字符打印流

  • 字符打印流底层具有缓冲区,想要自动刷新需要开启

# 构造方法 与字节流基本一致

构造方法 说明
public PrintWriter(OutputStream/File/String out) 关联字符输出流、文件、文件路径
piblic PrintWriter(String fileName,Charset charset) 指定字符编码
public PrintWriter(Write w,boolean autoFlush) 自动刷新
public PrintWriter(OutputStream out,boolean autoFlush,String encoding) 指定字符编码且自动刷新

# 成员方法 与字节流基本一致

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

注意

如果不开启自动刷新,那么只有在流关闭的时候才能写入数据,此时数据保存在缓冲区中

public class Main {
    public static void main(String[] args) throws IOException {
        //字符打印流
        PrintWriter printWriter = new PrintWriter(new FileWriter("attrs\\file\\字符打印流.txt"), true);
        printWriter.write(97);//a
        printWriter.println(97);//97
        printWriter.println(true);//true
        printWriter.printf("%s爱上了%s", "阿珍", "阿强");////阿珍爱上了阿强
        printWriter.close();
    }
}

# 字节流-解压缩流与压缩流

  • 解压缩流与压缩流都属于字节流
  • 解压缩流主要目的是读取压缩包中的文件,读取属于输入流
  • 压缩流主要目的是将文件中的数据写入到压缩包中,属于输出流

# 解压缩流

  • 解压本质:把每一个zipEntry对象中的数据按照层级拷贝到另一个文件夹中
  • java只能识别zip格式的压缩包,所以解压时需要指定压缩包类型为zip
public class Main {
    public static void main(String[] args) throws IOException {
        //解压缩流
        //定义需要解压的文件
        File src = new File("attrs\\file\\ZipStreamDemo\\ZipStreamDemo.zip");
        //定义需要解压的目的地
        File src1 = new File("attrs\\file\\ZipStreamDemo\\ZipStreamDemo");
        unZip(src, src1);
    }

    /**
     * 解压缩流
     * 定义一个方法用来解压
     *
     * @param src  File      原数据
     * @param src1 File      目标数据
     */
    public static void unZip(File src, File src1) throws IllegalArgumentException, IOException, FileNotFoundException {
        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(src), Charset.forName("GBK"));
        //获取压缩包中的每一个ZipEntry对象
        ZipEntry nextEntry;
        while ((nextEntry = zipInputStream.getNextEntry()) != null) {
            System.out.println(nextEntry.toString());
            //判断当前目录是文件还是文件夹
            //如果是文件夹就创建文件夹
            if (nextEntry.isDirectory()) {
                File file = new File(src1, nextEntry.toString());
                boolean mkdir = file.mkdirs();
                System.out.println(mkdir + "  " + file.toString());
            }
            //如果是文件就拷贝文件
            else {
                //读取目标中的文件 并把它存放到目的地文件夹中(按照底层目录进行存放)
                FileOutputStream fileOutputStream = new FileOutputStream(new File(src1, nextEntry.getName()));
                int b;
                while ((b = zipInputStream.read()) != -1) {
                    fileOutputStream.write(b);
                }
                fileOutputStream.close();
            }
        }
    }
}

# 压缩流

public class Main {
    public static void main(String[] args) throws IOException {
        //压缩流
        //将一个文件变成压缩包
        //定义需要压缩的文件
        File src2 = new File("attrs\\file\\ZipStreamDemo\\字符流");
        //定义压缩目的地
        File src3 = new File("attrs\\file\\ZipStreamDemo");
        onZip(src2, src3);
    }

    /**
     * 压缩流
     * 作用:将文件夹中的所有文件压缩成一个压缩包
     *
     * @param src  File      原文件
     * @param src1 File      压缩的目的地
     */
    public static void onZip(File src, File src1) throws IllegalArgumentException, IOException, FileNotFoundException {
        //获取文件夹中的所有文件
        ArrayList<File> allFile = getAllFile(src);
        System.out.println("所有数据" + allFile.toString());
        //创建压缩流对象 创建压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(src1, src.getName() + ".zip")));
        //循环压缩文件
        for (File file : allFile) {
            zos.putNextEntry(new ZipEntry(file.getPath().substring(src.getPath().length() + 1)));
            //读取文件内容 放入压缩包中
            FileInputStream fileInputStream = new FileInputStream(file);
            int b;
            while ((b = fileInputStream.read()) != -1) {
                zos.write(b);
            }
            fileInputStream.close();
        }
        zos.closeEntry();
        zos.close();
    }

    /**
     * 压缩流
     * 作用:压缩单个文件,不支持文件夹压缩
     *
     * @param src  File      原文件
     * @param src1 File      压缩的目的地
     */
    public static void zipFile(File src, File src1) throws IllegalArgumentException, IOException, FileNotFoundException {
        //创建压缩流对象
        //作用:使用压缩目的地创建一个空的压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(src1, src.getName().split("\\.")[0] + ".zip")));
        //创建zipEntry对象 表示压缩包里面的每一个对象
        //ZipEntry中的形参也表示压缩包里面的路径
        ZipEntry zipEntry = new ZipEntry(src.getPath());
        //将zipEntry对象添加到压缩流对象中 表示将文件放入压缩包中
        zos.putNextEntry(zipEntry);
        //把文件中的数据写入到压缩流中
        FileInputStream fileInputStream = new FileInputStream(src);
        int b;
        while ((b = fileInputStream.read()) != -1) {
            zos.write(b);
        }
        zos.closeEntry();
        zos.close();
    }
}

# Commons-io

  • Commons-io包是java中的一个开源包,提供了许多与io操作相关的工具类,如文件复制、文件移动、文件重命名、文件删除、文件重命名 第三方架包的使用步骤
  • 在项目中创建lib文件夹
  • 将架包复制粘贴到该文件夹下
  • 右键点击架包,选择add as library->点击ok
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

public class Main {
    public static void main(String[] args) throws IOException {
        //  commons-io
        //fileUtils类
        //复制文件 copyFile
        File src4 = new File("attrs\\file\\ZipStreamDemo\\a-解密文件.txt");
        File src5 = new File("attrs\\file\\ZipStreamDemo\\a-解密文件-复制.txt");
        FileUtils.copyFile(src4, src5);
        //复制文件夹
        File src6 = new File("attrs\\file\\ZipStreamDemo\\字符流");
        File src7 = new File("attrs\\file\\ZipStreamDemo\\字符流-复制");
        FileUtils.copyDirectory(src6, src7);

        //清空文件夹
        File src9 = new File("attrs\\file\\ZipStreamDemo\\字符流-复制");
        FileUtils.cleanDirectory(src9);
        //删除文件夹
        File src8 = new File("attrs\\file\\ZipStreamDemo\\字符流-复制");
        FileUtils.deleteDirectory(src8);
        //读取文件中的数据变成字符串
        File src10 = new File("attrs\\file\\ZipStreamDemo\\a-解密文件.txt");
        String content = FileUtils.readFileToString(src10, StandardCharsets.UTF_8);
        System.out.println(content);
        //写出数据
        File src11 = new File("attrs\\file\\ZipStreamDemo\\Common-io-写出数据.txt");
        FileUtils.writeStringToFile(src11, content, StandardCharsets.UTF_8);

        //IOUtils类
        //复制文件
        File src12 = new File("attrs\\file\\ZipStreamDemo\\a-解密文件.txt");
        File src13 = new File("attrs\\file\\ZipStreamDemo\\a-解密文件-复制(副本).txt");
        IOUtils.copy(new FileInputStream(src12), new FileOutputStream(src13));
        //复制大文件
        File src14 = new File("attrs\\file\\ZipStreamDemo\\a-解密文件.txt");
        File src15 = new File("attrs\\file\\ZipStreamDemo\\大文件-复制.txt");
        IOUtils.copyLarge(new FileInputStream(src14), new FileOutputStream(src15));
        //读取数据(一行读取)
        File src16 = new File("attrs\\file\\ZipStreamDemo\\a-解密文件.txt");
        List<String> strings = IOUtils.readLines(new FileReader(src16));
        System.out.println(strings.toString());
        //写出数据
        //lineEnding:每行结尾的字符
        File src17 = new File("attrs\\file\\ZipStreamDemo\\Common-io-写出数据.txt");
        IOUtils.writeLines(strings, "\n", new FileWriter(src17));
    }
}

# Hutool工具包

  • Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。

# IO流相关类

类名 作用
IoUtil 流操作工具类
FileUtil 文件操作工具类
FileUtil 文件类型判断工具类
watchMonitor 目录、文件监听
ClassPathResource 针对ClassPath中资源的访问封装
FileReader 封装文件读取
FileWriter 封装文件写入
import cn.hutool.core.io.FileUtil;

public class Main {
    public static void main(String[] args) throws IOException {
        //hutool工具包
        //根据参数获取一个file对象
        File file7 = FileUtil.file("attrs\\file\\ZipStreamDemo\\a-解密文件.txt");
        System.out.println(file7.getPath());
        //根据参数创建文件
        //创建父级
        File file8 = new File("attrs\\file\\aaa\\Hutool-创建文件.txt");
        File touch = FileUtil.touch(file8);
        System.out.println(touch.getPath());

        //读取数据
        //将文件中的数据读取到集合中
        List<String> list = FileUtil.readLines(new File("attrs\\file\\ZipStreamDemo\\a-解密文件.txt"), StandardCharsets.UTF_8);
        System.out.println(list.toString());
        //写入数据
        FileUtil.writeLines(list, file8, StandardCharsets.UTF_8);
    }
}

# IO流综合练习

# 使用网络爬虫获取随机姓名

# 使用正则表达式方式获取指定字符串

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class Main {
    //姓氏数组
    static ArrayList<String> familyNameList = new ArrayList<>();
    //男生名字数组
    static ArrayList<String> boyNameList = new ArrayList<>();
    //女生名字数组
    static ArrayList<String> girlNameList = new ArrayList<>();

    //获取数据 加载类时立即执行
    static {
        try {
            //通过正则表达式获取内容
            getNameDataListByRegx();

        } catch (URISyntaxException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        //1.爬取数据
        ArrayList<String> name = getName(100, 10);
        System.out.println(name);
        //使用字符流将数据写入本地文件中
        BufferedWriter fileWriterName = new BufferedWriter(new FileWriter("attrs\\file\\ZipStreamDemo\\网络爬虫-姓名爬取.txt"));
        for (String nameItem : name) {
            //写入数据
            fileWriterName.write(nameItem);
            //换行
            fileWriterName.newLine();
        }
        //关流
        fileWriterName.close();
    }

    /**
     * 网络爬虫-获取字符串数据
     *
     * @param url String 网络地址
     */
    public static String getUrlDataString(String url) throws URISyntaxException, IOException {
        Connection connect = Jsoup.connect(url);
        Document document = connect.timeout(6000).get();
        return document.toString();
    }

    /**
     * 网络爬虫 利用正则获取数据
     *
     * @param url   String 网络地址
     * @param regex String 正则
     * @param index Int 获取第几组数据
     * @return return ArrayList<String> 返回数据
     */
    private static ArrayList<String> getNameDataList(String url, String regex, int index) throws URISyntaxException, IOException {
        //获取网站内容字符串
        String urlString = getUrlDataString(url);
        //使用正则对象查找内容
        Pattern pattern = Pattern.compile(regex);
        //按照pattern的规则到url中获取数据
        Matcher matcher = pattern.matcher(urlString);
        //定义存储字符串集合
        ArrayList<String> list = new ArrayList<>();
        //循环寻找字符串(一行一行的读取)
        while (matcher.find()) {
            //匹配的字符
            String[] groupList = matcher.group(index).replaceAll("。", "").split(",");
            list.addAll(Arrays.asList(groupList));
        }
        return list;
    }

    /**
     * 利用正则获取姓名列表数据
     */
    public static void getNameDataListByRegx() throws URISyntaxException, IOException {
        String familyNameNet = "https://www.diyifanwen.com/tool/baijiaxing/";
        //男生名字
        String boyNameNet = "http://www.haoming8.cn/baobao/65736.html";
        //女生名字
        String girlNameNet = "http://www.haoming8.cn/baobao/92428.html";
        //姓名数据处理
        familyNameList = getNameDataList(familyNameNet, "([\\u4E00-\\u9FA5](,|。)){10}", 0);
        //男生名字数据处理
        boyNameList = getNameDataList(boyNameNet, "([\\u4E00-\\u9FA5]{2})(、|'')", 1);
        //女生名字数据处理
        girlNameList = getNameDataList(girlNameNet, "([\\u4E00-\\u9FA5]{2})(、|。)", 1);
    }

    /**
     * 获取随机姓名
     *
     * @param boyCount  男生数量
     * @param girlCount 女生数量
     */

    public static ArrayList<String> getName(int boyCount, int girlCount) throws URISyntaxException, IOException {
        if (boyCount == 0 || girlCount == 0) throw new IOException("boyCount或者girlCount不能为0");
        //男生名字集合
        HashSet<String> boyNameSet = new HashSet<>();
        while (boyNameSet.size() < boyCount) {
            //打乱姓氏集合
            Collections.shuffle(familyNameList);
            //打乱男生名字集合
            Collections.shuffle(boyNameList);
            //获取随机年龄17~27
            Random r = new Random();
            int age = r.nextInt(10) + 18;
            //添加数据
            boyNameSet.add(familyNameList.getFirst() + boyNameList.getFirst() + "-男-" + age);
        }
        //女生名字集合
        HashSet<String> girlNameSet = new HashSet<>();
        while (girlNameSet.size() < girlCount) {
            //打乱姓氏集合
            Collections.shuffle(familyNameList);
            //打乱女生名字集合
            Collections.shuffle(girlNameList);
            //获取随机年龄17~27
            Random r = new Random();
            int age = r.nextInt(10) + 18;
            girlNameSet.add(familyNameList.getFirst() + girlNameList.getFirst() + "-女-" + age);
        }
        ArrayList<String> nameList = new ArrayList<>();
        //将男生和女生的数据合并 添加到nameList集合中
        nameList.addAll(boyNameSet);
        nameList.addAll(girlNameSet);
        //打乱集合
        Collections.shuffle(nameList);
        return nameList;
    }
}

# 利用Jsoup架包获取文档元素内容方式

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class Main {
    //姓氏数组
    static ArrayList<String> familyNameList = new ArrayList<>();
    //男生名字数组
    static ArrayList<String> boyNameList = new ArrayList<>();
    //女生名字数组
    static ArrayList<String> girlNameList = new ArrayList<>();

    //获取数据 加载类时立即执行
    static {
        try {
            //通过选择元素获取内容
            getNameDataList();

        } catch (URISyntaxException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        //1.爬取数据
        ArrayList<String> name = getName(100, 10);
        System.out.println(name);
        //使用字符流将数据写入本地文件中
        BufferedWriter fileWriterName = new BufferedWriter(new FileWriter("attrs\\file\\ZipStreamDemo\\网络爬虫-姓名爬取.txt"));
        for (String nameItem : name) {
            //写入数据
            fileWriterName.write(nameItem);
            //换行
            fileWriterName.newLine();
        }
        //关流
        fileWriterName.close();
    }

    /**
     * 网络爬虫 爬取数据-get请求
     *
     * @param url String       爬取的网址
     */
    public static Document getUrlData(String url) throws URISyntaxException, IOException {
        Connection connect = Jsoup.connect(url);
        Document document = connect.timeout(6000).get();
        return document;
    }

    //获取姓名列表数据
    private static void getNameDataList() throws URISyntaxException, IOException {
//百家姓
        String familyNameNet = "https://www.diyifanwen.com/tool/baijiaxing/";
        //男生名字
        String boyNameNet = "http://www.haoming8.cn/baobao/65736.html";
        //女生名字
        String girlNameNet = "http://www.haoming8.cn/baobao/92428.html";
        //姓名处理
        Document familyNameData = getUrlData(familyNameNet);
        Element familyNamePm = familyNameData.getElementById("pm");
        Element familyNameChild = familyNamePm.child(1);
        String familyNameString = familyNameChild.toString();
        String[] familyNameStringList = familyNameString.replaceAll("<[^>]*>", "").replaceAll("。", "").replace("\n", "").split(",");
        //名字数组
        Collections.addAll(familyNameList, familyNameStringList);
        System.out.println(familyNameList);
        //男生名字处理
        Document boyNameData = getUrlData(boyNameNet);
        Element boyNameContentText = boyNameData.getElementById("contentText");
        //获取所有p元素
        Elements boyNameContentTextP = boyNameContentText.getElementsByTag("p");
        //字符串替换
        String[] bodyNameContentTextSplit = boyNameContentTextP.toString().replaceAll("</p>", "、").replaceAll("<[^>]*>", "").replaceAll("\n", "").split("、");
        //男生名字数组
        Collections.addAll(boyNameList, bodyNameContentTextSplit);
        System.out.println(boyNameList);

        //女生名字处理
        Document girlNameData = getUrlData(girlNameNet);
        Element girlNameContentText = girlNameData.getElementById("contentText");
        Elements girlNameContentTextP = girlNameContentText.getElementsByTag("p");
        Element girlNameContentTextPElement = girlNameContentTextP.get(2);
        String[] girlNameContentTextList = girlNameContentTextPElement.text().replaceAll("。", "").split("、");
        Collections.addAll(girlNameList, girlNameContentTextList);
        System.out.println(girlNameList);
    }
}

# 使用糊涂包爬取数据方式

import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HttpUtil;

public class Main {
    //姓氏数组
    static ArrayList<String> familyNameList = new ArrayList<>();
    //男生名字数组
    static ArrayList<String> boyNameList = new ArrayList<>();
    //女生名字数组
    static ArrayList<String> girlNameList = new ArrayList<>();

    //获取数据 加载类时立即执行
    static {
        try {
            //通过糊涂包获取内容
            getNameDataListByHutoolRegx();

        } catch (URISyntaxException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 使用hutool包爬取数据
     *
     * @param url String 网络地址
     */
    public static ArrayList<String> getNameDataListByHutool(String url, String regex, int index) throws URISyntaxException, IOException {
        //获取网站内容字符串
        String urlString = HttpUtil.get(url).replaceAll("。", "");
        List<String> stringList = ReUtil.findAll(regex, urlString, index);
        return (ArrayList<String>) stringList;
    }

    /**
     * 利用hutool包爬取并处理数据
     */
    public static void getNameDataListByHutoolRegx() throws URISyntaxException, IOException {
        String familyNameNet = "https://www.diyifanwen.com/tool/baijiaxing/";
        //男生名字
        String boyNameNet = "http://www.haoming8.cn/baobao/65736.html";
        //女生名字
        String girlNameNet = "http://www.haoming8.cn/baobao/92428.html";
        //姓名数据处理
        for (String itemName : getNameDataListByHutool(familyNameNet, "([\\u4E00-\\u9FA5](,|。)){10}", 0)) {
            String[] split = itemName.split(",");
            familyNameList.addAll(List.of(split));
        }
        //男生名字数据处理
        boyNameList = getNameDataListByHutool(boyNameNet, "([\\u4E00-\\u9FA5]{2})(、|'')", 1);
        //女生名字数据处理
        girlNameList = getNameDataListByHutool(girlNameNet, "([\\u4E00-\\u9FA5]{2})(、|。)", 1);
    }
}

# io流-随机点名器

# 随机点名器1(只显示名字)

  • 需求:有一个文件存储了班级同学的信息,每信息占一行,格式为:姓名-性别-年龄

  • 要求:通过程序实现随机点名

  • 运行效果:

  • 第一次运行程序:随机同学姓名1(只显示名字)

  • 第二次运行程序:随机同学姓名2(只显示名字)

  • 第三次运行程序:随机同学姓名3(只显示名字)

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //随机点名器1(只显示名字)
        String name1 = getName1();
        System.out.println("随机点名器1:" + name1);
    }

    /**
     * 随机点名器1(只显示名字)
     *
     * @return String 学生名字
     */
    public static String getName1() throws IOException {
        //获取数据
        BufferedReader fileReader = new BufferedReader(new FileReader("attrs\\file\\ZipStreamDemo\\网络爬虫-姓名爬取.txt"));
        String b;
        ArrayList<String> nameList = new ArrayList<>();
        while ((b = fileReader.readLine()) != null) {
            nameList.add(b);
        }
        Collections.shuffle(nameList);
        return nameList.getFirst().split("-")[0];
    }
}

# 随机点名器2(加入性别概率)

  • 需求:一个文件里面存储了班级同学的信息,每一个学生信息占一行格式为:张三-男-23。

  • 要求:通过程序实现随机点名器

  • 运行效果:

  • 70%的概率随机到男生

  • 30%的概率随机到女生

  • 总共随机100万次,统计结果。

  • 注意观察:看生成男生和女生的比例是不是接近于7:3

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        String name2 = getName2();
    }

    /**
     * 获取姓名文件中的数据
     *
     * @return ArrayList<String> 姓名集合
     */
    public static ArrayList<String> getNameListFromFile() throws IOException {
        BufferedReader fileReader = new BufferedReader(new FileReader("attrs\\file\\ZipStreamDemo\\网络爬虫-姓名爬取.txt"));
        String b;
        ArrayList<String> nameList = new ArrayList<>();
        while ((b = fileReader.readLine()) != null) {
            nameList.add(b);
        }
        Collections.shuffle(nameList);
        return nameList;
    }

    /**
     * 随机点名器2 70%概率随机男生 30%概率随机女生
     */
    public static String getName2() throws IOException {
        //获取数据
        ArrayList<String> nameListFromFile = getNameListFromFile();
        //打乱数据
        Collections.shuffle(nameListFromFile);
        //生成1-10的随机数
        Random r = new Random();
        int i = r.nextInt(9) + 1;
        List<String> list;
        if (i <= 7) {
            list = nameListFromFile.stream().filter(s -> Objects.equals(s.split("-")[1], "男")).toList();
        } else {
            list = nameListFromFile.stream().filter(s -> Objects.equals(s.split("-")[1], "女")).toList();
        }
        return list.getFirst();
    }
}

# 随机点名器3(第三次运行程序必定是某人)

  • 需求:一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器。

  • 要求:第三次必定是张三同学

  • 运行效果:

  • 第一次运行程序:随机同学姓名1

  • 第二次运行程序:随机同学姓名2

  • 第三次运行程序:张三

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //随机点名器3 软件运行三次必定是张三
        for (int i = 0; i < 10; i++) {
            String name3 = getName3();
            System.out.println("随机点名器3:" + name3);
        }
    }

    /**
     * 随机点名器3(运行三次必定是某人)
     */
    public static String getName3() throws IOException {
        //获取数据
        ArrayList<String> nameListFromFile = getNameListFromFile();
        //打乱数据
        Collections.shuffle(nameListFromFile);
        //记录运行次数
        File file = new File("attrs\\file\\ZipStreamDemo\\随机点名器-记录软件运行次数.txt");
        //如果文件不存在则创建文件
        file.createNewFile();
        BufferedReader fileReader = new BufferedReader(new FileReader(file));
        String s = fileReader.readLine();
        fileReader.close();
        if (s == null) s = "0";
        //写入软件运行次数
        BufferedWriter fileWriter = new BufferedWriter(new FileWriter(file));
        fileWriter.write(String.valueOf(Integer.parseInt(s) + 1));
        fileWriter.close();
        //当软件运行次数大于等于3次时,随机点名器3必定是某人
        int i = Integer.parseInt(s);
        if (i >= 3) return "张三";
        return nameListFromFile.getFirst().split("-")[0];
    }
}

# 随机点名器4 (学生独立完成)

  • 需求:一个文件里面存储了班级同学的姓名,每一个姓名占一行。

  • 要求:通过程序实现随机点名器。

  • 运行效果:

  • 如果班级中所有的学生都点完了,需要自动的重新开启第二轮点名

  • 细节1:假设班级有10个学生,每一轮中每一位学生只能被点到一次,程序运行10次,第一轮结束

  • 细节2:第11次运行的时候,我们自己不需要手动操作本地文件,要求程序自动开始第二轮点名

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        //随机点名器4 被点到的学生不会再被点到
        //如果班级中所有的学生都点完了,需要自动的重新开启第二轮点名
        for (int i = 0; i < 100; i++) {
            String name4 = getName4();
            System.out.println("随机点名器4:" + name4);
        }
    }

    /**
     * 随机点名器4(被点到的学生不会再被点到)
     * 如果班级中所有的学生都点完了,需要自动的重新开启第二轮点名
     */
    public static String getName4() throws IOException {
        //获取数据
        ArrayList<String> nameListFromFile = getNameListFromFile();
        //打乱数据
        Collections.shuffle(nameListFromFile);
        File file = new File("attrs\\file\\ZipStreamDemo\\随机点名器-记录已点到的学生.txt");
        file.createNewFile();
        //点名的集合
        ArrayList<String> list = new ArrayList<>(nameListFromFile);
        //获取已点到的学生
        BufferedReader fileReader = new BufferedReader(new FileReader(file));
        String s;
        while ((s = fileReader.readLine()) != null) {
            list.remove(s);
        }
        fileReader.close();

        //如果班级的学生全部被点到,则清空文件
        if (list.isEmpty()) {
            //清空文件
            file.delete();
            //重新开始点名
            System.out.println("班级学生全部被点到,重新开始第二轮点名");
            return getName4();
        }
        //记录被点到的学生
        String nameItem = list.getFirst();
        BufferedWriter fileWriter = new BufferedWriter(new FileWriter(file, true));
        if (nameItem != null) {
            fileWriter.write(nameItem);
            fileWriter.newLine();
            fileWriter.close();
            return nameItem.split("-")[0];
        }
        return "123";
    }
}

# 随机点名器5 (带权重的随机算法)

  • TxT文件中事先准备好一些学生信息,每个学生的信息独占一行。

  • 要求1:每次被点到的学生,再次被点到的概率在原先的基础上降低一半。

  • 举例:80个学生,点名5次,每次都点到小A,概率变化情况如下:

  • 第一次每人概率:1.25%。

  • 第二次小A概率:0.625%。 其他学生概率:1.2579%

  • 第三次小A概率:0.3125%。 其他学生概率:1.261867%

  • 第四次小A概率:0.15625%。其他学生概率:1.2638449%

  • 第五次小A概率:0.078125%。其他学生概率:1.26483386%

  • 提示:本题的核心就是带权重的随机

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        getName5();
    }

    /**
     * 获取姓名文件中的数据
     *
     * @return ArrayList<String> 姓名集合
     */
    public static ArrayList<String> getNameListFromFile() throws IOException {
        BufferedReader fileReader = new BufferedReader(new FileReader("attrs\\file\\ZipStreamDemo\\网络爬虫-姓名爬取.txt"));
        String b;
        ArrayList<String> nameList = new ArrayList<>();
        while ((b = fileReader.readLine()) != null) {
            nameList.add(b);
        }
        Collections.shuffle(nameList);
        return nameList;
    }

    /**
     * 随机点名器5(带权重的随机算法)
     */
    public static String getName5() throws IOException {
        //将文件中所有的学生信息读取到内存中
        //并把 学生信息封装成学生对象放到集合中
        ArrayList<String> nameListFromFile = getNameListFromFile();
        ArrayList<Student> studentList = new ArrayList<>();
        for (String student : nameListFromFile) {
            String[] split = student.split("-");
            studentList.add(new Student(split[0], split[1], Integer.parseInt(split[2]), Double.parseDouble(split[3])));
        }
        System.out.println(studentList);
        //获取所有人权重的总和
        double countWeight = 0;
        for (Student student : studentList) {
            countWeight += student.getWeight();
        }
        System.out.println("权重总和" + countWeight);

        ArrayList<Double> weightList = new ArrayList<>();
        weightList.add(studentList.getFirst().getWeight() / countWeight);
        for (int i = 1; i < studentList.size(); i++) {
            //计算每个人的权重占比
            double v = studentList.get(i).getWeight() / countWeight;
            //计算权重占比范围
            double v1 = weightList.getLast() + v;
            //将结果存入集合中
            weightList.add(v1);
        }
        System.out.println("权重占比" + weightList);
        //随机一个小数开始点名
        double num = Math.random();
        System.out.println("随机数" + num);
        //判断num在weightList中的范围
        //因为数据是有序的,所以使用二分查找
        //方法返回 -插入点-1
        //获取num这个小数在数组插入点的位置
        int i = -Arrays.binarySearch(weightList.toArray(), num) - 1;
        System.out.println("num在weightList中的位置" + i);
        //根据索引获取学生
        Student student = studentList.get(i);
        System.out.println("被点到的学生" + student.getName());
        //被点到的学生修改该学生的权重
        student.setWeight(student.getWeight() / 2);
        System.out.println("修改后的学生" + student);
        //将集合再次写到文件中
        Student.writeFile(studentList);
        return student.getName();
    }
}

package Io;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Objects;

public class Student {
    //姓名
    private String name;
    //    性别
    private String sex;
    //    年龄
    private int age;
    //    权重
    private double weight;

    public Student(String name, String sex, int age, double weight) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.weight = weight;
    }

    //写入文件方法
    public static void writeFile(List<Student> list) throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("attrs\\file\\ZipStreamDemo\\网络爬虫-姓名爬取.txt"));
        for (Student student : list) {
            bufferedWriter.write(student.getFileDate());
            bufferedWriter.newLine();
        }
        bufferedWriter.close();
    }

    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;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Double.compare(weight, student.weight) == 0 && Objects.equals(name, student.name) && Objects.equals(sex, student.sex);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, sex, age, weight);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                '}';
    }

    public String getFileDate() {
        return name + "-" + sex + "-" + age + "-" + weight;
    }
}

上次更新: 2/7/2025, 10:06:36 PM