IO部分笔记

IO

概述

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

作用: 用于读写文件中的数据(可以读写文件, 或网络中的数据)

IO流的分类

按流的方向: 输入流, 输出流

按操作文件类型:

字节流: 可以操作所有类型的文件

字符流: 只能操作纯文本文件

纯文本文件: windows自带的记事本打开能读懂(txt md xml lrc是) word和excel都不是

IO流体系

FileOutputStream

FileOutputStream基本使用

操作本地文件的字节输出流, 可以把程序中的数据写道本地文件中

java 复制代码
package IOTest1;

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

public class IODemo1 {
    public static void main(String[] args) throws IOException {
        // 创建输出流对象
        FileOutputStream fos = new FileOutputStream("IO\\a.txt");
        // 写数据
        fos.write(97);
        // 释放资源
        fos.close();
    }
}

细节

1.创建字节输出流对象

  • 细节1:参数是字符串表示的路径或者File对象都是可以的
  • 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
  • 细节3:如果文件已经存在,则会清空文件

2.写数据

  • 细节: write方法的参数是整数,但 是实际上写到本地文件中的是整数在ASCII上对应的字符

3.释放资源

  • 细节:每次使用完流之后都要释放资源
FileOutputStream写数据的三种方式
java 复制代码
package IOTest1;

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

public class Demo2 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("IO\\a.txt");
        // fos.write(99); // c
        byte[] arr = {97,98,99,100,101};
        // fos.write(arr); // abcde

        // 第二个参数位起始索引, 第三个参数为写入个数
        fos.write(arr, 1, 2); // bc
        fos.close();
    }
}

两个小问题

1.换行

windows: \r\n

linux: \n

mac: \r

2.不覆盖, 续写

想要续写, 打开续写开关即可

java 复制代码
package IOTest1;

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

public class Demo3 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("IO\\a.txt", true);
        String str = "guanjunyanshidashazi";
        byte[] bytes1 = str.getBytes();
        fos.write(bytes1);
        // 换行
        String str3 = "\r\n";
        byte[] bytes3 = str3.getBytes();
        fos.write(bytes3);

        String str2 = "666";
        byte[] bytes2 = str2.getBytes();
        fos.write(bytes2);
        fos.close();
    }
}

程序输出:

java 复制代码
guanjunyanshidashazi
666guanjunyanshidashazi
666

FileInputStream

基本使用

操作本地文件的字节输入流, 可以把本地文件中的数据读取到程序中来

书写步骤:

  1. 创建字节输入流对象
  2. 读数据
  3. 释放资源
java 复制代码
package IOTest1;

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

public class InputDemo1 {
    public static void main(String[] args) throws IOException {
        // 创建对象
        FileInputStream fis = new FileInputStream("IO\\a.txt");
        // 读取数据
        int r1 = fis.read();
        System.out.println((char)r1); // a
        int r2 = fis.read();
        System.out.println((char)r2); // b
        int r3 = fis.read();
        System.out.println((char)r3); // c
        int r4 = fis.read();
        System.out.println((char)r4); // d
        int r5 = fis.read();
        System.out.println(r5); // -1
        // 释放资源
        fis.close();
    }
}
书写细节

创建字节输入流对象

细节:如果文件不存在,就直接报错。

读取数据

细节1: 一次读一个字节,读出来的是数据在ASCII上对应的数字

细节2:读到文件末尾了,read方法返回-1。

释放资源

细节:每次使用完流必须要释放资源。

循环读取

注意: 每调用一次read()方法, 读取数据的指针就会后移, 因此不要多次调用read()函数

java 复制代码
package IOTest1;

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

public class Demo4 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("IO\\a.txt");
        int b = 0;
        while((b = fis.read()) != -1){
            System.out.print((char)b); // abcdefg
        }
        fis.close();
    }
}

练习

文件拷贝
java 复制代码
package IOTest1;

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

public class Demo5 {
    public static void main(String[] args) throws IOException {
        // 创建对象
        FileInputStream fis = new FileInputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶.md");
        FileOutputStream fos = new FileOutputStream("IO\\copy.md");
        int b;
        // 核心思想: 边读边写
        long l1 = System.currentTimeMillis();
        while((b = fis.read()) != -1){
            fos.write(b);
        }
        long l2 = System.currentTimeMillis();
        // 打印copy时间
        System.out.println(l2 - l1);
        // 释放资源: 最后释放最先打开的流对象
        fos.close();
        fis.close();
    }
}

文件拷贝的问题

弊端: 一次第一个字节, 导致速度很慢

FileInputStream一次读多个字节

注意: 一次都一个字节数组的数据, 每次读取会尽可能把数组装满

数组长度一般为1024的整数倍(1024 * 1024 * 5)

java 复制代码
package IOTest2;

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

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 一次读取多个数据
        // 文件中的数据是abcde
        FileInputStream fis = new FileInputStream("IO\\a.txt");
        // 一次读两个数据
        byte[] bytes = new byte[2];
        int len1 = fis.read(bytes);
        System.out.println(len1); // 2
        String str1 = new String(bytes);
        System.out.println(str1); // ab

        int len2 = fis.read(bytes);
        System.out.println(len2); // 2
        String str2 = new String(bytes);
        System.out.println(str2); // cd

        int len3 = fis.read(bytes);
        System.out.println(len3); // 1
        String str3 = new String(bytes);
        System.out.println(str3); // ed
        
        int len4 = fis.read(bytes);
        System.out.println(len4); // -1
        String str4 = new String(bytes);
        System.out.println(str4); // ed
    }
}

解释: 文件中的数据位abcde

每次都两个, 前两次len都为2, 数据分别为ab 和 cd(cd把byte数据的ab覆盖了)

第三次的时候, 只剩一个数据了, 所以len = 1, 然后e把c覆盖了, d没有被覆盖, 所以输出ed

第四次的时候, 没有数据所以返回-1, 数组中的数据ed不变

解决方法

使用string的另一种构造方法:

java 复制代码
package IOTest2;

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

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 一次读取多个数据
        // 文件中的数据是abcde
        FileInputStream fis = new FileInputStream("IO\\a.txt");
        // 一次读两个数据
        byte[] bytes = new byte[2];
        int len1 = fis.read(bytes);
        System.out.println(len1); // 2
        String str1 = new String(bytes, 0, len1);
        System.out.println(str1); // ab

        int len2 = fis.read(bytes);
        System.out.println(len2); // 2
        String str2 = new String(bytes, 0, len2);
        System.out.println(str2); // cd

        int len3 = fis.read(bytes);
        System.out.println(len3); // 1
        String str3 = new String(bytes, 0, len3);
        System.out.println(str3); // e

        int len4 = fis.read(bytes);
        System.out.println(len4); // -1
        // String str4 = new String(bytes, 0, len4);
        // System.out.println(str4); // 这两行会报错
    }
}

表示从byte数组的0位置转len个字符为字符串

文件拷贝改进
java 复制代码
package IOTest2;

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

public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 创建对象
        FileInputStream fis = new FileInputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶.md");
        FileOutputStream fos = new FileOutputStream("IO\\copy.md");
        int len;
        byte[] bytes = new byte[1024 * 1024 * 5];
        long l1 = System.currentTimeMillis();
        while((len = fis.read(bytes)) != -1){
            fos.write(bytes, 0, len);
        }
        long l2 = System.currentTimeMillis();
        System.out.println(l2-l1); // 2
        fos.close();
        fis.close();
    }
}

速度显著提升

异常处理

finally

在try...catch语句的下面可以加finally, 其中的代码一定会执行, 除非JVM退出

JDK9之后的捕获异常的代码:

java 复制代码
package IOTest;

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

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 创建对象
        FileInputStream fis = new FileInputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶.md");
        FileOutputStream fos = new FileOutputStream("IO\\copy.md");

        try (fis; fos){
            int len;
            byte[] bytes = new byte[1024 * 1024 * 5];
            while((len = fis.read(bytes)) != -1){
                fos.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

了解即可, 一般都是直接抛出异常

字符集详解

计算机存储规则

任意数据以二进制形式来存储

字节: 计算机中最小的存储单元 8bit

存储英文, 一个字节就足够

ASCII字符集

编码规则: 前面补0, 补齐8位

解码规则: 直接转成十进制

英文的存储规则:

汉字存储规则

  1. GB2312字符集:1980年发布,1981年5月1日实施的简体中文汉字编码国家标准。
    收录7445个图形字符,其中包括6763个简体汉字
  2. BIG5字符集:台湾地区繁体中文标准字符集,共收录13053个中文字,1984年实施。
  3. GBK字符集 :2000年3月17日发布,收录21003个汉字。
    包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字
    windows系统默认使用的就是GBK。
    系统显示: ANSI
  4. Unicode字符集:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。
GBK

英文

用一个字节存储, 完全兼容ASCII

不足8位, 前面补0

汉字

规则1: 汉字使用两个字节存储(前面的是高位字节, 后面的是低位字节)

规则2: 高位字节二进制一定以1开头, 转成十进制之后是一个负数(为了和英文区分开)

总结

1.在计算机中,任意数据都是以二进制的形式来存储

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

3.ASCII字符集中,一个英文占一个字节

4.简体中文版Windows,默认使用GBK字符集

5.GBK字符集完全兼容ASCII字符集
一个英文占一个字节,二进制第一位是0
一个中文占两个字节,二进制高位字节的第一位是1

Unicode(万国码)

英文存储方式

UTF-16编码规则: 用2-4个字节保存

UTF-32编码规则: 固定使用四个字节保存

UTF-8编码规则: 用1-4个字节保存

只需知道英文字母 - 1个字节(前面补0)

简体中文 - 3个字节

填补方式: 红色的即为填补的

编码图解:

总结

UTF-8是字符集吗?

不是, 它是**unicode字符集**的一种编码方式

一个英文占一个字节,二进制第一位是0,转成十进制是正数

一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数

乱码

原因1: 读取数据时, 未读完整个汉字(字节流: 一次读取一个字节)

原因2: 编码和解码的方式不统一, 比如UTF-8编码, GBK解码

如何不产生乱码?

1.不要用字节流读取文本文件

2.编码解码时使用同一个码表,同一个编码方式

字节流读取中文会乱码, 但是拷贝是不会乱码的

编码和解码

编码的方法

解码的方法

java 复制代码
package IOTest;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class Demo2 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        // 编码
        String str = "ai你呦";
        // 默认方式编码 UTF-8
        byte[] bytes1 = str.getBytes();
        System.out.println(Arrays.toString(bytes1)); // [97, 105, -28, -67, -96, -27, -111, -90]
        // 1+1+3+3 = 8 个字节

        // 指定GBK编码
        byte[] bytes2 = str.getBytes("GBK");
        System.out.println(Arrays.toString(bytes2)); // [97, 105, -60, -29, -33, -49]
        // 1+1+2+2 = 6 个字节

        // 解码
        // bytes1是UTF-8编码, bytes2是GBK编码
        // UTF-8 解码 bytes1 正常
        String str2 = new String(bytes1);
        System.out.println(str2); // ai你呦

        // 我用UTF-8解码bytes2 乱码
        String str3 = new String(bytes2);
        System.out.println(str3); // ai����

        // 用GBK解码bytes1 乱码
        String str4 = new String(bytes1, "GBK");
        System.out.println(str4); // ai浣犲懄

        // 用GBK解码bytes2 正常
        String str5 = new String(bytes2, "GBK");
        System.out.println(str5); // ai你呦
    }
}

字符输入流

概述

底层就是字节流(加上了字符集)

特点:

输入流:一次读一个字节, 遇到中文时, 一次读多个字节

输出流: 底层会把数据按照指定的编码方式进行编码, 变成字节在写到文件中

使用场景

对于纯文本文件进行操作

继承结构

蓝色框的都是抽象类 不能直接创建对象 需要使用子类

FileReader

创建字符输入流对象

细节: 如果文件不存在, 就直接报错

读取数据

  • 细节1:按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
  • 细节2:读到文件末尾了,read方法返回-1。

释放资源

空参read方法的细节
java 复制代码
package IOTest;

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

public class Demo3 {
    public static void main(String[] args) throws IOException {
        // 创建对象
        FileReader fr = new FileReader("IO\\a.txt");
        // 读取数据
        int ch;
        while((ch = fr.read()) != -1){
            System.out.print((char)ch);
        }
        // 释放资源
        fr.close();
    }
}

细节:

字符流的底层也是字节流,默认也是一个字节一个字节的读取的。

如果遇到中文就会一次读取多个,GBK一次读两个字节,UTF-8一次读三个字节

read () 细节:

1.read:默认也是一个字节一个宁节的读取的,如果遇到中文就会一次读取多个

2.在读取之后,方法的底层还会进行解码并转成十进制。最终把这个十进制作为返回值

这个十进制的数据也表示在字符集上的数字

英文: 文件里面二进制数据 0110 0001

read方法进行读取,解码并转成十进制97

中文:文件里面的二进制数据 11100110 1110001 10001001

read方法进行读取,解码并转成十进制27721

我想看到中文汉字,就是把这些十进制数据,再进行强转就可以

带参read方法的细节
java 复制代码
package IOTest;

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

public class Demo4 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("IO\\a.txt");
        char[] chars = new char[3]; // 一次读三个
        int len;
        while ((len = fr.read(chars)) != -1){
            System.out.print(new String(chars, 0, len));
        }
        fr.close();
    }
}

带参read的细节:

read(chars): 读取数据, 解码, 强转三部合并了, 把强转之后的字符放到数组当中

相当于是空参read + 强制类型转换

字符输出流

构造方法
成员方法

书写细节

  1. 创建对象
  • 细节1:参数是字符串表示的路径或者File对象都是可以的
  • 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
  • 细节3:如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关

2.写数据

细节: 如果write方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符

3.释放资源

细节: 每次使用完流之后都要释放资源

java 复制代码
package IOTest;

import java.io.FileWriter;
import java.io.IOException;

public class Demo5 {
    public static void main(String[] args) throws IOException {
        // 字符输出流
        FileWriter fw = new FileWriter("IO\\a.txt", true);// 打开了续写开关

        // 一次写一个字符
        // fw.write(25105);

        // 一次写一个字符串
        // fw.write("范德萨");

        // 写出一个字符数组
        char[] chars = {'a', 'b', '但'};
        fw.write(chars);

        fw.close();
    }
}
字符流原理解析
输入流

每次调用read函数的时候, 都会判断缓冲区中是否有数据, 如果缓冲区中没有数据, 就回到数据源中获取数据

(字节流是没有缓冲区的)

原理剖析

创建字符输入流对象
底层: 关联文件,并创建缓冲区(长度为8192的字节数组)

读取数据底层:

  • 判断缓冲区中是否有数据可以读取
  • 缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区如果文件中也没有数据了,返回-1
  • 缓冲区有数据:就从缓冲区中读取。
  1. 空参的read方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回
  2. 有参的read方法: 把读取字节,解码,强转三步合并了,强转之后的字符放到数组中
输出流

图中的三种情况即为目的地读取缓冲区数据的情况

flush和close方法

两个函数的区别: flush后还能继续写出数据, close不能

字节流和字符流使用场景

字节流:

拷贝任意类型的文件

字符流:

读取纯文本文件中的数据

往纯文本文件中写出数据

练习

练习1

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

java 复制代码
package Practice;

import java.io.*;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 拷贝文件夹, 包含子文件夹
        // 创建对象表示数据源 和 目的地
        File file1 = new File("D:\\java\\笔记\\进阶笔记");
        File file2 = new File("D:\\java\\笔记\\进阶笔记2");
        copyDir(file1, file2);
    }

    private static void copyDir(File file1, File file2) throws IOException {
        file2.mkdirs();
        File[] files = file1.listFiles();
        for (File file : files) {
            // 判断
            if(file.isFile()){
                // 拷贝文件 字节流
                FileInputStream fis = new FileInputStream(file);
                FileOutputStream fos = new FileOutputStream(new File(file2, file.getName()));
                int len;
                byte[] bytes = new byte[1024 * 1024 * 5];
                while((len = fis.read(bytes)) != -1){
                    fos.write(bytes, 0, len);
                }
                fos.close();
                fis.close();
            }
            else{
                // 文件夹
                copyDir(file, new File(file2, file.getName()));
            }
        }
    }
}
练习2(异或应用)

为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理。

加密原理:

对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中

解密原理:

读取加密之后的文件,按照加密的规则反向操作,变成原始文件。

java 复制代码
package Practice;

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

public class Demo3 {
    public static void main(String[] args) throws IOException {
        // 加密和解密
        // 加密是a->b 解密是b->c
        FileInputStream fis = new FileInputStream("IO\\b.txt");
        FileOutputStream fos = new FileOutputStream("IO\\c.txt");

        // 加密处理
        int b;
        while((b = fis.read()) != -1){
            fos.write(b ^ 10);
        }
        // 释放资源
        fos.close();
        fis.close();
    }
}
练习3

文本文件中有以下的数据:

2-1-9-4-7-8

将文件中的数据进行排序,变成以下的数据:

1-2-4-7-8-9

java 复制代码
package Practice;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.StringJoiner;

public class Demo1 {
    public static void main(String[] args) throws IOException {
//        File file = new File("IO\\a.txt");
//        file.createNewFile();

        // 写入数据
        // 2-1-9-4-7-8
        /*
        FileWriter fw = new FileWriter("IO\\a.txt");
        String str = "2-1-9-4-7-8";
        fw.write(str);
        fw.close();
        */

        // 读数据 字符串形式
        FileReader fr = new FileReader("IO\\a.txt");
        StringBuilder sb = new StringBuilder();
        int ch;
        while((ch = fr.read()) != -1){
            sb.append((char)ch);
        }
        String str2 = sb.toString();
        System.out.println(str2); // 2-1-9-4-7-8

//        数据拆分, 放到数组中
        String[] split = str2.split("-");
        ArrayList<Integer> nums = new ArrayList<>();
        for(int i = 0; i < split.length; ++i){
            nums.add(Integer.parseInt(split[i]));
        }
        // 排序
        Collections.sort(nums);

        // 拼接成字符串
        StringJoiner sj = new StringJoiner("-","","");
        for (Integer num : nums) {
            sj.add(num.toString());
        }
        String str3 = sj.toString();
        System.out.println(str3);

        // 把str3写入到a.txt中
        FileWriter fw = new FileWriter("IO\\a.txt");
        fw.write(str3);
        fw.close();
    }
}

高级流

缓冲流

体系结构

字节缓冲流

分为字节缓冲输入流和字节缓冲输出流

原理: 底层自带了长度为8192的缓冲区提高性能

常用方法

对象还是基本流, 只不过借助缓冲流速度会更快

练习

利用字节缓冲流拷贝文件

一次操作一个字节:

cpp 复制代码
package Practice2;

import java.io.*;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 利用字节缓冲流拷贝文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("IO\\a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("IO\\b.txt"));
        // 循环读取并写到目的地
        int b;
        while((b = bis.read()) != -1){
            bos.write(b);
        }
        // 释放资源
        bos.close(); // FileInputStream 在 BufferInputStream.close()的底层已经关闭了
        bis.close();
    }
}

一次操作多个字节:

java 复制代码
package Practice2;

import java.io.*;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("IO\\a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("IO\\b.txt"));

        byte[] bytes = new byte[1024];
        int len;
        while((len = bis.read(bytes)) != -1){
            bos.write(bytes, 0, len);
        }

        // 释放资源
        bos.close();
        bis.close();
    }
}
字符缓冲流

底层自带了8192的缓冲区提高性能

构造方法

特有方法

方法底层会先判断是什么操作系统, 在换行

字符缓冲输入流代码:

java 复制代码
package BufferedTest;

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

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 创建对象
        BufferedReader br = new BufferedReader(new FileReader("IO\\a.txt"));
        // 读取数据
        // 细节: readLine这个函数一次读一行, 遇到回车结束, 但是不会把回车也读进来
        String line;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }
        // 释放资源
        br.close();
    }
}

字符缓冲输出流:

java 复制代码
package BufferedTest;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 字符缓冲输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("IO\\b.txt", true)); // 打开续写开关
        bw.write("123");
        bw.newLine();
        bw.write("345");
        bw.newLine();
        bw.close();
    }
}
总结

1.缓冲流有几种?

  • 字节缓冲输入流: BufferedInputStream
  • 字节缓冲输出流: BufferedOutputStream
  • 字符缓冲输入流: BufferedReader
  • 字符缓冲输出流: BufferedWriter

2.缓冲流为什么能提高性能

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

3.字符缓冲流两个特有的方法是什么?

  • 字符缓冲输入流BufferedReader: readLine ()
  • 字符缓冲输出流BufferedWriter: newLine ()
练习
练习1 拷贝文件

四种方式拷贝文件,并统计各自用时

  • 字节流的基本流:一次读写一个字节
  • 字节流的基本流:一次读写一个字节数组
  • 字节缓冲流:一次读写一个字节
  • 字节缓冲流:一次读写一个字节数组
java 复制代码
package Practice3;

import java.io.*;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        /*
        - 字节流的基本流:一次读写一个字节
        - 字节流的基本流:一次读写一个字节数组
        - 字节缓冲流:一次读写一个字节
        - 字节缓冲流:一次读写一个字节数组
        * */

        // byteStreamCopy(); // 1039
        // bytesStreamCopy(); // 7
        // byteBufferedCopy(); // 21
        bytesBufferedCopy(); // 6
    }
    private static void bytesBufferedCopy() throws IOException{
        long l1 = System.currentTimeMillis();
        // 创建对象
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶.md"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶4.md"));
        int len;
        byte[] bytes = new byte[1024 * 1024 * 5];
        while((len = bis.read(bytes)) != -1){
            bos.write(bytes, 0, len);
        }
        // 释放资源
        bos.close();
        bis.close();
        long l2 = System.currentTimeMillis();
        System.out.println(l2 - l1);
    }
    private static void byteBufferedCopy() throws IOException{
        long l1 = System.currentTimeMillis();
        // 创建对象
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶.md"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶4.md"));

        int len;
        while((len = bis.read()) != -1){
            bos.write(len);
        }
        // 释放资源
        bos.close();
        bis.close();
        long l2 = System.currentTimeMillis();
        System.out.println(l2 - l1);
    }
    private static void bytesStreamCopy() throws IOException{
        long l1 = System.currentTimeMillis();
        // 创建对象
        FileInputStream fis = new FileInputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶.md");
        FileOutputStream fos = new FileOutputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶3.md");
        int len;
        byte[] bytes = new byte[1024 * 1024 * 5];
        while((len = fis.read(bytes)) != -1){
            fos.write(bytes, 0, len);
        }
        // 释放资源
        fos.close();
        fis.close();
        long l2 = System.currentTimeMillis();
        System.out.println(l2 - l1);
    }
    // 字节流基本流
    static void byteStreamCopy() throws IOException{
        long l1 = System.currentTimeMillis();
        // 创建对象
        FileInputStream fis = new FileInputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶.md");
        FileOutputStream fos = new FileOutputStream("D:\\java\\笔记\\进阶笔记\\JAVA进阶2.md");
        int b;
        while((b = fis.read()) != -1){
            fos.write(b);
        }
        // 释放资源
        fos.close();
        fis.close();
        long l2 = System.currentTimeMillis();
        System.out.println(l2 - l1);
    }
}
练习2

排序文件中的出师表

每句话前面有序号

java 复制代码
package Practice3;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 文件乱序在a.txt中
        // 先读取
        // 用字符缓冲流
        BufferedReader br = new BufferedReader(new FileReader("IO\\a.txt"));
        // 存储数据的容器
        ArrayList<String> list = new ArrayList<>();
        String len;
        while((len = br.readLine()) != null){
            list.add(len);
        }
        System.out.println(list);
        // 排序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                String[] arr1 = o1.split("\\.");
                String[] arr2 = o2.split("\\.");
                String s1 = arr1[0];
                String s2 = arr2[0];
                int i1 = Integer.parseInt(s1);
                int i2 = Integer.parseInt(s2);
                return i1 - i2;
            }
        });
        System.out.println(list);
        // 写入到a.txt中
        BufferedWriter bw = new BufferedWriter(new FileWriter("IO\\a.txt"));
        for (int i = 0; i < list.size(); ++i) {
            bw.write(list.get(i));
            bw.newLine();
        }

        bw.close();
        br.close();
    }
}
练习3

实现一个验证程序运行次数的小程序,要求如下:

1.当程序运行超过3次时给出提示:本软件只能免费使用3次,欢迎您注册会员后继续使用~

2.程序运行演示如下:

  • 第一次运行控制台输出: 欢迎使用本软件,第1次使用免费~
  • 第二次运行控制台输出: 欢迎使用本软件,第2次使用免费~
  • 第三次运行控制台输出: 欢迎使用本软件,第3次使用免费~
  • 第四次及之后运行控制台输出:本软件只能免费使用3次,欢迎您注册会员后继续使用~
java 复制代码
package Practice3;

import java.io.*;

public class Demo3 {
    public static void main(String[] args) throws IOException {
        // 创建对象
        BufferedReader br = new BufferedReader(new FileReader("IO\\a.txt"));
        // 读取文件中的使用次数 初始为0
        String s = br.readLine();
        br.close();
        int count = Integer.parseInt(s);
        // 判断是否超过3次
        if(++count <= 3){
            System.out.println("欢迎使用本软件, 这是您第" + count + "次免费试用");
        }
        else{
            System.out.println("试用次数已到, 请冲会员");
        }
        // 把count写入到文件中
        // 随用随创建
        BufferedWriter bw = new BufferedWriter(new FileWriter("IO\\a.txt")); // 这一行不能写在上面, 否则会直接清空
        bw.write(count + "");

        bw.close();
    }
}

转换流

是字符流和字节流之间的桥梁

作用1: 指定字符集读写(淘汰了)

作用2: 字节流想要使用字符流中的方法

练习1

需求1:手动创建一个GBK的文件,把文件中的中文读取到内存中,不能出现乱码

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

需求3:将本地文件中的GBK文件,转成UTF-8

java 复制代码
package Practice3;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;

public class Demo4 {
    public static void main(String[] args) throws IOException {
        // 需求1
        // 读取GBK文件
        FileReader fr = new FileReader("IO\\a.txt", Charset.forName("GBK"));
        int ch;
        while ((ch = fr.read()) != -1){
            System.out.println((char) ch);
        }
        fr.close();

        // 需求2: 按照指定的码表写出数据
        FileWriter fw = new FileWriter("IO\\b.txt", Charset.forName("GBK"));
        fw.write("你好你好");
        fw.close();

        // 需求3: 将本地的GBK文件转成UTF-8
        FileReader fr2 = new FileReader("IO\\b.txt", Charset.forName("GBK"));
        FileWriter fw2 = new FileWriter("IO\\c.txt", Charset.forName("UTF-8"));
        int ch1;
        while ((ch1 = fr2.read()) != -1){
            fw2.write(ch1);
        }
        fw2.close();
        fr2.close();
    }
}

练习2

java 复制代码
package Practice3;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class Demo5 {
    public static void main(String[] args) throws IOException {
        // 利用字节流获取文件中的数据
        // 每次读一行, 而且不能出现乱码

        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("IO\\a.txt")));
        String line;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }
        br.close();
    }
}

总结

1.转换流的名字是什么?

  • 字符转换输入流:InputStreamReader
  • 字符转换输出流: OutputStreamWriter

2.转换流的作用是什么?

  • 指定字符集读写数据(JDK11之后已淘汰)
  • 字节流想要使用字符流中的方法了

序列化流
序列化流

可以把Java中的对象写到本地文件中(看不懂)

可以利用反序列化流从文件中读取出来

细节:

使用对象输出流将对象保存到文件时会出现NotserializableException异常

解决方案: 需要让Javabean类实现Serializable接口

一旦实现了这个接口, 就表示这个类可以被序列化

Serializable这个接口没有抽象方法, 被称为标记型接口

java 复制代码
package ObjectStream;

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

public class Demo1 {
    public static void main(String[] args) throws IOException {
        Student s = new Student("zhangsan", 23);
        // 创建对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("IO\\a.txt"));
        oos.writeObject(s);
        oos.close();
    }
}
反序列化流

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

java 复制代码
package ObjectStream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Demo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 利用反序列化流把a.txt中的信息打印
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("IO\\a.txt"));
        Object o = ois.readObject();
        System.out.println(o); // Student{name = zhangsan, age = 23}
        ois.close();
    }
}
细节

当我们把类的对象序列化的写入到文件中后, 如果这时我们对类做了修改(加了一个属性), 这时在反序列化读取数据的时候就会报错

原因是如果一个类实现了Serializable接口, Java就会根据这个类的对象的各种属性值计算出一个序列号, 可以理解为版本号, 在我们修改类的时候, 版本号就会发生变化, 导致数据无法读取

解决方案: 在类中固定版本号

java 复制代码
private static final long serialVersionUID = 1L;

2.当我们不想把某个属性值序列化的时候 可以加关键字transient, 此时的标准JavaBean类

java 复制代码
package ObjectStream;

import java.io.Serializable;

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private transient String address;
    // 瞬态关键字
    // 不会把当前属性序列化到本地文件中


    public Student() {
    }

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

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

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

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

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

    /**
     * 获取
     * @return address
     */
    public String getAddress() {
        return address;
    }

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

    public String toString() {
        return "Student{ name = " + name + ", age = " + age + ", address = " + address + "}";
    }
}
总结
  • 使用序列化流将对象写到文件时,需要让Javabean类实现Serializable接口。否则,会出现NotSerializableException异常
  • 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了
  • 序列化对象后,修改了Javabean类,再次反序列化,会不会有问题?
    会出问题,会抛出InvalidclassException异常
    解决方案:给Javabean类添加serialVersionUID (序列号、版本号)
  • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
    解决方案:给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
练习

将多个自定义对象序列化到文件中, 但是对象的个数不确定, 该如何操作?

可以把对象都方法ArrayList中, 然后只需序列化一次

反序列化也只需要一次


打印流
字节打印流

只有输出流

PrintStream和PrintWriter两个类

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

构造方法

成员方法

java 复制代码
package printStream;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 创建字节打印流对象
        PrintStream ps = new PrintStream(new File("IO\\a.txt"));
        ps.println(97);
        ps.print(true);
        ps.println();
        ps.printf("%s爱上了%s", "阿珍", "阿强");
        ps.close();
    }
}
字符打印流

构造方法

成员方法

java 复制代码
package printStream;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 创建字符打印流对象
        PrintWriter pw = new PrintWriter(new FileWriter("IO\\a.txt"), true);
        // 写入数据
        pw.println("fcsa");
        pw.print(78);
        pw.close();
    }
}
打印流和打印语句的联系
java 复制代码
package printStream;

import java.io.PrintStream;

public class Demo3 {
    public static void main(String[] args) {
        // 获取打印流的对象, 此打印流在虚拟机启动的时候有虚拟机创建
        // 默认值向控制台
        // 特殊的打印流, 系统中的标准输出流, 不能关闭
        // 在系统中是唯一的
        PrintStream ps = System.out;

        // 调用打印流中的方法println
        // 写出数据, 自动换行, 自动刷新
        ps.println("123");

        ps.close();
        // 关闭之后就不能继续打印了
        ps.println("345");
    }
}

上述代码只会打印123

总结

打印流有几种?各有什么特点?

  • 有字节打印流和字符打印流两种
  • 打印流不操作数据源,只能操作目的地
  • 字节打印流:默认自动刷新,特有的println自动换行
  • 字符打印流:自动刷新需要开启,特有的println自动换行

解压流
解压缩流

读取压缩包的文件

解压本质: 把每一个ZipEntry按照层级拷贝到本地另一个文件夹中

java 复制代码
package com.itheima.myzipstream;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/*
*   解压缩流
*
* */
public class ZipStreamDemo1 {
    public static void main(String[] args) throws IOException {

        //1.创建一个File表示要解压的压缩包
        File src = new File("D:\\aaa.zip");
        //2.创建一个File表示解压的目的地
        File dest = new File("D:\\");

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

    }

    //定义一个方法用来解压
    public static void unzip(File src,File dest) throws IOException {
        //解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中
        //创建一个解压缩流用来读取压缩包中的数据
        ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
        //要先获取到压缩包里面的每一个zipentry对象
        //表示当前在压缩包中获取到的文件或者文件夹
        ZipEntry entry;
        while((entry = zip.getNextEntry()) != null){
            System.out.println(entry);
            if(entry.isDirectory()){
                //文件夹:需要在目的地dest处创建一个同样的文件夹
                File file = new File(dest,entry.toString());
                file.mkdirs();
            }else{
                //文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
                FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));
                int b;
                while((b = zip.read()) != -1){
                    //写到目的地
                    fos.write(b);
                }
                fos.close();
                //表示在压缩包中的一个文件处理完毕了。
                zip.closeEntry();
            }
        }
        zip.close();
    }
}
压缩流

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

压缩单个文件

java 复制代码
package com.itheima.myzipstream;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipStreamDemo2 {
    public static void main(String[] args) throws IOException {
        /*
         *   压缩流
         *      需求:
         *          把D:\\a.txt打包成一个压缩包
         * */
        //1.创建File对象表示要压缩的文件
        File src = new File("D:\\a.txt");
        //2.创建File对象表示压缩包的位置
        File dest = new File("D:\\");
        //3.调用方法用来压缩
        toZip(src,dest);
    }

    /*
    *   作用:压缩
    *   参数一:表示要压缩的文件
    *   参数二:表示压缩包的位置
    * */
    public static void toZip(File src,File dest) throws IOException {
        //1.创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
        //2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
        //参数:压缩包里面的路径
        ZipEntry entry = new ZipEntry("aaa\\bbb\\a.txt");
        //3.把ZipEntry对象放到压缩包当中
        zos.putNextEntry(entry);
        //4.把src文件中的数据写到压缩包当中
        FileInputStream fis = new FileInputStream(src);
        int b;
        while((b = fis.read()) != -1){
            zos.write(b);
        }
        zos.closeEntry();
        zos.close();
    }
}

压缩文件夹

Java 复制代码
package com.itheima.myzipstream;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipStreamDemo3 {
    public static void main(String[] args) throws IOException {
        /*
         *   压缩流
         *      需求:
         *          把D:\\aaa文件夹压缩成一个压缩包
         * */


        //1.创建File对象表示要压缩的文件夹
        File src = new File("D:\\aaa");
        //2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
        File destParent = src.getParentFile();//D:\\
        //3.创建File对象表示压缩包的路径
        File dest = new File(destParent,src.getName() + ".zip");
        //4.创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
        //5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
        toZip(src,zos,src.getName());//aaa
        //6.释放资源
        zos.close();
    }

    /*
    *   作用:获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
    *   参数一:数据源
    *   参数二:压缩流
    *   参数三:压缩包内部的路径
    * */
    public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
        //1.进入src文件夹
        File[] files = src.listFiles();
        //2.遍历数组
        for (File file : files) {
            if(file.isFile()){
                //3.判断-文件,变成ZipEntry对象,放入到压缩包当中
                ZipEntry entry = new ZipEntry(name + "\\" + file.getName());//aaa\\no1\\a.txt
                zos.putNextEntry(entry);
                //读取文件中的数据,写到压缩包
                FileInputStream fis = new FileInputStream(file);
                int b;
                while((b = fis.read()) != -1){
                    zos.write(b);
                }
                fis.close();
                zos.closeEntry();
            }else{
                //4.判断-文件夹,递归
                toZip(file,zos,name + "\\" + file.getName());
            }
        }
    }
}
常用工具包
Commons-io

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

作用: 提高IO开源效率

使用步骤

  1. 在项目中创建一个文件夹lib
  2. 将jar包复制粘贴到lib文件夹
  3. 右键点击jar包,选择Add as Library -> 点击OK
  4. 在类中导包使用

常见方法

java 复制代码
package Commons_io;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 拷贝文件
        File src = new File("IO\\a.txt");
        File dest = new File("IO\\b.txt");

        FileUtils.copyFile(src, dest);
        
        // 拷贝文件夹
        // FileUtils.copyDirectory(); // 这个方法是把文件夹直接拷贝到dest路径下
        // FileUtils.copyDirectoryToDirectory(); // 这个方法是把文件夹拷贝到路径文件夹里面
    }
}
Hutool工具包

先导入jar包

Java 复制代码
package com.itheima.myhutool;

import cn.hutool.core.io.FileUtil;

import java.util.List;

public class Test1 {
    public static void main(String[] args) {
    /*
        FileUtil类:
                file:根据参数创建一个file对象
                touch:根据参数创建文件

                writeLines:把集合中的数据写出到文件中,覆盖模式。
                appendLines:把集合中的数据写出到文件中,续写模式。
                readLines:指定字符编码,把文件中的数据,读到集合中。
                readUtf8Lines:按照UTF-8的形式,把文件中的数据,读到集合中

                copy:拷贝文件或者文件夹
    */
       /* File file1 = FileUtil.file("D:\\", "aaa", "bbb", "a.txt");
        System.out.println(file1);//D:\aaa\bbb\a.txt

        File touch = FileUtil.touch(file1);
        System.out.println(touch);

        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("aaa");
        list.add("aaa");

        File file2 = FileUtil.writeLines(list, "D:\\a.txt", "UTF-8");
        System.out.println(file2);*/

      /*  ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("aaa");
        list.add("aaa");
        File file3 = FileUtil.appendLines(list, "D:\\a.txt", "UTF-8");
        System.out.println(file3);*/

        List<String> list = FileUtil.readLines("D:\\a.txt", "UTF-8");
        System.out.println(list);
    }
}

综合练习

制造假数据

需求: 制造假数据也是开发中的一个能力, 在各个网上爬取数据, 是其中一个方法

获取姓氏

https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d\&from=kg0

男生名字

http://www.haoming8.cn/baobao/10881.html

女生名字

http://www.haoming8.cn/baobao/7641.html

java 复制代码
package Practice1;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 利用网络爬虫获取姓氏, 男生名, 女生名
        // 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 familyNameStr = webCrawler(familyNameNet);
        String boyNameStr = webCrawler(boyNameNet);
        String girlNameStr = webCrawler(girlNameNet);

//        System.out.println(familyNameStr);

        // 3.用正则表达式提取数据
        ArrayList<String> familyTempList = getData(familyNameStr, "(.{4})(,| 。)", 1);
        ArrayList<String> boyNameTempList = getData(boyNameStr, "([\\u4E00-\\u9FA5]{2})(、| 。)", 1);
        ArrayList<String> girlNameTempList = getData(girlNameStr, "(.. ){4}..", 0); // 雅晶 月莹 秀竹 凡梦 虹影

        // 4.数据处理
        // 姓氏处理方案: 每个元素的每个字单独拿出来
        ArrayList<String> familyList = new ArrayList<>();
        for (String str : familyTempList) {
            for (int i = 0; i < str.length(); i++) {
                familyList.add(str.charAt(i) + "");
            }
        }
        // System.out.println(familyList);
        // 男生名处理方案: 去重
        ArrayList<String> boyNameList = new ArrayList<>();
        for (String str : boyNameTempList) {
            if(!boyNameList.contains(str)) boyNameList.add(str);
        }
        // System.out.println(boyNameList);
        // 女生名处理方案: split 空格
        ArrayList<String> girlNameList = new ArrayList<>();
        for (String str : girlNameTempList) {
            String[] split = str.split(" ");
            for (String s : split) {
                girlNameList.add(s);
            }
        }
        // System.out.println(girlNameList);

        // 5.生成数据
        // 姓名(唯一)-性别-年龄
        ArrayList<String> list = getInfos(familyList, boyNameList, girlNameList, 70, 50);
        // System.out.println(list.size()); // 120

        // 6.写出数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("IOPractice\\name.txt"));
        for (String info : list) {
            bw.write(info);
            bw.newLine();
        }
        bw.close();

    }

    private static ArrayList<String> getInfos(ArrayList<String> familyList, ArrayList<String> boyNameList,
                                              ArrayList<String> girlNameList, int boyNum, int girlNum){
        // 男生名
        HashSet<String> boyhs = new HashSet<>();
        while(boyhs.size() < boyNum){
            Collections.shuffle(familyList);
            Collections.shuffle(boyNameList);
            boyhs.add(familyList.get(0) + boyNameList.get(0));
        }
        // 女生名
        HashSet<String> gilrhs = new HashSet<>();
        while(gilrhs.size() < girlNum){
            Collections.shuffle(familyList);
            Collections.shuffle(girlNameList);
            gilrhs.add(familyList.get(0) + girlNameList.get(0));
        }
        ArrayList<String> list = new ArrayList<>();
        Random r = new Random();
        // 把男生加进集合
        for (String boy : boyhs) {
            int age = r.nextInt(10) + 18; // [18, 27]
            list.add(boy + "-男-" +age);
        }
        // 女生加进集合
        for (String girl : gilrhs) {
            int age = r.nextInt(8) + 18; // [18, 25]
            list.add(girl + "-女-" + age);
        }
        Collections.shuffle(list);
        return list;
    }

    private static ArrayList<String> getData(String familyNameStr, String regex, int index) {
        // 创建集合存放数据
        ArrayList<String> list = new ArrayList<>();
        // 按照正则表达式的规则, 去获取数据
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(familyNameStr);
        while(matcher.find()){
            String group = matcher.group(index);
            list.add(group);
        }
        return list;
    }

    private static String webCrawler(String familyNameNet) throws IOException {
        // 1.定义stringBuilder拼接数据
        StringBuilder sb = new StringBuilder();
        // 2.创建一个URL对象
        URL url = new URL(familyNameNet);
        // 3.连接上这个网址
        URLConnection conn = url.openConnection();
        // 4.读取数据 转换流
        InputStreamReader isr = new InputStreamReader(conn.getInputStream());
        int ch;
        while ((ch = isr.read()) != -1){
            sb.append((char)ch);
        }
        // 5.释放资源
        isr.close();
        // 6.返回数据
        return sb.toString();
    }
}

利用Hutool包, 更改了部分数据

java 复制代码
package Practice1;

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

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 利用网络爬虫获取姓氏, 男生名, 女生名
        // 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 familyNameStr = webCrawler(familyNameNet);
//        String boyNameStr = webCrawler(boyNameNet);
//        String girlNameStr = webCrawler(girlNameNet);

        // 利用Hutool爬取
        String familyNameStr = HttpUtil.get(familyNameNet);
        String boyNameStr = HttpUtil.get(boyNameNet);
        String girlNameStr = HttpUtil.get(girlNameNet);

//        System.out.println(familyNameStr);

        // 3.用正则表达式提取数据
//        ArrayList<String> familyTempList = getData(familyNameStr, "(.{4})(,| 。)", 1);
//        ArrayList<String> boyNameTempList = getData(boyNameStr, "([\\u4E00-\\u9FA5]{2})(、| 。)", 1);
//        ArrayList<String> girlNameTempList = getData(girlNameStr, "(.. ){4}..", 0); // 雅晶 月莹 秀竹 凡梦 虹影

        // 用Hutool提取数据
        List<String> familyTempList = ReUtil.findAll("(.{4})(,| 。)", familyNameStr, 1);
        List<String> boyNameTempList = ReUtil.findAll("([\\u4E00-\\u9FA5]{2})(、| 。)", boyNameStr, 1);
        List<String> girlNameTempList = ReUtil.findAll("(.. ){4}..", girlNameStr, 0);

        // 4.数据处理
        // 姓氏处理方案: 每个元素的每个字单独拿出来
        ArrayList<String> familyList = new ArrayList<>();
        for (String str : familyTempList) {
            for (int i = 0; i < str.length(); i++) {
                familyList.add(str.charAt(i) + "");
            }
        }
        // System.out.println(familyList);
        // 男生名处理方案: 去重
        ArrayList<String> boyNameList = new ArrayList<>();
        for (String str : boyNameTempList) {
            if(!boyNameList.contains(str)) boyNameList.add(str);
        }
        // System.out.println(boyNameList);
        // 女生名处理方案: split 空格
        ArrayList<String> girlNameList = new ArrayList<>();
        for (String str : girlNameTempList) {
            String[] split = str.split(" ");
            for (String s : split) {
                girlNameList.add(s);
            }
        }
        // System.out.println(girlNameList);

        // 5.生成数据
        // 姓名(唯一)-性别-年龄
        ArrayList<String> list = getInfos(familyList, boyNameList, girlNameList, 70, 50);
        // System.out.println(list.size()); // 120

        // 6.写出数据
//        BufferedWriter bw = new BufferedWriter(new FileWriter("IOPractice\\name.txt"));
//        for (String info : list) {
//            bw.write(info);
//            bw.newLine();
//        }
//        bw.close();

        // 利用Hutool写出数据
        // 细节: 糊涂报的相对路径, 不是相对于当前项目而言的, 而是相对于class文件而言的
        FileUtil.writeLines(list, "names.txt", "UTF-8");

    }

随机点名器
版本1(独立完成)

需求:

有一个文件里面存储了班级同学的信息,每一个信息占一行

格式为:张三-男-23

要求通过程序实现随机点名器。
运行效果:

  • 第一次运行程序:随机同学姓名1 (只显示名字)
  • 第二次运行程序:随机同学姓名2(只显示名字)
  • 第三次运行程序:随机同学姓名3(只显示名字)
java 复制代码
package duanmingqi;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

public class Test01 {
    public static void main(String[] args) throws IOException {
        // 1.读取文件中学生的姓名
        ArrayList<String> list = new ArrayList<>();
        BufferedReader br = new BufferedReader(new FileReader("IOPractice\\name.txt"));
        String line;
        while ((line = br.readLine()) != null){
            list.add(line);
        }
        br.close();

        // 2.随机抽取
        Random r = new Random();
        int index = r.nextInt(list.size());
        String str = list.get(index);
        String[] info = str.split("-");
        System.out.println(info[0]);
    }
}
版本2(独立完成)

需求:

一个文件里面存储了班级同学的信息,每一个学生信息占一行

格式为:张三-男-23

要求通过程序实现随机点名器。
运行效果:

  • 70%的概率随机到男生
  • 30%的概率随机到女生
  • 总共随机100万次,统计结果。
  • 注意观察:看生成男生和女生的比例是不是接近于7:3
java 复制代码
package duanmingqi;

import javax.sql.rowset.serial.SerialStruct;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

public class Test02 {
    public static void main(String[] args) throws IOException {
        /*
        * 需求:
        一个文件里面存储了班级同学的信息,每一个学生信息占一行
        格式为:张三-男-23
        要求通过程序实现随机点名器。

        运行效果:
        - 70%的概率随机到男生
        - 30%的概率随机到女生
        - 总共随机100万次,统计结果。
        - 注意观察:看生成男生和女生的比例是不是接近于7:3
        * */

        // 读取数据
        ArrayList<String> list = new ArrayList<>();
        BufferedReader br = new BufferedReader(new FileReader("IOPractice\\name.txt"));
        String line;
        while((line = br.readLine()) != null){
            list.add(line);
        }
        br.close();
        System.out.println(list);

        // 分两个集合 一男一女
        ArrayList<String> boyList = new ArrayList<>();
        ArrayList<String> girlList = new ArrayList<>();
        // 遍历集合, 判断并进行导入
        for (String str : list) {
            String[] info = str.split("-");
            if("男".equals(info[1])){
                boyList.add(str);
            }
            else{
                girlList.add(str);
            }
        }
//        System.out.println(boyList);
//        System.out.println(girlList);

        // 创建集合确定权重
        // 0 - boy
        // 1 - girl
        ArrayList<Integer> weight = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            weight.add(0);
        }
        for (int i = 0; i < 3; i++) {
            weight.add(1);
        }

        // System.out.println(sex);

        int countBoy = 0;
        int countGirl = 0;

        for (int i = 0; i < 10000; ++i) {
            // 从里面随机选一个数, 0就随机男生, 1随机女生
            Collections.shuffle(weight);
            int sex = weight.get(0);
            if(sex == 0){
                // 随机男生
//                Collections.shuffle(boyList);
//                System.out.println(boyList.get(0));
                ++countBoy;
            }
            else{
                // 随机女生
//                Collections.shuffle(girlList);
//                System.out.println(girlList.get(0));
                ++countGirl;
            }
        }
        System.out.println(countBoy); // 7033
        System.out.println(countGirl); // 2697
    }
}
版本3(独立完成)

需求:

一个文件里面存储了班级同学的姓名,每一个姓名占一行要求通过程序实现随机点名器。
第三次必定是张三同学
运行效果:

  • 第一次运行程序:随机同学姓名1
  • 第二次运行程序:随机同学姓名2
  • 第三次运行程序:张三
java 复制代码
package duanmingqi;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;

public class Test03 {
    public static void main(String[] args) throws IOException {
        ArrayList<String> list = new ArrayList<>();
        // 读取数据
        BufferedReader br = new BufferedReader(new FileReader("IOPractice\\name.txt"));
        String line;
        while((line = br.readLine()) != null){
            list.add(line);
        }
        br.close();

        // 创建一个文件, 记录第几次点名
//        BufferedWriter bw = new BufferedWriter(new FileWriter("IOPractice\\count.txt"));
//        bw.write("0");
//        bw.close();
        // 开始点名
        // 先读文件看是第几次点名
        BufferedReader br2 = new BufferedReader(new FileReader("IOPractice\\count.txt"));
        String countStr = br2.readLine();
        int count = Integer.parseInt(countStr);
        BufferedWriter bw = new BufferedWriter(new FileWriter("IOPractice\\count.txt"));
        if(count == 2){
            // 第三次点名, 直接打印张三
            System.out.println("张三");
            // 更改文件中次数
            ++count;
            bw.write(count + "");
        }
        else{
            // 正常点名
            Collections.shuffle(list);
            String info = list.get(0);
            String name = info.split("-")[0];
            System.out.println(name);
            // 更改文件中次数
            ++count;
            bw.write(count + "");
        }
        bw.close();
    }
}

版本4(独立完成)

需求:

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

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

运行效果:

  • 被点到的学生不会再被点到
  • 如果班级中所有的学生都点完了,需要自动的重新开启第二轮点名
  • 细节1:假设班级有10个学生,每一轮中每一位学生只能被点到一次,程序运行10次,第一轮结束
  • 细节2:第11次运行的时候,我们自己不需要手动操作本地文件,要求程序自动开始第二轮点名

登录注册
案例1

需求:写一个登陆小案例。

步骤:

  1. 将正确的用户名和密码手动保存在本地的userinfo.txt文件中
  2. 保存格式为:username=zhangsan&password=123
  3. 让用户键盘录入用户名和密码
  4. 比较用户录入的和正确的用户名密码是否一致
  5. 如果一致则打印登陆成功
  6. 如果不一致则打印登陆失败
java 复制代码
package com.itheima.myiotest7;


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

public class Test {
    public static void main(String[] args) throws IOException {
       /*
        需求:写一个登陆小案例。

        步骤:
            将正确的用户名和密码手动保存在本地的userinfo.txt文件中。
            保存格式为:username=zhangsan&password=123
            让用户键盘录入用户名和密码
                    比较用户录入的和正确的用户名密码是否一致
            如果一致则打印登陆成功
                    如果不一致则打印登陆失败
        */

        //1.读取正确的用户名和密码
        BufferedReader br = new BufferedReader(new FileReader("myiotest\\src\\com\\itheima\\myiotest7\\userinfo.txt"));
        String line = br.readLine();//username=zhangsan&password=123
        br.close();
        String[] userInfo = line.split("&");
        String[] arr1 = userInfo[0].split("=");
        String[] arr2 = userInfo[1].split("=");

        String rightUsername = arr1[1];
        String rightPassword = arr2[1];

        //2.用户键盘录入用户名和密码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名");
        String username = sc.nextLine();
        System.out.println("请输入密码");
        String password = sc.nextLine();

        //3.比较
        if(rightUsername.equals(username) && rightPassword.equals(password)){
            System.out.println("登陆成功");
        }else{
            System.out.println("登陆失败");
        }
    }
}

配置文件

概述

优点:

  • 好处1: 可以把软件的设置永久化存储
  • 好处2: 如果我们要修改参数,不需要改动代码,直接修改配置文件就可以了

因为修改代码的话需要重新打包, 重新发布, 很麻烦

常见的配置文件

XML ini properties YAML

properties

properties是一个双列集合集合,拥有Map集合所有的特点。

重点:有一些特有的方法,可以把集合中的数据,按照键值对的形式写到配置文件当中也可以把配置文件中的数据,读取到集合中来

Properties类基本用法
java 复制代码
package Test1;

import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Demo1 {
    public static void main(String[] args) {
        // 基本用法
        Properties prop = new Properties();
        // 插入数据
        prop.put("aaa", "111");
        prop.put("bbb", "222");
        prop.put("ccc", "333");
        prop.put("ddd", "444");
        // 遍历
        Set<Map.Entry<Object, Object>> entries = prop.entrySet();
        for (Map.Entry<Object, Object> entry : entries) {
            Object key = entry.getKey();
            Object value = entry.getValue();
            System.out.println(key + "=" + value);
        }
    }
}
写出数据
java 复制代码
package Test1;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 基本用法
        Properties prop = new Properties();
        // 插入数据
        prop.put("aaa", "111");
        prop.put("bbb", "222");
        prop.put("ccc", "333");
        prop.put("ddd", "444");

        FileOutputStream fos = new FileOutputStream("properties\\a.properties");
        prop.store(fos, "test");
        fos.close();
    }
}
读数据
java 复制代码
public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 读数据
        Properties prop = new Properties();
        FileInputStream fis = new FileInputStream("properties\\a.properties");
        prop.load(fis);
        fis.close();
        System.out.println(prop); // {aaa=111, ccc=333, bbb=222, ddd=444}
    }
}
相关推荐
confiself13 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041518 分钟前
J2EE平台
java·java-ee
XiaoLeisj24 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
豪宇刘39 分钟前
SpringBoot+Shiro权限管理
java·spring boot·spring
Elaine20239143 分钟前
02多线程基础知识
java·多线程
gorgor在码农1 小时前
Redis 热key总结
java·redis·热key
百事老饼干1 小时前
Java[面试题]-真实面试
java·开发语言·面试
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
2402_857589361 小时前
SpringBoot框架:作业管理技术新解
java·spring boot·后端