文件IO流API
一、为什么使用IO流
我们知道File类的对象可以表示计算机硬盘中的某个文件或文件夹,但是这些文件对象只是对实体文件的抽象描述,它只包含文件的一些属性和文件创建、修改、删除操作等方法,而不能对其存储的内容进行读写。在Java中我们引入了IO流的概念,用来进行文件读写操作。
这里以文件流为例,流就像是程序和目标文件之间的通道,构造流对象传入file对象后,则像是打开了它们两端之间通道,然后就可以利用这个通道来读写数据(也可以理解为传输数据),当流对象不再使用时,就将其关闭。
二、分类和继承关系
1. 分类
- 按流的方向:输入流和输出流
- 按数据读写单位:字节流和字符流
2. 继承关系
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileReader |
三、API简介和使用
1. InputStream
InputStream是以字节为单位来读取数据,它是一个抽象类,提供了读数据的几种方法,通过构造其实现子类FileInputStream的对象来进行读文件操作。
成员方法
方法签名 | 描述 |
---|---|
read() | 从流中读取一个字节并返回,如果读到了文件末尾则返回-1 |
read(byte[] buffer) | 从流中最多读取buf.length长的数据到buf中,返回实际读取的字节数,若读到文件末尾则返回-1 |
read(byte[] buffer,int offset,int len) | 从流中最多读取len-offset长的数据到buf中,从offset位置开始,返回实际读取的字节数,若读到文件末尾则返回-1 |
close() | 关闭字节输入流 |
FileInputStream
构造方法
方法签名 | 描述 |
---|---|
FileInputStream(String name) | 根据指定文件路径来构造流对象 |
FileInputStream(File file) | 根据指定file对象来构造流对象 |
当使用FileInputStream(String name)来构造对象时,该对象内部会帮我们自动的创建一个File类对象,FileInputStream要求File类对应的实体文件必须存在,否则会抛FileNotFoundException。
1. 读文件中的内容
- 方式一:使用read()
//需要在当前工作路径下准备一个test.txt。并在文件中写入Hello,my friend.
public static void main(String[] args){
//使用FileInputStream(String name)来构造流对象
try(FileInputStream is = new FileInputStream("test.txt");){
//读文件
int b = 0;
while((b = is.read()) != -1){
//输出到控制台
System.out.printf("%c ",b);
}
}catch(IOException e){
e.printStackTrace();
}
}
- 方式二:使用read(byte[] buf) - 推荐使用
//需要在当前工作路径下准备一个test.txt。并在文件中写入Hello,my friend.
public static void main(String[] args){
//使用FileInputStream(String name)来构造流对象
try(FileInputStream is = new FileInputStream("test.txt");){
//读文件
byte[] buf = new byte[1024];
int len = 0;
while((len = is.read(buf)) != -1){
//输出到控制台
System.out.println(new String(buf,0,len));
}
}catch(IOException e){
e.printStackTrace();
}
}
2. 利用Scanner对象
我们通过字节流来读取字符并不方便,Java官方提供的java.util.Scanner类是用来将输入流中的数据分成tokens,用指定的分隔符来分割,并提供以不同类型来获取tokens值的方法。我们通常构造Scanner对象时会用new Scanner(System.in),其参数System.in代表标准输入流,通常情况下是键盘。
我们来看看Scanner对象的构造方法
Scanner类提供了很丰富的构造器,我们这里可以将自己构造的流对象传给Scanner构造器,从而初始化其内部的流对象,然后就可以使用Scanner对象的方法获取到流中的数据。
代码演示
//需要在当前工作路径下准备一个test.txt文件,并在文件中写入一些数据。
public static void main(String[] args) {
//构造Scanner类对象,传入我们构造的输入流对象
try(Scanner scanner = new Scanner(new FileInputStream("./test.txt"),"UTF-8")){
while (scanner.hasNextLine()){
//获取每一行内容,显示到控制台
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
2. OutputStream
InputStream是以字节为单位来写数据,它是一个抽象类,提供了写数据的几种方法,通过构造其实现子类FileOutputStream的对象来进行写文件操作。
成员方法
方法签名 | 描述 |
---|---|
write(int b) | 将指定的字节输出到流中 |
write(byte[] buf) | 将指定buf数组中的所有内容输出到流中 |
write(byte[] buf, int offset, int len) | 将指定buf数组中的内容从offset开始最长到len输出到流中 |
flush() | 将流中缓冲区保存的数据全部输出 |
close() | 关闭字节输出流对象 |
FileOutputStream
构造方法
方法签名 | 描述 |
---|---|
FileOutputStream(File file) | 创建一个指向file对象的字节输出流对象 |
FileOutputStream(File file, boolean append) | 创建一个指向file对象的字节输出流对象,若append参数为true,则将数据输出到该文件的末尾而不是开头 |
FileOutputStream(String name) | 创建一个字节输出流对象,指向name命名的file对象 |
FileOutputStream(String name, boolean append) | 创建一个字节输出流对象,指向name命名的file对象,若append参数为true,则将数据输出到该文件的末尾而不是开头 |
FileOutputStream不要file对应的实体文件必须存在,当不存在时会自动创建。当存在时,若没有使用包含append参数的构造器或append参数为fasle,为对原文件中的内容进行清空然后再写入。
1. 写数据到文件中
- 方式一:使用write(int b)
public static void main(String[] args) {
//创建FileOutputStream对象,并指明文件路径
try(FileOutputStream outputStream = new FileOutputStream("./demo.txt");){
//写入数据
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}catch (IOException e){
e.printStackTrace();
}
}
-
方式二:使用write(byte[] buf)
public static void main(String[] args) { //创建FileOutputStream对象,并指明文件路径 try(FileOutputStream outputStream = new FileOutputStream("./demo.txt");){ //写入数据 String data = "Happy New Year!"; outputStream.write(data.getBytes()); }catch (IOException e){ e.printStackTrace(); } }
-
方式三:使用write(byte[] buf, int offset, int len)
public static void main(String[] args) { //创建FileOutputStream对象,并指明文件路径 try(FileOutputStream outputStream = new FileOutputStream("./demo.txt");){ //写入数据 String data = "Happy New Year!"; outputStream.write(data.getBytes(),0,5); }catch (IOException e){ e.printStackTrace(); } }
2. 利用PrintWriter
使用字节流来写字符数据不是很方便,所以我们可以用Java提供的java.io.PrintWriter来进行数据写入,该类中包含了print、println、printf等方法,方便我们使用。但是使用该类需要将字节输入流先转换为字符输入流,对此Java也提供了具体的实现类OutputStreamWriter,来看看具体怎么使用。
/*
使用PrintWriter,通过层层构造流的方式
优点:可以显式调用各流的API,传入其不同流的构造器的其他参数
缺点:代码稍长
*/
public static void main(String[] args) {
//创建字节输出流对象,同时指明文件路径,并设置将数据追加到文件末尾
try(FileOutputStream fos = new FileOutputStream("./demo.txt",true)){
//创建转换流对象(字节输入流转字符输入流)
try(OutputStreamWriter osw = new OutputStreamWriter(fos)){
try(PrintWriter pw = new PrintWriter(osw)){
//写数据
pw.append("My friend.\n");
pw.write("Wish you good luck in 2023!\n");
pw.print("Your being makes this world better!\n");
}
}
}catch (IOException e){
e.printStackTrace();
}
}
/*
使用一次构造
优点:代码简洁
缺点:除了本层流以外,其他流不能指定初始化其他属性
*/
public static void main(String[] args) {
try(PrintWriter pw = new PrintWriter("./demo2.txt")){
pw.append("Happy New Year!\n");
pw.write("My friend.\n");
pw.print("Wish you good luck in 2023!\n");
pw.println("Your being makes this world better!");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
四、小程序练习
1. 指定目录文件删除
/**
* 编写一个小程序:
* 扫描指定目录,查看目录中的各文件的文件名是否包含指定关键字(不包含目录),如果包含,就提示用户是否进行删除。
* 目录中可能包含很多文件或子目录
*/
public class DirectorySearchDemo {
//创建Scanner对象
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
while(true){
//输入指定目录
System.out.println("请输入指定目录路径:");
String basePath = scanner.next();
File baseDic = new File(basePath);
//检验所输入目录
if(!baseDic.isDirectory()){
System.out.println("当前输入的路径不是一个目录!");
continue;
}
//输入关键字
System.out.println("请输入要删除的文件的关键字:");
String nameToDelete = scanner.next();
//递归遍历目录,同时将包含关键字的文件对象存入list
List<File> resultList = new ArrayList<>();
search(baseDic,nameToDelete,resultList);
//遍历resultList,询问用户是否删除文件
System.out.println("共匹配到" + resultList.size() + "个包含" + nameToDelete + "的文件。");
for(File file : resultList){
System.out.println("请问是否确认删除文件\"" + file.getName() + "\"?");
System.out.print("Y/N:");
if("Y".equalsIgnoreCase(scanner.next())){
if(file.delete()){
System.out.println("已删除!");
}else{
System.out.println("删除取消。");
}
}
}
}
}
//对给定根目录进行搜索,如有匹配指定关键字的文件就存入list里
private static void search(File baseDic, String nameToDelete, List<File> resultList) {
//查看当前根路径
System.out.println("当前扫描路径:" + baseDic.getAbsoluteFile());
//列出根目录下的所有文件
File[] files = baseDic.listFiles();
if(files == null || files.length == 0){
//如果根目录为空目录,则当前递归终止
return;
}else{
//遍历根目录下的所有文件
for(File file : files){
//如果当前文件是目录,则进行递归
if(file.isDirectory()){
search(file,nameToDelete,resultList);
}else{
//判断当前文件是否包含指定关键字
if(file.getName().contains(nameToDelete)){
resultList.add(file);
}
}
}
}
}
}
2. 指定目录文件查找
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* 编写一个小程序
* 扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
* 注意:我们现在的方案性能较差,所以尽量不要在太复杂的目录下或者大文件下实验
*/
public class DirectorySearchDemo2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true){
//输入指定目录
System.out.println("请输入指定目录:");
String basePath = scanner.next();
//检查目录输入正确性
File rootFile = new File(basePath);
if(!rootFile.isDirectory()){
System.out.println("目录路径输入错误!");
continue;
}
//输入关键字
System.out.println("请输入关键字:");
String key = scanner.next();
//检查关键字合法性
if("".equalsIgnoreCase(key) || key.length() == 0){
System.out.println("关键字输入有误!");
continue;
}
//递归遍历目录,将匹配的文件保存到list里
List<File> result = new ArrayList<>();
dirSearch(rootFile,key,result);
//遍历result,输出匹配的文件名
System.out.println("共匹配到" + result.size() + "个包含" + key + "的文件。");
for(File file : result){
System.out.println(file.getName());
}
}
}
//遍历根目录,将符合条件的文件存到list里
private static void dirSearch(File rootFile, String key, List<File> result) {
//查看当前根路径
System.out.println("当前扫描路径:" + rootFile.getAbsoluteFile());
//列出当前目录下的所有文件
File[] files = rootFile.listFiles();
if(files == null || files.length == 0){
//根目录为空就终止递归
return;
}else{
//遍历根目录中的子文件
for(File file : files){
if(file.isDirectory()){
//当前file是子目录
dirSearch(file, key, result);
}else{
//当前file是普通文件,检查是否包含关键字
if(isMatchKey(file,key)){
result.add(file);
}
}
}
}
}
//检查指定文件的文件名或内容中是否包含指定关键字
private static boolean isMatchKey(File file, String key) {
//检查文件名
if(file.getName().contains(key)){
return true;
}else{
//检查文件内容
try(FileInputStream inputStream = new FileInputStream(file);){
//使用字符串查找子串的方式
StringBuilder str = new StringBuilder();
//通过Scanner对象扫描文件内容
Scanner scanner = new Scanner(inputStream, "UTF-8");
while (scanner.hasNextLine()){
//拼接到str中,使之成为一个长字符串
str.append(scanner.nextLine());
}
//检查该长字符串中是否包含key子串
return str.indexOf(key) != -1;
}catch (IOException e){
e.printStackTrace();
}
}
return false;
}
}
3. 文件复制
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
/**
* 实现对普通文件的复制
*/
public class FileCopyDemo {
public static void main(String[] args) {
//输入源文件和目标文件路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入源文件路径:");
String sourcePath = scanner.next();
System.out.println("请输入目标文件路径:");
String destPath = scanner.next();
//对两个路径进行校验
//是否相同
if(sourcePath.equalsIgnoreCase(destPath)){
System.out.println("源文件与目标文件路径不能相同!");
return;
}
//源文件是否为普通文件
File srcFile = new File(sourcePath);
if(!srcFile.isFile()){
System.out.println("源文件路径输入错误!");
return;
}
//目标文件是否存在
File destFile = new File(destPath);
if (destFile.isFile()){
System.out.println("目标文件已存在!");
return;
}
//进行文件拷贝,创建源文件输入流和目标文件输出流
try (FileInputStream inputStream = new FileInputStream(srcFile);
FileOutputStream outputStream = new FileOutputStream(destFile)) {
//创建缓冲区
byte[] buf = new byte[1024];
while (true){
//读取源文件输入流数据
int len = inputStream.read(buf);
if(len == -1){
break;
}
//输出到目标文件
outputStream.write(buf,0,len);
}
System.out.println("复制成功!");
}catch (IOException e){
e.printStackTrace();
}
}
}
文章为本人独立编写,难免会有错误之处。
如发现有误,恳请评论提出!