哈士奇发布IO流笔记

IO流体系

  • 字节流 【抽象类】

    • 字节输入流 【InputStream】

      • FileInputStream 【基本流】

      • BufferedInputStream 【字节缓冲输入流】

      • ObjectInputStream 【反序列化流】

    • 字节输出流【OutputStream】

      • FileOutputStream 【基本流】

      • BufferedOutputStream 【字节缓冲输出流】

      • ObjectOutputStream 【序列化流】

      • PrintStream 【字节打印流】

  • 字符流 【抽象类】

    • 字符输入流【Reader】

      • FileReader 【基本流】

      • BufferedReader 【字符缓冲输入流】

      • InputStreamReader 【转换输入流】

    • 字符输出流【Writer】

      • FileWriter 【基本流】

      • BufferedWriter 【字符缓冲输出流】

      • OutputStreamWriter 【转换输出流】

      • PrintWriter 【字符打印流】

1、FileOutputStream 【操作本地文件】

1.1、字节输出流的细节

  • 创建字节输出流对象

    • 参数是字符串表示的路径或者是File对象都是可以的

    • 如果文件不存会创建一个新的文件,但是要保证父路径是存在的

    • 如果文件已经存在,则会覆盖原文件

  • 写数据

    • write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符【例:97 =》a】

  • 释放资源

    • 每次使用完流之后都要释放资源

// 创建对象
FileOutputStream fos = new FileOutputStream("src/a.txt");
// 写出数据
fos.write(97);
// 释放资源
fos.close();

1.2、写数据的三种方式

方法名称说明
void write(int b)一次写一个字节数据
void write(byte[] b)一次写一个字节数组数据
void write(byte[] b,int off,int len)一次写一个字节数组的部分数据

第一种

// 创建对象
FileOutputStream fos = new FileOutputStream("src/a.txt");
// 写出数据【第一种】
fos.write(97); // a
fos.write(98); // b
// 释放资源
fos.close();

第二种

// 创建对象
FileOutputStream fos = new FileOutputStream("src/a.txt");
// 写出数据【第二种】
byte[] bytes = {97, 98, 99, 100, 101}; // abcde
fos.write(bytes);
// 释放资源
fos.close();

第三种

// 创建对象
FileOutputStream fos = new FileOutputStream("src/a.txt");
// 写出数据【第三种】
byte[] bytes = {97, 98, 99, 100, 101}; // cd
fos.write(bytes, 2, 2);
// 释放资源
fos.close();

1.3、写数据时的两个问题【换行写、续写】

换行写 【再次写出一个换行符】

  • window:\r\n

  • Linux:\n

  • Mac:\r

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

续写

如果想要续写,打开续写开关即可,开关位置:创建对象的第二个参数;

默认false,表示关闭续写,此时创建对象会清空文件;

手动传递true,表示打开学些,此时创建对象不会清空文件

FileOutputStream fos = new FileOutputStream("src/a.txt", true);

2、FileInputStream 【操作本地文件】

// 创建对象
FileInputStream fis = new FileInputStream("src/a.txt");
// 写入数据【】
int b1 = fis.read();
System.out.println(b1); // a
// 释放资源
fis.close();

2.1、字节输入流的细节

  • 创建字节输入流对象

    • 如果文件不存在,就直接报错

  • 写数据

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

    • 读到文件末尾了,read方法返回-1

  • 释放资源

    • 每次使用完流之后都要释放资源

2.2、循环读取

// 创建对象
FileInputStream fis = new FileInputStream("src/a.txt");
// 循环读取
int b1 = 0;
while ((b1 = fis.read()) != -1) {
    System.out.print((char) b1); // a
}
// 释放资源
fis.close();

2.3、读入文件、读出文件

// 创建输入流对象
FileInputStream fis = new FileInputStream("F:\\xaiomeimei.png");
// 创建输出流对象
FileOutputStream fos = new FileOutputStream("G:\\photo\\xiaomeimei.png");
// 循环读入和写出
int b1 = 0;
while ((b1 = fis.read()) != -1) {
    fos.write((char)b1);
}
// 释放资源
fos.close();
fis.close();

2.4、读取问题 【一次读多个字节】

方法名称说明
public int read()一次读一个字节数据
public int read(byte[] buffer)一次读一个字节数组数据
// 创建输入流对象
FileInputStream fis = new FileInputStream("F:\\xaiomeimei.png");
// 创建输出流对象
FileOutputStream fos = new FileOutputStream("G:\\photo\\xiaomeimei.png");
// 循环读入和写出
byte[] bytes = new byte[1024];
// 记录一次拷贝多少字节数据
int len = 0;
// 记录拷贝前的时间
long startTime = new Date().getTime();
System.out.println(startTime);

while ((len = fis.read(bytes)) != -1) {
    fos.write(bytes, 0, len);
}

// 记录拷贝后的时间
long endTime = new Date().getTime();
System.out.println(endTime);
// 输出总耗时
System.out.println("总耗时:" + (endTime - startTime));

// 释放资源
fos.close();
fis.close();

2.5、try-catch异常处理

简化前 【一般都是抛出异常】

FileInputStream fis = null;
FileOutputStream fos = null;

try {
    // 创建输入流对象
    fis = new FileInputStream("F:\\xaiomeimei.png");
    // 创建输出流对象
    fos = new FileOutputStream("G:\\photo\\xiaomeimei.png");
    // 循环读入和写出
    byte[] bytes = new byte[1024];
    // 记录一次拷贝多少字节数据
    int len;
    // 记录拷贝前的时间
    long startTime = new Date().getTime();
    System.out.println(startTime);
    while ((len = fis.read(bytes)) != -1) {
        fos.write(bytes, 0, len);
    }
    // 记录拷贝后的时间
    long endTime = new Date().getTime();
    System.out.println(endTime);

    System.out.println("总耗时:" + (endTime - startTime));
} catch (IOException e) {
    throw new RuntimeException(e);
} finally {
    // 释放资源
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if (fos != null) {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

jdk7 以及 jdk9 处理方法 【格式】【简化后】

try(输入流;输出流){
    
} catch() {
    
}
// jdk7、8 演示
try(FileInputStream fis = new FileInputStream("F:\\xaiomeimei.png");
FileOutputStream fos = new FileOutputStream("G:\\photo\\xiaomeimei.png")) {
// 循环读入和写出
byte[] bytes = new byte[1024];
// 记录一次拷贝多少字节数据
int len;
// 记录拷贝前的时间
long startTime = new Date().getTime();
System.out.println(startTime);
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
// 记录拷贝后的时间
long endTime = new Date().getTime();
System.out.println(endTime);

System.out.println("总耗时:" + (endTime - startTime));
} catch (IOException e) {
throw new RuntimeException(e);
}
// jdk9 演示 [外面的fis、fos还是要抛出异常]
FileInputStream fis = new FileInputStream("F:\\xaiomeimei.png");
FileOutputStream fos = new FileOutputStream("G:\\photo\\xiaomeimei.png");
try(fis; fos) {
// 循环读入和写出
byte[] bytes = new byte[1024];
// 记录一次拷贝多少字节数据
int len;
// 记录拷贝前的时间
long startTime = new Date().getTime();
System.out.println(startTime);
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
// 记录拷贝后的时间
long endTime = new Date().getTime();
System.out.println(endTime);

System.out.println("总耗时:" + (endTime - startTime));
} catch (IOException e) {
throw new RuntimeException(e);
}

3、计算机的存储规则

  • GBK 【国家标准】

    • 汉字由两个字节存储

    • 高位字节二进制一定以1开头,转成十进制之后是一个负数

  • Unicode 字符集 【国际标准】

    • 英文一个字节存储,兼容ASCII,二进制前面补0

    • 汉字由三个字节存储,高位还是1开头

    • UTF-8是Unicode一种编码方式

3.1、如何不产生乱码

  • 不要用字节流读取文本文件

  • 编码解码时使用统一个码表,同一个编码方式

3.2、为什么会有乱码

  • 读取数据时未读完整个汉字

  • 编码和解码时的方式不统一

4、字符流 【字节流 + 字符集】

字符输入流【Reader】、字符输出流【Writer】

特点

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

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

使用场景

  • 对于纯文本文件进行读写操作

4.1、FileReader

  • 创建字符输入流对象

    • 如果文件不存在,直接报错

构造方法说明
public FileReader(File file)创建字符输入流关联本地文件
public FileReader(String pathname)创建字符输入流关联本地文件
  • 读取数据

    • 按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数

    • 读到文件末尾,read方法返回-1

成员方法说明
public int read()读取数据,读到末尾返回-1
public int read(char[] buffer)读取多个数据,读到末尾返回-1
  • 释放资源

成员方法说明
public int close()释放资源 / 关流

无参方法

// 1、创建对象并关联本地文件
FileReader fr = new FileReader("G:\\aaa\\s.txt");
// 2、读取数据 read()
// 字符流的底层也是字节流,默认也是一个字节一个字节的读取的
// 如果遇到中文就会一次读取多个,GBK一次读两个字节,UTF-8一次读三个字节

/**
* read() 细节:
* 1、read():默认也是一个字节一个字节读取的,如果遇到中文就会一次读取多个
* 2、在读取之后,方法的底层还会进行解码,并转成十进制
* 最终把这个十进制作为返回值,这个十进制的数据也表示在字符集上的数字
* 英文:文件里面二进制数据 0110 0001
*      read方法进行读取,解码并转成十进制97
* 中文:文件里面的二进制数据 11100110 10110001 10001001
*      read方法进行读取,解码并转成十进制27721
*/
// 如果想要看见中文,就将这些十进制数据进行强转

int ch;
while ((ch = fr.read()) != -1) {
    System.out.print((char) ch);
}
// 3、释放资源
fr.close();

有参方法

// 1、创建对象并关联本地文件
FileReader fr = new FileReader("G:\\aaa\\s.txt");
// 2、读取数据 read()
char[] chars = new char[2];
int len;
/**
* read(chars): 读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中
*  空参的read + 强转类型转换
*/
while ((len = fr.read(chars)) != -1) {
    // 把数组中的数据变成字符串在进行打印
    System.out.print(new String(chars, 0, len));
}
// 3、释放资源
fr.close();

字符输入流原理解析

  • 创建字符输入流对象

    • 底层:关联文件,并创建缓冲区长度为8192的字节数组

  • 读取数据

    • 底层:

      • 判读那缓冲区是否有数据可以读取

      • 缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区;如果文件中也没有数据,返回-1

      • 缓冲区有数据:就从缓冲区中读取。空参的read方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回;有参的read方法:把读取字节,解码,强转三步合并了,强转之后的字符放到数组中。

好奇点

// 创建对象并关联本地文件
FileReader fr = new FileReader("G:\\aaa\\s.txt");
// 会把文件中的数据放到缓冲区中
fr.read();
// 清空文件
FileWriter fw = new FileWriter("G:\\aaa\\s.txt");
// 问题:再次使用fr进行读取,能读取到数据吗? 能,能把缓冲区的数据进行读取(虽然原文件被清空)
int ch;
while ((ch = fr.read()) != -1) {
    System.out.println((char) ch);
}
// 关闭资源
fw.close();
fr.close();

4.2、FileWriter

构造方法说明
public FileWriter(File file)创建字符输出流关联本地文件
public FileWriter(String pathname)创建字符输出流关联本地文件
public FileWriter(File file, boolean append)创建字符输出流关联本地文件,续写
public FileWriter(String pathname, boolean append)创建字符输出流关联本地文件,续写

成员方法说明
void write(int c)写出一个字符
void write(String str)写出一个字符串
void write(String str, int off, int len)写出一个字符串的一部分
void write(char[] cbuf)写出一个字符数组
void write(char[] cbuf, int off, int len)写出字符数组的一部分
  • 创建字符输出流对象

    • 参数是字符串表示的路径或File对象都是可以的

    • 如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的

    • 如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关

  • 写数据

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

  • 释放资源

    • 每次使用完流之后都要释放资源

// 1、创建对象并关联本地文件
FileWriter fw = new FileWriter("G:\\aaa\\s.txt", true);
// 2、写出数据 write()
char[] chars = {'a', 'b', 'c', '我'};
fw.write(chars);
// 3、关闭资源
fw.close();

flush 和 close 方法

成员方法说明
public void flush()将缓冲区中的数据,刷新到本地文件中【刷新之后,还可以继续往文件中写出数据】
public void close()释放资源 / 关流 【断开通道,无法再往文件中写出数据】

5、字节流和字符流的使用场景

  • 字节流

    • 拷贝任意类型的文件

  • 字符流:

    • 读取纯文本文件中的数据

    • 往纯文本文件中写出数据

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

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

        // 1、创建对象表示数据源
        File src = new File("G:\\aaa");
        // 2、创建对象表示目的地
        File dest = new File("G:\\dest");
        // 3、调用方法开始拷贝
        copyDir(src, dest);
    }

    /**
     * 功能:拷贝文件夹
     * @param src 数据源
     * @param dest 目的地
     */
    public static void copyDir(File src, File dest) throws IOException {
        // 如果文件夹不存在就创建
        dest.mkdirs();
        // 进入数据源
        File[] srcFiles = src.listFiles();
        if (srcFiles == null) {
            return;
        }
        // 遍历数组
        for (File srcFile : srcFiles) {
            if (srcFile.isFile()) {
                // 判断文件,拷贝 【文件开始,文件结束】
                // 要拷贝的文件
                FileInputStream fis = new FileInputStream(srcFile);
                // 拷贝的目的地
                FileOutputStream fos = new FileOutputStream(new File(dest, srcFile.getName()));
                // 设置字节数组传递数据
                byte[] bytes = new byte[1024];
                // 设置长度
                int len;
                // 循环输入输出
                while ((len = fis.read(bytes)) != -1) {
                    fos.write(bytes, 0, len);
                }
                // 关闭流
                fos.close();
                fis.close();
            } else {
                // 判断文件夹,递归
                copyDir(srcFile, new File(dest, srcFile.getName()));
            }
         }
    }
}

5.2、文件加密

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

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

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

演示加密【记得抛异常】

// 创建对象关联原始文件
FileInputStream fis = new FileInputStream("G:\\aaa\\011028.jpg");
// 创建对象关联加密文件
FileOutputStream fos = new FileOutputStream("src/ency.jpg");
// 加密处理
int b;
while((b = fis.read()) != -1) {
    fos.write(b ^ 10);
}
// 释放资源
fos.close();
fis.close();

演示解密 【记得抛异常】

// 创建对象关联加密文件
FileInputStream fis = new FileInputStream("src/ency.jpg");
// 创建对象关联解密文件
FileOutputStream fos = new FileOutputStream("src/redu.jpg");
// 解密处理
int b;
while((b = fis.read()) != -1) {
    fos.write(b ^ 10);
}
// 释放资源
fos.close();
fis.close();

5.3、修改文件中的数据

方法一

// 读取纯文本文件数据
FileReader fr = new FileReader("src/a.txt");
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = fr.read()) != -1) {
    sb.append((char) ch);
}
fr.close();
// 排序
String str = sb.toString();
String[] arrStr = str.split("-");
ArrayList<Integer> arrList = new ArrayList<>();
for (String s : arrStr) {
    int i = Integer.parseInt(s);
    arrList.add(i);
}
Collections.sort(arrList);
// 写出数据 x-x-x-x 【使用普通for循环】
FileWriter fw = new FileWriter("src/a.txt");
for (int i = 0; i < arrList.size(); i++) {
    if (i == arrList.size() - 1) {
        fw.write(arrList.get(i) + "");
    } else {
        fw.write(arrList.get(i) + "-");
    }
}
fw.close();

方式二 【使用方法引用 + 字符串分割替代】

// 读取纯文本文件数据
FileReader fr = new FileReader("src/a.txt");
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = fr.read()) != -1) {
    sb.append((char) ch);
}
// 释放资源
fr.close();
// 排序
Integer[] arr = Arrays.stream(sb.toString()
    .split("-"))
    .map(Integer::parseInt)
    .sorted()
    .toArray(Integer[]::new);
// 写出
FileWriter fw = new FileWriter("src/a.txt");
String s = Arrays.toString(arr).replace(", ", "-");
String result = s.substring(1, s.length() - 1).trim();
fw.write(result);
// 释放资源
fw.close();

6、字节缓冲流

6.1、原理

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

方法名称说明
public BufferedInputStream(InputStream is)把基本流包装成高级流,提高读取数据的性能
public BufferedOutputStream(OutputStream os)把基本流包装成高级流,提高写出数据的性能

6.2、拷贝文件

一次一字节

// 创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/b.txt"));
// 循环读取并写到目的地
int b1;
while ((b1 = bis.read()) != -1) {
    bos.write(b1);
}
// 释放资源
bos.close();
bis.close();

一次一字节数组

// 创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/b.txt"));
// 循环读取并写到目的地
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
    bos.write(bytes, 0, len);
}
// 释放资源
bos.close();
bis.close();

7、字符缓冲流

7.1、原理

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

方法名称说明
public BufferedReader(Reader r)把基本流包装成高级流
public BufferedWriter(Writer r)把基本流包装成高级流

7.2、独有方法

字符缓冲输入流特有方法说明
public String readLine()读取一行数据,如果没有数据可读了,会返回null
字符缓冲输出流特有方法说明
public void newLine()跨平台的换行
// 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("src/a.txt"));
// 创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("src/b.txt"));
// 读取数据
/**
* readLine方法细节:
*  在读取的时候,一次读一整行,遇到回车换行结束,
*  但是它不会把回车换行读到内存中
*/
String line;
while ((line = br.readLine()) != null) {
    bw.write(line + "\n");
}
// 释放资源
bw.close();
br.close();

7.3、总结

  • 缓冲流有四种

    • 字节缓冲输入流

    • 字节缓冲输出流

    • 字符缓冲输入流

    • 字符缓冲输出流

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

    • 缓冲流自带长度8192的缓冲区

    • 可以显著提高字节流的读写性能

    • 对于字符流提升不明显,对于字符缓冲流而言关键点是有两个特有的方法

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

    • 字符缓冲输入流BufferedReader:readLine()

    • 字符缓冲输出流BufferedWriter:newLine()

8、四种拷贝方式效率对比

public class ByteStreamDemo {
    public static void main(String[] args) throws IOException {
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        // method1(); // 3,274,830 字节  耗时 10.014秒
        // method2(); // 3,274,830 字节  耗时 0.008秒
        // method3(); // 3,274,830 字节  耗时 0.142秒
        // method4(); // 3,274,830 字节  耗时 0.006秒
        // 记录结束时间
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000.0 + "秒");
    }

    /**
     * 字节流的基本流:一次读写一个字节
     * @throws IOException
     */
    public static void method1() throws IOException {
        FileInputStream fis = new FileInputStream("G:\\aaa\\011028.jpg");
        FileOutputStream fos = new FileOutputStream("G:\\aaa\\bomei.jpg");
        int b;
        while ((b = fis.read()) != -1) {
            fos.write(b);
        }
        fos.close();
        fis.close();
    }

    /**
     * 字节流的基本流:一次读写一个字节数组
     * @throws IOException
     */
    public static void method2() throws IOException {
        FileInputStream fis = new FileInputStream("G:\\aaa\\011028.jpg");
        FileOutputStream fos = new FileOutputStream("G:\\aaa\\bomei.jpg");
        byte[] bytes = new byte[8192];
        int len;
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, len);
        }
        fos.close();
        fis.close();
    }

    /**
     * 高级流:一次读写一字节
     * @throws IOException
     */
    public static void method3() throws IOException {
        BufferedInputStream fis = new BufferedInputStream(new FileInputStream("G:\\aaa\\011028.jpg"));
        BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("G:\\aaa\\bomei.jpg"));
        int b;
        while ((b = fis.read()) != -1) {
            fos.write(b);
        }
        fos.close();
        fis.close();
    }

    /**
     * 高级流:一次读写一字节数组
     * @throws IOException
     */
    public static void method4() throws IOException {
        BufferedInputStream fis = new BufferedInputStream(new FileInputStream("G:\\aaa\\011028.jpg"));
        BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream("G:\\aaa\\bomei.jpg"));
        byte[] bytes = new byte[8192];
        int len;
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, len);
        }
        fos.close();
        fis.close();
    }
}

综上所述:使用第二种和第四种的速度是差不多的

9、练习

第一种写法

// 读取数据
BufferedReader br = new BufferedReader(new FileReader("G:\\aaa\\abc.txt"));
String line;
// 创建一个集合存放读取的每行数据
ArrayList<String> list = new ArrayList<>();
while ((line = br.readLine()) != null) {
    list.add(line);
}
br.close();
// 排序
// 排序规则:按照每一行前面的序号进行排序
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        // 获取 o1和o2的序号
        int i1 = Integer.parseInt(o1.split("\\.")[0]);
        int i2 = Integer.parseInt(o2.split("\\.")[0]);
        return i1 - i2;
    }
});
// 写出
BufferedWriter bw = new BufferedWriter(new FileWriter("G:\\aaa\\cba.txt"));
for (String str : list) {
    bw.write(str);
    bw.newLine();
}
bw.close();

第二种方式 【使用TreeMap】

// 读取数据
BufferedReader br = new BufferedReader(new FileReader("G:\\aaa\\abc.txt"));
String line;
TreeMap<Integer, String> tm = new TreeMap<>();
while ((line = br.readLine()) != null) {
    String[] arr = line.split("\\.");
    tm.put(Integer.parseInt(arr[0]), line);
}
br.close();
// 写出数据
BufferedWriter bw = new BufferedWriter(new FileWriter("G:\\aaa\\cba.txt"));
Set<Map.Entry<Integer, String>> entries = tm.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
    String value = entry.getValue();
    bw.write(value);
    bw.newLine();
}
bw.close();

10、控制软件运行次数

/**
* 实现一个验证程序运行次数的小程序,要求如下:
* 1、当程序运行超过3次,给出提示:本软件只能免费试用3次,欢迎您注册会员后继续使用
* 2、程序运行演示如下:
*  第一次运行控制台输出:欢迎使用本软件,第一次免费使用
*  第二次运行控制台输出:欢迎使用本软件,第二次免费使用
*  第三次运行控制台输出:欢迎使用本软件,第三次免费使用
*  第四次及之后运行控制台输出:本软件只能免费试用3次,欢迎您注册会员后继续使用
*/

// 把文件中的数字读取到内存中 【IO流:1、随用随创建 2、什么时候不用什么时候关闭】
BufferedReader br = new BufferedReader(new FileReader("src/a.txt"));
String line = br.readLine();
br.close();
int count = Integer.parseInt(line);
// 表示当前软件又运行了一次
count++;
// 判断
if (count <= 3) {
    System.out.println("欢迎使用本软件,第 " + count + " 次免费使用");
} else {
    System.out.println("软件只能免费试用3次,欢迎您注册会员后继续使用");
}
// 将 count++ 写回原文件
BufferedWriter bw = new BufferedWriter(new FileWriter("src/a.txt"));
bw.write(count + ""); // 如果写的是数字 那么就会变成 ASCII码
bw.close();

11、转换流 【是字符流和字节流之间的桥梁】

11.1、基本用法

利用转换流按照指定字符编码读取

/**
* 利用转换流按照指定字符编码读取(了解即可,在jdk11被替代了,但现在大多数都是jdk8)
*/
// 创建对象并指定字符编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("src/s.txt"), "GBK");
// 读取数据
int ch;
while ((ch = isr.read()) != -1) {
    System.out.print((char) ch);
}
// 关闭资源
isr.close();

/* jdk11
FileReader fr = new FileReader("src/s.txt", Charset.forName("GBK"));
int ch;
while ((ch = fr.read()) != -1) {
   System.out.print((char) ch);
}
// 关闭资源
fr.close();
*/

利用转换流按照指定字符编码写出

// 创建转换流的对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src/b.txt"), "GBK");
// 写出数据
osw.write("哈士奇");
// 狮子资源
osw.close();

/* jdk11
FileWriter fw = new FileWriter("src/b.txt", Charset.forName("GBK"));
fw.write("哈士奇");
fw.close();
*/

将本地文件中的GBK文件 转成UTF-8

// jdk11以前的方案
InputStreamReader isr = new InputStreamReader(new FileInputStream("src/s.txt"), "GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src/d.txt"), "UTF-8");
int b;
while ((b = isr.read()) != -1) {
    osw.write(b);
}
osw.close();
isr.close();

// 替代方案
/*
FileReader fr = new FileReader("src/s.txt", Charset.forName("GBK"));
FileWriter fw = new FileWriter("src/d.txt", Charset.forName("UTF-8"));
int b;
while ((b = fr.read()) != -1) {
  fw.write(b);
}
fw.close();
fr.close(); 
*/

利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码

// 字节流在读取中文的时候,是会出现乱码的,但是字符流可以搞定
// 字节流里面是没有读一整行的方法的,只有字符缓冲流才能搞定

// FileInputStream fis = new FileInputStream("src/d.txt");
// InputStreamReader isr = new InputStreamReader(fis);
// BufferedReader br = new BufferedReader(isr);
// String line = br.readLine();
// System.out.println(line);
// br.close();

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

11.2、总结

  • 转换流的名字

    • 字符转换输入流

    • 字符转换输出流

  • 转换流的作用

    • 指定字符集读写数据(jdk11之后已淘汰)

    • 字节流想要使用字符流中的方法

12、序列化流 【对象流】

可以把Java中的对象写到本地文件中

构造方法说明
public ObjectOutputStream(OutputStream out)把基本流包装成高级流
成员方法说明
public final void writeObject(Object obj)把对象序列化(写出)到文件中去
  • 细节

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

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

// 创建对象
Student stu = new Student("zhangsan", 23);
// 创建序列化流的对象/对象操作输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/d.txt"));
// 写出数据
oos.writeObject(stu);
// 释放资源
oos.close();

13、反序列化流 【对象流】

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

构造方法说明
public ObjectInputStream(InputStream out)把基本流变成高级流
成员方法说明
public Object readObject()把序列化到本地文件中的对象,读取到程序中来
// 创建反序列化流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/d.txt"));
// 读取数据
Student stu = (Student) ois.readObject();
// 打印对象
System.out.println(stu);
// 释放资源
ois.close();

13.1、细节

  • 使用序列化流将对象写到文件时,需要让JavaBean类实现Serializable接口。否则,就会出现NotSerializableException异常

  • 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了

  • 序列化对象后,修改了JavaBean类,再次反序列化,会出现问题,抛出InvalidClassException异常

    • 解决方案:给JavaBean类添加serialVersionUID(序列号、版本号)

  • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现

    • 解决方案:给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程。

13.2、用对象流读写多个对象

需求:将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列化流该如何读取

// 1、序列化多个对象
Student s1 = new Student("zhangsan", 23, "福建");
Student s2 = new Student("lisi", 22, "福建");
Student s3 = new Student("wangwu", 21, "福建");

ArrayList<Student> studentList = new ArrayList<>();
studentList.add(s1);
studentList.add(s2);
studentList.add(s3);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/d.txt"));
oos.writeObject(studentList);

oos.close();

// 2、创建反序列化流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/d.txt"));
// 读取数据,使用集合
ArrayList<Student> list = (ArrayList<Student>) ois.readObject();
// 输出
for (Student student : list) {
    System.out.println(student);
}
// 释放资源
ois.close();

14、打印流

  • 分类

    • 打印流一般是指:PrintStream、PrintWriter两个类

  • 特点

    • 打印流只操作文件目的地,不操作数据源

    • 特有的写出方法可以实现,数据原因写出

    • 特有的写出方法,可以实现自动刷新,自动换行

14.1、字节打印流

构造方法说明
public PrintStream(OutputStream/File/String)关联字节输出流/文件/文件路径
public 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)特有方法:带有占位符的打印语句,不换行

14.2、字符打印流 【底层有缓冲区,想要自动刷新需要开启】

构造方法说明
public PrintWriter(Writer/File/String)关联字节输出流/文件/文件路径
public PrintWriter(String fileName, Charset charset)指定字符编码
public PrintWriter(Write w, boolean autoFlush)自动刷新
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset)指定字符编码且自动刷新
成员方法说明
public void write(int b)常规方法:规则跟之前一样,将指定的字节写出
public void println(Xxx xx)特有方法:打印任意数据,自动换行
public void print(Xxx xx)特有方法:打印任意数据,不换行
public void printf(String format, Object ... args)特有方法:带有占位符的打印语句

14.2.1、代码演示

// 创建字符打印流的对象 【一般不会自动刷新,需手动添加 true】
PrintWriter pw = new PrintWriter(new FileWriter("src/a.txt"), true);
// 写出数据
pw.println("哈士奇");
pw.print("你好啊朋友");
pw.printf("它喜欢博美诶");
// 释放资源
pw.close();

14.2.2、应用场景

// 获取打印流的对象,此打印流在虚拟机启动的时候,由虚拟机创建,默认指向控制台
// 特殊的打印流,系统中的标准输出流,是不能关闭的,在系统中是唯一的
PrintStream ps = System.out;
// 调用打印流中的方法println
// 写出数据,自动换行,自动刷新
ps.println("哈士奇");
// 如果这里关闭了打印流,那么接下来的打印都无法生效

15、解压缩流/压缩流

15.1、解压缩流

public class ByteStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 创建一个FIle表示要解压的压缩包
        File src = new File("G:\\压缩-解压示范\\简历模板+Java八股文+参考简历.zip");
        // 黄建一个FIle表示解压的目的地
        File dest = new File("G:\\压缩-解压示范");
        // 调用方法
        unzip(src, dest);

    }

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

15.2、压缩流 【有点模糊】

压缩单个文件

public class ByteStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 创建File对象表示要压缩的文件
        File src = new File("G:\\压缩-解压示范\\hhhhhh.txt");
        // 创建File对象表示压缩包的位置
        File dest = new File("G:\\压缩-解压示范");
        // 调用方法用来压缩
        toZip(src, dest);
    }

    /**
     * 作用:压缩文件【不包括文件夹】
     * @param src 表示要压缩的文件
     * @param dest 表示压缩包的位置
     * @throws IOException 异常
     */
    public static void toZip(File src, File dest) throws IOException {
        // 创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, "hhhhhh.zip")));
        // 创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
        ZipEntry entry = new ZipEntry("hhhhhh.txt");
        // 把ZipEntry对象放到压缩包当中
        zos.putNextEntry(entry);
        // 把src文件中的数据写到压缩包当中
        FileInputStream fis = new FileInputStream(src);
        int b;
        while ((b = fis.read()) != -1) {
            zos.write(b);
        }

        zos.closeEntry();
        zos.close();
    }
}

 压缩文件夹

public class ByteStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        // 1、创建File对象表示要压缩的文件夹
        File src = new File("G:\\压缩-解压示范\\aaa");
        // 2、创建File对象表示压缩包放在哪里,压缩包的交换路径
        File destParent = src.getParentFile(); // 放在要压缩的文件夹的父级目录下
        // 3、创建File对象表示压缩包的路径
        File dest = new File(destParent, src.getName() + ".zip");
        // 4、创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest), Charset.forName("GBK"));
        // 5、获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包中
        // 调用方法用来压缩
        toZip(src, zos, src.getName()); // 将要压缩的文件夹名传递
        // 6、释放资源
        zos.close();
    }

    /**
     * 获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
     * @param src 数据源
     * @param zos 压缩流
     * @param name 压缩包内部的路径
     * @throws IOException 异常
     */
    public static void toZip(File src, ZipOutputStream zos, String name) throws IOException {
        // 1、进入src文件夹
        File[] files = src.listFiles();
        // 2、遍历数组
        for (File file : files) {
            // 3、判断是不是文件,如果是就变成ZipEntry对象,放入到压缩包中
            if (file.isFile()) {
                ZipEntry entry = new ZipEntry(name + "\\" + file.getName());
                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());
            }
        }
    }
}

结束~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值