本文介绍Java常用API中与文件相关的File与IO流操作。
1. File
File是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件
或文件夹
)。
File类可以用于获取文件信息、判断文件类型、创建文件/文件夹、删除文件/文件夹等操作。
File类只能对文件/文件夹本身进行操作,不能读写文件里存储的数据。
1.1 File对象构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void main(String[] args) {
File file = new File("java-io-demo/src/main/resources/abc.txt"); System.out.println(file.getPath()); System.out.println(file.length()); }
|
执行结果:
注意点:
- File对象:代指可以操作的系统文件对象,包括文件与文件夹。
- 绝对路径与相对路径
- 绝对路径:文件在系统中的完全路径,包括根路径,如系统盘。
- 相对路径:文件相对于项目的路径,一般以项目本身的根路径。
- 分割符:
- 斜杠:
\\
,相当于是转义字符
- 反斜杠:
/
(推荐)
- File.separator:自动获取当前系统的分割符(不推荐)
1.2 File常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) { File file = new File("java-io-demo/src/main/resources/abc.txt"); System.out.println(file.exists()); System.out.println(file.isFile()); System.out.println(file.isDirectory()); System.out.println(file.getName()); System.out.println(file.length()); System.out.println(LocalDateTime.ofInstant(Instant.ofEpochMilli(file.lastModified()), ZoneOffset.ofHours(8))); System.out.println(file.getPath()); System.out.println(file.getAbsolutePath()); }
|
执行结果:
File常用方法:
方法声明 |
功能描述 |
boolean exists() |
判断File对象对应的文件或目录是否存在,若存在则返回ture,否则返回false |
boolean delete() |
删除File对象对应的文件或目录,若成功删除则返回true,否则返回false |
boolean createNewFile() |
当File对象对应的文件不存在时,该方法将新建一个此File对象所指定的新文件,若创建成功则返回true,否则返回false |
String getName() |
返回File对象表示的文件或文件夹的名称 |
String getPath() |
返回File对象对应的路径 |
String getAbsolutePath() |
返回File对象对应的绝对路径(在Unix/Linux等系统上,如果路径是以正斜线/开始,则这个路径是绝对路径;在Windows等系统上,如果路径是从盘符开始,则这个路径是绝对路径) |
String getParent() |
返回File对象对应目录的父目录(即返回的目录不包含最后一级子目录) |
boolean canRead() |
判断File对象对应的文件或目录是否可读,若可读则返回true,反之返回false |
boolean canWrite() |
判断File对象对应的文件或目录是否可写,若可写则返回true,反之返回false |
boolean isFile() |
判断File对象对应的是否是文件(不是目录),若是文件则返回true,反之返回false |
boolean isDirectory() |
判断File对象对应的是否是目录(不是文件),若是目录则返回true,反之返回false |
boolean isAbsolute() |
判断File对象对应的文件或目录是否是绝对路径 |
long lastModified() |
返回1970年1月1日0时0分0秒到文件最后修改时间的毫秒值 |
long length() |
返回文件内容的长度 |
String[] list() |
列出指定目录的全部内容,只是列出名称 |
String[] list(FilenameFilter filter) |
接收一个FilenameFilter参数,通过该参数可以只列出符合条件的文件 |
File[] listFiles() |
返回一个包含了File对象所有子文件和子目录的File数组 |
1.3 File创建文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void main(String[] args) throws IOException { File file = new File("java-io-demo/src/main/resources/123.txt"); System.out.println(file.createNewFile()); File directory1 = new File("java-io-demo/src/main/resources/directory"); System.out.println(directory1.mkdir()); File directory2 = new File("java-io-demo/src/main/resources/aaa/bbb/ccc"); System.out.println(directory2.mkdirs()); System.out.println(file.delete()); System.out.println(directory1.delete()); System.out.println(directory2.delete()); }
|
执行结果:
注意点:
- createNewFile:创建文件
- mkdir:只能创建一级文件夹
- mkdirs:能创建多级文件夹
- delete:只能删除文件,或删除空文件夹。文件夹中有对象(文件或文件夹)则不能删除
1.4 File遍历文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) { File file = new File("D:\\Document"); String[] list = file.list(); for (String fileName : list) { System.out.println(fileName); } File[] files = file.listFiles(); for (File subFile : files) { System.out.println(subFile.getAbsolutePath()); } }
|
执行结果:
注意点:
- list:遍历文件夹下一级文件,返回String类型的文件名称。
- listFiles:遍历文件下一级文件,返回File类型的文件对象。
1.5 File搜索文件
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
| public static void main(String[] args) { long startTime = System.currentTimeMillis(); searchFile(new File("D:/"), "demo.txt"); long endTime = System.currentTimeMillis(); System.out.println("耗时" + (double)(endTime - startTime) / 1000 + "s"); }
public static void searchFile(File dir, String fileName) { if (null == dir || !dir.exists() || dir.isFile()) { return; } File[] files = dir.listFiles(); if (null != files && files.length > 0) { for (File file : files) { if (file.isFile()) { if (file.getName().contains(fileName)) { System.out.println("找到了:" + file.getAbsolutePath()); } } else { searchFile(file, fileName); } } } }
|
执行结果:
注意点:
- 采用递归算法进行搜索
- 循环条件:调用listFiles遍历文件夹下的一级文件
- 判断条件:是文件:是否为要搜索的文件;是文件夹:递归调用
- 终止条件:文件夹为空,或检索到文件
2. IO流
I代表Input,O代表Output,Java中的IO流用于对文本或网络中的数据进行输入和输出操作。
IO流按不同的分类方式,可以分为三种:
字节流与字符流
根据流操作的数据单位不同,可以划分为字节流与字符流。
字节流以字节(8bit)为单位读写数据,字符流以字符(如“A”)为单位读写数据。
字节流一般后缀带InputStream、OutputStream;字符流一般后缀带Reader、Writer。
输入流与输出流
根据流传输方向不同,可以划分为输入流与输出流。
传输方向是以内存为基准定义的,从文件中向内存输入数据,称为输入流,即为读;从内存中输出数据到文件,称为输出流,即为写。
输入流一般后缀带InputStream、Reader;输出流一般后缀带OutputStream、Writer。
节点流和处理流
根据流的功能不同,可以划分为节点流与处理流。
节点流又称为低级流,它只能直接连接数据源,进行数据的读写,如FileInputStream、FileOutputStream;处理流又称为高级流,它则是对低级流进行连接和封装,在低级流的基础上对数据进行处理,如BufferedInputStream、BufferedOutputStream,在使用高级流时,不会直接连接到数据源,而是连接到已存在的流上。
IO流的体系分类如下图所示:
2.1 字节流
2.1.1 概览
在计算机中,无论是文本、图片、音频还是视频,所有文件都是以二进制(字节)形式存在的,I/O流中针对字节的输入/输出提供了一系列的流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输入流和字节输出流。在JDK中,提供了两个抽象类InputStream和OutputStream,它们是字节流的顶级父类,所有的字节输入流都继承自InputStream,所有的字节输出流都继承自OutputStream。
InputStream被看成一个输入管道,OutputStream被看成一个输出管道,数据通过InputStream从源设备输入到程序,通过OutputStream从程序输出到目标设备,从而实现数据的传输。由此可见,I/O流中的输入/输出都是相对于程序(内存)而言的。
InputStream的常用方法
方法声明 |
功能描述 |
int read() |
从输入流读取一个8位的字节,把它转换为0~255之间的整数,并返回这一整数。当没有可用字节时,将返回-1 |
int read(byte[] b) |
从输入流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读取字节的数目 |
int read(byte[] b,int off,int len) |
从输入流读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组开始保存数据的起始下标,len表示读取的字节数目 |
void close() |
关闭此输入流并释放与该流关联的所有系统资源 |
前三个read()方法都是用来读数据的,第一个read()方法是从输入流中逐个读入字节;第二个和第三个read()方法则将若干字节以字节数组的形式一次性读入,从而提高读数据的效率。
在进行I/O流操作时,当前I/O流会占用一定的内存,由于系统资源宝贵,因此,在I/O操作结束后,应该调用close()方法关闭流,从而释放当前I/O流所占的系统资源。
OutputStream的常用方法
方法声明 |
功能描述 |
void write(int b) |
向输出流写入一个字节 |
void write(byte[] b) |
把参数b指定的字节数组的所有字节写到输出流 |
void write(byte[] b,int off,int len) |
将指定byte数组中从偏移量off开始的len个字节写入输出流 |
void flush() |
刷新此输出流并强制写出所有缓冲的输出字节 |
void close() |
关闭此输出流并释放与此流相关的所有系统资源 |
前三个是重载的write()方法,都用于向输出流写入字节,其中,第一个方法逐个写入字节,后两个方法是将若干个字节以字节数组的形式一次性写入,从而提高写数据的效率。
flush()方法用来将当前输出流缓冲区(通常是字节数组)中的数据强制写入目标设备,此过程称为刷新。close()方法是用来关闭流并释放与当前IO流相关的系统资源。
InputStream和OutputStream这两个类虽然提供了一系列和读写数据有关的方法,但是这两个类是抽象类,不能被实例化,因此,针对不同的功能,InputStream和OutputStream提供了不同的子类,这些子类形成了一个体系结构:
InputStream的子类:
OutputStream的子类:
2.1.2 读取文件
每次读取一个字节(byte)
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) throws IOException { InputStream inputStream = new FileInputStream("java-io-demo/src/main/resources/abc.txt"); int res; while ((res = inputStream.read()) != -1) { System.out.println((char)res); } inputStream.close(); }
|
文件内容:
执行结果:
每次读取一个字节的注意点:
- 性能很差
- 读汉字会乱码
- 流使用后要关闭,释放资源
每次读取多个字节(byte)
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
| public static void main(String[] args) throws IOException { InputStream inputStream = new FileInputStream("java-io-demo/src/main/resources/abc.txt"); byte[] buffer = new byte[3];
int length; while ((length = inputStream.read(buffer)) != -1) { System.out.println(new String(buffer, 0, length)); } inputStream.close(); }
|
文件内容:
执行结果:
每次读取多个字节的注意点:
- 减少了读取文件的次数,提高了效率
- 依然不能解决读取汉字乱码的问题
- 适合做文件拷贝类的操作
一次读取所有字节(byte)
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) throws IOException { File file = new File("java-io-demo/src/main/resources/abc.txt"); InputStream inputStream = new FileInputStream(file); long length = file.length(); byte[] buffer = new byte[(int) length]; int readLength = inputStream.read(buffer); System.out.println("缓冲字节数组长度:" + buffer.length); System.out.println("读取字节字节长度:" + readLength); System.out.println("读取字节解码内容:" + new String(buffer)); }
|
文件内容:
执行结果:
一次读取所有字节的注意点:
- 避免了出现汉字乱码的情况
- 读取大文件的话需要创建大的缓冲字节数组
2.1.3 写入文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) throws IOException { OutputStream outputStream = new FileOutputStream("java-io-demo/src/main/resources/output.txt", true);
outputStream.write(97); outputStream.write('b'); outputStream.write('海'); String str = "黑神话:悟空 Black Myth: Wukong"; byte[] bytes = str.getBytes(); outputStream.write(bytes); outputStream.write(bytes, 0, 18); outputStream.write("\r\n".getBytes()); outputStream.close(); }
|
执行结果:
OutputStream写入文件的注意点:
- 文件写入分为覆盖型与追加型,前者会覆盖掉文件的原始内容,后者在原始内容的后面追加写入。
- 文件在写入时以字节为单位,可能会出现汉字乱码问题,需要注意。
2.1.4 拷贝文件
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
| public static void main(String[] args) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = new FileInputStream("java-io-demo/src/main/resources/pic.jpg"); outputStream = new FileOutputStream("java-io-demo/src/main/resources/pic-copy.jpg"); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (null != inputStream) { inputStream.close(); } } catch (IOException e) { throw new RuntimeException(e); } try { if (null != outputStream) { outputStream.close(); } } catch (IOException e) { throw new RuntimeException(e); } } }
|
拷贝文件注意点:
- 使用finally关闭输入输出流
2.1.5 关闭资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void main(String[] args) { try ( InputStream inputStream = new FileInputStream("java-io-demo/src/main/resources/pic.jpg"); OutputStream outputStream = new FileOutputStream("java-io-demo/src/main/resources/pic-copy.jpg") ) { byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); } } catch (Exception e) { e.printStackTrace(); } }
|
在Java 7中引入了try-with-resources的异常处理机制,便于关闭在try-catch中声明的资源。
在此之前,使用try-catch-finally关闭资源十分繁琐,不方便。
使用try-with-resources,需要在try后紧跟括号,在括号里声明使用的资源,若是单行则不用分号,若是多行,则需要以分号结尾,最后使用的资源会被自动关闭。
需要注意的是,使用try-with-resources的前提条件,是资源必须实现了 java.lang.AutoCloseable 接口(它包含了实现java.io.Closeable 的所有对象)。
2.1.6 字节缓冲流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static void main(String[] args) { try ( InputStream fileInputStream = new FileInputStream("java-io-demo/src/main/resources/abc.txt"); OutputStream fileOutputStream = new FileOutputStream("java-io-demo/src/main/resources/abc.txt", true); InputStream bufferedInputStream = new BufferedInputStream(fileInputStream); OutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); ){ byte[] buffer = new byte[1024]; int len; while ((len = bufferedInputStream.read(buffer)) != -1) { bufferedOutputStream.write(buffer, 0, len); } System.out.println("复制完成"); } catch (IOException e) { e.printStackTrace(); } }
|
代码中创建了BufferedInputStream和BufferedOutputStream两个缓冲流对象,这两个流内部都定义了一个大小为8192的字节数组,当调用read()或者write()方法读写数据时,首先将读写的数据存入到定义好的字节数组,然后将字节数组的数据一次性读写到文件中,这种方式与前面小节中讲解的字节流的缓冲区类似,都对数据进行了缓冲,降低了IO频次,从而有效的提高了数据的读写效率。
2.2 字符流
2.2.1 概览
同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。其中Reader是字符输入流,用于从某个源设备读取字符。Writer是字符输出流,用于向某个目标设备写入字符。Reader和Writer作为字符流的顶级父类,也有许多子类,接下来通过继承关系图来列出Reader和Writer的一些常用子类。
字符流的常用方法与字节流相似。
Reader的子类:
Writer的子类:
2.2.2 读取文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) { try (Reader reader = new FileReader("java-io-demo/src/main/resources/abc.txt")){
char[] buffer = new char[3]; int len; while ((len = reader.read(buffer)) != -1) { System.out.println(new String(buffer, 0, len)); } } catch (IOException e) { throw new RuntimeException(e); } }
|
读取文件注意点:
- 读取单个字符返回的是字符的Unicode编码,读取多个字符返回的是字符个数。
2.2.3 写入文件
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
| public static void main(String[] args) { try (Writer writer = new FileWriter("java-io-demo/src/main/resources/abc-copt.txt", true)) { writer.write(97); writer.write('b'); writer.write('海'); writer.write("\r\n"); String str = "要做神仙,驾鹤飞天。点石成金,妙不可言。"; writer.write(str); writer.write("\r\n"); writer.write(str, 0, 10); writer.write("\r\n"); char[] buffer = {'春', '眠', '不', '觉', '晓'}; writer.write(buffer); writer.write("\r\n"); writer.write(buffer, 0, 3); writer.write("\r\n"); } catch (IOException e) { throw new RuntimeException(e); } }
|
2.2.4 字符转换流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static void main(String[] args) { try ( InputStream inputStream = new FileInputStream("java-io-demo/src/main/resources/abc-gbk.txt"); Reader inputStreamReader = new InputStreamReader(inputStream, "GBK"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); OutputStream outputStream = new FileOutputStream("java-io-demo/src/main/resources/abc-gbk.txt", true); Writer outputStreamWriter = new OutputStreamWriter(outputStream, "GBK"); BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); ) { String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); bufferedWriter.newLine(); bufferedWriter.write(line); } } catch (Exception e) { e.printStackTrace(); } }
|
转换流也是一种处理流,它提供了字节流和字符流之间的转换。在Java IO流中提供了两个转换流:InputStreamReader 和 OutputStreamWriter,这两个类都属于字符流。其中InputStreamReader将字节输入流转为字符输入流,继承自Reader。OutputStreamWriter是将字符输出流转为字节输出流,继承自Writer。
转换流的原理是:字符流 = 字节流 + 编码表,是字符流和字节流之间的桥梁。使用转换流可以使用指定编码进行文件的读写,可以有效避免乱码的问题。