文章目录
- 前言
- 一、File
- File中常见的成员方法
- 判断和获取
- 创建和删除
- 获取和遍历
- 二、IO流
- IO流体系结构
- 字节流
- 字节输出流:FileOutputStream
- 字节输入流FileInputStrea
- 文件拷贝
- try……catch异常处理
- 中文乱码现象
- 字符流
- 字符流读取FileReader
- 字符流输出FileWriter
- 底层原理
- 使用场景及综合练习
- 拷贝整个文件夹
- 文件加密
- 修改文件数据
- 缓冲流
- 字节缓冲流
- 字符缓冲流
- 四种字节流拷贝方法的读写用时统计
- 转换流
- 序列化流
- 打印流
- 字节打印流
- 字符打印流
- 系统打印流
- 解压缩流/压缩流
- 解压缩流
- 压缩流
- IO工具包
- Commons-io
- Hutool工具包
- 三、综合练习
前言
博客仅记录个人学习进度和一些查缺补漏。
学习内容:BV17F411T7Ao
一、File
将一些信息保存到指定位置的文件里
file对象表示的就是一个路径,可以是文件的路径,也可以是文件夹的路径
虽然打印出来都是字符串,但是file对象有自己的方法
也可以使用拼接方式创建file路径,本质上就是拼接,可以String之间拼接,也可以和file进行拼接
File中常见的成员方法
判断和获取
例如:判断文件
例如:判断文件夹
例如:判断不存在文件
例如:判断文件大小
例如:获取绝对路径
例如:获取名字
例如:获取最后修改时间
创建和删除
例如:创建新的空文件,返回值为是否创建成功
注意,因为创建文件可能会引起异常,需要进行异常处理
1、如果当前路径表示的文件不存在,可以创建,则创建成功返回true
2、如果当前路径表示的文件已存在,则不可以创建,创建失败返回false
3、如果当前父级路径是不存在的,则出现IO异常
4、如果没有指定文件类型,会创建出一个没有后缀的文件(创建的一定是文件)
例如:创建文件夹(目录)
例如:创建多级文件夹
多级文件夹也可以创建单击文件夹,因为多级文件夹底层就是单级文件夹
例如:删除文件或者空文件夹(无法删除有内容的文件夹)而且不走回收站
获取和遍历
例如:
当调用File表示的路径不存在,或者是一个文件的时候,返回null
当File表示的路径是一个空文件夹的时候,返回长度为0的数组
当File表示的路径是一个有内容的文件夹的二十号,将里面所有的文件和文件夹路径放到File数组返回
当File表示的路径是一个有隐藏文件的文件夹的时候,将包括隐藏文件夹。
当调用者File表示的路径需要权限才能访问的时候,返回null
例如:获取盘符,注意,是静态方法(无需File来提示,直指根目录)
例如:获取当前路径下所有内容名字
例如:添加过滤器的获取,注意,需要函数式方法
使用listfile完成相同效果:
同时,过滤器也可以使用父子路径的重载方法:
二、IO流
存储和读取数据的解决方案,专注读写文件内的数据。
程序为主体,读写内存中的文件内容。
纯文本文件:Windows自带的记事本能打开的文件
IO流体系结构
字节流
字节输出流:FileOutputStream
先创建字符流对象,然后使用写入方法写出数据,最后释放资源
基本代码:
在创建FileOutputStream对象的时候,可以是字符串路或者File对象
如果该文件不存在就会创建一个新文件,但是得保证父级路径存在,否则会出现异常
如果该文件已经存在,就会先把文件清空。此时为覆盖模式,如果仅想追加内容,可以在构造的时候多加一个参数。
// 追加模式,不会清空文件内容,注意此时光标不会自动换行,紧跟文件末尾开始写
FileOutputStream fosAppend = new FileOutputStream(filename, true);
写数据的时候注意,write方法参数时整数,但实际上写到本地文件中的是该整数对应的ASCII字符,每次只能写入一个字符。例如写出:97
最后如果没有释放资源,在程序结束前该文件会一直被占用。
除了一次写入一个字符的write,还有其他的写入方式
例如:写入byte类型的字节数组 (byte 是Java中的一个基本数据类型,用于表示8位有符号整数)也会被转义为ASCII码
此时会写入abcde
例如:写入byte类型字节数组中指定多个
此时写入b,c
每次传入byte数据非常麻烦,所以可以直接使用String对象的方法getBytes
例如:换行写
字节输入流FileInputStrea
例如:直接读取文本文件中的内容
read方法每次返回一个整型,对应ASCII码,每次读取一个字符,如果读不到则会返回-1。
如果要读取的文件不存在,则会直接报错,对应输出流不存在时会创建文件,是因为输出流是有数据需要写,所以需要创建文件来接收数据。但是输入流需要的是对应文件中的数据,如果文件不存在那数据也就不存在,自然没有必要创建文件。
注意空格也是一个字符,也会被读取。
循环读取:
例如:使用(char)来强制转换int类型
注意,如果没有第三方变量b,则会出现多次读取read的情况,每次读取都会移动指针会出现如下情况:
导致数据丢失
文件拷贝
就是先读再写
例如:小文件拷贝
速度很慢,其根本原因是每次都只读了一个字节
如果需要一次考虑多个字节,一般使用1024的整数倍数组大小,尽可能把数组装满
每次读取2个字符
但是出现读取到字符串末尾的情况会出现如下情况:
实际上是因为每次读取后byte数组都没有清空,导致后续读取的数据仅覆盖了读到了的部分
这时可以通过指定读取被更新的部分来解决这个问题,其中len是读取的长度。
更新后的拷贝方法:
很明显的速度变快了
try……catch异常处理
如果发生写入异常导致文件无法正常释放是十分麻烦的情况
为了避免这种情况的发生需要使用finally代码块,除非jvm退出,否则一定会执行,顺序在try……catch之后,或者发生异常之后。
但是,局部变量显然只能在当前代码块中生效,会出现如下情况的报错:
所以需要在外部使用仅定义的方式来先申明变量,注意变量在申明的时候需要初始化。
此时finally中需要考虑到空指针异常,所以要先做非空判断
额外的,可以使用AutoCloseable接口来实现自动关闭。
例如:JDK7中的异常捕获写法,直接把流创建到try后面的小括号中
注意,只有实现了AutoCloseable接口的方法才能在try后面的小括号中创建对象。
例如:JDK9的异常捕获方法
中文乱码现象
实际上是因为所使用的编码解码规则不一样
对于ASCII字符集来说:
中文字符集GBK:
英文等符号完全兼容ASCII码表,汉字等符号需要使用两个字节
之所以要在第一个字符加符号,是为了区别单字节的英文ASCII码,提示计算机当前读到的字符是汉字还是英文,例如:
万国码Unicode:
为了避免内存浪费,所以基本使用utf-8编码规则,其中分类规则类似IPV4网络分类
utf-8是一种编码规则,是将unicode编码以某种规则呈现,例如UTF-8编码中,红色的部分是要求的固定格式,黑色的是原汉字对应的Unicode编码的填充。
例如:
为了保证编码和解码的规则相同,java也提供了相应的方法
例如:IDEA默认使用utf-8规则
其中英文1个字节,中文3个字节
通过在getByte方法中给指定编码方式可以改变读取结果,例如:
其中英文占1个字节,中文占2个字节
解码即指定String的构造方法,idea默认UTF-8
也可以指定解码方式,例如:
综上,乱码的出现分为以下两个原因。
字节流每次读一个字节,而中文起码占两个字节,所以每次读1字节的时候如果按照中文编码来翻译就会出现乱码。
所以不可以用字节流来读中文文件,应该用字符流来读取
字符流
字符流读取FileReader
会自动识别当前字符是中文还是英文,如果是英文就会读一个字节,如果是中文就会读多个字节,保证每次都读到一个字符
例如:需要注意此时read读到的都会作为十进制返回,该十进制数字指对应字符集的字符码
只需要对返回的int类型进行char强转就可以了
同样的,可以通过数组实现一次读多个数据:
其中read方法有参数时,底层会自动进行int到char的强制类型转换,并放到char数组中
字符流输出FileWriter
字节流每次只能操作一个字节,所以写多字节数据的时候(如中文)会乱码,如果时字符流写入,会先编码再写入,保证每次写入一个字符。
当然,直接传入字符串也是被允许的:
写入字符数组也时可以的:
底层原理
注意,字节流底层并不会创建数组,注意,返回-1时读取的是文件,而不是缓冲区,缓冲区内的数据是覆盖制的。
细节:
写在入时,也会先写到缓冲区,在以下三种情况后会写到本地文件中
例如:
使用场景及综合练习
字节流:把文件看成1字节1字节的纯数据文件,多用于文件的拷贝
字符流:把文件看成1字符1字符有阅读意义的文本数据,多用于读取纯文本文件中的而数据和往纯文本文件中写数据
拷贝整个文件夹
package com.itheima.demo5;
import java.io.*;
public class StreamTest {
public static void main(String[] args) throws IOException {
//输入指定文件夹和目的文件夹路径
String ori_path = "C:\\Users\\ASUS\\Desktop\\sas\\aaaa";
String dir_path = "C:\\Users\\ASUS\\Desktop\\sas\\bbbb";
//创建两个file对象表示其实文件夹和目的地
File ori_file = new File(ori_path);
File dis_file = new File(dir_path);
//拷贝
copyFile(ori_file, dis_file);
}
public static void copyFile(File f1, File f2) throws IOException {
//防止空指针
f2.mkdirs();
//进入当前文件夹,列出目录
File[] files = f1.listFiles();
for (File file : files) {
//分为文件和文件夹两个情况
if(file.isFile()) {
//是文件则拷贝
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream(new File(f2, file.getName()));
//创建byte数组一次读多个,len表示读取的长度
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fileInputStream.read(bytes)) != -1) {
fileOutputStream.write(bytes, 0, len);
}
fileOutputStream.close();
fileInputStream.close();
}else {
//文件夹就递归
copyFile(file, new File(f2, file.getName()));
}
}
}
}
文件加密
最简单的加密:异或,数据异或两次会还原
//输入指定文件夹和目的文件夹路径
String ori_path = "C:\\Users\\ASUS\\Desktop\\sas\\aaaa\\ttttt.txt";
String dir_path = "C:\\Users\\ASUS\\Desktop\\sas\\bbbb\\ttttt.txt";
//创建两个file对象表示其实文件夹和目的地
File ori_file = new File(ori_path);
File dis_file = new File(dir_path);
//加密
FileInputStream fileInputStream = new FileInputStream(ori_file);
FileOutputStream fileOutputStream = new FileOutputStream(dis_file);
//创建byte数组一次读多个,len表示读取的长度
int b = 0;
while ((b = fileInputStream.read()) != -1) {
fileOutputStream.write(b ^ 2);
}
fileOutputStream.close();
fileInputStream.close();
//解密:交换两个路径
修改文件数据
public static void main(String[] args) throws IOException {
/**
* 假设现在有一文件 C:\Users\ASUS\Desktop\sas\aaaa\ttttt.txt
* 其内容为2-1-9-4-7-8
* 请排序后输入回原文件
*/
//输入指定文件路径
String ori_path = "C:\\Users\\ASUS\\Desktop\\sas\\aaaa\\ttttt.txt";
//创建两个file对象表示其实文件夹和目的地
File ori_file = new File(ori_path);
//获取文件内容
FileInputStream fileInputStream = new FileInputStream(ori_file);
int b = 0;
String str = "";
//收集内容到字符串中
while ((b = fileInputStream.read()) != -1) {
str += ((char) b);
}
//将数字收集到集合中
ArrayList<Integer> integerArrayList = new ArrayList<>();
for (String string : str.split("-")) {
integerArrayList.add(Integer.parseInt(string));
}
//排序
integerArrayList.sort((o1, o2) -> o1 - o2);
//整合成字符串
String res = "";
for (Integer i : integerArrayList) {
res += i + "-";
}
res = res.subSequence(0, res.length() - 1).toString();
//写回
FileOutputStream fileOutputStream = new FileOutputStream(ori_file);
fileOutputStream.write(res.getBytes());
fileOutputStream.close();
fileInputStream.close();
}
缓冲流
因为开拓了缓冲区,所以可以高效的读写数据
字节缓冲流
仅仅只是一种包装方法,底层还是通过输入输出流实现的
例如:注意此时并没有手动创建字节数组,而是直接将字节写入,所以不需要提示读取长度len
例如:一次读取多个字节
实际上就是开辟了两个缓冲区,然后在缓冲区之间进行数据交换
字符缓冲流
字符流本身就带缓冲区,所以提速并不明显,但是在字符缓冲流中添加了两个特有方法
例如:读取单行数据,每次都一整行,即读取到换行符就会停止,并且不会读入换行符
例如:每次读多行,没有数据时会返回null
例如:每次写一行,可以写string,会自动换行,并且每次建立连接的时候都会清空元数据
注意,续写是字符流的功能,true应该写在里面
四种字节流拷贝方法的读写用时统计
字节基本流 每次单字节 基本不考虑
字节基本流 字节数组 大概 20秒
字节缓冲流 每次单字节 大概 130秒
字节缓存流 字节数组 大概 18秒
转换流
是字符流和字节流之间的桥梁,总体上是字符流,可以使用字符流的方法,底层是通过字节流实现的。
可以把字节流转换成字符流读入,把字符流转换成字节流写出
例如:读取GBK的文件,注意,底层是读取字节,所以会出现乱码,需要制定GBK的编码方式,而这个编码方式的指定本身是字符流的方法参数。
例如:现在File reader已经可以指定字符编码了,实际上就是字符基本流读取
例如:写出GBK文件,使用转换流
例如:现在的File Writer已经可以直接指定字符编码了,实际上就是字符基本流写出
例如:使用转换流读GBK,写UTF-8
例如:现在可以直接使用基本字符流来指定编码了
例如:使用字节流读取文件,每次读一行并且不出现乱码
注意,字节流本身没有读行功能,需要由缓冲字符流提供(缓冲流特定方法),所以将字节流交给转换流,再由转换流交给缓冲流
序列化流
是字节流的包装流,可以把java中的对象直接写到本地文件中,没有阅读性,但是虚拟机能理解就可以
例如:存储一个学生的对象
产生报错,是因为没有实现Serializable接口(标记性接口,表示当前对象可以被序列化)
也可以将文件中的对象读取到程序中
例如:将刚刚写出来的学生对象再读入程序,注意读入时是object对象,需要强制转换
注意,每个类在实现Serializable接口的时候都会生成版本号,如果类进行了更新,该版本号会自动更新,之前存储的对象无法取回对于的类型。为解决这个问题,可以在类中主动定义版本号
设置如下:将提示生成版本号
例如:如果反序列化中出现了新的成员变量,会自动生成默认值
如果需要一次读写多个对象的情况:
例如:写出多个对象,注意序列化版本号定义应该是类写完后进行的
例如:反序列化时,如果对于数量不同,则会出现异常
解决方法:对于多个数据的读写应该通过集合进行
打印流
字节和字符都有打印流
字节打印流
例如:将指定的文本文件作为控制台进行打印
字符打印流
例如:字符打印流在文本文件中打印
系统打印流
观察out变量:
是一个System类中的最终变量,由虚拟机在启动时创建。
解压缩流/压缩流
解压缩流
例如:使用File指向压缩包,注意java只能识别zip 只有解压缩流能读取压缩包文件,获取到的都是zipentry对象
getNextEntry方法会返回当前的对象并指向下一个对象,深度优先搜索,获取不到zipentry对象时会返回null。
压缩流
例如:压缩单个文件,要注意单个文件是直接通过压缩流对象进行数据写入的,zipentry仅提供了压缩包内部的,与具体文件内容无关。文件内容一定是通过当前压缩流实现的。closeEntry表示当前的entry对象书写完毕了,开始下一个。
例如:把整个文件夹变成压缩包
外部代码:
内部代码:
注意String name的传入表示当前路径。
IO工具包
一系列第三方提供的专用于输入输出的代码库
Commons-io
例如:在项目路径下先新建lib文件夹
例如:下载到需要的jar包
例如:导入并关联jar包
例如:拷贝文件,不需要手动创建关闭流了
例如:拷贝文件夹,不需要自去写循环了
Hutool工具包
官网:hutool.cn
例如:可变参数进行路径拼接
例如:根据参数创建文件,无视父路径是否存在,可以一并创建
例如:支持写入集合并且覆盖。集合默认换行。
例如:支持写入集合并且追加。集合默认换行。
例如:支持集合读取,默认每一行数据就是一个元素。
三、综合练习
等待后续更新