文件操作
顾名思义, 操作磁盘上的某⼀个⽂件或者某⼀个⽂件夹。 可以对他们进⾏创建、 删除、 移动、 属性获取、 属性设置等操作。 但是, 并不包含读取⽂件的内容、 拷⻉⽂件。
在Java中, 使⽤ java.io.File 类描述⼀个⽂件, 或者是⼀个⽂件夹。
IO流
什么是IO流
IO流: Input/Output Stream
流: 指的是⼀串流动的数据, 在数据在流中按照指定的⽅向进⾏流动。 实现数据的读取、写⼊的功能。
作⽤:实现两个设备之间数据的传递
IO流的使⽤场景
使⽤File类, 只能做关于⽂件的操作, 获取属性、 创建⽂件、 删除⽂件、 移动⽂件等操作, 但是不包含读取⽂件中的内容。 如果需要读取、修改⽂件中的内容, 此时就需要使⽤IO流来完成了。
使⽤场景: 对某⼀个⽂件进⾏读取或者写⼊操作。
注意事项:
IO流是对⼀个⽂件进⾏读写的, 不是⼀个⽂件夹! 在使⽤IO流的时候, 不要建⽴与⼀个⽂件夹的连接。
IO流的分类
按照不同的分类标准, 能够得到不同分类的IO流:
-
按照传输数据的单位:
- 字节流: 传输的是字节,是以字节为单位的。可以操作任意类型的数据 ------⾳频,视频,⽂件,图⽚等
- 字符流: 传输的是字节,不同点是在传输过程中加⼊了编码的操作,让我们的操作更⽅便------⽂本
-
按照数据传输的⽅向:
以内存为参考- 输⼊流: 数据从其他设备传到内存
- 输出流: 数据从内存传到其他设备
基础的IO流类的简介
其实在 java.io 包中, 有很多很多的类, 都是来描述IO流的。 但是基本上所有的IO
流的类, 都是直接或间接的继承⾃四⼤⽗类流。
字节流的两个⽗类:
- 字节输⼊流:InputStream
- 字节输出流:OutputStream
字符流的两个⽗类:
- 字符读⼊流:Reader
- 字符写出流:Writer
IO流使⽤的注意事项
- 四⼤⽗类流, 都是抽象类, 都不能实例化对象。 因此, 需要借助他们的⼦类实现数据的读写。
- 流对象⼀旦实例化完成, 将建⽴⼀个程序与⽂件之间的连接。 这个连接会持有这个⽂件。 如果这个连接不断, 此时这个⽂件就是⼀个被使⽤中的状态, 此时将⽆法对这个⽂件进⾏其他的操作, 例如删除。
- ⼀个流在使⽤完成之后, 切记! ⼀定要进⾏流的关闭。
⽂件操作
绝对路径和相对路径
相关概念
路径: ⽤来描述⼀个⽂件所在的地址, ⽤来定位⼀个⽂件的。 可以分为绝对路径和相对路径。
绝对路径: 从磁盘的根⽬录开始, ⼀层层的向下查找, 直到找到这个⽂件。
例如:
C:\Users\luds\Desktop\集合\assets\map.png
C:\shawn\documents\JavaPDF\IO流.pdf
相对路径: 找到⼀个参照物, 相对于这个参照物的路径。
例如:
assets/collection.png
比较
分隔符
分隔符的简介
在描述路径的字符串中, 有两种分隔符, 是⽐较常⻅的: ⽬录分隔符 和 路径分隔符。
⽬录分隔符
分隔开⼀个路径中的不同的⽂件夹, ⽤来描述层级关系、 包含关系。
在不同的操作系统中, ⽬录分隔符是不⼀样的。 在windows中, 使⽤ \ 作为⽬录分隔符; 在⾮windows的操作系统中, 例如: Linux、Unix… 使⽤的是 / 作为⽬录分隔符。
理论上来讲, 在windows中, 应该使⽤\作为⽬录分隔符。 但是在有些情况下, 使⽤/也是可以做⽬录分隔符的。
路径分隔符
分隔开⼀个字符串中的多个路径的。
在不同的操作系统中, 路径分隔符是不⼀样的。 在windows中, 使⽤ ; 作为路径分隔符; 在⾮windows的操作系统中, 例如: Linux、Unix… 使⽤的是 : 作为路径分隔符。
分隔符的表示
如果你的程序需要考虑在不同的平台上部署运⾏, 此时就需要使⽤以
下⽅法进⾏分隔符的获取。
File类
File类的简介
File是 java.io 包中的⼀个类。 是对磁盘上的某⼀个⽂件、⽂件夹(⽬录)的描述。所谓的⽂件操作, 其实都是需要使⽤这个类来完成的。
File类的构造⽅法
- ⽂件属性获取⽅法
- ⽂件操作⽅法
- ⼦⽂件获取
基础的IO流
建⽴程序与⽂件的连接
其实, 就是建⽴了程序与⽂件之间连接的管道, 实现数据在这个管道之内进⾏流动。 管道分为不同的类型: 字节输⼊流、 字节输出流、 字符输⼊流、 字符输出流。 下⾯以字节输⼊流 InputStream 为例。
标准流程
- try结构外⾯, 声明流对象, 为了在finally中使⽤。
- 在try结构⾥⾯, 实例化流对象, 并捕获异常。
- 在finally结构中, 对流进⾏关闭。 在关闭的时候, 需要考虑流对象是否是null, 以及要处理 IOException 异常。
try结构的特殊使⽤
在 JDK1.7 之后, 可以在try后⾯添加⼀对⼩括号。 将 AutoClosable 接⼝实现类的对象, 实例化放到⼩括号中完成。 此时, 在try结构执⾏结束的时候, 会⾃动的调⽤AutoClosable接⼝实现类中的close⽅法, 进⾏流的关闭。 这样写的流的建⽴⽐较简单, 也是最主要使⽤的⽅式。
try (InputStream inputStream = new FileInputStream("file\\day25\\source")) {
// 数据的读取操作
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
InputStream
这是⼀个字节输⼊流。 从⽅向来说, 是⼀个输⼊流, 数据是从⽂件中流动到程序中(数据从其他设备到内存), 是为了读取⽂件中的数据的。 从数据单位来说, 这个流中流动的数据直接是字节的形式。
⽂件的读取
- ⼀次读取⼀个字节
read():⼀个字节⼀个字节的读,每次读出⼀个字节
int num = 0;
while ((num = inputStream.read()) != -1) {
System.out.print((char)num);
}
- ⼀次读取多个字节(推荐)
read(数组):⼀次可以读出多个字节,数组的作⽤:每次会将读出的字节临时放到这个数组中
byte[] arr = new byte[2];
int num = 0;
while ((num = inputStream.read(arr)) != -1){
System.out.println(new String(arr,0,num)+"num:"+num);
}
- ⼀次读取全部字节
可以通过available()⽅法获取全部字节个数
//注意:这种⽅式适合⽂件的字节数⽐较⼩的时候,⼤概是⼏kb之内.
int num = inputStream.available();
byte[] bytes = new byte[num];
inputStream.read(bytes);
System.out.println(new String(bytes));
OutputStream
字节输出流。 从⽅向上来分, 是⼀个输出流, 数据从程序中流动到⽂件中(数据从内存到其他设备), 实现⽂件的写操作。 从流中流动的数据单位来分, 是⼀个字节流,流中流动的数据直接是字节的形式。
String message = "你好,师姐";
outputStream.write(message.getBytes());
Reader
这是⼀个字符输⼊流。 从⽅向来说, 是⼀个输⼊流, 数据是从⽂件中流动到程序中(数据从其他设备到内存), 是为了读取⽂件中的数据的。 从数据单位来说, 这个流中流动的数据以字节为单位的,不同的是在传输过程中加⼊了编码的操作,让我们的操作更⽅便。
// 1. 实例化⼀个字符数组
char[] array = new char[100];
// 2. 声明⼀个变量,⽤来记录每次读取到了多少个数据
int length = 0;
// 3. 循环读取数据
while ((length = reader.read(array)) != -1) {
String str = new String(array, 0, length);
System.out.print(str);
}
Writer
字符输出流。 从⽅向上来分, 是⼀个输出流, 数据从程序中流动到⽂件中(数据从内存到其他设备), 实现⽂件的写操作。 从流中流动的数据单位来分, 是⼀个字符流,流中流动的数据是以字节为单位,不同的是在传输过程中加⼊了编码的操作,让我们的操作更⽅便。
// 1. 实例化相关的类
try{
// 2. 将数据写⼊到输出流中
writer.write("hello, world");
// 3. 冲刷缓冲区
writer.flush();
}
案例: ⽂件拷⻉
实现, 将⼀个⽂件拷⻉到另外⼀个地⽅。 注意, 这个不是剪切, 拷⻉完成之后,原⽂件还在。
实现⽅式: 借助两个流来完成。
- 使⽤字符输⼊流, 循环读取原⽂件中的数据。
- 使⽤字符输出流, 将每次读取到的数据, 写⼊到⽬标⽂件中。
char[] array = new char[100];
int length = 0;
while ((length = reader.read(array)) != -1) {
writer.write(array, 0, length);
}
writer.flush();
缓冲流
缓冲流的简介
给普通的IO流, 套上⼀个缓冲区。 所有的使⽤缓冲流进⾏的读写操作, 都是和缓冲区进⾏交互的, 避免了频繁的IO操作。 这样⼀来, 带来的好处就是可以提⾼读写的效率。 这个缓冲区, 其实是⼀个数组。
- 缓冲流的作⽤
为了提⾼读写的能⼒,本身没有读写的能⼒,要想进⾏读写就必须借助于字符流/字节流实现.
-
常⻅的缓冲流:
- BufferedInputStream : 缓冲字节输⼊流
- BufferedOutputStream : 缓冲字节输出流
- BufferedReader : 缓冲字符输⼊流
- BufferedWriter : 缓冲字符输出流
-
字符流和缓冲字符流对⽐
使⽤缓冲流实现读写的步骤与字符流⼀样,只是需要我们先通过构造⽅法传⼊⼀个字符流对象.同时缓冲流可以提⾼读写效率.
- 总结
⼤家在使⽤流读写数据时,尽量使⽤缓冲流,缓冲流中尽量使⽤缓冲字符流,在字符缓冲流中⽐缓冲字节流多了readLine()和newLine()⽅法.
缓冲字节流
// 1. 实例化⼀个字节数组
byte[] array = new byte[1024];
// 2. 声明⼀个整型变量,⽤来记录每次读取了多少个字节数据
int length = 0;
// 3. 循环读取
while ((length = bufferedInputStream.read(array)) !=-1) {
// 4. 将读取到的数据转成字符串输出到控制台
String msg = new String(array, 0, length);
System.out.println(msg);
}
缓冲字符流
// 从流中读取数据
char[] array = new char[100];
int length = 0;
while ((length = bufferedReader.read(array)) != -1)
{
System.out.print(new String(array, 0, length));
}
缓冲字符流中的特殊⽅法
BufferedReader 类中多了⼀个⽅法 readLine()
- 意义: 读取缓冲流中的⼀⾏数据, 可以逐⾏读取。 ⼀直到读取到的数据是null,表示数据读取完了, 没有下⼀⾏数据了。
- 注意事项: readLine() 是逐⾏读取, 但是, 只能读取到⼀⾏中的内容,并不能读取⾛换⾏符。
// 1. 定义⼀个字符串,⽤来接收每⼀⾏读取到的数据
String line = "";
// 2. 循环读取数据
while ((line = reader.readLine()) != null) {
// 3. 将读取到的数据输出
System.out.println(line);
}
BufferedWriter 类中多了⼀个⽅法 newLine()
- 写换⾏符 ,不同的系统使⽤的默认换⾏符不⼀样 windows系统 \r\n linux\n
- 意义: ⽆参的⽅法, 写⼀个换⾏符,⽀持跨平台(平台⽆关性)
bufferedWriter.write("hello world");
bufferedWriter.newLine();
bufferedWriter.write("你好,世界");
bufferedWriter.newLine();
bufferedWriter.write("end");
LineNumberReader
是BufferedReader的⼦类,不能读.但是可以提⾼效率,特有功能:设置⾏号,获取⾏号
//设置⾏号,默认从0开始,从1开始打印
lineNumberReader.setLineNumber(10);
String data = null;
while ((data = lineNumberReader.readLine()) != null) {
System.out.print(lineNumberReader.getLineNumber());//获取⾏号
System.out.print(data);
System.out.println();
}
装饰设计模式
- 设计模式简介
设计模式, 前⼈总结出来的对⼀些常⻅问题的解决⽅案,后⼈直接拿来使⽤.
常⽤的设计模式:单例,⼯⼚,代理,适配器,装饰,模板,观察者等,⼀共有23种
-
装饰设计模式
基于已经实现的功能,提供增强的功能.
-
装饰设计模式特点
装饰设计模式的由来就来⾃于对缓冲流的实现.
从缓冲流的⻆度讲解
1.使流原来的继承体更加的简单
2.提⾼了效率
3.由于是在原有的基础上提⾼增强的功能,所以他还要属于原来的体系
- 如果⾃⼰设计装饰设计模式,怎么处理?
1.原来的类 Test—Reader
2.装饰类 BTest----MyBufferedReader
步骤:
1.让BTest 继承⾃Test
2.在BTest内有⼀个Test类型的成员变量
3.通过BTest内⼀个带参数的构造⽅法接收外部传⼊的⼀个Test类型的对象,交给内部的Test的属性
4.在实现功能的时候,调⽤传⼊的Test类型的对象实现原有的功能,⾃⼰实现增强的功能.
Scanner类
这个类, 并不是⼀个IO流。 是⼀个扫描器, 这个类最主要的作⽤, 是从⼀个⽂件中或者从⼀个流中浏览数据。 在这个类中封装了若⼲个⽅法, ⽅便了数据的读取。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5SdPR8p-1628926418302)(E:\qianfeng\2102\SZBigData2102资料\markdown\17IO流.assets\image-20210806200427664.png)]
这⾥nextLine和BufferedReader中的readLine都可以读取⼀⾏数据。 但是区别在于: 结束条件不同。
- BufferedReader: 如果读取到的数据是null, 说明没有下⼀⾏数据了。
- Scanner: 如果没有下⼀⾏了,再去读取,会出现异常。 所以, 此时的结束条件是 hasNextLine() 为false。
标准输⼊输出流
- 标准输⼊流: System.in : “标准”输⼊流。此流已打开并准备提供输⼊数据。通常,此流对应于键盘输⼊或者由主机环境或⽤户指定的另⼀个输⼊源。
输⼊源:可以发送数据到内存的设备
输出源:可以接收内存的数据的设备
1.当前的流已经打开并关联了输⼊源–键盘
2.如果不想让键盘充当输⼊源,可以通过setIn进⾏更换
3.是⼀个字节流
- 标准输出流: System.out : 标准”输出流。此流已打开并准备接受输出数据。通常,此流对应于显示器输出或者由主机环境或⽤户指定的另⼀个输出⽬标。
byte[] array = new byte[128];
int length = 0;
while ((length = bis.read(array)) != -1) {
String str = new String(array, 0, length);
System.out.println(str);
}
转换流
在进⾏⽂件读取的时候, 如果项⽬采⽤的字符集和⽂件的字符集不同,会出现乱码的情况。
输⼊流
char[] array = new char[128];
int length = 0;
while ((length = reader.read(array)) != -1) {
System.out.println(new String(array, 0,length));
}
输出流
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("file\\day26\\dst",true), "GBK")) {
writer.write("hello world");
writer.write("你好,世界");
}
打印流
分类
除了拥有输出流的特点之外,还有打印的功能.
- 字节打印流: PrintStream
- 字符打印流: PrintWriter
字节打印流
字节打印流⽀持的设备:
- 1.File类型的⽂件
- 2.字符串类型的⽂件
- 3.字节输出流
字符打印流
-
字符打印流⽀持的设备:
- 1.File类型的⽂件
- 2.字符串类型的⽂件
- 3.字节输出流
- 4.字符写出流
-
注意点:
⽅法:public PrintWriter(Writer out, boolean autoFlush)
autoFlush - boolean 变量;如果为 true,则 println、printf 或 format ⽅法将⾃动刷新输出缓冲区但是执⾏print⽅式时需要⼿动刷新
编码问题
-
常⽤字符集
- 中国的字符集:GBK/GB2312
- 欧洲的:ISO8859-1
- 通⽤的:UTF-8
- 美国的:ASCII
-
对中⽂的处理
-
⼀个汉字:
GBK:2个字节
ISO8859-1:1个字节
utf-8:3个字节
unicode:2个字节(java内部编码)
-
说明:GBK,UTF-8是⽀持中⽂的,ISO8859-1不⽀持中⽂
-
-
编码:将字符串转化成byte序列的过程
-
解码:是将byte序列转成字符串的过程
-
编码错误:乱码:在执⾏读与写的时候,由于使⽤的字符集不同,造成了编码的错误.
解决办法:再编码再解码
- 常⽤编码解码⽅法
编码:
byte[] getBytes() //对于中⽂ 默认的格式
使⽤平台的默认字符集将此 String 编码为 byte 序列,
并将结果存储到⼀个新的 byte 数组中。byte[] getBytes(Charset charset)
使⽤给定的 charset 将此 String 编码到 byte 序列,并将结果存储到新的byte 数组。解码:
String(byte[] bytes) //对于中⽂ 默认是格式
通过使⽤平台的默认字符集解码指定的 byte 数组,构造⼀个新的 String。String(byte[] bytes, Charset charset)
通过使⽤指定的 charset 解码指定的 byte 数组,构造⼀个新的 String。
中⽂乱码处理
-
注意点:乱码解决的办法是再编码再解码
但是如果是编码出错了,⽆法解决.
如果是解码出错了,可以利⽤再编码再解码
-
使⽤常⽤字符集GBK,utf8,ISO8859-1,进⾏编码解码出现乱码的情况分析
/* 编码 解码 结果
GBK utf8 不可以(GBK2个字节,utf83个字节)
GBK ISO8859-1 可以
utf8 GBK 有时可以
utf8 ISO8859-1 可以
ISO8859-1 GBK 不可以(编码就出错了)
ISO8859-1 utf8 不可以(编码就出错了)
*/
- 总结:只有使⽤GBK或utf8进⾏编码,使⽤ISO8859-1进⾏解码,才能使⽤再编码再解码解决乱码问题
序列化流
-
将短期存储的数据实现⻓期存储,这个过程对应的流就是序列化流
-
数据的存储分成两类:
- 1.短期存储:存放在内存中,随着程序的关闭⽽释放—对象,集合,变量,数组
- 2.⻓期存储:存储在磁盘中,即使程序关闭了,数据仍然存在------⽂件
-
序列化:将数据从内存放⼊磁盘,可以实现数据的⻓久保存
-
反序列化:将数据从磁盘放回内存
注意事项
- ObjectInputStream、 ObjectOutputStream, 主要是⽤来做对象的序列化和反序列化的。
- 序列化、 反序列化, 是对象的持久化存储的⼀种常⽤⼿段。
- 所有的要序列化到本地的类的对象, 类必须实现 java.io.Serilizable 接⼝。
实现了Serializable接⼝的类可以达到的⽬的:
1.可以进⾏序列化
2.进⾏序列化的类的元素都必须⽀持序列化
3.可序列化类的所有⼦类型本身都是可序列化的。
4.接⼝本身没有⽅法或字段,只是⽤来表示可序列化的语义
- 如果需要序列化多个⽂件到本地, 尽量不要序列化到⼀个⽂件中。 如果需要序列化多个⽂件到本地, 通常采⽤的⽅式, 是存集合。 将多个对象存⼊⼀个集合中, 将这个集合序列化到本地。
注意点:
- ClassNotFoundException:当前的类没有找到
分析:将Person对象进⾏序列化之后,将Person类删除,再进⾏反序列化的时候出现了异常
原因:反序列化在执⾏的时候依赖字节码⽂件,当类没有了,字节码⽂件⽆法创建,反序列化失败- java.io.InvalidClassException ⽆效的类
出现的原因:没有声明⾃⼰的serialVersionUID,⽽使⽤系统的.在进⾏反序列化的时候,类被改动了,系统认为现在的类已经不是原来的类了(在使⽤系统的id进⾏识别的时候,重写给Person设置了id),认为此类⽆效- 使⽤系统的serialVersionUID与⾃定义的ID的区别?
使⽤系统的,序列化和反序列化,id不能⼿动设置,使⽤的是编译器默认⽣成的,⼀旦类发⽣了改动,id会重新赋值。
使⽤⾃定义的,序列化和反序列化,id不会发⽣改变,所以当反序列化的时候,即使对
Person类进⾏了⼀些改动,也能继续反序列化
- 合理使⽤序列化流和反序列化流,要与输⼊流与输出流配合使⽤
- 进⾏序列化的类⼀定要实现Serializable接⼝,只要实现了接⼝就可以序列化.包括集合,包装类等
- 进⾏序列化的类要保证当前类与内部的类都要实现Serializable接⼝
Properties
Properties也不是⼀个IO流, 是⼀个集合。 是Hashtable的⼦类。
使⽤Properties主要是为了描述程序中的属性列表⽂件。 有时候, 我们会将⼀些⽐较简单的项⽬的配置信息, 以 .properties 格式的⽂件进⾏存储。 可以使⽤Properties对象读写 .properties ⽂件。
因为存储的是属性,属性本来就是以键值对的⽅式存储.这⾥的键和值都必须是字符串.所以不需要考虑泛型。
- Properties作⽤
1.是HashTable的⼦类,所以也是键值对形式,保存,操作更容易
2.默认键值都是字符串,所以不需要再考虑泛型
3.提供了⼀批好⽤的⽅法,⽅便使⽤(load(),store(),list()等)
实际应⽤
加载⼀个 .properties ⽂件中的数据,后缀名也可以不是properties.但是⼀般我们写成properties,⽅便使⽤.
⽂件内容
注意:
1.这⾥是修改过⼀次的⽂件
2.Propertes⽂件中对应的也应该是键值对
3.键和值之间可以是=或者空格或者冒号
4.默认每⾏只写⼀个键值对
#\u6539\u53D8\u4E86\u51B0\u51B0\u7684\u503C
#Fri Oct 16 17:52:51 IRKT 2020
name=我们
wss|06=
zhaoliu,05=
zhangsan=02
lisi=03
bingbing=buok
zhaoliu=04
- 读取数据/遍历数据/修改数据/写回数据
//创建对象
Properties properties = new Properties();
//利⽤load⽅法将内容从磁盘读⼊Properties对象(给一个字符读入流作参数)
properties.load(new FileReader("D:\\ideaProgram\\BigDataBK2001N06\\a.txt"));
//获取所有键的名字
Set<String> set = properties.stringPropertyNames();
//遍历得到值
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.println("key:"+key+"value:"+properties.getProperty(key));
}
//改变对象的内容
properties.setProperty("bingbing","buok");
//使⽤store⽅法将数据写⼊磁盘(第二个参数为描述信息)
properties.store(new FileWriter("D:\\ideaProgram\\BigDataBK2001N06\\a.txt"),"改变了冰冰的值");
//写⼊控制台---通过list(给输出流做参数)
properties.list(System.out);
s = new Properties();
//利⽤load⽅法将内容从磁盘读⼊Properties对象(给一个字符读入流作参数)
properties.load(new FileReader("D:\\ideaProgram\\BigDataBK2001N06\\a.txt"));
//获取所有键的名字
Set<String> set = properties.stringPropertyNames();
//遍历得到值
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.println("key:"+key+"value:"+properties.getProperty(key));
}
//改变对象的内容
properties.setProperty("bingbing","buok");
//使⽤store⽅法将数据写⼊磁盘(第二个参数为描述信息)
properties.store(new FileWriter("D:\\ideaProgram\\BigDataBK2001N06\\a.txt"),"改变了冰冰的值");
//写⼊控制台---通过list(给输出流做参数)
properties.list(System.out);