写在前面:今天回来的有些晚了,闲话就不说了,开始搞起。
Java中经常会遇到文件的读写操作,这时就需要IO,JAVA中的IO关系比较复杂和繁琐,因此今天我们来复习下IO的相关知识。
1.文件
在学习IO流之前,先要了解文件,因为流的本质也是对文件的处理。
File类描述的是一个文件或文件夹。(文件夹也可以称为目录)
该类的出现是对文件系统的中的文件以及文件夹进行对象的封装。可以通过对象的思想来操作文件以及文件夹。
构造一个File类实例:
//通过将给定路径来创建一个新File实例。
File file1 = new File(String pathname);
//根据parent抽象路径名和child路径名创建一个新File实例。 parent是指上级目录的路径,完整的路径为parent.getPath()+child.
File file2 = new File(File parent, String child);
注意:
(1) 创建File对象需要导包import java.io.File。
(2) File对象没有无参数构造.创建对象需要传参。
(3) 如果指定的路径不存在(没有这个文件或是文件夹),不会抛异常,这时file.exists()返回false。
路径分隔符:
在Windows中分隔符为'\',在Unix/Linux中分隔符为'/'。
更专业的做法是使用File.separatorChar,这个值就会根据系统得到的相应的分割符。
注意,如果是使用"\",则需要进行转义,写为"\\"才可以,如果是两个"\",则写为"\\\\"。
常用的方法:
创建:
createNewFile()在指定位置创建一个空文件,成功就返回true,如果已存在就不创建然后返回false
mkdir()在指定位置创建目录,这只会创建最后一级目录,如果上级目录不存在就抛异常。
mkdirs()在指定位置创建目录,这会创建路径中所有不存在的目录。
renameTo(File dest)重命名文件或文件夹,也可以操作非空的文件夹,文件不同时相当于文件的剪切,剪切时候不能操作非空的文件夹。移动/重命名成功则返回true,失败则返回false。
删除:
delete()删除文件或一个空文件夹,如果是文件夹且不为空,则不能删除,成功返回true,失败返回false。
deleteOnExit()在虚拟机终止时,请求删除此抽象路径名表示的文件或目录,保证程序异常时创建的临时文件也可以被删除
判断:
exists()文件或文件夹是否存在。
isFile()是否是一个文件,如果不存在,则始终为false。
isDirectory()是否是一个目录,如果不存在,则始终为false。
isHidden()是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute()测试此抽象路径名是否为绝对路径名。
获取:
getName()获取文件或文件夹的名称,不包含上级路径。
getPath() 返回绝对路径,可以是相对路径,但是目录要指定
getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系
length()获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent()返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
lastModified()获取最后一次被修改的时间。
文件夹相关:
staic File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)
list()返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles()返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
2. IO的分类
I/O类库中使用“流”这个抽象概念。Java对设备中数据的操作是通过流的方式。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
由于Java用于操作流的对象都在IO包中。所以使用IO流需要导包如:import java.io.*;
分类:
流按操作数据类型的不同分为两种:字节流与字符流。
流按流向分为:输入流,输出流(以程序为参照物,输入到程序,或是从程序输出)
3.字节流
计算机中都是二进制数据,一个字节是8个2进制位。字节可以表示所有的数据,比如文本,音频,视频.图片,都是作为字节存在的。
字节流的抽象基类:
输入流:java.io.InputStream
输出流:java.io.OutputStream
特点:
字节流的抽象基类派生出来的子类名称都是以其父类名作为子类名的后缀。如:FileInputStream, ByteArrayInputStream等。
说明:
字节流处理的单元是一个字节,用于操作二进制文件(计算机中所有文件都是二进制文件)
(1) 输入流InputStream
实现;显示指定文件内容。
明确使用流,使用哪一类流?使用输入流,FileInputStream
第一步:
打开流(即创建流)
第二步:
通过流读取内容
第三步:
用完后,关闭流资源
输入流读取方式一
read方法():一次读取一个字节,读到文件末尾返回-1。
// 打开流
FileInputStream fis = new FileInputStream(path);
int len;
while ((len = fis.read()) != -1) {
System.out.print((char) len);
}
// 使用完关闭流
fis.close();
输入流读取方式二
使用read(byte[] b) 方法。使用缓冲区(关键是缓冲区大小的确定)
使用read方法的时候,流需要读一次就处理一次,可以将读到的数据装入到字节数组中,一次性的操作数组,可以提高效率。
// 打开流
FileInputStream fis = new FileInputStream(path);
// 通过流读取内容
byte[] byt = new byte[1024];
int len = fis.read(byt);
for (int i = 0; i <len; i++) {
System.out.print(byt[i]);
}
// 使用完关闭流
fis.close();
输入流读取方式三
使用read(byte[] b,int off,int len)
b显然是一个byte类型数组,当做容器来使用,off是指定从数组的什么位置开始存字节,len希望读多少个
// 打开流
FileInputStream fis = new FileInputStream(path);
// 通过流读取内容
byte[] byt = new byte[1024];
// 从什么地方开始存读到的数据
int start = 5;
// 希望最多读多少个(如果是流的末尾,流中没有足够数据)
int maxLen = 6;
// 实际存放了多少个
int len = fis.read(byt, start, maxLen);
for (int i = start; i < start + maxLen; i++) {
System.out.print((char) byt[i]);
}
// 使用完关闭流
fis.close();
使用skip方法skip(long n),参数跟的是要跳过的字节数。
输入流读取方式四
使用缓冲(提高效率),并循环读取(读完所有内容)。
FileInputStream fis = new FileInputStream(path);
byte[] byt = new byte[1024];
int len = 0;
while ((len = fis.read(byt)) != -1) {
System.out.println(new String(byt, 0, len));
}
(2) 输出流OutputStream
第一步:
打开文件输出流,流的目的地是指定的文件
第二步:
通过流向文件写数据
第三步:
用完流后关闭流
输出流写出方式一
使用write(int b)方法,一次写出一个字节。
// 1:打开文件输出流,流的目的地是指定的文件
FileOutputStream fos = new FileOutputStream(path);
// 2:通过流向文件写数据
fos.write('j');
fos.write('a');
fos.write('v');
fos.write('a');
// 3:用完流后关闭流
fos.close();
注意:使用write(int b)方法,虽然接收的是int类型参数,但是write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。
输出流写出方式二
使用write(byte[] b),就是使用缓冲.提高效率。
// 1:打开文件输出流,流的目的地是指定的文件
FileOutputStream fos = new FileOutputStream(path,true);
// 2:通过流向文件写数据
byte[] byt = "java".getBytes();
fos.write(byt);
// 3:用完流后关闭流
fos.close();
注意:第二个参数,append - 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处。
(3) 字节流文件拷贝
什么是文件拷贝?很显然,先开一个输入流,将文件加载到流中,再开一个输出流,将流中数据写到文件中。就实现了文件的拷贝。
分析:
第一步:需要打开输入流和输出流
第二步:读取数据并写出数据
第三步:关闭流
字节流文件拷贝实现一:
// 打开输入流,输出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 读取和写入信息
int len = 0;
while ((len = fis.read()) != -1) {
fos.write(len);
}
// 关闭流
fis.close();
fos.close();
评价:程序很慢,时间比较长,因为每次读一个字节再写一个字节效率很低。
字节流文件拷贝实现二:
// 打开输入流,输出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 读取和写入信息
int len = 0;
// 使用字节数组,当做缓冲区
byte[] byt = new byte[1024];
while ((len = fis.read(byt)) != -1) {
fos.write(byt);
}
// 关闭流
fis.close();
fos.close();
评价:拷贝后的文件要比源文件多一些内容问题就在于我们使用的容器,这个容器我们是重复使用的,新的数据会覆盖掉老的数据,显然最后一次读文件的时候,容器并没有装满,出现了新老数据并存的情况。
字节流文件拷贝实现三:
// 打开输入流,输出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 读取和写入信息
int len = 0;
// 使用字节数组,当做缓冲区
byte[] byt = new byte[1024];
while ((len = fis.read(byt)) != -1) {
fos.write(byt, 0, len);
}
// 关闭流
fis.close();
fos.close();
(4) 字节缓冲流
Java其实提供了专门的字节流缓冲来提高效率:BufferedInputStream和BufferedOutputStream
它们可以通过减少读写次数来提高输入和输出的速度。
// 打开输入流,输出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 使用缓冲流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 读取和写入信息
int len = 0;
while ((len = bis.read()) != -1) {
bos.write(len);
}
// 关闭流
bis.close();
bos.close();
4.字符流
字符流是建立在字节流之上的,它能够提供字符层次的编码和解码。例如,在写入一个字符时,Java虚拟机会将字符转为文件指定的编码(默认是系统默认编码),在读取字符时,再将文件指定的编码转化为字符。
常见的码表如下:
ASCII: 美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表,用一个字节的8位表示。
GB2312: 英文占一个字节,中文占两个字节.中国的中文编码表。
GBK: 中国的中文编码表升级,融合了更多的中文文字符号。
Unicode: 国际标准码规范,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。
UTF-8: 最多用三个字节来表示一个字符。
字符流就是:字节流 + 编码表,为了更便于操作文字数据。字符流的抽象基类:Reader, Writer。
由这些类派生出来的子类名称都是以其父类名作为子类名的后缀,如FileReader、FileWriter。
(1) Reader
方法:
第一步:int read():
读取一个字符。返回的是读到的那个字符。如果读到流的末尾,返回-1。
第二步:int read(char[]):
将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是往数组里装的元素的个数。如果读到流的末尾,返回-1。
第三步:close()
读取字符其实用的是window系统的功能,就希望使用完毕后,进行资源的释放。
使用字节流读取文件:
Reader reader = new FileReader(path);
int len = 0;
while ((len = reader.read()) != -1) {
System.out.print((char) len);
}
reader.close();
(2) Writer
Writer中的常见的方法:
write(ch): 将一个字符写入到流中。
write(char[]): 将一个字符数组写入到流中。
write(String): 将一个字符串写入到流中。
flush():刷新流,将流中的数据刷新到目的地中,流还存在。
close():关闭资源:在关闭前会先调用flush(),刷新流中的数据去目的地。然流关闭。
将文本数据存储到一个文件中:
Writer writer = new FileWriter(path);
writer.write('中');
writer.write("世界".toCharArray());
writer.write("中国");
writer.close();
追加文件
默认的FileWriter方法新值会覆盖旧值,想要实现追加功能需要使用如下构造函数创建输出流 append值为true即可。
FileWriter(String fileName, boolean append)
FileWriter(File file, boolean append)
flush方法
只要指定close方法就不用再flush方法,因为close也调用了flush方法。
(3) 字符流拷贝文件
实现一:
public static void copyFile(String path1, String path2) throws Exception {
Reader reader = new FileReader(path1);
Writer writer = new FileWriter(path2);
int ch = -1;
while ((ch = reader.read()) != -1) {
writer.write(ch);
}
reader.close();
writer.close();
}
评价:但是这个一次读一个字符就写一个字符,效率不高。把读到的字符放到字符数组中,再一次性的写出。
实现二:
public static void copyFile3(String path1, String path2) throws Exception {
Reader reader = new FileReader(path1);
Writer writer = new FileWriter(path2);
int ch = -1;
char [] arr=new char[1024];
while ((ch = reader.read(arr)) != -1) {
writer.write(arr,0,ch);
}
reader.close();
writer.close();
}
评价:字节流不可以拷贝视频和音频等文件,字符流只能拷贝以字符为单位的文本文件
(4) 字符流的缓冲区
Reader有一个子类BufferedReader。子类继承父类显然子类可以重写父类的方法,也可以增加自己的新方法。例如一次读一行就是常用的操作。那么BufferedReader 类就提供了这个方法,可以查看readLine()方法具备 一次读取一个文本行的功能。很显然,该子类可以对功能进行增强。
private static void readFile(String path) throws IOException {
Reader read = new FileReader(path);
BufferedReader br = new BufferedReader(read);
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
注意:
在使用缓冲区对象时,要明确缓冲的存在是为了增强流的功能而存在,所以在建立缓冲区对象时,要先有流对象存在。
缓冲区的出现提高了对流的操作效率。原理:其实就是将数组进行封装。
使用字符流缓冲区拷贝文本文件:
private static void copyFile(File srcFile, File destFile) throws IOException {
// 创建字符输入流
FileReader fr = new FileReader(srcFile);
// 创建字符输出流
FileWriter fw = new FileWriter(destFile);
// 字符输入流的缓冲流
BufferedReader br = new BufferedReader(fr);
// 字符输出流的缓冲流
BufferedWriter bw = new BufferedWriter(fw);
String line = null;
// 一次读取一行
while ((line = br.readLine()) != null) {
// 一次写出一行.
bw.write(line);
// 刷新缓冲
bw.flush();
// 进行换行,由于readLine方法默认没有换行.需要手动换行
bw.newLine();
}
// 关闭流
br.close();
bw.close();
}
5.JAVA流总结
(1)在JAVA中有数据传输的地方都用到I/O流(通常是文件、网络、内存和标准输入输出等)。
(2)InputStream和OutputStream是所有字节流的祖先(只有RandomAccessFile类时一个例外),read和write是它们最基本的方法,读写单位是字符。(3)Reader和Writer是所有字符流的祖先,read和write是它们最基本的方法,读写单位是字符。
(4)在众多的流对象中,并不是每一种都单独使用,其中过滤流的子类在数据送出去之前做必要的处理。
(5)File(Input/Output)Stream是处理本地文件的类。
(6)Buffered(Input/Output)Stream的作用是在数据送到目的之前先缓存,达到一定数量时再送到目的,已减少阻塞次数。