参考原链接:http://blog.csdn.net/zhaoyanjun6/article/details/54292148
流的概念和作用
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
程序需要数据 --> 读进来 --> 输入
程序保存数据 --> 写出去 --> 输出
IO流的分类
- 根据处理数据类型的不同分为:字符流和字节流
- 根据数据流向不同分为:输入流和输出流
- 根据功能的不同可分为:节点流和处理流
节点流:直接操作目标设备,例如:磁盘或一块内存区域。
处理流:通过操作节点流,从而间接完成输入或输出功能的流。处理流是的存在是建立在一个已经存在的输入流或输出流的基础之上的。
操作流的步骤
以文件流为准:
- 使用 File 类找到一个文件对象,得到 IO 操作的源或目标
- 通过字节流或字符流的子类创建对象,(得到 IO 操作的通道)
- 进行读或写的操作,(IO 操作)
- 关闭输入/输出,(打完收工,注意节约资源,关掉)
由于流的操作属于资源操作,所以在操作的最后一定要关闭以释放资源。
联系生活中的例子:比如想把水井里的水弄到家里的大水缸去,怎么搞呢?
1.找到水井在哪里;2.找根管子一头接在水井里,一头接在家里的大水缸里;3.打开管子上的龙头,放水;4.水放满了,关掉水龙头.
内存缓冲区,程序每一次的 write 方法都是写到这个内存缓冲区中,只有这个缓冲区装满了之后,系统才将这个缓冲区的内容一次集中写到外部设备.
好处:1.有效提高了 CPU 的使用率;2.write 方法并没有马上真正写入到外部设备,我们还有机会回滚部分写入的数据;
代码实现:
package IoTest;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
public class PrintDemo9 {
public static void main(String[] args) throws Exception {
// 第一步:创建源!
String filename = "6.4";// 这个文件是在工作空间里面,没有后缀名!
// 第二步:创建管道!
InputStream ips = new FileInputStream(filename);
// 第三步:操作!
byte[] buff = new byte[1024];
int len;// 定义缓冲区
while ((len = ips.read(buff)) != -1) {
System.out.println(new String(buff, 0, buff.length));// 输出到控制台!此时的输出流就是打印流!System.out.println("==========================================");//打印下,看哪里在1024。1024的地方被隔开了
}
// 第四步:关闭资源(字符流必须关闭资源,因为它中间有缓冲区!对于字节流可以不用关闭,但是还是建议写上,习惯!)
ips.close();
}
}
字符流和字节流
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。
字节流和字符流的区别:
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符(char)类型的数据。
- 字节流:一次读入或读出是8位二进制。
- 字符流:一次读入或读出是16位二进制。
- 在进行字符流操作的时候会使用到缓冲区(内存中),而字节流操作的时候是不会使用到缓冲区的。
- 在输出的时候,OutputStream 类即使最后没有关闭内容也可以输出。但是如果是 Writer的话,则如果不关闭,最后一条内容是无法输出的,因为所有的内容都是保存在了缓冲区之中,每当调用了 close()方法就意味着清空缓冲区了。那么可以证明字符流确实使用了缓冲区。
- 字节流:程序 → 文件
- 字符流:程序 → 缓冲区(内存中) → 文件
设备上的数据无论是图片或者视频,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
输入流和输出流
输入流只能进行读操作,输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
输入字节流 InputStream
InputStream
是所有的输入字节流的父类,它是一个抽象类。ByteArrayInputStream
、StringBufferInputStream
、FileInputStream
是三种基本的介质流,它们分别从Byte 数组
、StringBuffer
、和本地文件
中读取数据。PipedInputStream
是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。ObjectInputStream
和所有FilterInputStream
的子类都是装饰流(装饰器模式的主角)。
输出字节流 OutputStream
OutputStream
是所有的输出字节流的父类,它是一个抽象类。ByteArrayOutputStream
、FileOutputStream
是两种基本的介质流,它们分别向Byte 数组
、和本地文件
中写入数据。PipedOutputStream
是向与其它线程共用的管道中写入数据。ObjectOutputStream
和所有FilterOutputStream
的子类都是装饰流。
总结:
- 输入流:InputStream或者Reader:从文件中读到程序中; 程序需要数据 --> 读进来 --> 输入
- 输出流:OutputStream或者Writer:从程序中输出到文件中;程序保存数据 --> 写出去 --> 输出
节点流和处理流
节点流
节点流:直接与数据源相连,读入或读出。
直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。
常用的节点流
- 父 类 :
InputStream
、OutputStream
、Reader
、Writer
- 文 件 :
FileInputStream
、FileOutputStrean
、FileReader
、FileWriter
文件进行处理的节点流 - 数 组 :
ByteArrayInputStream
、ByteArrayOutputStream
、CharArrayReader
、CharArrayWriter
对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组) - 字符串 :
StringReader
、StringWriter
对字符串进行处理的节点流 - 管 道 :
PipedInputStream
、PipedOutputStream
、PipedReader
、PipedWriter
对管道进行处理的节点流
处理流
处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。如BufferedReader
.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
常用的处理流
- 缓冲流:
BufferedInputStrean
、BufferedOutputStream
、BufferedReader
、BufferedWriter
增加缓冲功能,避免频繁读写硬盘。 - 转换流:
InputStreamReader
、OutputStreamReader
实现字节流和字符流之间的转换。 - 数据流:
DataInputStream
、DataOutputStream
等-提供将基础数据类型写入到文件中,或者读取出来。
转换流
FileWriter 和 FileReader 分别是 OutputStreamWriter 和 InputStreamReader 的直接子类,而不 是Writer 和Reader 的直接子类,区别于 FileInputStream 和 InputStream。
InputStreamReader
、OutputStreamWriter
要InputStream
或OutputStream
作为参数,实现从字节流到字符流的转换。
InputStreamReader(InputStream); //通过构造函数初始化,使用的是本系统默认的编码表GBK。
InputStreamWriter(InputStream,String charSet); //通过该构造函数初始化,可以指定编码表。
OutputStreamWriter(OutputStream); //通过该构造函数初始化,使用的是本系统默认的编码表GBK。
OutputStreamwriter(OutputStream,String charSet); //通过该构造函数初始化,可以指定编码表。
IO异常的处理:
关流操作放在finall里面
1处:要重新定义变量,fw.close()的fw是访问不到的,所以需要声明变量,但是不能直接在try外面,因为不能捕捉新建文件存在的异常,所以进行的操作是:在外面声明变量,在里面进行初始化。
2处:fw.close()也需要异常处理,所以需要单独try....catch
3处:如果文件不能正常建立,会出现一个空指针异常:因为文件为空,不能调用close()方法,所以采取3处解决方法
自动关闭资源的 try 语句
Java7 简化资源清理(try-with-resources)自动关闭资源的 try语句
自动关闭资源格式:(不再是finally里面关闭资源)
try( )//此处多了圆括号,()圆括号内写打开资源的代码,在这里创建的对象必须实现
Autocloseable 接口
{
IO 操作
}
catch(){
处理异常的代码
}
具体如下所示:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Demo8 {
public static void main(String[] args) throws IOException {
File src = new File("E:/自荐信.doc");
File tar = new File("E:/自荐信1.doc");
copy(src, tar);
System.out.println("Well done !");
}
public static void copy(File src, File tar) throws IOException {
try (InputStream is = new FileInputStream(src);
OutputStream os = new FileOutputStream(tar);) //圆括号 内写打开资源的操作
{
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1) {
os.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
内存操作流
操作内存流的时候(从读取出来,注意一定要把真正的数据用toByteArray或者toCharArray 将数据读出来)
之前的文件操作流是以文件的输入输出为主的,当输出的位置变成了内存,那么就称为内 存操作流。此时要使用内存流完成内存的输入和输出操作。
如果程序运行过程中要产生一些临时文件,可采用虚拟文件方式实现;
直接操作磁盘的文件很耗性能,使用内存流可以提升性能;jdk 里提供了内存流可实现类似于内存虚拟文件的功能。
ByteArrayInputStream:将内容写到内存中 CharArrayReader
ByteArrayOutputStream:将内存中的数据写出 CharArrayWriter
ByteArrayInputStream:构造方法:
public ByteArrayInputStream(byte[] buf):全部内容
public ByteArrayInputStream(byte[] buf,int offset,int length):指定范围的内容
ByteArrayOutputStream:
public ByteArrayOutputStream()
内存流操作:先把数据写到内存中去,然后再从内存中读取出来!提升了性能!
package Array;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayDemo7 {
public static void main(String[] args) throws IOException {
String s = "java is a good language";
ByteArrayOutputStream bos = new ByteArrayOutputStream();//输出流
bos.write(s.getBytes()); // 已经把信息写到了内存中
byte[] bys = bos.toByteArray();// 得到真正的数据
ByteArrayInputStream bis = new ByteArrayInputStream(bys);// 输入流,需要源。
byte[] b = new byte[1024]; int len;
while ((len = bis.read(b)) != -1) {
String data = new String(b, 0, len);
System.out.println(data);
}
}
}
打印流
(只有两个,PrintWriter 和PrintStream)
思考:如果现在要想完成一个字符串或者是boolean 型或者是字符型的数据输出使用 OutputStream 是否方便? 肯定是不方便的,因为 OutputStream 中只能操作字节数据,所以其他的数据类型很难操作, 那么在Java 的 IO 包中为了解决这种问题增加了两种类:PrintStream、PrintWriter。
打印流有非常好的打印功能,可以打印任何的数据类型。如,整数,小数,字符串等。
观察PrintStream 类的构造:
public PrintStream(File file)throws FileNotFoundException
public PrintStream(OutputStream out)
虽然PrintStream 是 OutputStream 的子类,但是在实例化的时候依然需要一个 OutputStream 的对象。
PrintWriter 和 PrintStream 都属于输出流,分别针对字符和字节。
PrintWriter 和 PrintStream 重载的 print()和println()用于多种数据类型的输出。 print()里的参数不能为空;println()可以
PrintWriter 和 PrintStream 输出操作不抛出异常
PrintStream 调用 println 方法有自动 flush 功能;
与PrintStream不同,若PrintWriter使用了自动刷新方法,那么必须调用 println,print,format这些方法的其中一个才可以实现操作
缓冲流
缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写效率, 同时增加了一些新的方法。
四种缓冲流
BufferedReader(Reader in)
BufferedReader(Reader in,int sz)//sz 表示自定义缓冲区大小
BufferedWriter(Writer out)
BufferedWriter(Writer out,int sz)
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in,int sz)
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out,int sz)
BufferedReader 提供 readLine 方法用于读取一行字符串。
BufferedWriter 提供了 newLine 方法用于写入一个行分隔符。等价于//.writer("\r\n");
对于输出的缓冲流,写出的数据会先在内存中缓冲,使用 flush 方法将会使内存中的数据立刻写出。
package Array;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
//用缓冲流,性能相对高些
public class BufferedInputStreamDemo22 {
public static void main(String[] args) throws IOException {
/* *
BufferedInputStream bis = new BufferedInputStream(new * FileInputStream("68.txt"));
BufferedOutputStream bos = new * BufferedOutputStream(new FileOutputStream("buffer.txt"));
* int len = 0;
* while((len = bis.read()) != -1){
* bos.write(len);
* }
* bos.close();
* bis.close();
* }
*/
try ( BufferedReader br=newBufferedReader(newFileReader("68.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter( "bufferWriter.txt")))
{//java7新特性,自动关闭资源
String Line = null;
while ((Line = br.readLine()) != null) {
bw.write(Line); bw.newLine();//此时必须加上换行操作,注意这是个新用法(方法)
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
合并流
需要两个源文件,还有输出的目标文件
SequenceInputStream: 将两个文件的内容合并成一个文件
该类提供的方法: SequenceInputStream(InputStream s1,InputStream s2) :根据两个字节输入流对象来创建合并 流对象。
备注:谁放在前面,谁就先打印出来
实战演练
- FileInputStream类的使用:读取文件内容
package IoTest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class PrintDemo9 {
public static void main(String[] args) {
PrintDemo9 a1 = new PrintDemo9();
//电脑d盘中的abc.txt 文档
String filePath = "D:/abc.txt" ;
String reslut = a1.readFile( filePath ) ;
System.out.println( reslut );
}
/**
* 读取指定文件的内容
* @param filePath : 文件的路径
* @return 返回的结果
*/
public String readFile( String filePath ){
FileInputStream fis=null;
String result = "" ;
try {
// 根据path路径实例化一个输入流的对象
fis = new FileInputStream( filePath );
//2. 返回这个输入流中可以被读的剩下的bytes字节的估计值;
int size = fis.available() ;
//3. 根据输入流中的字节数创建byte数组;
byte[] array = new byte[size];
//4.把数据读取到数组中;
fis.read( array ) ;
//5.根据获取到的Byte数组新建一个字符串,然后输出;
result = new String(array);
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
if ( fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result ;
}
}
- FileOutputStream 类的使用:将内容写入文件
package IoTest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class PrintDemo9 {
public static void main(String[] args) {
PrintDemo9 a2 = new PrintDemo9();
//电脑d盘中的abc.txt 文档
String filePath = "D:/abc.txt" ;
//要写入的内容
String content = "今天是2017/1/9,天气很好" ;
a2.writeFile( filePath , content ) ;
}
/**
* 根据文件路径创建输出流
* @param filePath : 文件的路径
* @param content : 需要写入的内容
*/
public void writeFile( String filePath , String content ){
FileOutputStream fos = null ;
try {
//1、根据文件路径创建输出流
fos = new FileOutputStream( filePath );
//2、把string转换为byte数组;
byte[] array = content.getBytes() ;
//3、把byte数组输出;
fos.write( array );
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally{
if ( fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:
- 在实际的项目中,所有的IO操作都应该放到子线程中操作,避免堵住主线程。
FileInputStream
在读取文件内容的时候,我们传入文件的路径("D:/abc.txt"
), 如果这个路径下的文件不存在,那么在执行readFile()
方法时会报FileNotFoundException
异常。
FileOutputStream
在写入文件的时候,我们传入文件的路径("D:/abc.txt"
), 如果这个路径下的文件不存在,那么在执行writeFile()
方法时, 会默认给我们创建一个新的文件。还有重要的一点,不会报异常。