IO流

Java IO流的概念

    java的IO是实现输入和输出的基础,可以方便实现数据的输入和输出操作。在java中把不同的输入/输出源(键盘,文件,网络连接等)抽象的表述为“流”(stream)。通过流的形式允许java程序使用相同的方式来访问不同的输入/输出源。stream是从源(source)到接收(sink)的有序数据。

注:java把所有的传统的流类型都放到在java io包下,用于实现输入和输出功能。

 IO流分支图(引自网络)

按照不同的分类方式,可以把流分为三种类型

按照流的流向划分:输入流和输出流

  • 输入流: 从数据源中读取数据到程序内存中,是一个拉取数据的过程。

  • 输出流: 把程序内存中的数据写入到文件,是一个推送数据的过程。

如图所示

数据从服务器通过网络流向客户端,在这种情况下,Server端的内存负责将数据输出到网络里,因此Server端的程序使用输出流;Client端的内存负责从网络中读取数据,因此Client端的程序使用输入流。

按照操作单元划分:字节流和字符流

    字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的数据单元是8位的字节,字符流操作的数据单元为16位的字符。

字节流主要是由InputStream和outPutStream作为基类,而字符流则主要有Reader和Writer作为基类。

按照流的角色划分:节点流和处理流

    节点流:可以从(向)一个特定的IO设备(如磁盘,网络)读(写)数据的流,节点流也被称为低级流。
     处理流:用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。处理流也被称为高级流。

    当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入和输出节点连接。使用处理流的一个明显的好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源,随着处理流所包装的节点流的变化,程序实际所访问的数据源也相应的发生变化。

流的原理浅析

    java IO流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, 这40多个类都是从如下4个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

 

分类字节输入流字节输出流字符输入流字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串     StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流     InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream    
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流   PrintStream   PrintWriter
推回输入流 PushbackInputStream   PushbackReader  
特殊流 DataInputStream DataOutputStream    

 java输入/输出流体系中常用的流的分类表

注:表中粗体字所标出的类代表节点流,必须直接与指定的物理节点关联:斜体字标出的类代表抽象基类,无法直接创建实例。

常用的IO流的用法

IO体系的基类(InputStream/Reader,OutputStream/Writer)

InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可使用的方法。 


InputStream中包含3个方法

int read();从输入流中读取单个字节(相当于从图15.5所示的水管中取出一滴水),返回所读取的字节数据(字节数据可直接转换为int类型)。 

int read(byte[] b);从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。

int read(byte[] b,int off,int len); 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。

Reader中包含3个方法

int read();从输入流中读取单个字符,返回所读取的字符数据(字节数据可直接转换为int类型)。 

int read(char[] b);从输入流中最多读取b.length个字符的数据,并将其存储在字节数组b中,返回实际读取的字符数。

int read(char[] b,int off,int len); 从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数。

InputStream和Reader提供了一些移动指针的方法

void mark(int readAheadLimit); 在记录指针当前位置记录一个标记(mark)。 

boolean markSupported(); 判断此输入流是否支持mark()操作,即是否支持记录标记。

void reset(); 将此流的记录指针重新定位到上一次记录标记(mark)的位置。

long skip(long n); 记录指针向前移动n个字节/字符。

 

OutputStream和Writer的用法也非常相似,两个流都提供了如下三个方法

void write(int c); 将指定的字节/字符输出到输出流中,其中c即可以代表字节,也可以代表字符。 

void write(byte[]/char[] buf); 将字节数组/字符数组中的数据输出到指定输出流中。

void write(byte[]/char[] buf, int off,int len ); 将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。

因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。

Writer里面还包含如下两个方法

void write(String str); 将str字符串里包含的字符输出到指定输出流中。 

void write (String str, int off, int len); 将str字符串里面从off位置开始,长度为len的字符输出到指定输出流中。

IO体系的基类文件流的使用(FileInputStream/FileReader ,FileOutputStream/FileWriter)

   InputStream和Reader都是抽象类,本身不能创建实例,但它们分别有一个用于读取文件的输入流:FileInputStream和FileReader,它们都是节点流——会直接和指定文件关联。下面程序示范使用FileInputStream和FileReader。 
使用FileInputStream读取文件

public class MyClass {
 
public static void main(String[] args)throws IOException{
 
FileInputStream fis=null;
 
try {
 
//创建字节输入流
 
fis=new FileInputStream("E:\learnproject\Iotest\lib\src\main\java\com\Test.txt");
 
//创建一个长度为1024的竹筒
 
byte[] b=new byte[1024];
 
//用于保存的实际字节数
 
int hasRead=0;
 
//使用循环来重复取水的过程
 
while((hasRead=fis.read(b))>0){
 
//取出竹筒中的水滴(字节),将字节数组转换成字符串进行输出
 
System.out.print(new String(b,0,hasRead));
 
}
 
}catch (IOException e){
 
e.printStackTrace();
 
}finally {
 
fis.close();
 
}
 
}
 
}

 注:上面程序最后使用了fis.close()来关闭该文件的输入流,与JDBC编程一样,程序里面打开的文件IO资源不属于内存的资源,垃圾回收机制无法回收该资源,所以应该显示的关闭打开的IO资源。Java 7改写了所有的IO资源类,它们都实现了AntoCloseable接口,因此都可以通过自动关闭资源的try语句来关闭这些Io流。

 

使用FileReader读取文件

public class FileReaderTest {
public static void main(String[] args)throws IOException{
 
FileReader fis=null;
 
try { 
//创建字节输入流
fis=new FileReader("E:\learnproject\Iotest\lib\src\main\java\com\Test.txt");
 
//创建一个长度为1024的竹筒
char[] b=new char[1024];
 
//用于保存的实际字节数
int hasRead=0;
 
//使用循环来重复取水的过程
while((hasRead=fis.read(b))>0){
 
//取出竹筒中的水滴(字节),将字节数组转换成字符串进行输出 
System.out.print(new String(b,0,hasRead)); 
}
}catch (IOException e){
e.printStackTrace();
}finally { 
fis.close();
}
} 
}

可以看出使用FileInputStream和FileReader进行文件的读写并没有什么区别,只是操作单元不同而且。

FileOutputStream/FileWriter是IO中的文件输出流,下面介绍这两个类的用法。

FileOutputStream的用法

public class FileOutputStreamTest {
public static void main(String[] args)throws IOException { 
FileInputStream fis=null;
FileOutputStream fos=null;

try {
//创建字节输入流
fis=new FileInputStream("E:\learnproject\Iotest\lib\src\main\java\com\Test.txt");
 
//创建字节输出流
fos=new FileOutputStream("E:\learnproject\Iotest\lib\src\main\java\com\newTest.txt");
byte[] b=new byte[1024];
int hasRead=0;
 
//循环从输入流中取出数据
while((hasRead=fis.read(b))>0){
 
//每读取一次,即写入文件输入流,读了多少,就写多少。
fos.write(b,0,hasRead);
} 
}catch (IOException e){ 
e.printStackTrace(); 
}finally {
fis.close(); 
fos.close(); 
} 
} 
}

运行程序可以看到输出流指定的目录下多了一个文件:newTest.txt, 该文件的内容和Test.txt文件的内容完全相同。FileWriter的使用方式和FileOutputStream基本类似。

: 使用java的io流执行输出时,不要忘记关闭输出流,关闭输出流除了可以保证流的物理资源被回收之外,可能还可以将输出流缓冲区中的数据flush到物理节点中里(因为在执行close()方法之前,自动执行输出流的flush()方法)。java很多输出流默认都提供了缓存功能,其实我们没有必要刻意去记忆哪些流有缓存功能,哪些流没有,只有正常关闭所有的输出流即可保证程序正常。

缓冲流的使用(BufferedInputStream/BufferedReader, BufferedOutputStream/BufferedWriter)

字节缓存流的用法(和字符缓冲用法一致)

public class BufferedStreamTest {
public static void main(String[] args)throws IOException {
FileInputStream fis=null;
FileOutputStream fos=null;
BufferedInputStream bis=null; 
BufferedOutputStream bos=null;
try {
//创建字节输入流
fis=new FileInputStream("E:\learnproject\Iotest\lib\src\main\java\com\Test.txt");
 
//创建字节输出流
fos=new FileOutputStream("E:\learnproject\Iotest\lib\src\main\java\com\newTest.txt");
 
//创建字节缓存输入流
bis=new BufferedInputStream(fis);
 
//创建字节缓存输出流
bos=new BufferedOutputStream(fos);
byte[] b=new byte[1024];
int hasRead=0;
 
//循环从缓存流中读取数据
while((hasRead=bis.read(b))>0){
 
//向缓存流中写入数据,读取多少写入多少
bos.write(b,0,hasRead);
}
}catch (IOException e){ 
e.printStackTrace();
}finally { 
bis.close();
bos.close(); } } }

可以看到使用字节缓存流读取和写入数据的方式和文件流(FileInputStream,FileOutputStream)并没有什么不同,只是把处理流套接到文件流上进行读写。

:上面代码中我们使用了缓存流和文件流,但是我们只关闭了缓存流。这个需要注意一下,当我们使用处理流套接到节点流上的使用的时候,只需要关闭最上层的处理就可以了。java会自动帮我们关闭下层的节点流。

转换流的使用(InputStreamReader/OutputStreamWriter)

   以获取键盘输入为例来介绍转换流的用法。java使用System.in代表输入。即键盘输入,但这个标准输入流是InputStream类的实例,使用不太方便,而且键盘输入内容都是文本内容,所以可以使用InputStreamReader将其包装成BufferedReader,利用BufferedReader的readLine()方法可以一次读取一行内容,如下代码所示:

public class InputStreamReaderTest {
public static void main(String[] args)throws IOException {
try {
// 将System.in对象转化为Reader对象
InputStreamReader reader=new InputStreamReader(System.in);
 
//将普通的Reader包装成BufferedReader
BufferedReader bufferedReader=new BufferedReader(reader);
String buffer=null;
while ((buffer=bufferedReader.readLine())!=null){
// 如果读取到的字符串为“exit”,则程序退出 if(buffer.equals("exit")){ System.exit(1); } //打印读取的内容 System.out.print("输入内容:"+buffer); } }catch (IOException e){ e.printStackTrace(); }finally { } } }

上面程序将System.in包装成BufferedReader,BufferedReader流具有缓存功能,它可以一次读取一行文本——以换行符为标志,如果它没有读到换行符,则程序堵塞。等到读到换行符为止。运行上面程序可以发现这个特征,当我们在控制台执行输入时,只有按下回车键,程序才会打印出刚刚输入的内容。

对象流的使用(ObjectInputStream/ObjectOutputStream)

public static void writeObject(){
OutputStream outputStream=null;
BufferedOutputStream buf=null;
ObjectOutputStream obj=null;
try { 
//序列化文件輸出流 
outputStream=new FileOutputStream("E:\learnproject\Iotest\lib\src\main\java\com\myfile.tmp"); 

//构建缓冲流 buf=new BufferedOutputStream(outputStream); //构建字符输出的对象流 obj=new ObjectOutputStream(buf); //序列化数据写入 obj.writeObject(new Person("A", 21));//Person对象 //关闭流 obj.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }

读取对象:

/**
* 读取对象
*/
public static void readObject() throws IOException {
try {
InputStream inputStream=new FileInputStream("E:\learnproject\Iotest\lib\src\main\java\com\myfile.tmp");
 
//构建缓冲流
BufferedInputStream buf=new BufferedInputStream(inputStream);
 
//构建字符输入的对象流
ObjectInputStream obj=new ObjectInputStream(buf);
Person tempPerson=(Person)obj.readObject();
System.out.println("Person对象为:"+tempPerson);
 
//关闭流 
obj.close();
buf.close(); 
inputStream.close();
} catch (FileNotFoundException e) { 
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) { 
e.printStackTrace(); 
} 
}

注意事项 

读取顺序和写入顺序一定要一致,不然会读取出错。 
在对象属性前面加transient关键字,则该对象的属性不会被序列化。

在开发中正确使用IO流

了解了java io的整体类结构和每个类的一下特性后,我们可以在开发的过程中根据需要灵活的使用不同的IO流进行开发。

原则

如果是操作二进制文件,使用字节流,如果操作的是文本文件就使用字符流。
尽可能多的使用处理流,这会使我们的代码更加灵活,复用性更好。

注意:

System是java.lang中的一个类,out是System内的一个成员变量,这个变量是一个java.io.PrintStream类的对象,println就是一个方法。

原文地址:https://www.cnblogs.com/lucky1024/p/11022606.html