目录
- 前言
- 一.初识IO流
- 二.流的分类
- 三.基本字节流
- 3.1 FileOutputStream
- 3.2 FileInputStream
- 四.文件拷贝
- 五.字符集
- 六.乱码原因
- 七.基本字符流
- 7.1 FileReader
- 7.2 FileWriter
- 经典案例Demo
- 八.高级流
- 8.1 缓冲流
- 8.1.1 字节缓冲流
- 8.1.2 字符缓冲流
- 8.2 转换流
- 8.3 序列化流
- 8.4 打印流
- 8.4.1 字节打印流
- 8.4.2 字符打印流
- 8.5 解压缩流/压缩流
- 九.Commons-io工具包
- 十.Hutool工具包
前言
首先感谢您的阅览,个人水平有限,不足之处多多包涵,也欢迎您的指正与补充,最后也希望本篇文章能为您解除疑惑
一.初识IO流
注:File类详细介绍跳转地址
注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。
而IO流可以进行数据读取,写入,传输
IO流:用于读写文件中的数据(可以读写文件,或网络中的数据…)
二.流的分类
注:word,excel不属于纯文本文件
IO流体系结构
三.基本字节流
一个字节一个字节读取,遇到中文,可能会乱码,中文在UTF-8编码格式中是占3个字节
3.1 FileOutputStream
字节输出流,把程序中的数据写到本地文件上
原理
建立连接
写入数据
关闭连接
字节输出流写出数据的细节
1.创建字节输出流对象
细节1:参数是字符串表示的路径或者File对象都是可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
细节3:如果文件已经存在,则会清空文件
2.写数据
细节: write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
比如上面的97,对应写入a.txt文件中就是字母a
3.释放资源
细节:每次使用完流之后都要释放资源,解除了资源的占用
写数据方式
FileOutputStream写数据的3种方式
第三个方法中,第一个参数是数组,第二个参数是起始索引,第三个参数是写入个数
示例:
续写:
如果想要续写,打开续写开关即可;开关位置:创建对象的第二个参数
默认false:表示关闭续写,此时创建对象会清空文件;手动传递true:表示打开续写,此时创建对象不会清空文件
换行符
再次写出一个换行符就可以了,各个操作系统表示换行符不一致
windows: \r\n
Linux : \n
Mac : \r
下面是续写和换行使用的代码
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\hello.txt",true); //这第二个参数就是续写开关
String str1 = "hello";
byte[] bytes1 = str1.getBytes();
fos.write(bytes1);
String wrap = "\r\n";
byte[] bytes = wrap.getBytes();
fos.write(bytes);
String str2 = "Java Girl";
byte[] bytes2 = str2.getBytes();
fos.write(bytes2);
fos.close();
}
FileOutputStream的作用
可以把程序中的数据写到本地文件上,是字节流的基本流。
3.2 FileInputStream
字节输入流,可以把本地文件的数据读取到程序中
使用步骤及注意细节
1.创建字节输入流对象
细节1:如果文件不存在,就直接报错。
2.读取数据
细节1:一次读一个字节,读出来的是数据在ASCII上对应的数字
细节2:读到文件末尾了, read方法返回-1。read :表示读取数据,而且是读取一个数据就移动一次指针
3.释放资源
细节1∶每次使用完流必须要释放资源。
循环读取
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\hello.txt");
int b = 0;
//read :表示读取数据,而且是读取一个数据就移动一次指针
while ((b = fis.read()) != -1){//注意要用变量接收
System.out.print((char) b);
}
fis.close();
}
四.文件拷贝
边读边写,下面这种方式只适用于小文件拷贝
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");
//边度边写
int b = 0;
while (( b = fis.read() ) != -1){
fos.write(b);
}
//释放资源注意:先开的最后关闭
fos.close();
fis.close();
}
耗时如下
这种方式拷贝慢的原因
是因为FilelnputStream一次读写一个字节,拷贝文件有多少个字节,这里就要循环多少次
解决方法
一次读取多个数据
注意:一次读一个字节数组的数据,每次读取会尽可能把数组装满
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");
int len = 0;
byte[] bytes = new byte[1024 * 1024];
while ((len = fis.read(bytes)) != -1){
//由于数组每次元素不会清空,只是在读时,把读到的元素覆盖到数组对应索引中,若没读满的情况,数组剩余位置元素还是老数据没清除
fos.write(bytes, 0, len); //读多少,写多少
}
fos.close();
fis.close();
}
耗时如下
这个用数组拷贝比上面的单个字节拷贝快了近80多倍
五.字符集
ASCII字符集存储英文的情况,ASCII表中对应的最大数字时127,所以英文都可以刚好用一个字节就能表示出来
ASCII表中是没有汉字的,所以存储汉字,也需要有一个像ASCII表那样的存储规则,然后GBK和Unicode出手了。
汉字之所以是用两个字节(16位二进制)存储,是因为一个字节只能表示8位二进制,也就是256个汉字,明显不够存储所有汉字种类,所以规定用2个字节存储,也就是2的16次方, 转十进制为65535,6万个已经够表示全部汉字种类了。
GBK完全兼容ASCII字符集,为了和英文区别开
核心1:GBK中,一个英文字母一个字节,二进制第一位是0
核心2:GBK中,一个中文汉字两个字节,二进制第一位是1
Unicode字符集的UTF-8编码格式
一个英文占一个字节,二进制第一位是0,转成十进制是正数
一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数
六.乱码原因
原因1:读取数据时未读完整个汉字
以字节流读取为例,UTF-8中汉字占3个字节,字节流每次读取一个字节,当然会乱码
原因2:编码和解码时的方式不统一
上面既然说到了乱码原因,那么解决方法也随之而来,第二个原因就不说了,手动统一格式就完了,主要是原因一的,下面字符流就带着解决方法来了
七.基本字符流
一个字符一个字符读取,可以读取中文而不乱码
主要是对纯文本文件进行读写操作
7.1 FileReader
空参Read方法使用如下
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("IO\\src\\main\\resources\\go.txt");
int ch;
while ((ch = fr.read()) != -1){
System.out.print((char) ch);
}
fr.close();
}
read ()细节:
1.read():默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个
2.在读取之后,方法的底层还会进行解码并转成十进制。
最终把这个十进制作为返回值
这个十进制的数据也表示在字符集上的数字,如下示例
英文:文件里面二进制数据0110 0001
read方法进行读取,解码并转成十进制97
中文:文件里面的二进制数据11100110 10110001 100010日1
read方法进行读取,解码并转成十进制27721
字节输入流除了一个字节一个字节的,还可以通过数组读;当然,这个字符输入流也不输它,字符输入流也可以使用数组读,代码格式如下
FileReader fr = new FileReader("IO\\src\\main\\resources\\go.txt");
char[] chars = new char[2];//用数组的方式,每次读两个
int len = 0;
while ((len = fr.read(chars)) != -1){
System.out.println(new String(chars, 0, len));
}
fr.close();
read带参方法底层实现
read(chars):读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中
相当于空参的read +强转类型转换
字符输入流底层原理解析
读取数据,会先放入缓冲区,缓冲区读满后还在继续读数据,就会再从头开始覆盖缓冲区前面的数据
注意:字节流没有缓冲区,字符流有缓冲区
7.2 FileWriter
构造方法
成员方法
各种细节规则和字节输出流的基本一致
成员方法使用范例
字符输出流底层原理解析
字符输出流,写出数据,是先写到缓冲区的,如下图
上面漏了一条,第三种情况,close关流时也会把缓冲区的数据保存到本地的
flush刷新:刷新之后,还可以继续往文件中写出数据——fw.flush()
close关流:断开通道,无法再往文件中写出数据——fw.close()
下面进行示例展示,毕竟实践才是检验真理的唯一标准
向文件写入数据
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("IO\\src\\main\\resources\\go.txt");
fw.write("敲你吗?");
fw.close();
}
debug断点可以看到,确实有缓冲区,大小8192个字节
接下来往下走一步
缓冲区写入了10个字节,上面写入了3个汉字,一个英文问号,在UTF-8编码方式中,一个汉字3个字节
此时,打开目的文件里面空空如也
再往下走一步,走到close方法,会发现目的文件数据写入了。
经典案例Demo
案例:拷贝文件夹及其子文件到目标文件夹中
public static void main(String[] args) throws IOException {
/*
文件夹拷贝
*/
//创建源文件对象和目标文件对象
File src = new File("D:\\a");
File dest = new File("D:\\b");
copyDir(src,dest);
}
public static void copyDir(File src, File dest) throws IOException {
dest.mkdirs();
File[] files = src.listFiles();
if (files == null || files.length == 0){
System.out.println("没有访问权限");
}
for (File file : files) {
if (file.isFile()){
int len = 0;
byte[] bytes = new byte[1024];
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));
while ((len = fis.read(bytes)) != -1){
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
}else {
copyDir(file, new File(dest, file.getName()));
}
}
}
案例:文件加密与解密
public static void JiaMi() throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");
//加密处理,进行异或
int b = 0;
while((b = fis.read()) != -1){
fos.write(b^2);
}
fos.close();
fis.close();
}
对文件中的数字进行排序
public static void main(String[] args) throws IOException {
//对test.txt中的数字排序 2-4-5-1-3
//变成1-2-3-4-5
FileReader fr = new FileReader("IO\\src\\main\\resources\\test.txt");
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = fr.read()) != -1){
sb.append((char)ch);//用StringBuffer来接收读入的数据
}
fr.close();
System.out.println(sb);
//转为字符串
String str = sb.toString();
//进行切割,获取数字
String[] strings = str.split("-");
//存入list集合排序
ArrayList<Integer> list = new ArrayList<>();
if (strings == null || strings.length == 0){
System.out.println("文件中没有数据");
}
for (String s : strings) {
int i = Integer.parseInt(s);
list.add(i);
}
Collections.sort(list);
System.out.println(list);
//把排序后的数据写出
FileWriter fw = new FileWriter("IO\\src\\main\\resources\\test.txt");
for (int i=0; i < list.size(); i++){
if (i == list.size() - 1){
fw.write(list.get(i) + "");
}else {
fw.write(list.get(i) + "-");
}
}
fw.close();
}
八.高级流
由于基本输入输出流的效率太低,所以基于基本流又出现了缓冲流,转换流,序列化流,打印流等等
8.1 缓冲流
8.1.1 字节缓冲流
真正读写数据的还是原来的基本流
下面是字节缓冲流的基本使用
利用字节缓冲流拷贝文件
字节缓冲流提高效率的底层原理
8.1.2 字符缓冲流
它也是底层自带了长度为8192的缓冲区提高性能,但是基本的字符流已经自带缓冲区了,所以这个字符缓冲流效率提升的不是很明显。
特有方法,读取一行的使用示例
public static void main(String[] args) throws IOException {
//创建字符缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("IO\\src\\main\\resources\\hello.txt"));
//读取数据
//readline方法在读取的时候,一次读一整行,遇到回车换行自动结束读取
//但是它不会把回车换行读到内存当中
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
特有方法,跨平台换行
//创建字符缓冲输出流对象,注意看续写true放的位置
BufferedWriter bw = new BufferedWriter(new FileWriter("IO\\src\\main\\resources\\hello.txt",true));
//写出数据
bw.write("你歪嘴的样子,龙王自愧不如");
//加一个所有系统通用的换行符
bw.newLine();
bw.write("如果我结婚了,你一定要来哦,没有新娘我会很尴尬");
bw.newLine();
//释放资源
bw.close();
细节1:字符缓冲流的缓冲区大小为16k
细节2:输出流关联文件时,文件存在,若没有添加续写开关,内容即清空
8.2 转换流
作用:可以避免乱码,字节流也可以使用字符流中的特定方法
读取GBK文件
public static void main(String[] args) throws IOException {
//1.创建转换流对象,并指定字符编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("GBK格式文件路径"), "GBK");//字节流转字符流
//2.读取数据
int ch;
while ((ch = isr.read()) != -1){
System.out.println((char) ch);
}
isr.close();
}
案例
字节流使用字符流的独有方法,比如readLine()
利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
1.字节流在读取中文的时候,是会出现乱码的,但是字符流可以搞定
2.字节流里面是没有读一整行的方法的,只有字符缓冲流才能搞定
字节流——>字符流——>字符缓冲流
8.3 序列化流
序列化流是高级流的一种,它也是字节流
序列化流/对象操作输出流
可以把Java中的对象写到本地文件中,正常方式打开显示的是乱码
作用:就是把对象存储本地文件,避免其他人篡改数据
序列化流也是高级流,所以也需要关联基本流,让基本流干活,它只是包装
//1.创建对象
//2.创建序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("把对象写入哪个文件的路径"));
//3.写出数据,对象放进去
//oos.writeObject();
//4.释放资源
oos.close();
序列化流的小细节
使用对象输出流将对象保存到文件时会出现NotSerializableException异常
解决方案:需要让Javabean类实现Serializable接口
反序列化流/对象操作输入流
可以把序列化到本地文件中的对象,读取到程序中来
使用方式如下
读取的操作类似不再展示,先写再读,序列号要在对象属性创建完毕后,最后一步再生成序列号,因为序列号是根据类中所有内容计算出来的。
为了方便读取,一般先把所有对象都add到list集合中,写的时候,直接传list进去,读取的时候也可以用list接收读取的数据,增强for遍历出来
细节汇总
8.4 打印流
注意打印流不能读,只能写,所以只有字节,字符的输出流,为了方便记忆,就认为它是流中的唯一单身狗,其他都是成双成对。
8.4.1 字节打印流
字节打印流的构造方法
字节打印流成员方法
使用范例
public static void main(String[] args) throws IOException {
//1.创建字节打印流对象
PrintStream ps = new PrintStream(new FileOutputStream("IO\\src\\main\\resources\\printlnTest.txt"),true,"UTF-8");
//2.写出数据——>三个功能:写出+自动刷新+换行
ps.println(97);
ps.println(true);
ps.printf("%s 爱上了 %s","阿珍","阿强");//%s是字符串占位符
ps.close();
}
8.4.2 字符打印流
字符打印流的构造方法
字符打印流成员方法
使用范例
8.5 解压缩流/压缩流
Java只能识别zip后缀的
解压本质就是把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地
ZipEntry表示当前在压缩包里的每一个文件或文件夹
解压缩代码如下
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()){
//文件夹,需要在目的地创建一个同样的文件夹
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.close();
}
压缩代码
public static void toZip(File src, File dest) throws IOException{
//1.创建压缩流关联压缩包 或者文件名.zip
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, src.getName().split("\\.")[0] + ".zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
ZipEntry entry = new ZipEntry("要压缩的文件");
//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();
}
九.Commons-io工具包
简化代码编写,相关操作只需要直接调用工具包中的api接口。
常见方法
使用示范:
十.Hutool工具包
国内大牛开发的,解释都是中文,简明易懂。其中也有关于io和file的工具包,导包后可以直接调用它对应的api,简化开发。
官网:https://hutool.cn/
API文档:https://apidoc.gitee.com/dromara/hutool/
中文使用文档:https://hutool.cn/docs/#/
至此,IO流基础知识也写的差不多了,后续我有新的认知会继续补充这段笔记,感谢您的阅览