目录
针对文件内容,使用“流对象”进行操作
Java标准库的流对象,从类型上分为两个大类:
1.字节流——操作二进制数据的
InputStream——FileInputStream
OutputStream——FileOutStream
2.字符流——操作文本数据的
Reader——FileReader
Writer——FileWriter
这些类的使用方式是非常固定的,核心就是四个操作:
1)打开文件(构造对象)
2)关闭文件(close)
3)读文件(read)——针对InputStream和Reader
4)写文件(write)——针对OutputStream和Writer
我们可以看到,read有三个版本:
1)read无参数版本:一次读一个字节
2)read一个参数版本:把读到的内容填充到参数的字节数组里(此处的参数是个“输出型参数”),返回值是实际读取的字节数。
3)read三个参数版本:和2类似,只不过是往数组的一部分区间里尽可能填充
下面我们用字节流来读取文件:
1)read无参数版本:一次读一个字节
//使用字节流来读取文件
public class IODemo3 {
public static void main(String[] args) throws IOException {
//创建InputStream对象的时候,使用绝对路径或者相对路径,都是可以的,也可以使用File对象。
InputStream inputStream = new FileInputStream("d:/test.txt");
//进行读操作
while (true) {
int b = inputStream.read();
if (b == -1) {
//读取完毕
break;
}
System.out.println("" + (byte) b);
}
inputStream.close();//关闭
}
}
这些数字,就是hello的Ascii码
2)read一个参数版本:
while (true) {
byte[] buffer = new byte[1024];//提前准备好一个数组
int len = inputStream.read(buffer);
//这里的传参操作,是把准备好的数组,交给read方法,让read方法内部针对这个数组进行填写(此处参数相当于“输出型参数”)
System.out.println("len:" + len);
if (len == -1) {
break;
}
//此时读取的结果就被放到 byte 数组中
for (int i = 0; i < len; i++) {
System.out.printf("%x\n",buffer[i]);
}
}
inputStream.close();//关闭
}
我们读一个大一点的文件观察:
上面我们使用了InputStream来读文件,我们还可以使用OutputStream来写文件:
public class IODemo4 {
//进行写文件
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("d:/test.txt");
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
outputStream.close();
}
}
同样的,write也有三种用法
大家有没有发现一个问题,我们每次结束都要调用close()操作呢,这是为什么呢?
当然一般写代码,还是要注意这个close,,那么如何才能确保这个close被执行到呢?这里有一个好的写法:
public static void main(String[] args) throws IOException {
try (OutputStream outputStream = new FileOutputStream("d:/test.txt")){
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.write(100);
}
}
这个写法虽然没有显示的写close,但只要try语句块执行完毕,就可以自动执行到close!
这个语句,在Java中被称为 try with resources,那我们是不是随便拿个对象放到try()里就能够自动释放呢?——得满足一定条件!
很明显,这里的txt是文本文件,使用字节流可以读,但是不方便,我们更希望使用的是一个个字符表示,所以使用字符流,会更方便:
Reader
public static void main1(String[] args) {
try (Reader reader = new FileReader("d:/test.txt")) {
while (true) {
int ch = reader.read();
if (ch == -1) {
break;
}
System.out.println("" + (char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
Writer
public static void main(String[] args) {
try (Writer writer = new FileWriter("d:/test.txt")){
writer.write(("hello world"));
} catch (IOException e) {
e.printStackTrace();
}
}
像这样的写操作,其实是先写到缓冲区里(缓冲区存在很多种形态,咱们自己的代码里可以有缓冲区;标准库里也可以有缓冲区;操作系统内核里也可以有缓冲区.....)
写操作执行完了,内容可能在缓冲区里,还没有真的进入硬盘。
close操作,就会触发缓冲区的刷新(刷新操作,就是把缓冲区里的内容写到硬盘)
除了close之外,还可以通过flush方法,也能起到刷新缓冲区的效果。
public static void main(String[] args) {
try (Writer writer = new FileWriter("d:/test.txt")) {
writer.write(("hello world"));
//手动刷新缓冲区
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
其他流对象:Scanner
public static void main(String[] args) {
//Scanner scanner = new Scanner(System.in);//System.in其实就是一个流对象
try (InputStream inputStream = new FileInputStream("d:/test.txt")) {
Scanner scanner = new Scanner(inputStream);//Scanner的close本质上是要关闭内部包含的这个流对象
//此时,内部的inputStream对象已经被try()关闭了,里面的这个Scanner不关闭也没事
//此时读取的内容就是从 文件 进行读取了
scanner.next();
} catch (IOException e) {
e.printStackTrace();
}
}
下面我们写几个小程序来练习一下文件的操作
小程序1
public class IODemo7 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
//让用户输入一个指定搜索的目录
System.out.println("请输入要搜索的路径:");
String basePath = scanner.next();
//针对用户输入进行简单的判定
File root = new File(basePath);
if (!root.isDirectory()) {
//路径不存在,或者只是一个普通文件,此时无法进行搜索
System.out.println("输入的目录有误!");
return;
}
//再让用户输入一个要删除的文件名
System.out.println("请输入要删除的文件名:");
//此处要使用next,不能使用nextLine()!!!
String nameToDelete = scanner.next();
//针对指定的路径进行扫描,递归操作
//先从根目录出发(root)
//先判定一下,当前这个根目录里,是否包含要删除的文件,如果是,就删除;否则就跳过下一个
//如果当前包含了一些目录,针对子目录再进行递归
scanDir(root, nameToDelete);
}
private static void scanDir(File root, String nameToDelete) {
System.out.println("[scanDir]" + root.getAbsolutePath());
//1.先列出 root 下的文件和目录
File[] files = root.listFiles();
if (files == null) {
//当前 root 目录下没东西,是一个空目录
//结束继续递归
return;
}
//2.遍历当前的列出结果
for (File f : files) {
if (f.isDirectory()) {
//如果还是目录,进一步递归
scanDir(f, nameToDelete);
} else {
//如果是普通文件,判定是否要删除
if (f.getName().contains(nameToDelete)) {
System.out.println("确认是否要删除" + f.getAbsolutePath() + "?");
String choice = scanner.next();
if (choice.equals("y") || choice.equals("Y")) {
f.delete();//包含就删除
System.out.println("删除成功!");
} else {
System.out.println("取消删除!");
}
}
}
}
}
}
小程序2
public class IODemo8 {
public static void main(String[] args) {
//输入两个路径
//源 和 目标,(从哪里,拷贝到哪里)
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要拷贝哪个文件:");
String srcPath = scanner.next();
System.out.println("请输入要拷贝到哪个地方:");
String destPath = scanner.next();
File srcFile = new File(srcPath);
if (!srcFile.isFile()) {
//如果源不是一个文件(是个目录或者不存在)
//此时就不做任何操作
System.out .println("您当前输入的源路径有误!");
return;
}
File destFile = new File(destPath);
if (destFile.isFile()) {
//如果已经存在,认为也不能拷贝
System.out.println("您当前输入的目标路径有误!");
return;
}
//进行拷贝操作
//try()语法,支持包含多个流对象。多个流对象之间,使用;分隔开
try (InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)) {
//进行读文件操作
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
outputStream.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}