流
上篇中的File能进行一些常规的文件操作,但是没有读写两个核心操作
流(Stream)
就是Java中针对文件操作,又进行了进一步的抽象
流是一组类/一组API,描述了如何来进行文件读写操作
不同的类的读写方式都会存在差异
图中黄色部分为常用类.
所有的IO流相关的类,一共分成两个大的部分
- 字节流 读写数据以字节位基本单位 (byte)
- 字符流 读写数据以字符位基本单位 (char)
当我们处理文本文件的时候,使用字符流
当我们处理二进制文件的时候,使用字节流
区分二进制文件和文本文件的简单方式:
用记事本打开文件,如果能看懂,那就是文本文件
如果看不懂,那就是二进制文件
字节流
- InputStream:
输入,从输入设备读取数据到内存中. - OutputStream:
输出.把内存中的数据写入到输出设备中
如果发现某个类的名字中带有inputStream/OutputStream 说明这就是字节流
但是也有个例外,PrintStream不带上面的内容,但他也是字节流
字符流
- Reader:输入
- Writer:输出
如果发现某个类的名字中带有Reader或这Writer,说明这个类就是字符流~
但是也有特例:
- InputStreamReader
- OutputStreamWriter
这两个是字符流,主要是看一个单词的后面一部分,最后一部分是主体,前面的部分都是修饰的
以字节流为例,使用流对象读写文件
需要知道的两个知识点:
FileInputStream(文件输入流)
- FileInputStream(File file) : 向file对象的文件读取数据
- FileInputStream(String path) : 向path文件读取数据
读取数据: 一个一个字节读取
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("e:/BaiduNetdiskDownload/test.txt");
int len = -1;
//每次只读一个字节,如果没读完就返回下一个数据字节,如果读完了就返回-1,读完了就退出循环
while((len = fileInputStream.read()) != -1) {
System.out.print((char)len);
}
fileInputStream.close();
}
输出:
test
读取数据: 先把字节存入到缓存区数组中,一次读一个指定长度的字节数组
public static void main(String[] args) throws IOException {
File file = new File("e:/BaiduNetdiskDownload/test.txt");//创建对象描述文件
FileInputStream fileInputStream = new FileInputStream("e:/BaiduNetdiskDownload/test.txt");//创建一个字节输入流对象
//每次只读一个字节,如果没读完就返回下一个数据字节,如果读完了就返回-1,读完了就退出循环
byte[] bytes = new byte[1024];//根据文件大小来创建数组
StringBuffer sb = new StringBuffer();
int len = -1;
while((len = fileInputStream.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len));
}
fileInputStream.close();
}
FileOutputStream(文件输出流)
- FileOutputStream(File file) : 向file对象的文件写入数据
- FileOutputStream(File file, boolean append) : 向file对象的文件追加数据
- FileOutputStream(String path) : 向指定的文件写入数据
- FileOutputStream(String path, boolean append) : 向指定的文件追加数据
当append的值为true时,向文件写入的数据就会追加到原数据后面,如果为false则会重写文件数据,默认为false
写入方法: 一个字节一个字节的写入
public static void main(String[] args) throws IOException {
FileOutputStream outputStream = new FileOutputStream("e:/BaiduNetdiskDownload/test2.txt");
//将要写入的数据
String string = "Hello Earth";
//将写入的数据转化成字节数组
byte[] bytes = string.getBytes();
for(byte b: bytes) {
//每次写一个字节,写入到目标文件中
outputStream.write(b);
}
//最后记得关闭目标文件
outputStream.close();
}
写入方法: 一次写一个字符数组
public static void main(String[] args) throws IOException {
FileOutputStream outputStream = new FileOutputStream("e:/BaiduNetdiskDownload/test3.txt");
String string = "Hello Mars";
byte[] bytes = string.getBytes();
outputStream.write(bytes);
outputStream.close();
}
操作场景:
拷贝文件:e:/BaiduNetdiskDownload/wallpaper.jpg 的数据到 e:/BaiduNetdiskDownload/wallpaper2.jpg 中
public static void main(String[] args) throws IOException {
copyFile("e:/BaiduNetdiskDownload/wallpaper.jpg", "e:/BaiduNetdiskDownload/wallpaper2.jpg");
}
private static void copyFile(String srcPath, String destPath) throws IOException {
//0.先打开文件,才能够读写,(创建inputSteam/OutputStream对象的过程)
//很多编程语言要对文件进行输入输出之前必须要先打开,java直接new相关对象就可以进行操作了
//这是文件输入流
FileInputStream fileInputStream = new FileInputStream(srcPath);
//实例化这个对象的时候,可以使用字符串路径来实例化,也可以使用一个File对象来实例化,并且要处理异常
//这是文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(destPath);
//1.需要读取srcPath对应的文件内容
byte[] buffer = new byte[1024];
//单次读取的内容是存在上限的(缓存区的长度),要想把整个文件都读完,需要搭配循环来使用
int len = -1;
//如果读到的length时-1,说明读取结束,循环就结束了
while((len = fileInputStream.read(buffer) )!= -1) {
//读取成功,就把读到的内容写到另一个文件中即可
//因为len的值不一定就是和缓冲区一样长
fileOutputStream.write(buffer, 0, len);
}
//2.把读取到的内容写入到destPath对应的文件中
File file = new File("BaiduNetdiskDownload/wallpaper.jpg");
//3.关闭文件
fileInputStream.close();
fileOutputStream.close();
}
其中fileInputStream.read时,有三个版本的read,分别对应三种方式:
一次读1个字节
一次读若干个字节,尝试把这个数组填满
一次读若干个字节,尝试填充b这个数组,从off下标开始,最多填充length个元素
这里我用的第二个方式.
并且这个read拥有int返回值,这表示返回值表示这次read操作实际读取到几个字节~
buffer实际长度是1024,但是这一次不一定能填满
如果文件一共10个字节,显然不能填满,返回值就是10
当文件已经读完了,就会返回-1
read的时候还可能会抛出IOException异常.
FileNotFoundException时IOException异常的子类,
所以就不应再写File…异常了
这里的fileOutputStream.write()也有三个版本
一次写1个字节
一次写若干个字节,尝试把b这个数组填满
一次写若干个字节,尝试填充b这个数组,从off下标开始,最多填充length个元素
运行代码,得到
再看看相应的目录下已经有了复制后的 wallpaper2.jpg 文件
总结:
流对象的核心操作:
1.打开文件(构造方法)
2.read:把文件从数据读到内存中
3.write:把数据从内存中写入到文件中.
4.close:关闭文件(和打开对应的文件)
如果不关闭文件,就会造成文件资源泄露
- 一个一个的应用程序对应的进程在内核中都会对应着一个或者很多PCB,这些PCB通过双向链表链接.
- 当一个进程中,打开一个文件的时候,就会在PCB中进行一定的记录
- 在PCB里有一个属性,这个属性叫做文件描述符表,这张表是一个数组
- 这个数组的每一个元素叫做file_struct (结构体),描述了文件的相关属性
- Java中的File是在JVM描述文件,file_struct是在内核中描述文件(功能类似于File类)
- 代码中每次打开文件就是在文件描述符表中创建了一个项
- 代码中每次关闭文件就是在文件描述符表中删除了一个项
- 那么关键就在于,这个文件描述符表元素个数是有上限的
- 如果代码中一直反复打开新的文件,而没有关闭,那么文件描述符表就会被打满
- 一旦满了,后面再想打开新的文件就会打开失败
- 不过一个进程的文件描述符表的上限是可配置的
文件描述符表是PCB的一个属性
一个进程可能有多个PCB(每个PCB对应一个线程)
同一个进程的若干个PCB之间其实共享这同一个文件描述符表