前面几篇介绍了java IO的基础部分,现在进入核心内容的学习,如File类、动态读取和序列化等,如下。

一、File类

1、概述

是 java.io 包中唯一代表磁盘文件本身的对象(可以通过 File 类操作文件和目录),定义了一些操作文件的方法,如新建、删除、重命名文件和目录等。

File 类不能访问文件内容本身(访问要使用输入/输出流)

2、构造方法(重载–3个)

构造方法用于创建对象(实例化)

1
2
3
File(String path):如果 path 是实际存在的路径,则该 File 对象表示的是目录;如果 path 是文件名,则该 File 对象表示的是文件。
File(String path, String name):path 是路径名(目录),name 是文件名。
File(File dir, String name):dir 是路径对象(目录),name 是文件名。

3、File类常用方法

不用死记,可通过查看API文档来获取文件属性

1)常用汇总

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
canRead()    测试程序是否能从指定的文件中读取
canWrite() 测试程序是否能写当前文件
delete() 删除指定的文件
exists() 测试当前 File 是否存在
getAbsolutePath() 返回文件的绝对路径名
getName() 返回对象的文件名或路径名(如果是路径,则返回最后一级子路径名)
getParent() 返回当前 File 对象所对应目录(最后一级子目录)的父目录名
isAbsolute() 测试文件是否为一个绝对路径名。
isDirectory() 测试文件是否为一个路径(目录)
isFile() 测试文件是否为一个“普通”文件
lastModified() 返回文件最后修改的时间
length() 返回文件长度
list() 返回指定的路径文件列表
list(FilenameFilter) 返回指定的目录中满足指定过滤器的文件列表
mkdir() 创建一个目录,它的路径名由当前 File 对象指定
mkdirs() 创建一个目录(多级目录),它的路径名由当前 File 对象指定
renameTo(File) 将文件更名为给定参数 File 指定的路径名

2)说明
创建&&删除文件

1
2
3
创建:createNewFile()
删除:delete()
注:在创建或删除前都先用exists()方法判断文件是否存在

创建&&删除目录

1
2
3
4
创建:mkdir() 
注:创建多级目录(即目录中还包含目录),用mkdirs
删除:delete()
注:在创建或删除前都先用exists()方法判断目录是否存在

遍历目录(list方法–重载)

1
2
String[] list():返回由 File 对象表示目录中所有文件和子目录名称组成的字符串数组,如果调用的 File 对象不是目录,则返回 null。(list() 方法返回的数组中仅包含文件名称,而不包含路径)
String[] list(FilenameFilter filter):返回数组中仅包含符合 filter 过滤器的文件和目录,如果 filter 为 null,则接受所有名称。

带过滤器参数的 list() 方法(当希望只列出目录下的某些文件,就需要调用)

1
2
首先创建文件过滤器,该过滤器必须实现 java.io.FilenameFilter 接口。
在 accept() 方法中指定允许的文件类型。

4、示例

1)获取文件属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.File; // 导包
import java.util.Date;
//获取文件属性
public class FlieDemo {
public static void main(String[] args){
String path="D:\\Ultimate JavaCode\\src\\test6"; // 文件路径
File f=new File(path,"date1_24.txt"); // File(String path, String name)构造方法实例化File对象
System.out.println("D:\\Ultimate JavaCode\\src\\test6\\date1_24.txt文件信息如下:");
System.out.println("============================================"); //以下通过实例化对象f调用Flie类常用方法
System.out.println("文件名称:"+f.getName());
System.out.println("文件路径:"+f.getPath());
System.out.println("绝对路径:"+f.getAbsoluteFile());
System.out.println("文件长度:"+f.length()+"字节");
System.out.println("文件或者目录:"+(f.isFile()?"是文件":"不是文件"));//三元表达式(boolean x?x=true时执行该部分:x=false时执行该部分)
System.out.println("文件或者目录:" + (f.isDirectory() ? "是目录" : "不是目录"));
System.out.println("是否可读:"+(f.canRead()?"可读取":"不可读取"));
System.out.println("是否可写:"+(f.canWrite()?"可写入":"不可写入"));
System.out.println("是否隐藏:"+(f.isHidden()?"是隐藏文件":"不是隐藏文件"));
System.out.println("最后修改日期:"+new Date(f.lastModified())); // 实例化日期对象
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
D:\Ultimate JavaCode\src\test6\date1_24.txt文件信息如下:
============================================
文件名称:date1_24.txt
文件路径:D:\Ultimate JavaCode\src\test6\date1_24.txt
绝对路径:D:\Ultimate JavaCode\src\test6\date1_24.txt
文件长度:72字节
文件或者目录:是文件
文件或者目录:不是目录
是否可读:可读取
是否可写:可写入
是否隐藏:不是隐藏文件
最后修改日期:Tue Jan 24 11:33:48 CST 2023

2)在 D:\Ultimate JavaCode\src\test6下创建一个 date1_24_1.txt 文件,程序启动时会检测该文件是否存在,如果不存在则创建;如果存在则删除再创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.File; //导包
import java.io.IOException;

public class FileDemo1 {
public static void main(String[] args) throws IOException { // 抛出IO异常
String path="D:\\Ultimate JavaCode\\src\\test6"; // 文件路径
File f1=new File(path,"date1_24_1.txt"); // File(String path, String name)构造方法实例化File对象
if(f1.exists()){ // 判断文件是否存在
f1.delete(); // 存在先删除
}
f1.createNewFile(); // 再创建
}
}

优化:不同操作系统路径的分隔符是不同的。Windows 中用反斜杠\表示目录的分隔符,Linux 则用正斜杠/,

在操作文件时一定要使用 File.separator 表示分隔符(使用符合本地操作系统要求的分隔符),养成良好的开发习惯。

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.File; //导包
import java.io.IOException;

public class FileDemo1 {
public static void main(String[] args) throws IOException { // 抛出IO异常
String path="D:\\Ultimate JavaCode\\src\\test6"+File.separator+"date1_24_1.txt"; // 文件路径,用 File.separator
File f1=new File(path); // File(String path)构造方法实例化File对象
if(f1.exists()){ // 判断文件是否存在
f1.delete(); // 存在先删除
}
f1.createNewFile(); // 再创建
}
}

3)编写程序判断D盘根目录下是否存在Date1_24目录,若存在则先删除再创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.io.File; //导包

public class FileDemo2 {
public static void main(String[] args){
String path="D:/Date1_24/"; // 指定目录位置
File f=new File(path); // File(String path)构造方法创建File对象
if (f.exists()){
f.delete();
}
f.mkdir(); // 创建目录
}
}
// 结果:会发现D盘下多了一个Date1_24的空文件夹

4)使用 list()方法遍历D盘根目录下的所有文件和目录,并显示文件或目录名称、类型及大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.File; //导包

public class FileDemo3 {
public static void main(String[] args){
File f=new File("D:/"); // File(String path)构造方法创建File对象
System.out.println("文件名称\t\t文件类型\t\t文件大小");
System.out.println("===================================================");
String fileList[]=f.list(); // 调用不带参数的list()方法,返回一个数组
for(int i=0;i<fileList.length;i++){ // 遍历返回的字符数组
System.out.print(fileList[i]+"\t\t");
//三元表达式对文件类型归类(文件或文件夹)
System.out.print((new File("D:/",fileList[i])).isFile()?"文件"+"\t\t":"文件夹"+"\t\t");
System.out.println((new File("D:/",fileList[i])).length()+"字节");
//由于 list() 方法返回的字符数组中仅包含文件名称,要获取文件类型和大小,必须先转换为 File 对象再调用其方法。
}
}
}

运行结果(展示部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
文件名称        文件类型        文件大小
===================================================
$RECYCLE.BIN 文件夹 0字节
360downloads 文件夹 4096字节
360RecycleBin 文件夹 4096字节
360安全浏览器下载 文件夹 0字节
Anaconda 文件夹 24576字节
Android 文件夹 0字节
apache-tomcat-8.5.83 文件夹 4096字节
BaiduNetdiskDownload 文件夹 0字节
centbrowser_4.3.9.248 文件夹 0字节
Date1_24 文件夹 0字节
Dev-Cpp 文件夹 4096字节
HBuilderX.3.3.11.20220209 文件夹 0字节
ideaIU-2021.3.3.win 文件夹 4096字节
Maven 文件夹 0字节
Microsoft Visio 文件夹 0字节
MSOCache 文件夹 0字节
mydrivers 文件夹 0字节
mysql 文件夹 4096字节
Node.js 文件夹 4096字节

注:由于 list() 方法返回的字符数组中仅包含文件名称,要获取文件类型和大小,必须先转换为 File 对象再调用其方法。

5)带过滤器参数的 list() 方法示例(自行实现)

1
2
3
4
5
6
7
8
public class ImageFilter implements FilenameFilter {
// 实现 FilenameFilter 接口
@Override
public boolean accept(File dir, String name) {
// 指定允许的文件类型
return name.endsWith(".sys") || name.endsWith(".txt") || name.endsWith(".bak");
}
}

其他代码与4)中相同。

二、动态读取文件内容

所谓动态读取,就是从文件的任意位置开始访问文件,而不是必须从文件开始位置读取到文件末尾。

1、RandomAccessFile 类

1.1 概述

是 Java 输入/输出流体系中功能最丰富的文件内容访问类,提供了众多方法访问文件内容,既可以读取文件内容,也可以向文件输出数据。

RandomAccessFile 可以从任意位置访问文件,在只需要访问文件部分内容的情况下,可以使用 RandonAccessFile 类。

RandomAccessFile 对象包含了一个记录指针,用以标识当前读写处的位置。当程序新创建一个 RandomAccessFile 对象时,指针位于文件头(也就是 0 处),当读/写了 n 个字节后,文件记录指针将会向后移动 n 个字节。除此之外,RandonAccessFile 可以自由移动该记录指针,既可以向前移动,也可以向后移动。

1.2 RandomAccessFile 类的构造方法(重载)

构造方法用来创建对象(实例化)

1
2
RandomAccessFile(File file, String mode):访问参数 file 指定的文件,访问形式由参数 mode 指定,mode 参数有两个常用的可选值 r 和 rw( r 表示只读,rw 表示读写)
RandomAccessFile(String name, String mode):访问参数 name 指定的文件,mode含义同上。

1.3 RandomAccessFile 类的常用方法

会用一些常用的就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
boolean readBoolean()    从文件中读取一个 boolean
byte readByte() 从文件中读取一个带符号位的字节
char readChar() 从文件中读取一个字符
int readlnt() 从文件中读取一个带符号位的整数
long readLong() 从文件中读取一个带符号位的 long
String readLine() 从文件中读取下一行文本
void seek(long pos) 指定从文件起始位置开始的指针偏移量
void writeBoolean(boolean v) 以字节的形式向文件中写入一个 boolean
void writeByte(int v) 以单字节的形式向文件中写入一个 byte
void writeChar(int v) 以双字节的形式向文件中写入一个 char
void writelnt(int v)4字节的形式向文件中写入一个整数
writeLong(long v) 以8字节的形式向文件中写入一个 long
void writeBytes(String s) 以字节序列的形式向文件中写入一个字符串
void skipBytes(int n) 以当前文件指针位置为起始点,跳过 n 字节

1.4 示例

使用 RandomAccessFileDemo 类创建一个 weather.txt 文件,然后写入一个长中文字符串,再从第 6 个字节开始读取并输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.io.File; // 导包
import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException { // 抛出异常
// 先创建一个空的“weather.txt“文件
File file=new File("D:\\Ultimate JavaCode\\src\\test6\\weather.txt");//指定文件路径
if(file.exists()){ // 判断文件是否存在
file.delete();
file.createNewFile(); // 创建文件
}
file.createNewFile();

// 再创建RandomAccessFile对象并写入字符串
RandomAccessFile rf=new RandomAccessFile(file,"rw"); // rw表示读写
String str1="晴天,阴天,多云,小雨,大风,中雨,小雪,雷阵雨"; // 要写入的字符串
// 编码转换,防止乱码
String str2=new String(str1.getBytes("GBK"),"ISO_8859_1");
rf.writeBytes(str2); // 写入文件

// 最后从第 6 个字节开始读取并输出
System.out.println("当前文件指针的位置:"+rf.getFilePointer());
rf.seek(6); // 设置指针偏移量为6,即移动6个字节
System.out.println("从文件头跳过6个字节后,文件的内容如下:");
// 定义一个长度为2的byte数组,进行内容的循环读取
byte[] b=new byte[2];
int len=0;
while ((len=rf.read(b,0,2))!=-1){
System.out.print(new String(b,0,len));
}
rf.close(); // 关闭文件,释放资源
}
}

三、转换流

1)概述
用于字节流和字符流之间的转换,有两种

InputStreamReader
将字节的输入流按指定字符集转换为字符的输入流。即将InputStream转换为Reader(编码:字节—->字符)

OutputStreamWriter
将字符输出流按指定字符集转换为字节输出流。即将Writer转换为OutputStream(解码:字符—->字节)

2)说明

当文件中含有中文英文数字时,使用字节流将文件内容在内存中显示,英文和数字显示正常,而中文却却显示乱码。这时可以使用转换流将其转化为字符流显示在内存中。

3)何时使用

1
2
3
4
5
6
1.当字节和字符之间有转换动作时;
2.流操作的数据需要编码或解码时;
- 编码:字节/字节数组---->字符/字符数组
- 解码:字符/字符数组---->字节/字节数组

转换流作用:提供字节流与字符流之间的转换,通常使用转换流来处理文件乱码问题,实现编码和解码的功能。

四、序列化

1、对象序列化流(ObjectOutputStream)

1.1 概述

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来完成对象的持久存储。 如果流是网络套接字流,则可以在另一个主机或另一个进程中重新构建对象。

1.2 构造方法

1
objectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream

1.3 序列化对象的方法

1
void writeObject(Object obj):将指定的对象写入ObjectOutputStream

注:

一个对象要想被序列化,该对象所属的类必须实现Serializable接口

Serializable是一个标记接口,实现该接口,不需要重写任何方法

1.4 示例

1)创建一个学生类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.Serializable;

public class Student implements Serializable { // 创建一个学生类
private String name; // 封装成员变量
private int age;

public Student() { // 无参构造方法
}
public Student(String name, int age) { // 带全部参数的构造方法
this.name = name;
this.age = age;
}
// 提供get()和set()方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

2)使用序列化流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
// NotSerializableException:当实例需要具有Serializable接口时抛出。 序列化运行时或实例的类可以抛出此异常。
// 一个对象要想被序列化,该对象所属的类必须实现Serializable接口,否则会报NotSerializableException错误
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建序列化流对象
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:\\Ultimate JavaCode\\src\\test8\\oos.txt"));
// 创建学生对象
Student s=new Student("林青霞",20);
oos.writeObject(s); // 调用writeObject()方法将对象写入序列化流
// 释放资源
oos.close();
}
}
// result:打开文件发现存在乱码问题,因此需要使用反序列化流转换才能读懂

2、对象反序列化流

2.1 说明

1
2
3
4
5
6
7
8
9
10
ObjectInputStream
- ObjectInputStream反序列化前先使用ObjectOutputStream编写的原始数据和对象

构造方法
ObjectInputStream(InputStream in)
- 创建从指定的InputStream读取的ObjectInputStream

反序列化对象的方法
Object readObject()
- 从ObjectInputStream读取一个对象

2.2 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建反序列化流对象
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:\\Ultimate JavaCode\\src\\test8\\oos.txt"));
// 调用readObject()方法将对象读入反序列化流
Object obj = ois.readObject();
Student s=(Student) obj; // 大转小强转,向下转型
System.out.println(s.getName()+","+s.getAge());
//释放资源
ois.close();
}
}
// result(经过对象反序列化后输出不会再乱码)
// 林青霞,20

3、java序列化小结

  • 将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,即对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。整个过程都是 Java 虚拟机(JVM)独立的,说明在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象
  • 类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含反序列化和序列化对象的方法。

五、Properties类

1、概述

Properties类是一个Map体系的集合类,父类是Hashtable。Properties可以保存到流中或从流中加载。

2、示例

Properties作为Map集合的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.Properties;
import java.util.Set;

public class PropertiesDemo {
public static void main(String[] args) {
// 创建Properties集合对象
Properties prop=new Properties();
// 调用父类Map集合的put()方法添加集合键值元素
prop.put("101","zhangsan");
prop.put("102","lishi");
prop.put("103","wangwu");
// 遍历集合
Set<Object> keySet=prop.keySet(); // 得到所有keySet的集合
for(Object key:keySet){
Object value=prop.get(key); // 获取所有keySet对应的值,注意Map集合的遍历方式,键值遍历
System.out.println(key+","+value);
}
}
}

运行结果

1
2
3
103,wangwu
102,lishi
101,zhangsan

3、Properties作为集合的特有方法

3.1 说明

1
2
3
Object setProperty(String key,String value):数组集合的键和值,都是String类型,底层调用Hashtable方法 put(设置键值对,可代替父类的put()方法)
String getProperty(String key):使用此属性列表中指定的键搜索属性(根据键key获取值value)
Set<String> stringPropertyName():从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串(获取得到所有键的集合)

3.2 示例

Properties作为集合的特有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Properties;
import java.util.Set;
// Properties作为集合的特有方法
public class PropertiesDemo1 {
public static void main(String[] args) {
// 创建集合Properties对象
Properties prop=new Properties();
// prop.put("101","zhangsan"); // 调用父类的put方法添加键值对元素
// 使用Properties的特有方法setProperty()方法添加键值对元素,键值都为String类型
prop.setProperty("101","zhangsan");
prop.setProperty("102","lishi");
// 使用Properties的特有方法getProperty()方法获取值
// System.out.println(prop.getProperty("101")); // lishi
// System.out.println(prop.getProperty("1001")); // null
// 使用Properties的特有方法stringPropertyName()方法获取所有键的集合,键值都为String类型(关键,重要一步)
Set<String> names = prop.stringPropertyNames();
// 增强for循环遍历集合
for(String key:names){
// System.out.println(key); // 遍历键
String value = prop.getProperty(key); // getProperty()方法获取键对应的值
System.out.println(key+","+value); // 输出键和值信息
}
}
}

运行结果

1
2
102,lishi
101,zhangsan

4、Properties和IO流结合的方法

4.1 方法说明

1
2
3
void load(InputStream inStream):从输入字节流读取属性列表(键和元素对)
void load(Reader reader):从输入字符流读取属性列表(键和元素对)
void store(OutputStream out,String comments):将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输入字符串(即将Properties集合数据保存到文件中)

4.2 示例

Properties和IO流结合的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class PropertiesDemo2 {
public static void main(String[] args) throws IOException{
// 将Properties集合中的数据保存到文件中
myStore(); // 演示第二个方法时先将该方法注释,因为文件已经创建成功,没有必要再次创建,浪费资源
// 将文件中的数据加载到Properties集合中
myLoad();
}
private static void myLoad() throws IOException{
// 创建Properties对象
Properties prop=new Properties();
// void load(Reader reader):从输入字符流读取属性列表(键和元素对)
// Reader为抽象类,创建其实现类即子类FileReader对象作为参数
FileReader fr=new FileReader("D:\\Ultimate JavaCode\\src\\test8\\fw.txt");
// 调用load()方法将文件中的数据加载到Properties集合中
prop.load(fr);
// 释放资源
fr.close();
// 打印集合,查看是否加载成功
System.out.println(prop);
}
private static void myStore() throws IOException {
// 创建Properties对象
Properties prop=new Properties();
// 使用特有的setProperty()方法设置字符串类型的键值
prop.setProperty("101","zhangsan");
prop.setProperty("102","lishi");
prop.setProperty("103","wangwu");
// void store(Writer writer,String comments)
// Writer为抽象类,创建其实现类即子类FileWriter对象作为第一个参数
FileWriter fw=new FileWriter("D:\\Ultimate JavaCode\\src\\test8\\fw.txt");
// 调用store()方法将Properties集合中的数据保存到文件中
prop.store(fw,null); // 第二个参数comments为描述信息,不描述可设置为null
// 释放资源
fw.close();
}
}

运行结果

1
{101=zhangsan, 102=lishi, 103=wangwu}