文章目录
Java 的 IO 包主要关注的是从原始数据源的读取以及输出原始数据到目标媒介。以下是最典型的数据源和目标媒介:
- 文件
- 管道
- 网络连接
- 内存缓存
- System.in, System.out, System.error(注:Java 标准输入、输出、错误输出)
1. 几个 IO 概念
1.1 流
在 Java IO 中,流是一个核心的概念:
- 可以分为字节流 (以字节为单位进行读写)和字符流 (以字符为单位进行读写);
- 也可以分为输入流(向内存输入)和输出流(为从内存输出)。
1.2 类 InputStream, OutputStream, Reader 和 Writer
一个程序需要 InputStream 或者 Reader 从数据源读取数据,需要 OutputStream 或者 Writer 将数据写入到目标媒介中。
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
- InputStreamReader 实现从字节流解码成字符流;
- OutputStreamWriter 实现字符流编码成为字节流。
Java IO 中包含了许多 InputStream、OutputStream、Reader、Writer 的子类,汇总如下:
2. Java IO:文件
2.1 通过 Java IO 读文件
如果你需要在不同端之间读取文件,你可以根据该文件是二进制文件还是文本文件来选择使用 FileInputStream 或者 FileReader。这两个类允许你从文件开始到文件末尾一次读取一个字节或者字符,或者将读取到的字节写入到字节数组或者字符数组。你不必一次性读取整个文件,相反你可以按顺序地读取文件中的字节和字符。
如果你需要跳跃式地读取文件其中的某些部分,可以使用 RandomAccessFile.
通常使用输入流中的 read()方法读取数据。read()方法返回一个整数,代表了读取到的字节的内容 (0 ~ 255)。当达到流末尾没有更多数据可以读取的时候,read() 方法返回 - 1。
字节流示例:
public static void readFile(String fileName) throws IOException {
InputStream input = new FileInputStream(fileName);
int data;
while ((data = input.read()) != -1){
logger.info("reading..." + data);
}
input.close();
}
你可以将流整合起来以便实现更高级的输入和输出操作。比如,一次读取一个字节是很慢的,所以可以从磁盘中一次读取一大块数据,然后从读到的数据块中获取字节。为了实现缓冲,可以把 InputStream 包装到 BufferedInputStream 中。
BufferedInputStream input = new BufferedInputStream(new FileInputStream(fileName));
字符流示例:
Reader reader = new BufferedReader(new FileReader(fileName));
while (reader.read() != -1) {
logger.info("reading..." );
}
reader.close();
2.2 通过 Java IO 写文件
如果你需要在不同端之间进行文件的写入,你可以根据你要写入的数据是二进制型数据还是字符型数据选用 FileOutputStream 或者 FileWriter。你可以一次写入一个字节或者字符到文件中,也可以直接写入一个字节数组或者字符数组。数据按照写入的顺序存储在文件当中。
通常使用输出流中的 write 方法写入数据。
字节流示例:
public static void writeFile(String fileName, byte[] content) throws IOException {
OutputStream output = new BufferedOutputStream(new FileOutputStream(fileName));
output.write(content);
logger.info("writing..." + content.toString());
output.flush();
output.close();
}
字符流示例:
Writer writer = new BufferedWriter(new FileWriter(fileName));
writer.write(content);
writer.flush();
writer.close();
文件内容的覆盖 Override VS 追加 Appending
当你创建了一个指向已存在文件的 FileOutputStream,你可以选择覆盖整个文件,或者在文件末尾追加内容。通过使用不同的构造函数可以实现不同的目的。
-
其中一个构造函数取文件名作为参数,会覆盖任何此文件名指向的文件。
-
另外一个构造函数取 2 个参数:文件名和一个布尔值,布尔值表明你是否追加文件。
OutputStream output = new BufferedOutputStream(new FileOutputStream(fileName,true));//不会覆盖文件
Writer writer = new BufferedWriter(new FileWriter(fileName,true));//不会覆盖文件
flush()
当你往 FileOutputStream 里写数据的时候,这些数据有可能会缓存在内存中。在之后的某个时间,比如,每次都只有 X 份数据可写,或者 FileOutputStream 关闭的时候,才会真正地写入磁盘。当 FileOutputStream 没被关闭,而你又想确保写入到 FileOutputStream 中的数据写入到磁盘中,可以调用 flush() 方法,该方法可以保证所有写入到 FileOutputStream 的数据全部写入到磁盘中。
close()
每次读/写文件结束之后,都应该调用流的 close() 方法,以节约资源
一般可以 finnally 块中显示关闭流,在 Java 7 之后,增强了try语句的功能,它允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源(此处的资源是指那些必须在程序结束时显式关闭的资源,比如数据库连接,网络连接等),try-with-resources 是一个定义了一个或多个资源的try 声明,try语句在该语句结束时自动关闭这些资源。try-with-resources确保每一个资源在处理完成后都会被关闭。这些资源必须实现AutoCloseable或者Closeable接口,实现这两个接口就必须实现close() 方法。
try ( Writer writer = new BufferedWriter(new FileWriter(fileName,false));){
writer.write(content);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
2.3 通过 Java IO 随机存取文件
随机存取并不意味着你可以在真正随机的位置进行读写操作,它只是意味着你可以跳过文件中某些部分进行操作,并且支持同时读写,不要求特定的存取顺序。这使得 RandomAccessFile 可以覆盖一个文件的某些部分、或者追加内容到它的末尾、或者删除它的某些内容,当然它也可以从文件的任何位置开始读取文件。
创建一个 RandomAccessFile
String fileName = "test.txt";
RandomAccessFile file = new RandomAccessFile(fileName, "rw");
请注意构造函数的第二个参数:“rw”,表明你以读写方式打开文件。
在 RandomAccessFile 中来回读写
在 RandomAccessFile 的某个位置读写之前,必须把文件指针指向该位置。通过 seek() 方法可以达到这一目标。可以通过调用 getFilePointer() 获得当前文件指针的位置。
RandomAccessFile file = new RandomAccessFile(fileName,"rw");
ile.seek(2);
long pointer = file.getFilePointer();
System.out.println(pointer);
file.close();
2.4 文件和目录信息的获取
有时候你可能需要读取文件的信息而不是文件的内容,举个例子,如果你需要知道文件的大小和文件的属性。对于目录来说也是一样的,比如你需要获取某个目录下的文件列表。通过 File 类可以获取文件和目录的信息。
Java IO API 中的 FIle 类可以让你访问底层文件系统,通过 File 类,你可以做到以下几点:
- 创建文件对象
String fileName = "test.txt";
File file = new File(fileName);
- 检测文件是否存在
boolean fileExists = file.exists();
- 读取文件长度
long length = file.length();
- 重命名或移动文件
boolean success = file.renameTo(new File("c:\\data\\new-file.txt"));
- 删除文件
boolean success = file.delete();
- 检测某个路径是文件还是目录
boolean isDirectory = file.isDirectory();
boolean isFile = file.isFile();
- 读取目录中的文件列表
String[] fileNames = file.list();
File[] files = file.listFiles();
2.5 递归地列出一个目录下的所有内容
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。
public static void AllFiles(String dir) {
File file = new File(dir);
if (file == null || !file.exists())
return;
if (file.isFile()) {
System.out.println(file.getName());
return;
}
for (File f : file.listFiles()) {
AllFiles(dir + "\\" + f.getName());
}
}
2.6 实现文件的复制
public static void copyFile(String src, String dest) {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dest)
) {
byte[] buffer = new byte[20 * 1024];
int data;
while ((data = in.read(buffer, 0, buffer.length)) != -1) {
out.write(buffer, 0, data);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
2.7 逐行读取文件
public static void readFile(String fileName){
try ( BufferedReader bf = new BufferedReader(
new FileReader(fileName))){
String line;
while((line = bf.readLine()) != null){
System.out.println(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}