InputStream、OutputStream、Buffer
I/O
- 输出流:程序(内存)---> 外界设备
- 输 ⼊流:外界设备---> 程序(内存)
处理数据类型分类
- 字符流:处理字符相关,如处理 ⽂本数据(如 txt⽂件), Reader/Writer
- 字节流: 处理字节相关,如声 ⾳或者图 ⽚等 ⼆进制,InputStream/OutputStream
字符流和字节流区别
- 字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,⼀次可能读多个字节。
- 字节流可以处理 ⼏乎所有 ⽂件,字符流只能处理字符类型的数据。
InputStream
InputStream 是输 ⼊字节流的 ⽗类,它是 ⼀个抽象类(⼀般 ⽤他的 ⼦类)
常见方法
//讲解:从输⼊流中读取单个字节,返回0到255范围内的int字节值,字节数据可直接转换为int类 型, 如果已经到达流末尾⽽没有可⽤的字节,则返回-1
int read()
//从输⼊流中读取⼀定数量的字节,并将其存储在缓冲区数组buf中, 返回实际读取的字节 数,如果已经到达流末尾⽽没有可⽤的字节,则返回-1
int read(byte[] buf)
//从输⼊流中跳过并丢弃 n 个字节的数据。
long skip(long n)
//返回这个流中有多少个字节数,可以把buf数组⻓度定为这个
int available()
//关闭输⼊流并释放与该流关联的系统资源
void close() throws IOException
常见子类 FileInputStream
⽂件字节输 ⼊流, 对 ⽂件数据以字节的形式进 ⾏读取操作。
@Test
public void ioStreamTesting() throws IOException {
//传入文件路径
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1\\1.txt");
//传入File对象
String dir = "C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1";
String name = "1.txt";
File file = new File(dir, name);
FileInputStream inputStream = new FileInputStream(file);
//对于汉字(两个字节) unicoe种的字符不能正常读取,因为每次只会读取一个字节,所以会显示乱码。
int read = inputStream.read();
System.out.println(read); //49 ASCII
System.out.println((char) read);//1
//跳过 2个字节
long skipSize = inputStream.skip(2);
System.out.println(skipSize);
int read2 = inputStream.read();
System.out.println(read2);
//字节数组:如果Buf的长度为0,则不读取任何字节并返回0,每次读取的字节数最多等于buf的长度。
//byte[] buf = new byte[1024];
byte[] buf = new byte[inputStream.available()];//流中的长度,作为字节数组的长度
int length;//用于判断读取时候是否返回 -1,代表读取到了尽头
while ((length = inputStream.read(buf)) != -1) { //不等于-1代表还可以继续读,返回值是读取到的字节数
System.out.println(new String(buf,0,length,"UTF-8")); //中文乱码问题可以换成 GBK 或 UTF-8 GBK中英文占用2个字节,UTF-8中文使用了3个字节。
}
}
OutputStream
常见方法
//将指定的字节写⼊输出流
void write(int b)
//将b.length个字节的byte数组写⼊当前输出流
void write(byte[] b)throws IOException
//关闭输⼊流并释放与该流关联的系统资源
void close() throws IOException
常见子类 FileOutputStream
@Test
public void outputStreamTesting() throws IOException {
//传入文件路径(会自动创建文件,但是必须父目录存在,否在不会自动创建多级目录)
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1\\1.txt");
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1\\2.txt");
//不覆盖文件,追加数据
//FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1\\2.txt",true);
//单个字节读取,中文会出现乱码
// int value = 0;
// while (value!=-1){
// value = fileInputStream.read();
// fileOutputStream.write(value);
// }
//使用字节数组读取
byte[] buf = new byte[1024];
int length;
while ( (length = fileInputStream.read(buf))!=-1){
fileOutputStream.write(buf,0,length);
}
//关闭流
fileInputStream.close();
fileOutputStream.close();
}
Buffer 缓冲区
它是内存空间的 ⼀部分,在内存空间中预留了 ⼀定的存储空间,这些存储空间 ⽤来缓冲输 ⼊或输出的数据,这部分空间就叫做缓冲区,缓冲区是具有 ⼀定 ⼤ ⼩的。
缓冲区的意义
- 缓和冲击,例如操作磁盘 ⽐内存慢的很多,所以不 ⽤缓冲区效率很低
- 数据传输速度和数据处理的速度存在不平衡,⽐如你每秒要写 100 次硬盘,对系统冲击很 ⼤, 浪费了 ⼤量时间在忙着处理开始写和结束写这两个事件,⽤ buffer 暂存起来,变成每 10 秒写 ⼀次硬盘,数据可以直接送往缓冲区,⾼速设备不 ⽤再等待低速设备,对系统的冲击就很 ⼩,写 ⼊效率 ⾼了
Java IO 包里面的两个缓冲类(高级流)
- BufferInputStream 和 BufferOutputStream
- 采 ⽤包装设计模式
BufferInputStream 缓冲字节输入流
BufferedInputStream 通过预先读 ⼊ ⼀整段原始输 ⼊流数据 ⾄缓冲区中,⽽外界对 BufferedInputStream 的读取操作实际上是在缓冲区上进 ⾏,如果读取的数据超过了缓冲区 的范围,那么 BufferedInputStream 负责重新从原始输 ⼊流中载 ⼊下 ⼀截数据填充缓冲区, 然后外界继续通过缓冲区进 ⾏数据读取。
- 避免了 ⼤量的磁盘 IO,原始的 InputStream 类实现的 read 是即时读取的,每 ⼀次读取 都会是 ⼀次磁盘 IO 操作(哪怕只读取了 1 个字节的数据),如果数据量巨 ⼤,这样的磁盘消耗 ⾮常可怕。
- 缓冲区的实现: 读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进 ⾏ ⼀次磁盘 IO,载 ⼊ ⼀段数据填充缓冲,下 ⼀次读取 ⼀般情况就直接可以从缓冲区读取,减少了磁盘 IO。
- 默认缓冲区 ⼤ ⼩是 8k, int DEFAULT_BUFFER_SIZE = 8192;
常见构造函数
//对输⼊流进⾏包装,⾥⾯默认的缓冲区是8k
public BufferedInputStream(InputStream in);
//对输⼊流进⾏包装,指定创建具有指定缓冲区⼤⼩的
public BufferedInputStream(InputStream in,int size);
常用方法
//从输⼊流中读取⼀个字节
public int read();
//从字节输⼊流中给定偏移量处开始将各字节读取到指定的 byte 数组中。
public int read(byte[] buf,int off,int len);
//关闭释放资源,关闭的时候这个流即可,InputStream会在⾥⾯被关闭
void close();
BufferOutputStream 缓冲字节输出流
构造函数
//对输出流进⾏包装,⾥⾯默认的缓冲区是8k
public BufferedOutputStream(OutputStream out);
//对输出流进⾏包装,指定创建具有指定缓冲区⼤⼩的
public BufferedOutputStream(OutputStream out,int size);
常用方法
//向输出流中输出⼀个字节
public void write(int b);
//将指定 byte 数组中从偏移量 off 开始的 len 个字节写⼊缓冲的输出流。
public void write(byte[] buf,int off,int len);
//刷新此缓冲的输出流,强制使所有缓冲的输出字节被写出到底层输出流中。
public void flush();
//关闭释放资源,关闭的时候这个流即可,OutputStream会在⾥⾯被关闭, JDK7新特性try(在这 ⾥声明的会⾃动关闭){}
void close();
练习
文件拷贝
@Test
public void copyFileTesting() throws IOException {
//文件路径
String sourceFilePath = "C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1\\1.txt";
String targetFilePath = "C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1\\2.txt";
//用buf包装FileInputStream
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(sourceFilePath));
//用buf包装FileOutputStream
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(targetFilePath));
//临时空间
int size;
byte[] buf = new byte[1024];
//从bis中读取数据 写入bos中
while ((size = bufferedInputStream.read(buf)) != -1) { //读取到bis缓冲区尽头才会返回-1
bufferedOutputStream.write(buf,0,size);
}
//刷新数据(没有填满BOS不会触发自动刷新,需要手动刷新)
bufferedOutputStream.flush();
//关闭资源(buf类会自动关闭 bis、bos) 如果A依赖于B,则先关闭B
bufferedInputStream.close();
bufferedOutputStream.close();
//触发GC
buf = null;
}
将一个目录下的所有图片,拷贝到另外一个目录
@Test
public void copyPictruePractice() throws IOException {
//目录
String sourceDir = "C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\1";
String targetDir = "C:\\Users\\Ryzen-7-3800X\\Pictures\\Camera Roll\\test\\2";
//遍历源目录中所有文件
File file = new File(sourceDir);
String[] listFile = file.list();
for (String s : listFile) {
String sourceNamePath = sourceDir + File.separator + s;
String targetNamePath = targetDir + File.separator + s;
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceNamePath));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetNamePath));
//临时数组
int size;
byte[] buffer = new byte[1024];
//从bis中读取到bos
while ((size = bis.read(buffer)) != -1) {
bos.write(buffer,0,size);
}
//bos缓冲区未满(不会自动触发刷新,需要手动刷新)
bos.flush();
//关闭资源
bis.close();
bos.flush();
//触发gc
buffer = null;
}
}