本篇博客主要介绍Java中基础IO的基本使用。
什么是文件?
在介绍基础IO之前,我们先来理解一下什么是文件?
- 文件可以简单的理解成,在外设硬盘上保存数据的一种方式;
- 文件一共可以由两部分组成:属性(文件大小、文件名、文件类型等)和内容(文件中放了什么);
File文件操作类
在java.io
包中,用File类可以对文件进行操作(创建、删除、获取属性信息等)。
常用构造方法
方法 | 说明 |
public File(String pathname) | 创建指定路径文件对象 |
public File(String parent, String child) | 同上,但可指明父路径和子路径 |
基本文件操作
方法 | 说明 |
public boolean exists() | 测试指定路径文件或目录是否存在 |
public boolean isDirectory() | 判断一个文件是目录 |
public boolean isFile() | 判断是否是文件 |
public boolean delete() | 删除文件 |
public boolean createNewFile() throws IOException | 创建一个新文件 |
下面看代码演示:
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
public class FileTest {
private String path = "D:\\test.txt";
@Test
public void existsTest() {
File file = new File(path);
Assert.assertTrue(file.exists());
}
@Test
public void isDirectoryTest() {
File file = new File(path);
Assert.assertFalse(file.isDirectory());
}
@Test
public void isFileTest() {
File file = new File(path);
Assert.assertTrue(file.isFile());
}
@Test
public void deleteTest() {
File file = new File(path);
Assert.assertTrue(file.delete());
}
@Test
public void createNewFileTest() throws IOException {
File file = new File(path);
Assert.assertTrue(file.createNewFile());
}
}
以上代码实现了最基本的文件操作,但是代码存在两个问题:
- 实际项目部署环境可能与开发环境不同。不同的环境下路径分隔符是不同的,Windows下使用\作为路径分隔符。而Unix/Linux系统下使用的是/。所以使用路径分隔符时都会采用File类的一个常量
public static final String separator
来描述。
- 在Java中要进行文件的处理操作是要通过本地操作系统支持的,在这之中如果操作的是同名文件,就可能出现延迟的问题(开发之中尽可能避免文件重名问题)。
目录操作
方法 | 说明 |
public boolean mkdir() | 创建一个空目录 |
public boolean mkdirs() | 创建目录(无论有多少级父目录,都会创建) |
public String getParent() | 取得父目录 |
public File getParentFile() | 取得父File对象 |
代码演示:
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
public class FileTest {
@Test
public void mkdirTest() {
File file = new File("D:\\test.txt");
Assert.assertTrue(file.mkdir());
}
@Test
public void mkdirsTest() {
File file = new File("D:\\a\\b\\c\\test.txt");
Assert.assertTrue(file.mkdirs());
}
@Test
public void getParentTest() {
File file = new File("D:\\test.txt");
Assert.assertEquals(file.getParent(), "D:\\");
}
@Test
public void getParentFileTest() {
File file = new File("D:\\test.txt");
Assert.assertEquals(file.getParentFile(), new File("D:\\"));
}
}
文件属性操作
方法 | 说明 |
public long length() | 获取文件大小(字节) |
public long lastModified() | 最后一个修改日期 |
代码演示:
import java.io.File;
import java.util.Date;
public class FileTest {
public static void main(String[] args) {
File file = new File("D:\\test.txt");
System.out.println("file size: " + file.length());
System.out.println("file last modified date: " + new Date(file.lastModified()));
}
}
其他操作
方法 | 说明 |
public File[] listFiles() | 列出一个目录下的文件 |
借助这个方法,我们来实现一个小功能,列举出一个目录下的所有文件,包含子目录下的文件:
import java.io.File;
import java.util.LinkedList;
import java.util.Queue;
public class FileTest {
public static void main(String[] args) {
File file = new File("C:\\Users\\叄拾叄画生\\Desktop\\test");
Queue<File> queue = new LinkedList<>();
for (File child : file.listFiles()) {
queue.offer(child);
}
while (!queue.isEmpty()) {
File curFile = queue.poll();
System.out.println(curFile);
if (curFile.isDirectory()) {
for (File child : curFile.listFiles()) {
queue.offer(child);
}
}
}
}
}
流
什么是流?
在Java中所有数据都是使用流读写的。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
- 按照流向分:输入流、输出流;
- 按照处理数据的单位分:字节流(8位的字节)、字符流(16位的字节)。
什么是输入输出流?
- 输入流:输入就是将数据从各种输入设备(包括文件、键盘等)中读取到内存中,如键盘就是一个标准的输入设备;
- 输出流:正好相反,将数据写入到各种输出设备(比如文件、显示器、磁盘等),显示器就是一个标准的输出设备。
文件既可以是输入设备,又可以是输出设备。
什么是字节流、字符流?
File类不支持文件内容处理,如果要处理文件内容,必须要通过流的操作模式来完成。
在java.io
包中,流分为两种:字节流和字符流。
- 字节流:数据流中最小的数据单元是字节。InputStream、OutputStream;
- 字符流:数据流中最小的数据单元是字符,Java中的字符是Unicode编码,一个字符占用两个字节。Reader、Writer。
Java中IO流的体系结构如图:
Java IO类图:
字节流
FileInputStream和FileOutputStream
public class FileInputStream extends InputStream {}
- FileInputStream:从文件系统中的某个文件中获得输入字节;
- FileInputStream:用于读取诸如图像数据之类的原始字节流。
方法 | 说明 |
FileInputStream(File file) | 通过打开与实际文件的连接创建一个FileInputStream,该文件由文件系统中的File对象file命名 |
FileInputStream(String name) | 通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名 |
public class FileOutputStream extends OutputStream {}
- 文件输出流是用于将数据写入到输出流File或一个FileDescriptor。文件是否可用或可能被创建取决于底层平台;
- 特别是某些平台允许一次只能打开一个文件来写入一个FileOutputStream(或其他文件写入对象)。
方法 | 解释 |
FileOutputStream(File file) | 创建文件输出流以写入由指定的File对象表示的文件 |
FileOutputStream(String name) | 创建文件输出流以指定的名称写入文件 |
代码演示:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileInputOutputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fin = new FileInputStream("C:\\Users\\叄拾叄画生\\Desktop\\test\\IO流.png");
FileOutputStream fout = new FileOutputStream("C:\\Users\\叄拾叄画生\\Desktop\\test\\IO流Copy.png");
int len = 0;
byte[] buf = new byte[1024];
while ((len = fin.read(buf)) != -1) {
fout.write(buf, 0, len);
}
fin.close();
fout.close();
}
}
字节缓冲流BufferedInputStream和BufferedOutputStream
为什么需要缓冲流?
当我们用read()
读取文件时,每读一个字节,访问一次硬盘,效率很低。文件过大时,操作起来也不是很方便。因此我们需要用到buffer缓存流,当创建buffer对象时,会创建一个缓冲区数组。当我们读一个文件时,先从硬盘中读到缓冲区,然后直接从缓冲区输出即可,效率会更高。
public class BufferedInputStream extends FilterInputStream {}
- BufferedInputStream为另一个输入流添加了功能,即缓冲输入和支持mark和reset方法的功能。当创建BufferedInputStream时,将创建一个内部缓冲区数组;
- 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。mark操作会记住输入流中的一点,并且reset操作会导致从最近的mark操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。
方法 | 说明 |
BufferedInputStream(InputStream in) | 创建一个BufferedInputStream并保存其参数,输入流in,供以后使用 |
BufferedInputStream(InputStream in, int size) | 创建BufferedInputStream具有指定缓冲区大小,并保存其参数,输入流in,供以后使用 |
public class BufferedOutputStream extends FilterOutputStream {}
- 该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用。
方法 | 说明 |
BufferedOutputStream(OutputStream out) | 创建一个新的缓冲输出流,以将数据写入指定的底层输出流 |
BufferedOutputStream(OutputStream out, int size) | 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流 |
代码演示:
import java.io.*;
public class BufferStreamTest {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\集合类.rar")
);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\集合类Copy.rar")
);
byte[] bytes = new byte[1024];
int len = 0;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.flush();
bos.close();
bis.close();
}
}
字符流
FileReader和FileWriter
public class FileReader extends InputStreamReader {}
- 如果要从文件中读取内容,可以直接使用FileReader子类;
- FileReader是用于读取字符流。要读取原始字节流,请考虑使用FileInputStream。
方法 | 说明 |
FileReader(File file) | 创建一个新的FileReader,给出File读取 |
FileReader(String fileName) | 创建一个新的FileReader,给定要读取的文件的名称 |
public class FileWriter extends OutputStreamWriter {}
- 如果是向文件中写入内容,应该使用FileWriter子类;
FileWriter
是用于写入字符流。要编写原始字节流,请考虑使用FileOutputStream
。
方法 | 说明 |
FileWriter(File file) | 给一个File对象构造一个FileWriter对象 |
FileWriter(String fileName) | 构造一个给定文件名的FileWriter对象 |
代码演示:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileReaderWriterTest {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader(
"C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\Java从入门到放弃.txt"
);
FileWriter fw = new FileWriter(
"C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\Java从入门到放弃copy.txt"
);
int tmp;
while ((tmp = fr.read()) != -1) {
fw.write(tmp);
}
fw.close();
fr.close();
}
}
字符缓冲流BufferedReader和BufferedWriter
为了提高字符流读写的效率,引入了缓冲机制,进行字符批量的读写,提高了单个字符读写的效率。BufferedReader
用于加快读取字符的速度,BufferedWriter
用于加快写入的速度。
BufferedReader
和BufferedWriter
类各拥有8192个字符大小的缓冲区。
- 当
BufferedReader
在读取文本文件时,会先尽量从文件中读入字符数据并放满缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取; - 使用
BufferedWriter
时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。
public class BufferedReader extends Reader {}
从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。
方法 | 说明 |
BufferedReader(Reader in) | 创建使用默认大小的输入缓冲区的缓冲字符输入流 |
BufferedReader(Reader in, int sz) | 创建使用指定大小的输入缓冲区的缓冲字符输入流 |
public class BufferedWriter extends Writer {}
将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。
方法 | 说明 |
BufferedWriter(Writer out) | 创建使用默认大小的输出缓冲区的缓冲字符输出流 |
BufferedWriter(Writer out, int sz) | 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区 |
代码演示:
import java.io.*;
public class BufferedReaderWriterTest {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader(
"C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\Java从入门到放弃.txt"
);
BufferedReader br = new BufferedReader(fr);
FileWriter fw = new FileWriter(
"C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\Java从入门到放弃copy.txt"
);
BufferedWriter bw = new BufferedWriter(fw);
String content = "";
while ((content = br.readLine()) != null) {
bw.write(content + "\r\n");
}
fr.close();
br.close();
fw.close();
bw.close();
}
}
字节流对比字符流
- 字节流操作的基本单元是字节;字符流操作的基本单元为Unicode码元;
- 字节流在操作的时候本身不会用到缓冲区的,是与文件本身直接操作的;而字符流在操作的时候使用到缓冲区的;
- 所有文件的存储都是字节(byte)的存储,在磁盘上保留的是字节;
- 在使用字节流操作中,即使没有关闭资源(close方法),也能输出;而字符流不使用close方法的话,不会输出任何内容。
字符字节转换流
有时候我们需要进行字节流与字符流二者之间的转换,因为这是两种不同的流,所以,在进行转换的时候我们需要用到OutputStreamWriter
和InputStreamReader
。
InputStreamReader
是Reader的子类,将输入的字节流转换为字符流。
public class InputStreamReader extends Reader {}
InputStreamReader
是从字节流到字符流的桥;它读取字节,并使用指定的charset将其解码为字符;- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
方法 | 说明 |
InputStreamReader(InputStream in) | 创建一个使用默认字符集的InputStreamReader |
InputStreamReader(InputStream in, Charset cs) | 创建一个使用指定字符集的InputStreamReader |
代码演示:
import java.io.*;
public class InputStreamReaderTest {
public static void main(String[] args) {
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
try {
is = new FileInputStream("C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\test.txt");
isr = new InputStreamReader(is, "GBK");
br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} finally {
if (br != null) br.close();
if (isr != null) isr.close();
if (is != null) is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStreamWriter
是Writer的子类,将输出的字符流转换为字节流。
public class OutputStreamWriter extends Writer {}
OutputStreamWriter
是字符流转换为字节流的桥梁,将其写入的字符编码成使用指定的字节charset;- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
方法 | 说明 |
OutputStreamWriter(OutputStream out) | 创建一个使用默认字符编码的OutputStreamWriter |
OutputStreamWriter(OutputStream out, Charset cs) | 创建一个使用给定字符集的OutputStreamWriter |
代码演示:
import java.io.*;
public class OutputStreamWriterTest {
public static void main(String[] args) throws IOException {
File file = new File("C:\\Users\\叄拾叄画生\\Desktop\\test\\Java\\test.txt");
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), "UTF-8")
);
bw.write("Hello, World!");
bw.newLine();
bw.flush();
bw.close();
}
}