1. java.io.File
- 分隔符
- 文件名分隔符:
File.pathSeparator
- 路径分隔符:
File.separator
;window系统下\\
为分隔符
- 文件名分隔符:
- 路径
- 绝对路径:以盘符开始的完整路径
- 相对路径:相对于当前项目的根目录的简化路径
- 构造方法
File(File parent, String child)
File(String parent, String parent)
File(String pathname)
- 获取方法
public String getAbsolutePath()
返回文件或文件夹的绝对路径public String getPath()
返回的是输入给构造方法字符串,相当于toString()
方法public String getName()
返回的是最后一个路径分隔符后的内容(文件名或文件夹名)public long length()
返回文件大小;如果File对象是一个文件夹,或文件不存在,length方法会返回0
- 判断方法
public boolean exists()
判断构造方法中传入的路径是否存在pubic boolean isDirectory()
只有该路径存在且以文件夹结尾才返回true
,否则false
public boolean isFile()
只有该路径存在且以文件结尾才返回true
,否则false
- 创建删除方法
public boolean createNewFile()
- 传入构造方法中的路径不存在,调用该方法会抛出异常
- 文件存在,则不创建文件,返回
false
- 文件不存在,则创建文件,返回
true
public boolean mkdir()
成功创建返回true
,未创建返回false
- 创建单级文件夹,创建多级文件夹不会报错,但无法创建
- 文件夹路径已存在,则不能创建
public boolean mkdirs()
文件夹不存在,则可成功创建返回true
;否则,未创建返回false
public boolean delete()
删除文件和文件夹(构造方法中最后一个路径分隔符后的内容)- 文件夹中有内容(文件、子文件夹),文件夹将不会被删除,返回false
- 直接在硬盘上删除,不会走回收站,删除需谨慎
- 遍历方法
下面两个方法用来遍历文件夹,如果给出的路径不是文件夹,或者路径不存在,会抛出空指针异常public String[] list()
返回String数组,来表示所有子文件夹和子文件(只是下面一级,不会深入子文件夹)public File[] listFiles()
返回File数组,来表示所有子文件夹和子文件listFiles
有两个重载方法,返回满足过滤器筛选条件的文件和文件夹public File[] listFile(FileFilter filter)
public File[] listFile(FilenameFilter filter)
两个接口都是函数式接口,仅有一个抽象方法 boolean accept(File pathname)
和boolean accept(File dir, String name)
;需要自己重写抽象方法,实现接口;可以用匿名内部类来实现
2. 遍历文件
- 递归使用前提
- 方法的主体不变,但是每次调用的方法不同
- 要有条件限定,保证递归能够停下来,否则将会出现StackOverflow异常(被调用的方法时会被压入栈,执行完毕之后才会从栈中释放,所以会导致栈内存溢出)
- 构造方法不能用递归!会导致栈中有无数个对象
递归访问所有子文件/文件夹```java public static void getAllFiles(File file) { if(! file.exists()) { // 判断路径是否存在
System.out.println("the input path doesn't exist!");
} else if (file.isDirectory()){ // 判断是否为文件夹
File[] fileList = file.listFiles(); if (fileList != null) { // 判断是否是空文件夹 for (File f : fileList) { String name = f.getAbsolutePath(); if(name.toLowerCase().endsWith(".java")){ System.out.println(name); } getAllFiles(f); } }
} } ```
使用FileFilter过滤```java public static void getAllFiles(File file) { if(! file.exists()) { // 判断路径是否存在
System.out.println("the input path doesn't exist!");
} else if (file.isDirectory()){ // 判断是否为文件夹而不是文件
// 使用FileFilter(匿名内部类) File[] javaFileList = file.listFiles(new FileFilter(){ @Override public boolean accept(File pathname) { return pathname.isFile() && pathname.getName().endsWith(".java"); } // 筛选出.java文件 }); // 使用FilenameFilter(匿名内部类) File[] classFileList = file.listFiles(new FilenameFilter(){ @Override public boolean accept(File dir, String name) { return new File(dir, name).isFile() && name.endsWith(".class"); } // 筛选出.class文件 }); // 使用FileFilter(匿名内部类) File[] dirList = file.listFiles(new FileFilter(){ // 筛选出子文件夹 @Override public boolean accept(File pathname) { return pathname.isDirectory(); } }); // 使用FilenameFilter(Lambda表达式) File[] dirList = file.listFiles((dir, name) -> new File(dir, name).isDirectory()) if (javaFileList != null) { // 判断是否存在.java文件 for (File f : javaFileList) { System.out.println(f.getAbsolutePath()); } } if (classFileList != null) { // 判断是否存在.class文件 for (File f : classFileList) { System.out.println(f.getAbsolutePath()); } } if (dirList != null) { // 判断是否存在子文件夹 for (File f : dirList) { getAllFiles(f); } }
} } ```
3. 字节流与字符流
- IO流概念
- I:Input,输入,从磁盘中读取到内存
- O:Output,输出,从内存中写入到硬盘进行保存
- “流”分为字节流和字符流,1字符占2字节,1字节占8位
- 字节输入流:InputStream
- 字节输出流:OutputStream
- 字符输入流:Reader
- 字符输出流:Writer
- 字节数出流 java.io.FileOutputStream
- 构造方法:创建一个FileOutputStream对象(需要处理
FileNotFoundException
,直接throwIOException
),根据构造方法中传递的文件名创建文件,并将FileOutputStream对象指向创建好的文件FileOutputStream(String name)
覆盖写FileOutputStream(File file)
覆盖写FileOutputStream(String name, boolean append)
追加写FileOutputStream(File file, boolean append)
追加写
- 写数据的流程:
java program --> JVM --> Operation System --> OS writes data to the file
。将二进制数据的字节写入文件,用记事本打开文件之后,会自动查询ASCII码表或GBK码表,将字节数据转化为字符数据来展示。 - 写入方法
public void write(int b)
输入的字节是正数(0~127)查询ASCII码表,如果为负数查询GBK汉字码表public void write(byte[] b)
可以先用 String 对象的public byte[] getBytes()
将字符串转换为字节数组,再写入文件public void write(byte[] b, int off, int len)
写入字符数组的一个片段
public void close()
释放资源public void flush()
- 构造方法:创建一个FileOutputStream对象(需要处理
- 字节输入流 java.io.FileInputStream
- 构造方法:创建一个FileInputStream对象(throw
IOException
),根据构造方法中传递的文件名创建文件,并将FileInputStream对象指向创建好的文件 - 读取数据的流程:
java program --> JVM --> Operation System --> OS reads data from the file
public int read()
每次读取一个字节并返回字节对应的整形数据,读取到文件末尾返回`-1````java FileInputStream fis = new FileInputStream(file);
- 构造方法:创建一个FileInputStream对象(throw
int i = 0; // 需要一个变量来接受读取字节数据(int类型) while((i = fis.read())!= -1){ // 读取到-1时,停止读取 System.out.print((char)i); }
fis.close();
4. `public int read(byte[] b)` 读取一定数量的字节数据,存放到字节数组中```java
FileInputStream fis = new FileInputStream(file);
int len = (int) file.length(); // 获取文件的字节数
byte[] bytes = new byte[len]; // 构造存放数据的字节数组
int num = fis.read(bytes); // 将file中的前NUM个字节读入,并且返回读入的字节数量
for (byte b : bytes) { // 打印读入的字符
System.out.print((char)b);
}
System.out.println(new String(bytes)); // 将字节数组转化为字符串输出
1. 如果字节数组的维度大于文件中的字节数,多余的位置全部为0;此时返回有效读取的字节数目,而不是字节数组的维度
2. 如果没有读入有效的字节数据,返回-1
3. 字节数组起到缓存作用,可以每次读取多个字节
public void close()
- 使用字节输入输出流复制文件```java public static void main(String[] args) throws IOException { File fromFile = new File(“C:\Users\LAU Sen\Desktop\白色恐怖时期的台大校长傅斯年.pdf”); File toFile = new File(“C:\Users\LAU Sen\Desktop\(copy)白色恐怖时期的台大校长傅斯年.pdf”);
FileInputStream fis = new FileInputStream(fromFile); // 文件输入流,读取文件 FileOutputStream fos = new FileOutputStream(toFile); // 文件输出流,写入文件
int len = (int) fromFile.length(); // 文件占用字节数 byte[] bytes = new byte[len]; // 保存字节数据
int num = fis.read(bytes); // 读取字节数据 fos.write(bytes); // 写入字节数据
fis.close(); // 释放资源 fos.close(); // 释放资源 } ```
- 字符输入流 java.io.FileReader
- 使用字节流读取中文文件时,可能会出现无法显示文本的情况(GBK编码占用2个字节,UTF-8占用3个字节),因为无法正确地将字节转化为字符;所以直接读取字符就显得有意义了
- 其中的方法与字节输入流大同小异
- 构造方法
FileReader(String fileName)
FileReader(File file)
- 读取方法
int read()
返回的int是字节数据int read(cahr[] cbuf)
返回的int是读取的有效长度
void close()
- 构造方法
- 字符输出流 java.io.FileWriter
- 构造方法
FileWriter(String fileName)
FileWriter(File file)
FileWriter(String fileName, boolean append)
FileWriter(File file, boolean append)
- 写入方法
void write(int c)
写入一个字节数据对应的字符void write(char[] cbuf)
写入多个字符java char[] cbuf = new char[(int) file.length()]; fr1.read(cbuf);
上书写法是不合理的,因为file.length()
获得的是字节数目,而不是字符数量;字符数组是装不满的void write(char[] cbuf, int off, int len)
写入字符数组中的一部分void write(String str)
写入一个字符串void write(String str, int off, int len)
写入字符串中的一部分
void flush()
刷新流的缓冲void close()
- 字符输出流与字节数出流有所不同,FileWriter中的writer方法,将数据写入内存缓冲区。在此处,字符转化成字节。如果不调用flush方法,内存缓冲区中的字节数据将不会被刷新到文件中。当然,close方法也可以在关闭文件之前将缓冲区中的字节数据刷新到文件中。
- 构造方法
- 异常处理
java try{ 可能出现异常的代码块 } catch(异常类 变量名) { 异常处理的逻辑 } finally { 一定会执行的代码 释放资源 }
4. java.util.Properties
- 背景
- java.util.Map
有实现类 java.util.Hashtable ,已经甚少使用 - java.util.Hashtable
的子类 java.util.Properties作为唯一与IO流相关的属性集合,依然活跃 - Properties依然是双列集合,key和value都是字符串
- java.util.Map
方法
- 特有方法
Object setProperty(String key, String value)
相当于Map的put方法String getProperty(String key)
相当于Map 的get方法,通过key获取valueSet<String> stringPropertyNames()
返回属性列表中的所有键的集合;相当于Map的keySet方法
load
方法将硬盘中的文件(键值对)读取到集合中使用void load(Reader, reader)
处理字符输入流,可以处理中文数据void load(InputStream in)
处理字节输入流,不能读取含中文的数据```java public static void main(String[] args) throws IOException { File file = new File(“D:\xxxx.txt”);FileReader fr = new FileReader(file); FileInputStream fos = new FileInputStream(file);
prop.load(fr); // 载入数据
Set
set = prop.stringPropertyNames(); // 获取key的集合 for (String s : set) { System.out.println(“key=”+ s + “; value=” + prop.getProperty(s)); // 根据key来获取value } } ``` store
方法将集合中的临时数据,持久化写到硬盘中存储void store(Writer writer, String comments)
处理字符输出流,可以写中文void store(OutputStream out, String comments)
处理字节输出流,不可以写中文;默认使用unicode,因此comments也不能写中文```java public static void main(String[] args) throws IOException { File file = new File(“D:\xxxx.txt”);FileWriter fw = new FileWriter(file); Properties prop = new Properties(); prop.setProperty(“张三”, “123”); // 向Properties对象中添加键值对 prop.setProperty(“李四”, “456”); prop.setProperty(“王五”, “789”);
prop.store(fw, “save data”); } ```
- 特有方法
其他
- 键值对存储的文件中,键与值默认的连接符号可以用
=
、空格
、:
空格 - 键值对存储的文件中,可以用#进行注释,备注是的键值对将不会被读取
- 键值对存储的文件中,键值对默认都是字符串,不必再加引号
- 键值对存储的文件中,键与值默认的连接符号可以用
5. 缓冲流
- 缓冲流,也叫高效流。与四个基本的流一一对应:
- 字节输入缓冲流:
BufferedInputStream(InputStream in, int size)
- 字节输出缓冲流:
BufferedOutputStream(OutputStream out, int size)
- 字符输入缓冲流:
BufferedReader(Reader in, int size)
- 还有
String readLine()
读取一行数据,返回一个字符串 - 其余方法与
FileReader
相同
- 还有
- 字符输出缓冲流:
BufferedWriter(Writer out, int size)
void newLine()
写入一个行分隔符- 其余方法与
FileWriter
相同
- 字节输入缓冲流:
输出缓冲流可以增加缓冲区,提高写入效率;可以传递参数,指定缓冲流内部缓冲区的大小```java public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream(“\xxx.txt”, true); BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write(“这是一个中文字符串\n”.getBytes()); // 调用BufferedOutputStream对象的write方法;传入字节数组 bos.flush(); bos.close();
FileWriter fw = new FileWriter(“\yyy.txt”, true); BufferedWriter bw = new BufferedWriter(fw);
bw.write(“This is a English string”.toCharArray()); // 调用BufferedWriter对象的write方法; 传入字符数组 bw.newLine(); bw.flush(); bw.close(); // 只需要关闭缓冲流,而不需要关闭原有的字节流和字符流 } ```
输入缓冲流可以增加缓冲区,提高读取效率;也可以指定缓冲流内部缓冲区的大小
使用Arrays.copyOfRange(cbuf, 0, len)
来获取字符数组中的有效字符数据,字节数组也可以用该方法。```java public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream(“\xxx.txt”); BufferedInputStream bis = new BufferedInputStream(fis, 10);
int i = 0; while((i=bis.read())!=-1) { // 不适合处理中文字符
System.out.print((char)i);
}
FileReader fr = new FileReader(“\yyy.txt”); BufferedReader br = new BufferedReader(fr, 10);
char[] cbuf = new char[3]; int len = 0; while((len=br.read(cbuf))!=-1) { // 可以处理中文
for (char c : Arrays.copyOfRange(cbuf, 0, len)) { System.out.print((char)c); }
} } ```
其他
- 只需要关闭缓冲流,而不需要关闭原有的字节流和字符流
- 处理中文文本数据,首选Writer、Reader;而非InputStream、OutputStream
- 使用字符/字节数组缓冲,或者用BufferedXxx系列的对象实现缓冲,都能提高读取效率
复制文件```java public static void main(String[] args) throws IOException { String fromPath = “D:\Video\Documentary\赵普 下一站看风景.mp4”; String toPath = “D:\Video\Documentary\(copy)赵普 下一站看风景.mp4”;
FileInputStream fis = new FileInputStream(fromPath); FileOutputStream fos = new FileOutputStream(toPath); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos);
long timeBgn = System.currentTimeMillis();
int i = 0; while((i=bis.read())!=-1) { bos.write(i); } bis.close(); bos.close();
// 使用字节数组缓冲 int len = 0; byte[] bytes = new byte[1024]; while((len=bis.read())!=-1) { bos.write(bytes, 0, len); } bis.close(); bos.close(); long timeEnd = System.currentTimeMillis();
System.out.println(“拷贝数据用时:” + (timeEnd-timeBgn)); } ```
对文本根据序号排序```java public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new FileReader(“in.txt”)); BufferedWriter bw = new BufferedWriter(new FileWriter(“out.txt”));
Properties prop = new Properties();
String line; while((line = br.readLine())!= null) {
String[] arr = line.split("\\. "); prop.setProperty(arr[0], arr[1]);
}
for (String key : prop.stringPropertyNames()) {
String value = prop.getProperty(key); bw.write(key + ". " + value); bw.newLine();
}
br.close(); bw.close(); } ```
- 不关闭bufferedWriter不会写入数据
- 还是要熟悉字符串的操作方法
6. 转换流
- 字符集
- ASCII:英文字母、阿拉伯数字、标点符号
- GBK:GB=国标,对中文双字节编码;支持繁体中文、日韩汉字;
- Unicode:UTF-8, UTF-16, UTF-32;最多使用四个字节来表达字符,最常用的UTF-8用三个字节编码中文
- FileReader能够读取IDE默认的UTF-8编码,但是系统中的文本文件会使用中文系统默认的GBK编码格式,此时会产生乱码。
- 转换流
InputStreamReader
使用指定的字符集(Charset)将字节解码为字符;而FileReader只会查询IDE默认的UTF-8码表OutputStreamWriter
使用指定的字符集,将字符编码为字节存入文件;而FilerWriter只会查询IDE默认的UTF-8码表
- 举例```java
public static void writeUtf8() throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“utf_8.txt”),
osw.write(“知我者谓我心忧”); osw.flush(); osw.close(); }"utf-8"); // 指定Charset
public static void readUtf8() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(“utf_8.txt”), “utf-8”); // 指定Charset char[] chars = new char[3]; int len = 0; while((len = isr.read(chars))!= -1) { System.out.print(Arrays.copyOfRange(chars, 0, len)); } isr.flush(); isr.close(); }
public static void writeGBK() throws IOException { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“gbk.txt”), “gbk”); // 指定Charset osw.write(“不知我者谓我何求”); osw.flush(); osw.close(); }
public static void readGBK() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(“gbk.txt”), “gbk”); // 指定Charset char[] chars = new char[3]; int len = 0; while((len = isr.read(chars))!= -1) { System.out.print(Arrays.copyOfRange(chars, 0, len)); } isr.flush(); isr.close(); }
5. 注意事项
1. 记得释放资源
2. 转换流的构造方法中需要传入相应的**字节流类**,而不是File对象,或地址
3. 可以通过转换流来转换编码格式(A编码格式读入,B编码格式写入)
<a name="b2e062df"></a>
## 7. 序列化流与反序列化流
1. 概念
1. 对象的序列化/写对象:以流的方式,将对象写入文件,保存为字节文件。
2. 对象的反序列化/读对象:以流的方式,将对象从文件中读取出来。
2. 对象的序列化流 `ObjectOutputStream`
1. 创建`ObjectOutputStream`对象,在构造方法中传入字节输出流`OutputStream`对象
2. 使用`writeObject方法`,把对象写入文件
3. 释放资源
3. 对象的反序列化流 `ObjectInputStream`
1. 创建`ObjectInputStream`对象,在构造方法中传入字节输出流`InputStream`对象
2. 使用`readObject`方法,把对象读入内存
3. 释放资源
```java
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 写入
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("刘森", 22));
students.add(new Student("刘方", 23));
oos.writeObject(students);
oos.flush(); // 刷新缓存
oos.close(); // 释放资源
// 读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
ArrayList<Student> arr = (ArrayList<Student>) ois.readObject(); // 强制类型转换 Object --> ArrayList<Student>
for (Object o : arr) {
System.out.println((Student) o);
}
ois.close(); // 释放资源
}
- 其他
- 如果对象的类没有实现
Serializable
接口,则无法序列化与反序列化,抛出NotSerializableException
异常;只需要implements Serializable
- 反序列化过程中,如果JVM找不到对象所对应的类,将抛出
ClassNotFoundException
异常 - 反序列化过程中,如果class文件被修改,类与原来有所不同,会抛出
InvalidClassException
异常- 类包含位置数据类型
- 类没有可访问的无参构造方法
- 类的serialVersionUID与保存的文件中的serialVersionUID不匹配(常因为修改了类,导致重新编译了Class文件中生成了新的序列号)
- 可以强制
private static final long serialVersionUID = 12345L
,强制使得即使类被改变,序列版本号也不会被修改 - 想要保存多个对象,可以将对象保存到ArrayList中,通过ObjectOutputStream保存ArrayList对象
transient
关键字static
修饰过的变量,优先于对象加载到内存中,不能被序列化- 如果不想成员变量被序列化,就可以用
transient
关键字来修饰
- 如果对象的类没有实现