1.什么是IO,IO有什么特点
IO即输入和输出,指应用程序和外部设备之间的数据传递,比如数据库的IO,网络间的IO,文件的IO等
- 输出流:程序(内存)—>外界设备
- 输⼊流:外界设备—>程序(内存)
什么是流呢?
- 流其实是一个抽象的概念,可以理解为一连串的字符或者字节数据以先进先出的方式传送的通道
2.IO流分为哪几种类型
处理数据类型分类
- 字符流:处理字符相关,如处理⽂本数据(如txt⽂件), Reader/Writer 字节流:
- 处理字节相关,如声⾳或者图⽚等⼆进制,InputStream/OutputStream
两者区别:
- 字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,⼀次可能读多个 字节
- 字节流可以处理⼏乎所有⽂件,字符流只能处理字符类型的数据
功能不同,但是具有共性内容,通过不断抽象形成4个抽象类,抽象类下⾯有很多⼦类是具体的实
现
- 字符流 Reader/Writer
- 字节流 InputStream/OutputStream
3.Java输⼊流InputStream
- InputStream是输⼊字节流的⽗类,它是⼀个抽象类(⼀般⽤他的⼦类)
int read()
讲解:从输⼊流中读取单个字节,返回0到255范围内的int字节值,字节数据可直接转换为int类
型, 如果已经到达流末尾⽽没有可⽤的字节,则返回-1
int read(byte[] buf)
讲解:从输⼊流中读取⼀定数量的字节,并将其存储在缓冲区数组buf中, 返回实际读取的字节
数,如果已经到达流末尾⽽没有可⽤的字节,则返回-1
long skip(long n)
讲解:从输⼊流中跳过并丢弃 n 个字节的数据。
int available()
讲解:返回这个流中有多少个字节数,可以把buf数组⻓度定为这个
void close() throws IOException
讲解:关闭输⼊流并释放与该流关联的系统资源
- 常⻅⼦类 FileInputStream
常⽤构造函数
//传⼊⽂件所在地址
public FileInputStream(String name) throws FileNotFoundException
//传⼊⽂件对象
public FileInputStream(File file) throws FileNotFoundException
4.Java输出流 OutputStream
- OutputStream是输出字节流的⽗类,它是⼀个抽象类
void write(int b)
讲解:将指定的字节写⼊输出流
void write(byte[] b)throws IOException
讲解:将b.length个字节的byte数组写⼊当前输出流
void flush() throws IOException
讲解:write是写到缓冲区中,可以认为是内存中,当缓冲区满时系统会⾃动将缓冲区的内容写⼊
⽂件,但是⼀般还有⼀部分有可能会留在内存这个缓冲区中, 所以需要调⽤flush空缓冲区数据。
void close() throws IOException
讲解:关闭输⼊流并释放与该流关联的系统资源
- 常⻅⼦类 FileOutputStream
构造函数
//传⼊输出的⽂件地址
public FileOutputStream(String name)
//传⼊⽬标输出的⽂件对象
public FileOutputStream(File file)
//传⼊⽬标输出的⽂件对象, 是否可以追加内容
public FileOutputStream(File file, boolean append)
5.字节流出现乱码问题
-
编码⼩知识(节省空间)
操作的中⽂内容多则推荐GBK: GBK中英⽂也是两个字节,⽤GBK节省了空间, UTF-8 编码的中⽂使⽤了三个字节 如果是英⽂内容多则推荐UFT-8: 因为UFT-8⾥⾯英⽂只占⼀个字节 UTF-8编码的中⽂使⽤了三个字节
使用"IO"字节流的时候, 发现有时候读取文本(包含中文),输出到控制台,有乱码的存在
字节流每次读取一个字节,出现乱码的原因
package inputstream;
import java.io.FileInputStream;
import java.io.IOException;
//字节流每次读取一个字节,出现乱码的原因
public class InputStreamTest1 {
public static void main(String[] args) throws IOException{
//创建字节流
FileInputStream fis = null;
try {
fis = new FileInputStream("hello.txt");
// int len = file.read();
// while(len != -1) {//2.判断
// System.out.print((char)len);
// len = file.read(); //1.读取,赋值
// }
int len = 0;
//1.将读取,赋值,判断集于一身
while((len = fis.read()) != -1) {
System.out.print((char)len);
/**
* 读取hello.txt文件的时候,出现了乱码,为什么呢?
* read(),是每次读取一个字节,但中文一个字符占2个字节(GBK)或者3个字节(UTF-8)
* 所以它读的时候,只读到了一半就没了,所以会产生类似于乱码的符号
*/
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(fis != null) fis.close();
}
}
}
字节流每次读取多个字节,出现乱码的原因
package inputstream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
//字节流每次读取多个字节,出现乱码的原因
public class InputStreamTest2 {
public static void main(String[] args) throws Exception{
FileInputStream fis = null;;
try {
fis = new FileInputStream("hello.txt");
//int fileLength = fis.available();//269字节,可以获取该文件的字节数
byte[] bytes = new byte[5];
//read(byte[]): 意思是从输入流中最多读取bytes.length个 字节数据,并存储到字节数组bytes中,返回的是实际读取的字节个数
int len;
while((len = fis.read(bytes)) != -1) {
//然后将这次数组的数据,通过String(byte[] bytes, int offset, int length) 给解码显示出来
System.out.print(new String(bytes,0,len));
}
/**
* 一: 为什么当字节数组的长度 < 文件的字节个数 可能会出现乱码呢?
* hello.txt文本内容:
* hello王
*
* 情况一:如果刚好字节数组的长度是5,那么第一次会将hello输出,第二次就把王输出了,结果是不会出现乱码
*
* 情况二:如果刚好字节数组的长度是6,那么第一次会将hello+王这个字符的一半输出,
* 第二次将王的另一半+后面的字节数据输出,你说会不会乱码
*
* 结论: 所以你使用字节流读中文如果不想乱码,这种方式也是不可取的,因为这种情况是不确定性的
*
*
* 二: 只有字节数组的长度 >= 文件的字节个数,才一定不会出现乱码!
* 根据上面的结论得出:
* FileInputStream fis = new FileInputStream("hello.txt");
* byte[] bytes = new byte[fis.available()];
* 就刚好满足,文件的字节数 = 字节数组的长度 !!
*
* 但是我突然发现,字节数组的长度,不可能无限大吧,通过我的测试大约可以承受60M的文件!
* 修改虚拟机的最大内存虚拟机的最大内存,我把eclipse的vm arguments设置修改为:-Xms256M -Xmx768M
* 现byte数组的最大长度可以更长!,但是再长也不可能超过1G吧, 所以对于这种大文件来说,这种方式还是不可取的,所以根据自己
* 的需求决定!!
*
* 结论: 60M以上的小文件可取, 大文件不可取
*/
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis!=null) fis.close();
}
}
}
其实使用字节流读取文件 (带中文的) 不会出现乱码
看到这里, 你们肯定想说, 上面明明说,会产生乱码, 这里又说不会,到底是怎么一会事呢?
案例一,二:产生乱码的原因是, 每次获得一个字节/字节数组,就把该字节/字节数组转换为了字符数据,然后输出到控制台
案例三:确实是使用IO流读取数据,写到文本文件,你读取一个字节,我就写一个字节,中间没有做任何的转换,他会自己转换
package inputstream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class InputStreamTest3 {
public static void main(String[] args) throws Exception{
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("hello.txt");//输入的文件要存在,不然会报错
fos = new FileOutputStream("world.txt"); //输出的文件可以不存在,会自己创建的
byte[] bytes = new byte[1024];
int len;
while((len = fis.read(bytes)) != -1) {
fos.write(bytes);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fos!=null) fos.close();
if(fis!=null) fis.close();
}
}
}
那你可能有如下疑问:是如何识别将两个字节拼接成一个中文的呢?
中文一般是负数
6.Java IO包之缓冲Buffer输⼊输出流
- 什么是缓冲 Buffer 它是内存空间的⼀部分,在内存空间中预留了⼀定的存储空间,这些存储空间
⽤来缓冲输⼊或输出的数据,这部分空间就叫做缓冲区,缓冲区是具有⼀定⼤⼩的 - 为啥要⽤缓冲
传统的io流是阻塞的 是个 读/写,等待,读/写…这样的一个过程
缓存流是指在内存中开辟空间来存放读/写的数据。从而实现读写次数减少,即使用io资源的次数减少
例如操作磁盘⽐内存慢的很多,数据传输速度和数据处理的速度存在不平衡,⽐如你每秒要写100次硬盘,对系统冲击很⼤,浪费了⼤量时间在忙着处理开始写和结束写这两个事件,⽤buffer暂存起来,变成每10秒写⼀次硬盘,数据可以直接送往缓冲区,⾼速设备不⽤再等待低速设备,对系统的冲击就很⼩,写⼊效率⾼了,联想一下生活中的例子,我们搬砖的时候,一块一块地往车上装肯定是很低效的。我们可以使用一个小推车,先把砖装到小推车上,再把这小推车推到车前,把砖装到车上。这个例子中,小推车可以视为缓冲区,小推车的存在,减少了我们装车次数,从而提高了效率。 - 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();
使⽤缓冲输⼊输出流完成⽂件拷⻉
try {
FileInputStream fis = new
FileInputStream("C:\\Users\\79466\\Desktop\\test\\xdclass.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new
FileOutputStream("C:\\Users\\79466\\Desktop\\txet\\copy.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int size;
byte[] buf = new byte[1024];
while ((size = bis.read(buf)) != -1) {
bos.write(buf, 0, size);
}
//刷新此缓冲的输出流,才可以保证数据全部输出完成,close会⾃动关闭
//bos.flush();
//关闭的时候只需要关闭最外层的流就⾏了,源码⾥⾯会⾃动关闭inputstream对象的
bis.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
注意点
BufferedOutputStream在close()时会⾃动flush
BufferedOutputStream在不调⽤close()的情况下,缓冲区不满,⼜需要把缓冲区的内容写⼊到⽂件或通过⽹络发送到别的机器时,才需要调⽤flush.
流的关闭顺序
后开先关, 如果A依赖B,先关闭B