Java IO处理(上
在 Java 中,将通过不同输入输出设备(键盘、内存、显示器、网络等)之间的数据传输抽象表述为“流”,程序允许通过流的方式与输入输出设备进行数据传输。Java 中的“流”都位于 java.io 包中,称为 IO (输入输出)流。
IO 流有很多种,按照操作数据的不同,可以分为字节流和字符流,按照数据传输方向的不同又可分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据。
两类流(字节 / 字符)
JAVA.IO 包定义了两类流:
- 字节流 (JDK1.0):InputStream、OutputStream
- 字符流 (JDK1.1):Reader、Writer
如果要进行输入,输出操作一般都会按照如下的步骤进行(以文件操作为例):
- 通过 File 类定义一个要操作文件的路径 (不是文件就没有这一步);
- 通过字节流或则字符流的子类对象为父类对象实例化;
- 进行数据的读(写入),写(输出)操作;
- 数据流属于资源操作,资源操作必须关闭;
字节流
在计算机中,无论是文本、图片、音频还是视频,所有文件都是以二进制(字节) 形式存在的, IO 流中针对字节的输入输出提供了—系列的流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为 字节输入流 InputStream 和 字节输出流 OutputStream。
字节输入流 InputStream 和 字节输出流 OutputStream 它们是字节流的顶级父类,所有的字节输入流都继承自 InputStream, 所有的字节输出流都继承自 Output Stream。
InputStream 常用方法
方法声明 | 功能描述 |
---|---|
int read() | 从输入流读取—个 8 位的字节,把它转换为 0-255 之间的整数,并返回这—整数. |
int read(byte[] b) | 从输入流读取若干字节,把它们保存到参数 b 指定的字节数组中,返回的整数表示读取字节的数目 |
int read(byte[] b,int off, int len) | 从输入流读取若干字节,把它们保存到参数 b 指定的字节数组中, off 指定字节数组开始保存数据的起始下标, len 表示读取的字节数目 |
void close() | 关闭此输入流并释放与该流关联的所有系统资源 |
其中,第 1 个 read() 方法是从输入流中逐个读入字节,而第 2 个和第 3 个 read() 方法则将若干字节以字节数组的形式一次性读入,从而提高读数据的效率。
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() | 关闭此输出流并释放与此流相关的所有系统资源 |
后两个方法是将若干个字节以字节数组的形式一次性写入,从而提高写数据的效率。 flush() 方法用来将当前输出流缓冲区(通常是字节数组)中的数据强制写入目标设备,此过程称为刷新。close() 方法是用来关闭流并释放与当前 IO 流相关的系统资源。
子类继承关系
InputStream 和 OutputStream 这两个类虽然提供了一系列和读写数据有关的方法,但是这两个类是抽象类,不能被实例化。因此,针对不同的功能, lnputStream 和 OutputStream 提供了不同的子类:
lnputStream 和OutputStream 的子类有很多是大致对应的
InputStream:
OutputStream:
字节流读写文件
FilelnputStream
FileInputStream 是 InputStream 的子类,它是操作文件的字节输入流,专门用于读取文件中的数据。由于从文件读取数据是重复的操作,因此需要通过循环语句来实现数据的持续读取。
DEMO:
在 src 目录下 创建 test1.txt,并输入内容。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileIOStream {
public static void main(String[] args) {
File file;
try {
FileInputStream fin = new FileInputStream("src/test1.txt");
int r = 0;
while (r !=-1){
r = fin.read();
System.out.println(r);
}
} catch (FileNotFoundException e) {
System.out.println("找不到文件");
e.printStackTrace();
} catch (IOException e){
System.out.println("IO错误");
e.printStackTrace();
}
}
}
运行后输出如下:
这段代码中 FileInputStream,在 while 循环中,每次从文件 test1.txt 中读取一字节(1B)也就是8比特的数据。然后打印出来。
因为是直接读取的字节,所以 abc 就以 ASCII 码的方式输出了,分别为 97 98 99。其中txt中另起一行的操作,在 Windows 下存为回车符’\r’换行符’\n’,分别输出为 13 和 10。接着 123 输出为 49 50 51。读取完毕后,字节流会返回 -1,这时退出循环。
FileOutputStream
FileOutputStream 是 OutputStream 的子类,它是操作文件的字节输出流,专门用于把数据写入文件。
DEMO:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileIOoutStream {
public static void main(String[] args) {
File file;
try {
FileOutputStream fout = new FileOutputStream("src/test2.txt");
String s = "Hi EmptinessBoy!";
try {
fout.write(s.getBytes());
fout.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
System.out.println("文件找不到");
e.printStackTrace();
}
}
}
运行后输出如下:
src 目录下自动生成了 text2.txt,并成功写入了预设字符串的内容。
DEMO 文件拷贝
代码:
import java.io.*;
public class FileIOStreamCopyFile {
public static void main(String[] args) {
try {
FileInputStream fin = new FileInputStream("src/logo.png");
FileOutputStream fout = new FileOutputStream("src/logo-cpoy.png");
byte[] b = fin.readAllBytes();
fout.write(b);
fin.close();
fout.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("文件打开异常");
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}
运行后的结果就是把当前 src 下面的 logo.png 拷贝了一份,并命名为 logo-copy.png。经过测试,这个新图片是可以打开的。
注意:
Files.readAllBytes(Path) 方法把整个文件读入内存,此方法返回一个字节数组,还可以把结果传递给String的构造器,以便创建字符串输出。
要注意,此方法不适合读取很大的文件,因为可能存在内存空间不足的问题。
文件拷贝缓冲区效果
因此要拷贝大文件,还是多次分段读入写入比较合适(实现缓冲区效果):
import java.io.*;
public class FileIOStreamCopyFile {
public static void main(String[] args) {
try {
FileInputStream fin = new FileInputStream("src/logo.png");
FileOutputStream fout = new FileOutputStream("src/logo-cpoy.png");
int len;
byte[] buffer = new byte[4096];
while ((len = fin.read(buffer)) > 0) {
fout.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("文件打开异常");
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}
在拷贝过程中,通过 while 循环将字节逐个进行拷贝。每循环—次,就通过 FileInputStream 的 read() 方法读取小于4096字节的数据,并通过 FileOutputStream 的 write() 方法将该字节写入指定文件,循环往复,直到 len 的值为 -1 表示读取到了文件的末尾,结束循环,完成文件的拷贝。
字节缓冲流
在 IO 包中提供两个带缓冲的字节流,分别是 BufferedInputStream 和 BufferedOutputStream,它们的构造方法中分别接收 InputStream 和 OutputStream 类型的参数作为对象,在读写数据时提供缓冲功能。
源设备 – 字节流 – 字节缓冲流 – 应用程序
DEMO:(使用刚才的文件拷贝案例)
import java.io.*;
public class BufferIOStreamCopyFile2 {
public static void main(String[] args) throws Exception {
TimeClock t = new TimeClock();
//创建一个带缓冲区的输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/logo.png"));
//创建一个带缓冲区的输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/logologo.png"));
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bis.close();
bos.close();
t.end();
}
}
BufferedInputStream 和 BufferedOutputStream 两个缓冲流对象内部都定义了一个大小为 8192 的字节数组。当调用 read() 或者 write() 方法读写数据时,首先将读写的数据存入定义好的字节数组,然后将字节数组的数据一次性读写到文件中。这种方式与之前的
byte[] b = byte[4096]
这样的字节流的缓冲区类似,都对数据进行了缓冲,从而有效地提高了数据的读写效率。
这里,本质还是一个字节一个字节的把字节读取,然后存到 len 中,然后再把 len 拷贝到 outputstream,和之前不一样的是,这里会在把数据存到 len 的过程中,进行内部的缓存。这时,再一个一个读取 len 的时候,就会比直接从外部磁盘读取要快。这个和之前使用数组实现的缓冲区 byte[] b 实现的不一样。
两者结合
当然,手动开辟的缓冲数组可以和 Buffered(In/Out)putSteream 结合在一起使用:
import java.io.*;
public class BufferIOStreamCopyFile1 {
public static void main(String[] args) {
try {
BufferedInputStream bin = new BufferedInputStream(new FileInputStream("src/logo.png"));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream("src/logo-copy-buffered.png"));
byte[] buffer = new byte[4096];
int len;
while ((len = bin.read(buffer))!=-1){
bout.write(buffer,0,len);
}
bin.close();
bout.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流
前面已经讲解过 InputStream 类和 OutputStream 类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用字符流会更加方便。
大体方法和之前字节流类似
子类继承关系
Reader
Writer
字符流操作文件
之前我们使用字节流操作文件用到的是 FileInputStream 和 FileOutputStream。而要使用字符流,只需要改用 FileReader 和 FileWriter。
FileReader
使用字符输入流 FileReader, 通过此流可以从关联的文件中读取—个或—组字符。
DEMO:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderTest {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("src/test5.txt");
int c;
while((c=fr.read())!=-1){
System.out.print((char)c);
}
fr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行后输出如下:
注意:字符输入流的 read() 方法返回的是 int 类型的值,如果想获得字符就需要进行强制类型转换。
FileWriter
要向文件中写入字符就需要使用 File Writer 类,该类是 Writer 的一个子类
DEMO:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterTest {
public static void main(String[] args) {
File file;
try {
FileWriter fr = new FileWriter("src/test6.txt");
String s = "Hi Emptinessboy!\n";
fr.write(s);
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行效果:
File Writer 同 FileOutputStream 一样,如果指定的文件不存在, 就会先创建文件,再写入数据,如果文件存在,则会首先清空文件中的内容,再进行写入。如果想在文件末尾追加数据,同样需要调用重载的构造方法,
修改之前代码的 FileWriter:
FileWriter fr = new FileWriter("src/test6.txt", true) ;
运行效果:
字符缓冲流
字符流同样提供了带缓冲区的包装流,分别是 BufferedReader 和 BufferedWriter 。
需要注意的是,在 BufferedReader 中有一个重要的方法 readline(),该方法用于一次读取—行文本。
DEMO:
import java.io.*;
public class BufferedRWCpoyFile {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new FileReader("src/test7.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src/test8.txt"));
String s;
while (true){
s = br.readLine();
if(s==null)
break;
bw.write(s);
bw.newLine();
}
bw.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行效果:
上述案例使用了输入输出流缓冲区对象,并通过 while 循环实现了文本文件的拷贝。在拷贝过程中, 每次循环都使用 readline() 方法读取文件的—行,然后通过 write() 方法写入目标文件。其中, readline() 方法会逐个读取字符, 当读到回车符 ‘\r’ 或换行符 ‘\n’ 时会将读到的字符作为一行的内容返回。
需要注意的是,由于字符缓冲流内部使用了缓冲区,在循环中调用 BufferedWriter 的 write() 方法写入字符时, 这些字符首先会被写入缓冲区, 当缓冲区写满时或调用close() 方法时,缓冲区中的字符才会被写入目标文件。
因此在循环结束时一定要调用close() 方法,否则极有可能会导致部分存在缓冲区中的数据没有被写入目标文件。i
转换流
前面提到 IO 流可分为字节流和字符流,有时字节流和字符流之间也需要进行转换。在 JDK 中提供了两个类可以将字节流转换为字符流,它们分别是 InputStreamReader 和
OutputStreamWriter。
InputStreamReader 是 Reader 的子类,它可以将一个字节输入流转换成字符输入流,方便直接读取字符。(读取字节流,最终读入字符流)
OutputStreamWriter 是 Writer 的子类,它可以将—个字节输出流转换成字符输出流,方便直接写入字符。(输入字符流,最终输出字节流)
示意图
DEMO
准备 test9.txt ,里面随便写入两行话:
import java.io.*;
public class IOStreamRW {
public static void main(String[] args) {
try {
FileInputStream fin = new FileInputStream("src/test9.txt");
InputStreamReader ir = new InputStreamReader(fin); //转换为字符流
BufferedReader br = new BufferedReader(ir); //缓冲
FileOutputStream fout = new FileOutputStream("src/test10.txt"); //写入字节流
OutputStreamWriter or = new OutputStreamWriter(fout); //转换为字符流
BufferedWriter bw = new BufferedWriter(or); //缓冲
String s;
while ((s=br.readLine())!=null){
bw.write(s);
bw.newLine();
}
bw.close();
br.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行效果:
字节流和字符流之间的转换,将字节流转换为字符流,从而实现直接对字符的读写。需要注意的是,在使用转换流时,只能针对操作文本文件的字节流进行转换,如果字节流操作的是一张图片,此时转换为字符流就会造成数据丢失。
File 类
File 类封装了一个路径,并提供了一系列的方法用于操作该路径所指向的文件。
常用方法
构造方法:
方法声明 | 功能描述r |
---|---|
File(String pathname) | 通过指定的一个字符串类型的文件路径来创建一个新的 File 对象 |
File(String parent,String child) | 根据指定的—个字符串类型的父路径和—个字符串类型的子路径(包括文件名称)创建—个 File 对象 |
File(File parent,String child) | 根据指定的 File 类的父路径和字符串类型的子路径(包括文件名称)创建一个 File 对象 |
如果程序只处理—个目录或文件,并且知道该目录或文件的路径,使用第一个构造方法较方便。如果程序处理的是—个公共目录中的若干子目录或文件,那么使用第 2 个或者第 3 个构造方法会更方便。
使用方法:
方法声明 | 功能描述 |
---|---|
boolean exists() | 判断 File 对象对应的文件或目录是否存在,若存在则返回 ture, 否则返回 false |
boolean delete() | 删除 File 对象对应的文件或目录,若成功删除则返回 true, 否则返回 false |
boolean createNewFile() | 当 File 对象对应的文件不存在时,该方法将新建—个此 File 对象所指定的新文件,若创建成功则返回 true, 否则返回 false |
String getName() | 返回File 对象表示的文件或文件夹的名称 |
String getPath() | 返回File 对象对应的路径 |
String getAbsolutePath() | 返回FIIe 对象对应的绝对路径(在 UNIX/Linux 等系统上,如果路径是以正斜线/开始的,则这个路径是绝对路径;在 Windows 等系统上,如果路径是从盘符开始的,则这个路径是绝对路径) |
String getParent() | 返回 File 对象对应目录的父目录(即返回的目录不包含最后一级子目录) |
boolean canRead() | 判断 File 对象对应的文件或目录是否可读,若可读则返回 true, 反之返回 false |
boolean canWrite() | 判断 File 对象对应的文件或目录是否可写,若可写则返回 true , 反之返回 false |
boolean is File() | 判断 File 对象对应的是否是文件(不是目录),若是文件则返回 true, 反之返回 false |
boolean isDirectory() | 判断 File 对象对应的是否是目录(不是文件), 若是目录则返回 true , 反之返回 false |
boolean isAbsolute() | 判断 File 对象对应的文件或目录是否是绝对路径 |
long lastModfied() | 返回 1970 年 1 月1 日 0 时 0 分 0 秒到文件最后修改时间的毫秒值 |
long length() | 返回文件内容的长度 |
String[] List() | 列出指定目录的全部内容,只是列出名称 |
File[] listFiles() | 返回一个包含了 File 对象所有子文件和子目录的 File 数组 |
DEMO
import java.io.File;
public class FileClass {
public static void main(String[] args) {
File f = new File("src/test11.txt");
//获取文件名
System.out.println("文件名称:"+f.getName());
//获取相对路径
System.out.println("相对路径:"+f.getPath());
//获取绝对路径
System.out.println("绝对路径:"+f.getAbsolutePath());
//获取父路径
System.out.println("父路径:"+f.getParent());
//获取最后修改时间
System.out.println("最后修改时间:"+f.lastModified());
//得到文件大小
System.out.println("文件大小为:"+f.length()+"Bytes");
//判断文件是否可读
System.out.println(f.canRead()?"可读":"不可读");
//判断是否可写
System.out.println(f.canWrite()?"可写":"不可写");
//判断是不是文件
System.out.println(f.isFile()?"是文件":"不是文件");
//判断是不是目录
System.out.println(f.isDirectory()?"是目录":"不是目录");
//判断是不是绝对路径
System.out.println(f.isAbsolute()?"是绝对路径":"不是绝对路径");
//是否成功删除文件
System.out.println("删除成功?"+f.delete());
}
}
运行效果:
未创建目标文件 test11.txt 时:
已存在目标文件 test11.txt 时:
遍历目录下的文件
File 方法中有—个list() 方法,该方法用于遍历某个指定目录下的所有文件的名称:
DEMO:
import java.io.File;
public class ListFile {
public static void main(String[] args) {
File f = new File("src/自学IO操作");
String[] file = f.list();
int i=0;
for(String s:file){
if(i%4==0)
System.out.println();
System.out.print(s+" ");
i++;
}
System.out.println();
}
}
运行后输出如下:
有时程序只是需要得到指定类型的文件,如获取指定目录下所有的 “java” 文件。针对这种需求,File 类中提供了一个重载的 list(FilenameFilter filter) 方法,该方法接收—个 FilenameFilter 类型的参数。
过滤后缀名
FilenameFilter 是一个接口,被称作文件过滤器,当中定义了一个抽象方法 accept(File dir,String name)。在调用 list() 方法时,需要实现文件过滤器 FilenameFilter, 并在 accept() 方法中做出判断,从而获得指定类型的文件。
DEMO:
import java.io.File;
import java.io.FilenameFilter;
public class ListFileFilter {
public static void main(String[] args) {
File f = new File("src");
FileFilter ff = new FileFilter();
if(f.exists()){
String[] s = f.list(ff); //传入接口
int i=0;
for(String temp:s){
if(i%4==0)
System.out.println();
System.out.print(temp+" ");
i++;
}
System.out.println();
}
}
}
class FileFilter implements FilenameFilter{ //实现接口方法
@Override
public boolean accept(File dir, String name) {
if(dir.exists()&&name.endsWith(".png"))
return true; //png结尾的返回true
return false;
}
}
在接口 FilenameFilter 的 accept() 方法中, 对当前正在遍历的 dir 对象进行了判断,只有当 dir 对象代表文件,并且扩展名为 “.png” 时,才返回 true。在调用 File 对象的 list() 方法时, 将 filter 过滤器对象传入,就得到了包含所有 “.png” 文件名字的字符串数组
遍历目录树(原创代码)
在—个目录下,除了文件,还有子目录,如果想得到所有子目录下的 File 类型对象,list() 方法显然不能满足要求,这时需要使用 File 类提供的另一个方法 listFiles()
import java.io.File;
public class ListFilesTest {
public static void main(String[] args) {
File f = new File("src");
ListFileTree tree = new ListFileTree();
tree.listTree(f);
}
}
实现的类代码:
//自个写了给列出文件树类
class ListFileTree{
int i=0;
void listTree(File f){
File[] fl = f.listFiles();
for(File temp:fl){ //遍历文件序列
for(int j=0;j<i;j++)
System.out.print("\t"); //打印缩进空格
if(temp.isDirectory()){
System.out.println(">> "+temp.getName()); //是文件夹的话,就加两个箭头区分
i++; //统计文件夹层级
listTree(temp); //递归列出子目录
}
else
System.out.println(temp.getName());
}
i=0; //一个父文件夹列完后,将层级清零
}
}
运行效果:
资源管理器的文件结构:
可以看到,打印出来的树和系统资源管理器显示的完全一致!
删除文件及目录
可以使用 File 类的 delete() 方法删除文件。
(基本的删除用法)[#DEMO-1],会返回一个 boolean 值来反馈是否成功删除文件。但是对于文件夹(目录),无法使用简单的 delete 来一次性删除,必须一个个将文件夹内文件删除后,才能删除文件夹。
DEMO:
import java.io.File;
public class DeleteFolder {
public static void main(String[] args) {
File f = new File("src/rubbish");
System.out.println(deleteDir(f));;
}
//递归删除函数
static boolean deleteDir(File f){
if(f.exists()){
File[] fl =f.listFiles();
for(File temp:fl){
if(temp.isDirectory())
deleteDir(temp);
else
temp.delete();
}
return f.delete();
}
else
return false;
}
}
运行前目录:
运行后效果:
可以看到文件被删除,并且返回了 true。

This blog is under a CC BY-NC-ND 4.0 Unported License
本文链接:https://coding.emptinessboy.com/2020/05/Java-IO%E5%A4%84%E7%90%86/