在程序运行期间,可能需要读取文件属性或创建新文件等,这时就需要使用File类。File类的对象主要用于获取文件本身的信息(属性),而不涉及对文件的读写操作(内容)。
File对象的构造方法:
File(String pathname); // 通过将给定的路径名字符串转换为抽象路径名来创建一个新的File实例。
File(String parent, String child); // 从父路径名字符串和子路径名字符串创建一个新的File实例
File(File parent, String child); // 从父抽象路径名和子路径名字符串创建一个新的File实例。
File(URI uri); // 通过将给定的File: URI转换为抽象路径名来创建一个新的File实例。
不同平台路径分隔符的区别:Windows平台使用
\
作为路径分隔符,在Java字符串中需要用\\
表示一个\
(第一个\
为转义字符)。Unix平台使用/
作为路径分隔符。
File类的常用方法:
当程序需要从外部存储媒介或程序中读取数据时,就需要使用输入流;当程序需要将处理的结果写入存储媒介或者传送给其他程序时,就需要使用输出流。
java.io包(I/O流库)提供大量的流类用于传输数据,所有输入流都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类。所有输入流都是抽象类OuputStream(字节输出流)或抽象类Writer(字符输出流)的子类。
关于字节流和字符流的区别可以参考我的文章:字符串的使用>字符串与字符数组、字节数组。
使用输入流通常包括四个基本步骤:
使用输出流同样包括四个基本步骤:
流类最常用的方法为:
类名(String name); // 构造方法,通过文件名指定源或目的地
类名(File file); // 构造方法,通过File对象指定源或目的地
read(); // 输入流读取数据。
write(); // 输出流写入数据。
close(); // 关闭流。
流类的构造方法会抛出FileNotFoundException异常,例如文件不存在、是目录而不是常规文件,或者由于其他原因无法打开读取。因此程序必须在try-catch语句中创建流。
文件字节输入流的 read 方法以字节为单位读取源中的数据。
public static void main(String[] args) {
File file = new File("D:\\Desktop\\新建 Text 文件.txt"); // Windows平台中使用 "\\" 做路径分隔符
int len = (int) file.length(); // 获取文件长度并强转为int型
byte[] bytes = new byte[len]; // 用字节数组于存放将读取的数据
try { // 必须在try-catch语句中创建流。
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(Arrays.toString(bytes)); // 输出结果为字节数组形式
}
文件字节输出流相比输入流的构造方法多了一个boolean类型的参数append,如果指定append为true则输出流将字符写在文件末尾而不是开头。如果不指定则默认append为为false。
文件字节输出流以字节为单位向文件写入内容:
使用字节为单位读写字符时,由于各种字符编码方式的不同,处理不当可能出现乱码问题;因此,处理字符文件时,更常使用字符输入输出流。文件字符输入流:FileReader;文件字符输出流:FileWriter。
在使用时,文件字符输入输出流与文件字节输入输出流基本相同,但也存在一些区别:
在没有缓冲的情况下,每次读写可能都要调用底层系统从磁盘中进行文件读写,这样的结果是效率较低。缓冲的意思是先在内存中提供一块缓冲区,先将数据存入缓冲区再进行读写以提高效率。Java给每种输入输出流都提供了缓冲流,对应的构造方法为:
/* 其中参数sz和size都为缓冲区大小,是可选参数 */
BufferedReader(@NotNull Reader in, int sz);
BufferedWriter(@NotNull Writer out, int sz);
BufferedInputStream(@NotNull InputStream in, int size);
BufferedOutputStream(@NotNull OutputStream out, int size);
缓冲流的常用方法:
随机流既可以读文件又可以写文件。它并不是InputStream或OutoutStream的子类。如果想对一个文件中的数据同时进行读写操作时,可以考虑使用随机流。其构造参数为:
RandomAccessFile(String name, String mode);
RandomAccessFile(File file, String mode);
其中mode参数支持四个值“r”、“rw”、“rwd”和“rws”:
- “r” 仅供读。
- “rw” 开放读和写。如果该文件不存在,则会尝试创建它。
- “rwd” 与“rw”一样,允许读写,并且还要求对文件内容的每次更新都同步写入底层存储设备。
- “rws”和“rwd”相似,但使用“rwd”只需要更新文件的内容写入存储;使用“rws”需要更新文件的内容和要写入的元数据。
RandomAccessFile类中有一个方法void seek(long pos)
用来设置流的读写位置,参数pos即读写位置距离文件开头的字节个数;也可以使用long getFilePointer()
方法获取当前读写位置。除了支持字节与字符读写,随机流还支持各种基本类型的读写,显然随机流对文件的读写更加灵活。
需要注意的是,随机流的readLine()方法只支持ASCII字符的编码,如果需要读取存在非ASCII字符的文件时,可以使用 ISO_8859_1 编码将读取到的字符串转为字节数组再转回字符串
RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\Desktop\\新建 Text 文件.txt", "rw");
String s = randomAccessFile.readLine(); // 使用随机流读取地第一行
System.out.println(s); // 输出 ä½ å¥½ï¼ŒJava。
byte[] b = s.getBytes(StandardCharsets.ISO_8859_1); // 使用 ISO_8859_1 编码转为字节数组
s = new String(b); // 将字节数组转回字符串
System.out.println(s); // 输出 你好,Java。
字节数组输入流ByteArrayInputStream和字节数组输出流ByteArrayInptyStream可以使用字节数组作为源和目的地。
与字节数组流相对应,字符数组流分为CharArrayReader类和CharArrayWriter类。字符数组流以字符作为源和目的地。
数据流分为DataInputStream类和DataOutputStream类,和随机流类似,它们允许以Java基本数据类型为单位进行读写。这样做的好处是,当我们读取一个数值时,不用考虑这个数值需要多少个字节。
对象流分为ObjectInputStream类和ObjectOutputStream类,它们可以以对象为单位进行读写。需要注意的是,当使用对象流读写对象时,要保证对象是序列化的,这是为了保证能对对象正确的进行读写。
一个类如果实现了Serializable接口,那么这个类创建的对象就可以称为是序列化的对象。同时,类中的成员对象也必须是序列化的。(Serializable接口中的方法不可见,由JVM实现)
对于一个引用类型的对象,使用"="只能复制其引用。有时想复制一个对象引用的实体,就可以通过对象序列化进行复制。
/* 通过序列化实现对象克隆 */
public static Object objectClone(Object object) throws IOException, ClassNotFoundException {
/* 将对象序列化转为字节流 */
ByteArrayOutputStream outByte = new ByteArrayOutputStream();
ObjectOutputStream outObject = new ObjectOutputStream(outByte);
outObject.writeObject(object);
outObject.close();
/* 将字节流转回为对象 */
ObjectInputStream inObject = new ObjectInputStream(new ByteArrayInputStream(outByte.toByteArray()));
Object newObject = inObject.readObject();
inObject.close();
return newObject;
}
如果不希望对象中的某些变量值序列化,可以使用transient
关键字进行修饰。
有时需要对文件中的内容进行解析,一般的方法是:将文件内容全部读入内存,再通过字符串处理的方式进行解析,这样做的优点是在内存中进行解析速度较快;但当文件内容较多将消耗比较大的内存,这时可以考虑通过Scanner对文件内容进行解析。
Scanner类是Java中一个可以使用正则表达式解析字符串的类,它的构造法方法需要传入被解析的源,这个源可以是字符串、文件路径、输入流等。使用Scanner的优点是内存占用较小;但速度相对较慢。
和多线程类似,当出现多个程序同时处理一个文件的情况时,可能导致文件处理错误。这时可以通过FileLock和FileChannel类对文件加锁。
使用文件锁的步骤如下:
getChannel()
方法获得与底层文件相连FileChannel(信道)对象。tryLock()
或lock()
方法获得一个FileLock(文件锁)对象。lock.relase()
方法释放锁。RandomAccessFile randomAccessFile = new RandomAccessFile("Example.java", "rw"); // 建立指向文件的流对象
FileChannel fileChannel = randomAccessFile.getChannel(); // 获得与底层文件相连FileChannel(信道)对象
FileLock fileLock = fileChannel.tryLock(); // 获得一个FileLock(文件锁)对象
fileLock.release(); // 释放锁