您的位置:首页 > 大数据 > 人工智能

IO(一):传统IO(基于字符,字节,Socket) 与BIO,NIO,AIO 介绍

2015-11-16 19:53 295 查看
/article/3591677.html

一. Java IO 框架介绍

二. 传统 IO 介绍

2.1 基于字节操作 IO(InputStream,OutputStream)

2.2 基于字符操作 IO (Reader,Writer)

2.3 传统 IO 中的 Socket网络通信

三. BIO,NIO,AIO 简介

四.参考,PS,欠缺

一. Java IO 框架介绍

如下图:

在作者的理解中,Java IO 框架首先会分为两个大类,一个是 Java 传统的 IO,另外一个就是涉及到网络通信的 IO。在不涉及到网络通信的 IO 中,即使用我们传统的 IO,分为:


基于字节操作的 IO:InputStream,OutputStream 类。

基于字符操作的 IO:Reader,Writer类。

基于磁盘操作的 IO:File类。

基于网络通信的 IO:Socket等类。

那么在涉及到网络通信的 IO这一块,又有 BIO,NIO,AIO,概念如下:

BIO:同步阻塞。服务器实现模式为 一个连接一个线程,也就是当客户端有请求连接的时候就需要启动一个线程进行处理,如果这个连接不做任何事情,会造成不必要的线程开销,当然可以通过线程池机制来改善。

NIO:同步非阻塞。服务器实现模式为一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,多路复用器会轮询到连接有 I/O 请求时才启动一个线程进行处理。

AIO:异步非阻塞。服务器实现模式为一个有效请求一个线程,客户端的 I/O 请求都是由 OS(操作系统) 先完成了再通知服务器应用去启动相应的线程进行处理。

适用场景介绍:

BIO适用于连接数目比较小,而且固定的架构中,对服务器资源要求会比较高。

NIO适用于连接数比较多而且比较短的架构中,比如说聊天服务器,编程有点复杂。

AIO适用于连接数目比较多,而且连接比较长的架构中,比如相册服务器,充分调用OS(操作系统)参与并发操作,编程比较复杂,JDK7以上支持。

二. 传统 IO 介绍

2.1 基于字节操作 IO(InputStream,OutputStream)

java 的 io 是装饰者模式的典型例子。首先认识这两个模式:

装饰者模式:

装饰者模式能够实现动态为对象添加功能。那么在Java 的 io 中,根据数据类型和操作方式会有很多不同的类,不同的类处理不同的应用场景,很多时候我们既需要A类的操作方式,也需要B类的操作方式,此时利用装饰者模式就可以把不同的类组合起来,达到扩展功能的目的。
装饰者模式的实现如上图。


首先有一个共同的超类 Component,即组建对象接口。

然后会有ConcreteComponent类实现了Component接口。也称为被装饰的对象。

Decorator就是具体装饰器(ConcreteDecoratorA,B)的父类。里面维护了一个超类Component的引用。该对象其实就是被装饰的对象。

ConcreteDecoratorA,B 就是具体的装饰器类,实现具体要向被装饰对象添加的功能。用来装饰具体的组件对象或者另外一个具体的装饰器对象。

那么 在Java .io 包中,InputStream,OutputStream 就相当于上述的超类 Component。InputStream有很多实现类,比如比较常用的有ByteArrayInputStream,FileInputStream,FilterInputStream,ObjectInputStream等实现类。

ByteArrayInputStream:继承了InputStream,但是并没有维护一个InputStream对象的引用,所以说相当于一个基本组件的实现类。我们的流的来源和目的地不一定是文件,也可能是内存的一块空间,例如一个字节数组。那么,ByteArrayInputStream 和 ByteArrayOutputStream,就可以将一个字节数组作为一个流的来源,或者作为流的目的地。例如如下代码:

[java] view plaincopy

@org.junit.Test

public void testByteArrayInputStream(){

//构建流的来源
String sourceStr = "abcdefg";
byte[] source = sourceStr.getBytes();
byte[] des = new byte[source.length];

ByteArrayOutputStream bout = new ByteArrayOutputStream();
ByteArrayInputStream bin = new ByteArrayInputStream(source);

//篇幅有限,省略了异常捕获
bout.write(source);
bout.close();
bin.read(des);
System.out.println(new String(des));
bin.close();


}

FileInputStream:和ByteArrayInputStream一样,继承了InputStream,但是并没有维护一个InputStream对象的引用,所以说相当于一个基本组件的实现类。顾名思义,FileInputStream 可以用来读取 文件中的内容,当然是以字节的形式。如下代码:

[java] view plaincopy

@org.junit.Test

public void testFileInputStream(){

//篇幅有限,省略了异常捕获

FileInputStream fin = new FileInputStream(“C:\Users\whc\Desktop\abc.txt”);

byte data[] = new byte[1000];

fin.read(data);

System.out.println(new String(data));

fin.close();

}

FilterInputStream:该类继承了InputStream,并且内部维护了一个InputStream 的引用,该类则属于一个装饰者类的父类。从源码上看该类并没有太多的用处,只是作为后续多个具体装饰者类的父类。

BufferedInputStream:该类继承了 FilterInputStream,属于一个具体装饰者类。BufferedInputStream 默认只有 8k 的大小,当然我们可以通过构造函数来扩大缓冲容量。这个类中最主要的方法是 fill() 方法。它提供了缓冲区域的读取,写入,区域元素的移动更新等。

PS:fill() 方法的原理,区域移动看了两遍实在不会。。。!!

DataInputStream:和 BufferedInputStream 一样,DataInputStream 也是继承于 FilterInputStream,这样DataInputStream 也是一个具体的装饰者类。该类的主要功能是可以返回一些基本类型或者是String 类型的数据,否则的话只能返回 byte 字节类型的数据。利用它,能让我们更好的操作数据。例如:

[java] view plaincopy

@org.junit.Test

public void testDataInputStream() throws IOException{

DataInputStream din = new DataInputStream(new FileInputStream(“C:\Users\whc\Desktop\abc.txt”));

DataOutputStream dout = new DataOutputStream(new FileOutputStream("C:\\Users\\whc\\Desktop\\abc.txt"));
dout.writeInt(999);

System.out.println(din.readInt());
din.close();


}

ObjectInputStream 和 ObjectOutputStream 所读写的对象必须实现 Serializable 接口,对象中的 transient 和 static 类型成员变量不会被读取和写入。

[java] view plaincopy

@org.junit.Test

public void testObjectInputStream() throws FileNotFoundException, IOException, ClassNotFoundException{

ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(“C:\Users\whc\Desktop\abc.txt”));

User user = new User();
user.setNum(2);
oout.writeObject(user);

ObjectInputStream oin = new ObjectInputStream(new FileInputStream("C:\\Users\\whc\\Desktop\\abc.txt"));

System.out.println(oin.readObject());

oout.close();
oin.close();


}

[java] view plaincopy

public class User implements Serializable{

private static final long serialVersionUID = -8987587467273881932L;

private int num;

public int getNum() {

return num;

}

public void setNum(int num) {

this.num = num;

}

@Override

public String toString() {

return “User [num=” + num + “]”;

}

}

以上就是基于 字节的 IO。

总结:

装饰者模式

InputStream,OutputSteam 以及一系列的子类。

2.2 基于字符操作 IO (Reader,Writer)

不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作都是字节而不是字符,有字符 I/O 接口是因为我们通常在程序中操作的数据都是以字符的形式,为了操作方便提供了一个直接写字符的 I/O 接口。

对于Reader 和 Writer 是所有字符流类的抽象基类,用于简化对字符串的输入输出编程,即用于读写文本数据。使用FileWriter 写入字符数据要比 FileOutputStream 要简便很多,但是使用FileReader 并不比 FileInputStream 要简便很多。都是要读取一个字符数组或者字节数组,然后把数组转换成字符串。

常用类有:

BufferedReader:有8k 字符的缓冲(不是字节)。通常,Reader所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,建议用BufferedReader包装所有其read()操作可能开销很高的Reader(如FileReader和InputStreamReader)在BufferedReader 在读取文件的时候,会先尽量从文件中读入字符数据并置入缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取,使用BufferedWriter时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。

InputStreamReader:InputStreamReader 将字节流转换为字符流。是字节流通向字符流的桥梁。如果不指定字符集编码,该解码过程将使用平台默认的字符编码,如:GBK。

构造一个默认编码集的InputStreamReader类:

[java] view plaincopy

InputStreamReader isr = new InputStreamReader(InputStream in);

构造一个指定编码集的InputStreamReader类。

[java] view plaincopy

InputStreamReader isr = new InputStreamReader(InputStream in,String charsetName);

参数 in对象通过 InputStream in = System.in;获得。//读取键盘上的数据。或者 InputStream in = new FileInputStream(String fileName);//读取文件中的数据

FileReader:FileInputStream 和 FileReader之间的区别,见最后。

CharArrayReader:对于CharArrayReader,是一个把字符数组作为源的输入流。和ByteArrayReader同理。

2.3 传统 IO 中的 Socket网络通信

可参考文章,已经写得非常详细了:/article/3758516.html

三. BIO,NIO,AIO 简介

Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。那么NIO 是面向缓冲的。

关于阻塞:Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻 塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

选择器:Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

四.参考,PS,欠缺

参考:

1. Java BIO,NIO,AIO 学习:/article/4514233.html

2. ByteArrayInputStream 干什么用:http://zhidao.baidu.com/link?url=8LiqQzlPg3MSA1QtUaXHe08iRvp6ZhwIo3QgODQoD0gllV-hVtX6ukw2Fpi1aiC-aAjscrM9h7vidov9NF4LCq

3. 装饰器模式:http://blog.csdn.net/hust_is_lcd/article/details/7884320

4.InputStreamReader 和BufferedReader 用法及真实案例:http://www.tuicool.com/articles/U7VFFr

5.FileInputStream 和 FileReader 之间的区别:http://www.importnew.com/11537.html

6.Java Socket 编程:/article/3758516.html

7. Java NIO 与 IO:http://blog.csdn.net/keda8997110/article/details/19549493

Ps:

1. FileInputStream 和 FileReader 之间的区别:

在解释Java中FileInputStream和FileReader的具体区别之前,我想讲述一下Java中InputStream和Reader的 根本差异,以及分别什么时候使用InputStream和Reader。

实际上, InputStream和Reader都是抽象类,并不直接地从文件或者套接字(socket)中读取数据。然而,它们之间的主要差别在 于:InputStream用于读取二进制数据(字节流方式,译者注),Reader用于读取文本数据(字符流方式,译者注),准确地说,Unicode 字符。那么,二进制数据和文本数据的区别是什么呢?当然,所有读取的东西本质上是字节,然后需要一套字符编码方案,把字节转换成文本。

Reader类使用 字符编码来解码字节,并返回字符给调用者。Reader类要么使用运行Java程序平台的默认字符编码,要么使用Charset对象或者String类型 的字符编码名称,如“UTF-8”。尽管它是一个最简单的概念,当读取文本文件或从套接字中读取文本数据时,很多Java开发者会因没有指定字符编码而犯 错。记住,如果你没有指定正确的编码,或者你的程序没有使用的协议中已存在的字符编码,如HTML的 “Content-Type(内容类型)”、XML文件头指定的编码,你可能无法正确地读取的所有数据。一些不是默认编码呈现的字符,可能变成“?”或小 方格。

一旦你知道stream和reader之间的根本区别,理解FileInputStream和FileReader之间的差异就很容易了。既可以让 你从文件中读取数据,然而FileInputStream用于读取二进制数据,FileReader用来读取字符数据。

由于FileReader类继承了InputStreamReader类,使用的字符编码,要么由类提供,要么是平台默认的字符编码。

归根结底:

使用FileReader或BufferedReader从文件中读取字符或文本数据,并总是指定字符编码;使用FileInputStream从Java中文件或套接字中读取原始字节流。

欠缺:

1. fill() 方法的原理,区域移动看了两遍实在不会。。。!!

2. Socket 部分 没实际写过聊天室。

3. NIO,AIO 了解其概念,但是还未编程。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: