第3节 IO(上)
一、File类与文件基本操作
在程序中经常需要用到文件的操作,Java有专门的类来进行文件的操作——File
类。
1.1 File类概述
它是对文件和目录路径名的抽象表示。 即它本身不是一个文件,只是一个抽象表示,一个用于操作文件的对象(实现后)。
用户界面和操作系统使用依赖于系统的路径名字符串来命名文件和目录。此类提供了一个抽象的,与系统无关的分层路径名视图。
1.2 绝对路径于相对路径
-
绝对路径 :从盘符开始,是一个完整的路径,例如:c://a.txt;
-
相对路径 :在Java代码中是相对于项目目录路径 ,这是一个不完整的便捷路径,在Java开发中很常用。
看一个例子就明白了:
package com.kaikeba.coreclasslibrary.io;import java.io.File;public class path {public static void main(String[] args) {File file1 = new File("c://a.txt");File file2 = new File("a.txt");System.out.println("file1的路径:"+file1.getAbsolutePath());System.out.println("file2的路径:"+file2.getAbsolutePath());}
}结果如下:
file1的路径:c:\a.txt
file2的路径:E:\JAVAEE开发工程师\code\classcode\a.txt
其中项目的路径就是E:\JAVAEE开发工程师\code\classcode
。
1.3 File类字段
这边的路径分隔符和默认名称分隔符是为了适应不同的os,因为在不同的os上它们是不一样的,为了避免经常修改这些,可以利用File类给出的这些字段,在不同的os上会给出相应的分隔符,下面看一下Windows上的分隔符是什么:
System.out.println(File.pathSeparator);
System.out.println(File.separator);结果如下:
;
\
1.4 构造方法
parent和child构造的方法,parent是目录,child是具体文件名。
1.5 方法
1、三个测试:
2、比较两个抽象路径名:
3、在该路径下创建文件:存在则不新建
4、删除文件:
5、判断是否存在该文件:
6、得到绝对路径:可以是File类或String字符串
7、得到文件或目录名称:
8、测试是否是绝对路径、目录、文件、隐藏文件:
9、返回文件的长度(大小):
10、列出该目录下的所有文件或字符串路径:
11、新建目录或递归新建目录
上述就是一些常用方法。
1.6 文件遍历与文件过滤器
1.6.1 文件遍历(充分利用File类的方法)
如何在一个路径下找出所有的PDF文件?这就需要遍历路径下的所有文件和目录,遇到目录还要在进目录继续遍历:
代码如下:
package com.kaikeba.coreclasslibrary.io;import java.io.File;public class listfiles {public static void main(String[] args) {File e = new File("e:\\");File[] files = e.listFiles();listFiles(files);}private static void listFiles(File[] files) {if(files != null && files.length > 0) {for(File file:files) {if(file.isFile()) {//文件if(file.getName().endsWith(".pdf")) {//找到了一个大于10M的PDF文件if(file.length()>10*1024*1024) {System.out.println(file.getAbsolutePath());}}}else {//文件夹,继续递归遍历File[] files2 = file.listFiles();listFiles(files2);}}}}
}结果就是遍历的所有在E盘下的PDF的路径...
1.6.2 文件过滤器
上述遍历的框架已经有了,但是在判断是否是我们要找的文件时if判断是我们自己写的,还有一种方法,虽然不常用,但是也可以了解。
listFiles
方法的参数里有一个FileFilter
类型的filefilter
变量:
FileFilter
是一个接口,只有一个抽象方法:
可以把判断条件写在accept
里面,如果返回true就保存该文件,返回false就不保留该文件,例子如下:
package com.kaikeba.coreclasslibrary.io;import java.io.File;
import java.io.FileFilter;public class filefilter2 {public static void main(String[] args) {File e = new File("e:\\");listFiles(e);}private static void listFiles(File file) {//1. 创建一个过滤器规则 并 描述规则//2. 通过文件获取子文件夹File[] files = file.listFiles(new FileFilter() {@Overridepublic boolean accept(File pathname) {if(pathname.getName().endsWith(".pdf") || pathname.isDirectory()) {return true;}return false;}});if(files != null || files.length > 0) {for (File f : files) {if (f.isDirectory()) {listFiles(f);} else {System.out.println(f.getAbsolutePath());}}}}
}返回结果如上述代码一致。
二、流(输入输出)概述
计算机中的任何数据(文本,图片,视频,音乐等等)都是以二进制形式存储的。在数据传输时,也都是以二进制形式存储的。后续学习的任何流,在传输时底层都是二进制。
可以将数据传输的操作,看做一种数据的流动。按照流动的方向,分为输入Input和输出Output。
Java中的IO操作主要指的是java.io包下的一些常用类的使用,通过这些类,对数据进行读取(输入Input)和写出(Output)。
IO流的分类:
-
按照流的方向来分,可以分为:输入流和输出流 ;
-
按照流动的数据类型来分,可以分为:字节流和字符流 。
字节流:
-
输入流:
InputStream
-
输出流:
OutputStream
字符流:
-
输入流:
Reader
-
输出流:
Writer
三、字节流
3.1 字节输出流OutputStream类
3.1.1 OutputStream类概述
此抽象类是表示输出字节流的所有类的超类,输出流接收输出字节并将它们发送到某个接收器。它的实现子类最常用的就是:FileOutputStream
类。
方法:
1、关闭输出流:
2、刷新缓存,强制写出:
3、写出操作:将字节数组或字节写出
3.1.2 FileOutputStream类
是OutputStream
类最常用的一个实现类。
构造方法:
指定要输出写入的文件对象或String
路径(文件不存在会自动创建),append
表示是否追加写入,true
为追加模式,false
为清空内容从头写入。
方法: 基本就是使用OutPutStream
类的方法。
看个例子:
package com.kaikeba.coreclasslibrary.io;import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class outputstream {public static void main(String[] args) throws IOException {//FileOutputStreamFileOutputStream fos = new FileOutputStream("a.txt");
// byte[] bytes = {65,66,67,68,69};
// fos.write(65);byte[] bytes = "ABCDEF".getBytes();
// fos.write(bytes);fos.write(bytes, 1, 2);fos.close();System.out.println("已经写出");}
}结果为:
BC
3.2 字节输入流InputStream类
3.2.1 InputStream类概述
此抽象类是表示输入字节流的所有类的超类。最常见的子类是FileInputStream
类。
方法:
1、关闭输入流:
2、读取下一个字节:
3、读取一些字节:
4、读取一些字节,并指定开始位置和读取个数:
注意: 读取的方法返回的都是读取到的有效字节的个数 ,如果已经到文件末尾,将会返回-1 。
5、其他一些读取方法:
6、跳过并丢弃输入流的n字节数据:
3.2.2 FileInputStream类
是InputStream
类最常用的一个子类。
构造方法:
给定要读取的文件。
方法:
看一个例子:
首先在a.txt文件中写有Hello InputStream的内容。下面来将其文件中的内容读取到程序中:
package com.kaikeba.coreclasslibrary.io;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;public class inputstream {public static void main(String[] args) throws IOException {//FileInputStreamInputStream fis = new FileInputStream("a.txt");while(true) {byte b = (byte)fis.read();//b如果为-1,表示已经读取到文件末尾,读取完毕if(b == -1) {break;}System.out.print((char)b);}}
}结果为:
Hello InputStream
但是这种方式每次只读取一个字节,需要很多次读取(read)操作,与文件的连接次数太多会拖慢程序的速度,所以可以使用read的另一个方法:
package com.kaikeba.coreclasslibrary.io;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;public class inputstream {public static void main(String[] args) throws IOException {//FileInputStreamInputStream fis = new FileInputStream("a.txt");//推荐下面这种方式读取,因为读取的次数较少byte[] bytes = new byte[10];int len = fis.read(bytes);System.out.println(new String(bytes,0,len));len = fis.read(bytes);System.out.println(new String(bytes,0,len));len = fis.read(bytes);System.out.println(len);fis.close();}
}结果如下:
Hello Inpu
tStream
-1
用一个字节数组来接收读取到的固定长度的数据,但是要注意,读到最后,如果不满10个字节了,那么返回的将不是希望的结果,因为是将新读取到的覆盖数组,未满10个最后几个后面将不会被覆盖,即多出几个没用的字节。
解决:按照上述的写法,将成功读取到的字节个数接收,并用String的bytes数组指定长度的构造方法来输出。
优点:这样大大减少了连接文件的次数,加速了程序的运行。
3.2.3 字节流读取文字
如果将上述a.txt中的内容换成中文,比如 锄禾日当午,汗滴禾下土 ,再来看上述代码的输出:
锄禾日�
��午,�
�滴禾下
这并不是乱码,因为中文编码一般不是一个字节来表示一个字,所以它10个字节可能读到的是3个半字,那半个字是无法恢复的,它比乱码更加可怕 ,这就是字节流的缺点所在,后续学习字符流就是专门解决读取文字的问题。
四、字符流
字符流与字节流的区别就是字节流是一个字节一个字节的传输,但是字符流不太一样,虽然内部还是传输的字节,但是因为一个字符,比如一些非英文文字,需要几个字节来表示一个字符,所以它会一次读或写一个字符,这样就可以避免3.2.3出现的问题。
4.1 字符输出流Writer类
4.1.1 Writer类概述
用于写入字符流的抽象类。最常用的子类为OutputStreamWriter
下的FileWriter
。其中OutputStreamWriter
后续介绍。
方法:
比较常用的有:
1、append
:将字符或序列追加到此Writer,注意它的返回类型为Writer,即追加完后的文件自己。
其实它内部调用的还是write
方法,但是write
方法没有返回,这就使得append
方法可以连续操作(当然用write
也可以达到一样的效果)。
2、close
:关闭流,关闭之前先刷新缓存
3、flush
:刷新缓存,如果不刷新,内容是写在缓存区的,还没有真正写入文件:
close
方法中已经调用了flush
,这里我们不使用close
,只看使用flush
和不使用flush
的区别:
目前b.txt是空的,下面调用:
public class flush {public static void main(String[] args) throws IOException {FileWriter fw = new FileWriter("b.txt", true);fw.append("锄禾日当午").append(",").append("汗滴禾下土");}
}
结果还是空的,因为没有刷新缓冲区:
如果加上flush:
FileWriter fw = new FileWriter("b.txt", true);
fw.append("锄禾日当午").append(",").append("汗滴禾下土");
fw.flush();
结果为:
4、write
:写一个字符数组,字符数组的一部分,一个字符,一个字符串,字符串的一部分:
4.1.2 FileWriter类
使用默认缓冲区大小将文本写入字符文件。
构造方法:
参数:
1、File file / String fileName
:要将内容写入的文件;
2、Charset charset
:指定编码集,常见的有"gbk"、“uft8”;
3、boolean append
:是否是追加模式写入;
方法没有新的,都是用的或重写的父类方法。
看个例子:
package com.kaikeba.coreclasslibrary.io;import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;public class writer {public static void main(String[] args) throws IOException {//Writer//FileWriterFileWriter fw = new FileWriter("b.txt");fw.write('a');fw.write("锄禾日当午");//注意append的用法,因为它返回文件本身fw.append(",").append("汗滴禾下土");fw.close();}
}b.txt中的内容为:
a锄禾日当午,汗滴禾下土
4.2 字符输入流Reader类
4.2.1 Reader类概述
用于读取字符流的抽象类。常用的子类为InputStreamReader
下的FileReader
类。
方法:
常用的有:
1、close
:关闭流
2、read
:读入一个字符,返回的就是它的int型编码
3、将字符读入数组(一部分)或指定缓冲区
4.2.2 FileReader类
使用默认缓冲区大小从字符文件中读取文本,使用指定的编码集或默认编码集。
构造方法:
参数:
1、File file / String fileName
:从哪个文件读取;
2、Charset charset
:指定编码集。
方法也没有新的。
看个例子:
package com.kaikeba.coreclasslibrary.io;import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;public class reader {public static void main(String[] args) throws IOException {//ReaderFileReader fr = new FileReader("b.txt");/*while(true) {int c = fr.read();if(c == -1) {break;}System.out.print((char)c);}*/System.out.println("a:"+fr.read());System.out.println("锄:"+fr.read());char[] chars = new char[100];int len = fr.read(chars);System.out.println(new String(chars, 0, len));fr.close();}
}结果如下:
a:97
锄:38148
禾日当午,汗滴禾下土
五、字节流转换字符流
5.1 InputStreamReader类
InputStreamReader
类是从字节流到字符流的桥接器,这也是比较常用的,比如从外界接收到的流一般都是字节流,如果要正确读取文字的话,需要转为字符流。每次调用read方法都可能导致从底层字节输入流中读取一个或者多个字节。
它是Reader
类的一个子类,是FileReader
的父类。
构造方法:
主要的参数是要传入一个字节输入流InputStream
。
方法:
主要是read
方法,可以读取一个字符或者将字符输入数组的一部分。
看一个例子:
public class zhuanhuanliu {public static void main(String[] args) throws IOException {//字节流 ‘装饰’为 字符流 :使用了装饰者设计模式//自己新建一个字节输入流,用于模仿外界接收到的FileInputStream fis = new FileInputStream("b.txt");//将字节输入流,转换为字符输入流//参数1:要转换的字节流//参数2:指定编码名称InputStreamReader isr = new InputStreamReader(fis,"utf8");while(true) {int c = isr.read();if(c == -1) {break;}System.out.print((char)c);}}
}结果就是将b.txt中的内容全部输出
5.2 OutputStreamWriter类
OutputStreamWriter
是从字符流到字节流的桥接器,使用指定的charset将写入其中的字符编码为字节。调用write方法都会导致在给定字符上调用编码转换器,生成的字节在写入底层输出流之前在缓冲区中累积。
它是Writer
类的子类,是FileWriter
类的父类。
构造方法:
主要参数是要转换的输出字节流。
方法:
写一个字符或字符串或字符数组的一部分。
看一个例子:
public class zhuanhuanliu2 {public static void main(String[] args) throws IOException {//转换流//字符流 ‘装饰’为 字节流 :使用了装饰者设计模式FileOutputStream fos = new FileOutputStream("b.txt");OutputStreamWriter osw = new OutputStreamWriter(fos);osw.write("床前明月光");osw.close();}
}即将“床前明月光”写入b.txt。
六、打印流与缓存流
6.1 PrintStream类
PrintStream
类向另一个输出流添加功能,即能够方便地打印各种数据值的表示。
构造方法:
传入的参数主要是要写入的文件,可以是File型,String型或者字节输出流。
方法:
1、追加:
2、关闭、刷新:
3、打印
4、打印并换行:
5、写入:
看个例子:
public class print_bufferedreader {public static void main(String[] args) throws IOException {//(字符输出)打印流,注意它会自动刷新PrintStream ps = new PrintStream("b.txt");ps.println("锄禾日当午1");ps.println("锄禾日当午2");ps.println("锄禾日当午3");}
}b.txt中的内容就是:
锄禾日当午1
锄禾日当午2
锄禾日当午3
6.2 PrintWriter类
它和PrintStream
类基本上差不多,主要的区别是写入的时候不会自动刷新。
public class print_bufferedreader {public static void main(String[] args) throws IOException {PrintWriter pw = new PrintWriter("b.txt");pw.println("锄禾日当午1");pw.println("锄禾日当午2");pw.println("锄禾日当午3");pw.flush();}
}b.txt中的内容就是:
锄禾日当午1
锄禾日当午2
锄禾日当午3
也可以用输出流作为参数:
public class print_bufferedreader {public static void main(String[] args) throws IOException {FileOutputStream fos = new FileOutputStream("b.txt");PrintWriter pw = new PrintWriter(fos);pw.println("勇敢牛牛");pw.close();}
}b.txt中的内容就是:
勇敢牛牛
6.3 BufferedReader类
从字符输入流中读取文本,缓冲字符,以便有效地读取字符,数组和行。可以指定缓冲区大小,或者可以使用默认大小。
构造方法:
方法:
它比较独特的一个方法是readLine
方法:读取一行文字,看一个例子:
public class print_bufferedreader {public static void main(String[] args) throws IOException {//缓存读取流,将字符输入流 转换为带有缓存 可以一次读取一行的缓存字符读取流FileReader fw = new FileReader("b.txt");BufferedReader br = new BufferedReader(fw);String text = br.readLine();System.out.println(text);}
}结果解释读取b.txt中的一行文字。
七、收集异常日志
在程序运行过程中,不可能一直看着屏幕上的输出,所以当出现异常的时候,利用IO我们可以将这些异常写到指定的文件中,后续有时间打开这个记录异常的文件查看即可。
看一个例子:
public class printstacktrace {public static void main(String[] args) throws FileNotFoundException {try{//将可能出现异常的内容用try捕捉String s = null;s.toString();}catch (Exception e) {//新建打印流,指定输出的文件PrintWriter pw = new PrintWriter("b.txt");//把时间也记录下来SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");pw.println(sdf.format(new Date()));//将异常输出到打印流中e.printStackTrace(pw);//关闭打印流pw.close();}}
}b.txt中的内容就是异常:
2021-09-13 19:57
java.lang.NullPointerExceptionat com.kaikeba.coreclasslibrary.io.printstacktrace.main(printstacktrace.java:12)