
文件内容的操作(读写文件)
Java中,针对文件内容的操作,主要是通过一组"流对象"来实现的。
场景: 计算机中的" 流 "
可以理解,从文件中读取100byte的数据,有无数种读数据的方法(如1次把100byte都读完或者分2次一次读50byte等等)
因此,计算机针对读写文件,使用流(Stream),流是操作系统层面的术语,和语言无关。而Java中提供了一组类来表示"流"。
针对上述Java提供的多个流,分成两大个类别。
- 字节流
读写文件,以字节为单位;针对二进制文件使用。
- InputStream 输入
- OutputStream 输出
- 字符流
读写文件,以字符为单位;针对文本文件使用。
- Reader 输入
- Write 输出
Java中其他的流对象都直接/间接继承自这几个类。
- 从硬盘===>CPU ,叫输入
- 从CPU===>硬盘 , 叫输出
拓展: 一个字符可能对应多少个字节?这是不确定的,取决于编码方式的。如GBK编码1个汉字占2个字节,utf-8编码1个汉字占3个字节,unicode编码1个汉字占4个字节。
字节流:InputStream

InputStream流对象体系,不仅仅给文件提供操作,网络编程也离不开流。

文件资源不等同于内存资源,虽然GC能够自动管理内存,但是不能自动管理文件,需要手动释放,若不释放就会导致 "文件资源泄露" 。
简单理解,每次程序打开一个文件,就会在文件描述符表中申请一个表项,如果光打开,不关闭,就会使这里的文件描述符表的表项耗尽,后续再次打开,就会打开失败!!!
因此,为防止各种异常/return带来不能及时关闭文件资源于是采用try finally来解决
java
public class Demon7 {
public static void main(String[] args) throws IOException {
InputStream inputStream=null;
try{
//进行创建对象操作,一旦成功,就相当于"打开文件"
inputStream=new FileInputStream("./test.txt");
}finally {
inputStream.close();//关闭资源
}
}
}
由于上述采用try finally 不够简洁,于是再次改进采用Java1.5引入的语法try with resources

try()里面可以写多个对象
读取文件里面的内容


java
public class Demon7 {
public static void main(String[] args) throws IOException {
try (InputStream inputStream=new FileInputStream("./test.txt")){
//读文件操作
while (true){
int data=inputStream.read();
if (data==-1){
//返回-1 文件读完
break;
}
System.out.println(data);
}
}
}
}
此方法是按照字节读取。

通过查ASCII表就能提取到test.txt里面写的内容。
一次读取若干字节通过数组进行传参并存储
java
public class Demo8 {
public static void main(String[] args) throws IOException {
try(InputStream inputStream=new FileInputStream("./test.txt")){
while (true){
//一次读多个字节,数组的长度,自行定义
byte[] data=new byte[3];
//读操作,就会尽可能的把字节数组给填满
//若填不满,则能填几个算几个
//n代表实际读了几个字节
int n=inputStream.read(data);
System.out.println("n= "+n);
if (n==-1){
//文件读完
break;
}
for (int i = 0; i < n; i++) {
System.out.printf("0x%x\n",data[i]);//0x%x采用16进制进行输出
}
System.out.println("==========");
}
}
}
}

字节流:OutputStream

java
public class Demo12 {
public static void main(String[] args) {
try(OutputStream outputStream=new FileOutputStream("./output.txt")) {
//向output.txt文件写97在ascii表中就是a,若指定文件不存在则进行创建
//一个字节一个字节读
// outputStream.write(97);
// outputStream.write(98);
// outputStream.write(99);
//读若干字节
byte[] data={99,98,97};
outputStream.write(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
对于OutputStream,默认情况下会尝试创建不存在的写入文件,而且每次写入会清除上次写入的文件内容。
解决每次写入会清除上次写入在文件里的内容,因此采用追加(append)的方式

字符流 :Reader

java
public class Demo13 {
public static void main(String[] args) {
try(Reader reader=new FileReader("./output.txt")) {
while (true){
//一次读一个字符
int c=reader.read();
if (c==-1){
return;
}
System.out.println((char) c);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
java
public class Demo14 {
public static void main(String[] args) {
try(Reader reader=new FileReader("./output.txt")) {
while (true){
//基于数组,读字符数组
char[] buf=new char[1024];
int n=reader.read(buf);
if (n==-1){
break;
}
for (int i = 0; i < n; i++) {
System.out.println(buf[i]);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
字符流 :Writer
java
public class Demo15 {
public static void main(String[] args) {
try(Writer writer=new FileWriter("./output.txt",true)) {
writer.write("hello world!");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
知识拓展: 通常就是一段内存空间提高程序效率。由于直接读写硬盘,比较低效;因此进行IO的时候,就希望能够使用缓冲区,把要写的数据,先放到缓冲区里临时存储,在一起写;读也不是一次一次的读,一次读一批数据到缓冲区,在慢慢进行解析。
当前IO流对象,read和write就属于直接读写文件
要想提高效率
- 写代码的时候,手动创建缓冲区(byte[ ]),手动减少read write的次数。
- 使用标准库提供的"缓冲区流"BufferedStream,把InputStream之类的对象套上一层。
代码案例练习
示例1
扫描指定⽬录,并找到名称中包含指定字符的所有普通文件(不包含⽬录),并且后续询问用户是否要删除该文件。
java
public class Demo9 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要搜索的目录:");
String rootDir=scanner.next();
File rootFile=new File(rootDir);
if(!rootFile.isDirectory()){
System.out.println("您输入的不是目录!");
return;
}
System.out.println("请输入您要删除的关键字:");
String keyword=scanner.next();
scanDir(rootFile,keyword);
}
private static void scanDir(File rootFile, String keyword) {
//1.列出当前目录中所包含的内容
File[] files=rootFile.listFiles();
if(files==null){
System.out.println("当前目录为空!");
return;
}
//2.遍历当前目录的内容
for (File file:files) {
//System.out.println("遍历目录&文件:"+file.getAbsolutePath());
//3.判断当前文件是目录还是普通文件
if (file.isFile()){
//4.如果是普通文件,则判断文件名是否包含关键字
dealFile(file,keyword);
}else {
//5.如果是目录,则递归调用本方法
scanDir(file,keyword);
}
}
}
private static void dealFile(File file, String keyword) {
if (file.getName().contains(keyword)){
System.out.println("发现文件"+file.getAbsolutePath()+",包含关键字!是否删除?(y/n):");
Scanner scanner=new Scanner(System.in);
String input=scanner.next();
if (input.equalsIgnoreCase("y")){
file.delete();
System.out.println("文件已删除!");
}
}
}
}

示例2
进⾏普通⽂件的复制。
java
public class Demo11 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入源文件路径:");
String scrPath=scanner.next();
File scrFile=new File(scrPath);
if (!scrFile.isFile()){
System.out.println("源文件不存在或不是文件");
return;
}
System.out.println("请输入目标文件路径:");
String destPath=scanner.next();
File destFile=new File(destPath);
//要求destFile不一定存在,但是destFile所在的目录必须存在
if (!destFile.getParentFile().isDirectory()){
System.out.println("目标文件所在目录不存在");
return;
}
//进行文件复制操作
//此处不应该使用追加写,需要确保目标文件和源文件一模一样
try(InputStream inputStream=new FileInputStream(scrFile);
OutputStream outputStream=new FileOutputStream(destFile)) {
while (true){
byte[] buf=new byte[1024];
int n=inputStream.read(buf);
if (n==-1){
break;
}
//此处的write不应该写整个buf数组的
//buf数组不一定被填满,要按照实际的n这个长度来写入
outputStream.write(buf,0,n);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

示例3
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
java
public class Demo16 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要搜索的路径:");
String rootPath=scanner.next();
File rootFile=new File(rootPath);
if(!rootFile.isDirectory()){
System.out.println("输入的路径不是目录!");
return;
}
System.out.println("请输入要搜索的关键字:");
String keyword=scanner.next();
scanDir(rootFile,keyword);
}
private static void scanDir(File rootFile, String keyword) {
//列出目录下的所有内容
File[] files=rootFile.listFiles();
if (files==null){
System.out.println("当前目录为空!");
return;
}
for (File file:files) {
if (file.isFile()){
//是普通文件
dealFile(file,keyword);
}else {
//是目录递归调用
scanDir(file,keyword);
}
}
}
private static void dealFile(File file, String keyword) {
if (file.getName().equalsIgnoreCase(keyword)){
//1.包含关键字打印文件名
System.out.println("发现文件"+file.getAbsolutePath()+" 包含关键字");
return;
}
//2.判断文件内容是否包含关键字
StringBuffer stringBuffer=new StringBuffer();
try(Reader reader=new FileReader(file)) {
while (true){
char[] buf=new char[1024];
int n= reader.read(buf);
if (n==-1){
break;
}
stringBuffer.append(buf,0,n);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
//判断stringBuffer是否包含关键字
if (stringBuffer.indexOf(keyword)>=0){
//包含关键字
System.out.println("文件内容包含关键字:"+file.getAbsolutePath());
}
return;
}
}

拓展 文件的元数据(Meta Data) 指文件的大小,文件的类型,文件的创建者,文件的最后修改时间等属性。

