概念
File类中虽然能进行一些常规的文件操作,但是少了两个非常核心的操作,读文件、写文件。
流 是Java中针对文件操作,又进行了进一步的抽象
流是一组类/一组API,不同的类会存在差异
流对象的核心操作
1.打开文件(构造方法)
2. read:从文件中把数据读到内存中.
3. write:把数据从内存中写入文件中.
4. close:关闭文件.(和打开相对应的),不关闭会造成文件资源泄露
流相关类
文本文件和二进制文件区分方式很简单:
拿一 个记事本打开这个文件,
里面的内容能看懂,就是文本文件.
里面的内容看不懂,就是二进制文件.
1、字节(byte)流
读写数据以字节为单位,处理二进制文件/数据使用字节流
InputStream:输入,从输入设备读取数据到内存中
OutputStream:输出,把内存中的数据写入到输出设备中
如果发现某个类的名字中带有InputStream / OutputStream字样,说明这就是字节流
2、字符(char)流
读写数据以字符为单位,处理文本文件/数据使用字符流
Reader:输入
Writer:输出
如果发现某个类的名字中带有Reader或Writer,说明这个类就是字符流
特例
InputStreamReader
OutputStreamWriter
名字里把两个部分都占用了,是字符流~(看一个单词后面的部分,最后一个部分是主体,前面的部分都是修饰的)
字节流读写
一个二进制文件,实现文件的拷贝(读取原来文件的内容,然后依次写入到目标文件中)
图片也是二进制文件
拷贝
一次读一个字节.
一次读若干个字节, 尝试把b这个数组填满
一次读若干个字节, 尝试填充b这个数组,从off下标开始填充,最多填充len个元素
一次写一个字节.
一次写若干个字节, 尝试把b这个数组填满
一次写若干个字节, 尝试填充b这个数组,从off下标开始填充,最多填充len个元素
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IODemo {
public static void main(String[] args) throws IOException {
copyFile("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径
}
private static void copyFile(String srcPath,String destPath) throws IOException {//抛出异常,传入文件路径不存在
//先打开文件,再进行读写(创建InputStream/OutputStream对象)
FileInputStream fileInputStream = new FileInputStream(srcPath);//打开要拷贝的文件,,可以传入字符地址,或者File对象
FileOutputStream fileOutputStream = new FileOutputStream(destPath);//要写入的文件
byte[] bytes = new byte[1024];//缓冲区
//单词读取的内容是有上限的(缓冲区的长度)
//返回值表示本次读取实际读取的字节,如果文件读完返回-1
int len = -1;
while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
//2、将读取到的内容写入destPath对应的文件中
//读取,然后把读到的内容写入到另外一个文件中
//因为len的值不一定和缓冲区长度相同
fileOutputStream.write(bytes,0,len);//避免脏读
}
fileInputStream.close();
fileOutputStream.close();
}
}
在某些情况下可能close方法无法调用,因为中间存在IOException异常,一旦触发,此时方法就会被立刻终止,从而导致下面的close无法被调用
对代码进行优化
优化一
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class IODemo {
public static void main(String[] args) throws IOException {
copyFile("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径
copyFile1("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径
}
private static void copyFile(String srcPath,String destPath) throws IOException {//抛出异常,传入文件路径不存在
FileInputStream fileInputStream = null;//打开要拷贝的文件,,可以传入字符地址,或者File对象
FileOutputStream fileOutputStream = null;//要写入的文件
try {
//先打开文件,再进行读写(创建InputStream/OutputStream对象)
fileInputStream = new FileInputStream(srcPath);
fileOutputStream = new FileOutputStream(destPath);
byte[] bytes = new byte[1024];//缓冲区
//单词读取的内容是有上限的(缓冲区的长度)
//返回值表示本次读取实际读取的字节,如果文件读完返回-1
int len = -1;
while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
//2、将读取到的内容写入destPath对应的文件中
//读取,然后把读到的内容写入到另外一个文件中
//因为len的值不一定和缓冲区长度相同
fileOutputStream.write(bytes,0,len);//避免脏读
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void copyFile1(String srcPath,String destPath) throws IOException {
try( FileInputStream fileInputStream = new FileInputStream(srcPath);//打开要拷贝的文件,,可以传入字符地址,或者File对象
FileOutputStream fileOutputStream = new FileOutputStream(destPath)){//要写入的文件)
byte[] bytes = new byte[1024];
int len = -1;
while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
//2、将读取到的内容写入destPath对应的文件中
//读取,然后把读到的内容写入到另外一个文件中
//因为len的值不一定和缓冲区长度相同
fileOutputStream.write(bytes,0,len);//避免脏读
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
优化二
public static void copyFile1(String srcPath,String destPath) throws IOException {
//不需要显式调用close方法
//try语句会在代码执行完毕后,自动调用close方法(前提是这个类必须要实现Closeable接口)
//标准库中的流的方法都已经自动实现了Closeable接口
try( FileInputStream fileInputStream = new FileInputStream(srcPath);//打开要拷贝的文件,,可以传入字符地址,或者File对象
FileOutputStream fileOutputStream = new FileOutputStream(destPath)){//要写入的文件)
byte[] bytes = new byte[1024];
int len = -1;
while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
//2、将读取到的内容写入destPath对应的文件中
//读取,然后把读到的内容写入到另外一个文件中
//因为len的值不一定和缓冲区长度相同
fileOutputStream.write(bytes,0,len);//避免脏读
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedInputStream和BufferedOutputStream
这两个类内置缓冲区,提高程序的效率
程序访问内存比访问磁盘要快3-4个数量级
IO操作肯定涉及到磁盘访问,IO次数越多整体的程序效率就越低。
读写磁盘的时候每次读写一个字节,分100次读写,效率远远低于一次读写完100个字节。
import java.io.*;
public class IODemo2 {
public static void main(String[] args) throws IOException {
copyFile("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径
}
private static void copyFile(String srcPath,String destPath) throws IOException {
//先创建File的实例
//需要创建实例BufferedInputStream和 BufferedOutputStream方法内部特有了缓冲区对象
FileInputStream fileInputStream = new FileInputStream(srcPath);
FileOutputStream fileOutputStream = new FileOutputStream(destPath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
byte[] buffer = new byte[1024];
int length = -1;
while ((length = bufferedInputStream.read(buffer))!= -1 ) {
bufferedOutputStream.write(buffer,0,length);
}
//四个流对象关闭
//调用bufferedInputStream.close时,会自动关闭内部的,FileInputStream和FileOutputStream
bufferedInputStream.close();
bufferedOutputStream.close();
}
将close代码简化
代码中可以不使用close方法
private static void copyFile2(String srcPath,String destPath) throws IOException {
FileInputStream fileInputStream = new FileInputStream(srcPath);
FileOutputStream fileOutputStream = new FileOutputStream(destPath);
try ( BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)){
int len = -1;
byte[] buffer = new byte[1024];
while(true){
len = bufferedInputStream.read(buffer);
if(len == -1){
break;
}else {
bufferedOutputStream.write(buffer,0,len);
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
有无缓冲区的区别
不使用和使用缓冲区的区别,在比较大的文件会体现得比较明显
不设定缓冲区也不使用内置缓冲区
import java.io.*;
public class IODemo3 {
public static void main(String[] args) throws IOException {
//不使用和使用缓冲区的区别,在比较大的文件会体现得比较明显
teatNoBUffer("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");
teatBuffer("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");
}
private static void teatNoBUffer(String srcPath,String destPath){
//读的时候就是一个字节,一个字节读,不使用缓存区
long beg = System.currentTimeMillis();
try(FileInputStream fileInputStream = new FileInputStream(srcPath);
FileOutputStream fileOutputStream = new FileOutputStream(destPath)){
int ch = -1;
while ((ch = fileInputStream.read())!= -1) {
//啥都不干
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("no buffer"+(end - beg)+"ms");
}
private static void teatBuffer(String srcPath,String destPath) throws IOException {
long beg = System.currentTimeMillis();
try(BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcPath));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destPath))){
int ch = -1;
while ((ch = bufferedInputStream.read())!= -1){
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("have buffer"+(end - beg)+"ms");
}
}
no buffer 1594ms
have buffer 15ms
所以有缓冲区的运行速度要快
BufferedOutputStream这里还有一个特殊操作
flush操作——手动刷新缓冲区(把数据从缓冲区写入磁盘/IO设备).
缓冲区刷新
缓冲区刷新一次,缓冲区中的东西就会写入到IO,之后缓冲区就会空白了。
1、缓冲区如果满了,就会自动刷新
2、调用close方法的时候,也会触发刷新
3、主动调用flush也会刷新
文件资源泄露
文件资源泄露关键在于,文件描述符表是有上限的,如果不关闭,就会反复打开,文件描述符表就会被一直打开,直到上限,后面再想打开新的就会打开失败。
一个进程的文件描述符表的上限数目(打开的最多的文件数目),是可配置的,在Linux中65535为上限。
如果打开这么多就会上限,除非将前面不用的文件关闭。
此处的文件是广义的文件,包含普通文件,目录文件,也包含网卡这样的设备
对应的socket文件,每次有一个客户端和服务器建立连接的时候,系统中就会分配-一个 socket文件
关于扩容
好处能够更好的适应更多的场景
坏处:考虑到效率,考虑到线程安全
链式结构扩容方便,但是基本操作的效率不如顺序表(随机访问能力)
内核是一切应用程序的基础.对于效率要求是非常高的. (内核效率低,应用程序效率不可能高)顺序结构,扩容就很不方便