File&&字节流
File类
是什么?
是文件和目录路径名的抽象表达形式 ,Java中把文件或者目录(文件夹)都封装成File对象。创建File类对象时,系统并不会去验证此文件或文件夹是否存在。可以理解为只是一个字符串(抽象路径)
注意事项:
- 创建时:File类本身不会去检查文件名是否存在
- 操作的时候,会检查文件是否存在。
- 用\\表示层级
- 创建对象多用绝对路径
构造方法:
都是为了创建一个抽象路径
查API文档即可
成员方法:
查API文档即可
创建:
createNewFile()在指定位置创建一个空文件,成功就返回true,如果已存在就不创建,然后返回false。
mkdir() 在指定位置创建一个单级文件夹。
mkdirs() 在指定位置创建一个多级文件夹。
renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。删除:
delete() 删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。
deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。
判断:
exists() 文件或文件夹是否存在。
isFile() 是否是一个文件,如果不存在,则始终为false。
isDirectory() 是否是一个目录,如果不存在,则始终为false。
isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute() 测试此抽象路径名是否为绝对路径名。
获取:
getName() 获取文件或文件夹的名称,不包含上级路径。
getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系
length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
lastModified()获取最后一次被修改的时间。文件夹相关:
static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)
list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
注意事项:
-
单极目录,可以用mkdir方法创建文件夹。调用mkdirs方法的file对象可以是文件,也可以是文件夹
-
创建多级目录,调用mkdirs方法的file对象不能是文件,必须是文件夹
高级获取功能
-
public String[] list() 返回一个字符串数组,这些字符串指向此抽象的路径名表示的目录中的所有文件
和文件夹的名字如果该路径表示的是文件,会返回null
如果路径表示的文件夹没有读取的权限,也会返回null -
File[] listFiles(FileFilter filter) 获取这个文件夹下,满足filter过滤器的条件的文件
public interface FileFilter 过滤器是个接口,不能直接实例化,需要重写方法 boolean accept(File pathname)
里面的对象是以绝对路径保存的,只能获取一层的。
过滤器创建和使用步骤:
方法1:通过接口实现类来创建
- 先创建一个FileFilter接口的实现子类
- 重写accept方法
- 此过滤器在listFiles方法中,会将该文件夹中遍历的每个File对象作为pathname。作为accept方法的形参。
- 接下来就是根据你的需求去重写具体的方法体
- 满足你的过滤条件就返回true,反之。
- 重写完成,在调用listFiles方法时候,就会将满足过滤器(return true)的File对象放入,File数组中。
方法2:通过匿名类实现上例子:
File[] listF = derectory.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
boolean flag = false;
if (pathname.isFile()) {
String fileName = pathname.getName();
flag = fileName.startsWith("123");
}
return flag;
}
});
小试牛刀:
list()方法配合delete 实现递归删除文件夹
思路:
- 获取目录的下的所有File对象(包括文件和文件夹)
- 判断,如果是一个空目录或者file对象不是一个目录而是文件
- 直接删除
- 程序执行到这里,那么一定是一个目录,且不是空目录
- 遍历获取的file数组
- 如果这个file对象仍然是一个目录,递归删除该目录
- 如果这个file对象是文件,直接删除
- 最后不要忘记删除已经是空目录的当前目录
File[] files = dir.listFiles();
if(files == null || files.length == 0){
dir.delete();
return;
}
for(int i = 0; i < files.length; i++){
if(files(i).isDirectory){
delete(files[i]);
}else{
files[i].delete();
}
dir.delete();
}
把文件a移动到文件夹b中
a.renameTo(new File(b.getPath() + "/" a.getName()))
IO流
原则:先创建的流后关闭,后创建的流先关闭,否则可能会报错
注意事项:
流操作中的 Read()方法是阻塞方法!一个阻塞式的方法,也就是死皮赖脸型的。我向你要数据,你说什么?没有!没有我就不走了,我就在这等着,其他人也给我一边候着,只有等我要到了东西才有你们说话的份。 所以每当程序运行至temp = Input.read(data); 都会等待,直到InputStream中有数据为止,它就会马上据为己有。真是个心机婊。
为啥要叫IO流:
IO流用来处理设备之间(内存、硬盘、网络∙∙∙)的数据传输
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中(input/output)
IO操作:
- Output:把内存中的数据存储到持久化设备上这个动作称为输出(写)Output操
- Input:把持久设备上的数据读取到内存中的这个动作称为输入(读)Input操作
特点:
如水流一样,单向,不可再回(Java有些流可以回但也有限制)
IO流分类(部分):
按照数据流向划分 (站在内存、程序的角度)
输入流
从硬盘读入数据到内存
输出流
从内存写出数据到硬盘
按照数据类型划分(一般都是按此划分)
字节流
按字节进行读取(可以处理任意文件,任意类型的数据),把字节原封不动
地一个个进行搬运传输过去,弊端是可能出现乱码
字符流
相当于字节流 + 编码表
什么情况下使用哪种流呢?
不清楚用哪个就用字节流,清楚就用字符流。
FileInputStream字节输入流
写入数据的步骤:
-
创建字节输出流对象
创建之前,jvm到操作系统上去找我们指定的这个文件没有找到,会帮我们创建
找到了,会先清空里面内容 -
write方法写数据
-
关闭资源
FileputStream字节输入流
FileOutputStream字节输出流
读出数据的步骤:
-
创建字节输入流对象
创建之前,jvm到操作系统上去找我们指定的这个文件没有找到,会帮我们创建
找到了,会先清空里面内容 -
read方法写数据
-
关闭资源
文件读写异常处理方式
方式一:
try catch finally
- try外面创建字符输出流
- try里面读写
- finally 字符输出流释放
方式二:
try-with-resources
try(创建字符输出流){}
catch{}
finally{}
该种方式,try种的流对象会在finally执行前结束
在 try-with-resources 定义子句中创建的对象(在括号内的对象)必须实现 java.lang.AutoCloseable 接口,这个接口有一个方法:close()。
当 try() 括号里面的 IO 出现异常或者运行结束,会自动调用 close() 关闭对象。
private native void close0() throws IOException; static { initIDs(); }
疑问:对文件读写的整个流程的步骤?文件的读写是不是都要以一个数组作为中间去读出,和写入?
不用,可以以int型数据,或者byte数组来接收。看自己选择。
具体:
- 首先创建输入流,和输出流
- 选择中间暂存的数据的方式是单个字节还是以字节数组来存储
- 具体如下
// 单个字节的读出写入
int readData;
long start = System.currentTimeMillis();
while ((readData = in.read()) != -1){
out1.write(readData);
}
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - start));
// 多个字节的读入写出
int readCount;
byte[] bytes = new byte[1024];
long start2 = System.currentTimeMillis();
while ((readCount = in.read(bytes)) != -1){
out2.write(bytes, 0, readCount);
}
long end2 = System.currentTimeMillis();
System.out.println("耗时" + (end2 - start2));
in.close();
out1.close();
out2.close();
缓冲xxx流:
构造方法和成员方法什么的看API
问题:指定缓冲区大小有什么作用?字节缓冲流是如何读取或写入数据的?
硬盘和jvm内存中间有一个端口作为缓冲区
答:
-
字节缓冲流首先从因硬盘直接默认读出8kb到缓冲区。
-
然后再内存中的字节数组去接收
-
通过调节字节数组的大小能够提高从端口到内存的传输速度
硬盘到端口一次可以传8kb,端口到内存一次一个字节
用缓冲xx流读写数据步骤
- 创建缓冲输入流 (输入流也一并创建)
- 创建缓冲输出流 (输出流一并创建)
- 选择按字节或者字节数组存储和输入
- 关闭
为什么这么快?缓冲字节流一定比普通的字节流更快吗?
bufferIO流操作再计算机中的大致流程:
流程:
以file为输⼊流创建对象,将file⽂件中8kb全部先存⼊已经开辟好的默认8kb缓存空间,然后in缓存输⼊流调⽤read⽅法,缓冲区的按⾃⼰想要的⼤⼩读取出来放⼊⼀个字节数 组中,(是否最⼤为8162byte?)然后以⽬标⽂件创建的缓存输出流对象调⽤write ⽅ 法,讲字节数组中的数据写⼊到缓冲区,当缓冲区写⼊到8kb,以后缓冲区中的数 据, ⼀次性交给系统内存。重复以上操作直⾄全部写⼊⽬标⽂件。两个输⼊流对象释 放。 相对于之前的普通流的操作,程序与系统内存之间的通信次数减少。效率增⾼。
问: 如果普通输⼊流 以超⼤字符数组(⼤于8kb)做缓冲呢?是否能⽐缓冲流更快
答:经过测试发现,有时候普通输⼊流更快。所谓做缓冲,就相当于是普通输⼊流以⽐ 较⼤的字符数组做缓冲差不多的效果。底层同样是⼀次性就读出了很多。甚⾄经过测试,同样⼤⼩的数组作为中间缓冲。普通输⼊流都更快。综上所述,我暂时认定, buffer字符流只是为了让缓冲区默认就有⼀个很⼤的空间。⽽且中间暂存数据的数组, 由于和缓冲区都在jvm内存中。相互之间读写速度⾮常快,以⾄于,在⽤buffer流操作中 那暂存数据的数组⼤⼩的改变不太影响整个程序的速度。(因为只有缓冲区满了才进⾏ jvm内存和外部设备的交互)
终上:
读写速度起决定作⽤的是缓冲区的⼤⼩。
⼀般字节流:以暂存数据字节数组作为缓冲区
缓冲字节流:默认开辟了缓冲区(8162byte) 另外缓冲字节流中⽤于两个缓冲区交互的字节数组⼤⼩对程序的影响不⼤。
buffer或许在缓冲区这块封装了普通流,让其有了默认缓冲区,不用再搞一个巨大的暂存字节数组。底层运行的原理还是差不多的。为了减少jvm与系统的交互。
注意点:
flush():close():有啥用? 为什么要用?
可以没有flush但是必须要有close, 不然缓冲区中的数据无法刷新到硬盘中
-
flush方法,将包装流中的数据刷新到节点流中
-
close方法,会自动调用flush方法,并且将节点流的close也关闭
细枝末节:
-
读入不用flush
-
如何实现数据的追加写入?
FileOutputStream(File file, boolean append)
创建文件输出流以写入由指定的
File`对象表示的文件。