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());
}
}
}
}
结束~