一直谈IO,I/O到底是什么?
输入/输出问题,归根结底就是数据的流动。
要知道,在I/O性能上的小小投入就可换来可观的回报。
自从JVM优化之后,JVM运行字节码的速率已经接近本地编译代码,借助动态运行优化,其表现甚至还有所超越。意味着,多数java程序已不再受CPU的束缚,而更多时候受I/O的束缚(等待数据传输)
也有可能是, JVM 自身在 I/O 方面效率欠佳。操作系统与 Java 基于流的 I/O模型有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在硬件直接存储器存取(DMA)的协助下完成的。而 JVM 的 I/O 类喜欢操作小块数据——单个字节、几行文本。结果,操作系统送来整缓冲区的数据,java.io 的流数据类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。操作系统喜欢整卡车地运来数据,java.io 类则喜欢一铲子一铲子地加工数据。有了 NIO,就可以轻松地把一卡车数据备份到您能直接使用的地方
缓冲区操作
缓冲区,以及缓冲区是如何工作的,是所有I/O的基础。所谓“输入/输出”讲的无非就是把数据移进或移出缓冲区。
进程执行IO操作,归根起来,就是向操作系统发出请求,让它要么把缓冲区里的数据排干(写),要么用数据把缓冲区填满(读)。进程使用这一机制处理所有数据进出操作。
为什么不直接让磁盘控制器把数据送到用户空间的缓冲区呢?
- 磁盘通常不能直接访问用户空间
- 像磁盘这样基于块存储的硬件设备操作的是固定大小的数据块,而用户进程请求的可能是任意大小的或非对齐的数据块。在数据往来于用户空间与存储设备的过程中,内核负责数据的分解再组合工作。
用户空间和内核空间
- 用户空间:常规进程所在的区域。JVM就是常规进程,驻守于用户空间。
- 内核空间:操作系统所在的区域。能与设备控制器通讯,控制着用户区域进程的运行状态,等。所有的I/O都直接或间接通过内核空间。
判断是否是同步,往往根据从内核空间到用户空间的拷贝是自己完成的还是系统;
- 自己:同步
- 系统:异步
缓冲区
一切都是相对的~
深入研究缓冲区,了解各种不同的类型,并学会怎么使用。Buffer类是java.nio的构造基类。
一个Buffer对象是固定数量的数据的容器。其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索
缓冲区的工作与通道紧密联系。通道是 I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。对于离开缓冲区的传输,您想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据放置在您所提供的缓冲区中。这种在协同对象(通常是您所写的对象以及一到多个 Channel 对象)之间进行的缓冲区数据传递是高效
数据处理的关键。
属性
Capacity(容量)、上界(Limit)、位置(Position)、标记(Mark)
0<=mark<=position<=limit<=capacity
存取
get():指出下一个元素应从何处检索
put():指出下一个数据元素应该被插入的地方
翻转
filp():此函数将一个能继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态。
如果您接收到一个别处的被填满的缓冲区,您可能需要在检索前将其翻转
如果一个通道的read()操作完成,而您要查看被通道放入缓冲区内的数据,那么您需要在调用get之前翻转缓冲区。
释放
clear():将缓冲区重置为空状态。并不改变缓冲区中的任何数据元素,而是仅仅将上界设为容量的值,并把位置设回0.
注意和reset()的区别:
clear函数将清空缓冲区,而reset函数位置返回到一个先前设定的标记。如果标记未定义,则会导致InvalidMarkException异常。
字节总是八位吗?
目前,字节几乎被广泛认为是八个比特位。但这并非一直是实情。在过去的时代,每个字节可以是 3 到 12 之间任何个数或者更多个的比特位,最常见的是6 到 9 位。八位的字节来自于市场力量和实践的结合。它之所以实用是因为 8 位足以表达可用的字符集(至少是英文字符),8 是 2 的三次乘方(这简化了硬件设计),八恰好容纳两个十六进制数字,而且 8 的倍数提供了足够的组合位来存储有效的数值
通道
通道(Channel)是 java.nio 的第二个主要创新。它们既不是一个扩展也不是一项增强,而是全新、极好的 Java I/O 示例,提供与 I/O 服务的直接连接。Channel 用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。
通道会连接一个特定 I/O 服务且通道实例(channel instance)的性能受它所连接的 I/O 服务的特征限制,记住这很重要。一个连接到只读文件的Channel 实例不能进行写操作,即使该实例所属的类可能有 write( )方法。
通道可以是单向(unidirectional)或者双向的(bidirectional)。一个channel 类可能实现定义read( )方法的 ReadableByteChannel 接口,而另一个 channel 类也许实现 WritableByteChannel 接口以提供 write( )方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果
一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。
选择器
选择器
选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到有就绪的的通道。
可选择通道
这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。FileChannel 对象不是可选择的,因为它们没有继承 SelectableChannel(见图 4-2)。所有 socket 通道都是可选择的,包括从管道(Pipe)对象的中获得的通道。SelectableChannel 可以被注册到 Selector 对象上,同时可以指定对那个选择器而言,那种操作是感兴趣的。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次
选择键
选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被
SelectableChannel.register( ) 返回并提供一个表示这种注册关系的标记。选择键包含了
两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作
OP_CONNECT;
OP_READ;
OP_WRITE;
OP_ACCEPT
选择器才是提供管理功能的对象,而不是可选择对象。选择器对象对注册到它之上的通道执行就绪选择,并管理选择键
总而言之,记住NIO是面向缓冲区;
比面向流更高效的原因:1.channel通道;2.对CPU依赖性更弱
NIO模型
阻塞式实现
/**
* NIO阻塞式实现
* 可以实现阻塞非阻塞两种模式
*/
public class NIOServer {
private int port=5876;
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private ByteBuffer recBuffer;
private ByteBuffer sendBuffer;
private FileChannel fileChannel;
public NIOServer(){
try {
serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
recBuffer=ByteBuffer.allocate(1024);
sendBuffer=ByteBuffer.allocate(1024);
fileChannel=FileChannel.open(Paths.get("F:\\newa.txt"),
StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//客户端给服务器发送文件
} catch (IOException e) {
e.printStackTrace();
}
}
public void startNIOServer() throws IOException{
SocketChannel socketChannel = serverSocketChannel.accept();//阻塞形式
while (socketChannel.read(recBuffer)!=-1){//造成阻塞 read方法一直等待客户端传输数据
sendBuffer.flip();
fileChannel.write(recBuffer);
recBuffer.clear();//可能文件非常大,多次接受将上一次的数据清空
}
//接受客户端的数据
//给客户端回复信息
sendBuffer.put("服务器已经收到文件".getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
socketChannel.close();
fileChannel.close();
serverSocketChannel.close();
}
public static void main(String[] args) throws IOException {
new NIOServer().startNIOServer();
}
}
public class NIOClient {
private SocketChannel socketChannel;
private FileChannel fileChannel;
private ByteBuffer recBuffer;
private ByteBuffer sendBuffer;
private int port=5876;
public NIOClient() throws IOException {
socketChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1",port));
recBuffer=ByteBuffer.allocate(1024);
sendBuffer=ByteBuffer.allocate(1024);
fileChannel=FileChannel.open(Paths.get("F:\\a.txt"),
StandardOpenOption.READ);
}
public void startClient() throws IOException {
while (fileChannel.read(sendBuffer)!=-1){
sendBuffer.flip();
socketChannel.write(sendBuffer);
sendBuffer.clear();
}
//channel完成网络上的io与文件上的io
//等待接受服务的返回值
int len=-1;
while ((len=socketChannel.read(recBuffer))!=-1){
System.out.println(new String(recBuffer.array(),0,len));
recBuffer.clear();
}
//关闭所有的资源
socketChannel.close();
fileChannel.close();
}
public static void main(String[] args) {
try {
new NIOClient().startClient();
} catch (IOException e) {
e.printStackTrace();
}
}
}
非阻塞式实现
public class ClientNIO {
private SocketChannel socketChannel;
private FileChannel fileChannel;
private ByteBuffer recBuffer;
private ByteBuffer sendBuffer;
private int port = 5886;
private Selector selector;
public ClientNIO() throws IOException {
selector = Selector.open();
//建立连接和打开socketchannel
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
recBuffer = ByteBuffer.allocate(1024);
sendBuffer = ByteBuffer.allocate(1024);
// fileChannel=FileChannel.open(Paths.get("F:\\a.txt"),
// StandardOpenOption.READ);
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1", port));
}
public void startClientNIO() throws IOException {
try {
while (true) {
int select = selector.select();//返回发生的事件个数
if (select > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//发生具体的事件 对具体事件做对应处理
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
handlerKey(key);
iterator.remove(); //处理完的事件删除
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void handlerKey(SelectionKey key) throws IOException {
if (key.isValid() && key.isConnectable()) {
//是否可以建立连接
// SocketChannel clientChannel = (SocketChannel) key.channel();
// clientChannel.finishConnect();
// clientChannel == socketChannel;
//此时服务器已经处于运行态
boolean b = socketChannel.finishConnect();//完成连接的建立
//此时只有一个socketChannel
if (b) {
//只有true之后连接才算建立完成
//告诉selector监听写事件
//发出请求
String str = "client send hello";
sendBuffer.put(str.getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
socketChannel.register(selector, SelectionKey.OP_READ);
}
} else if (key.isValid() && key.isReadable()) {
//System.out.println("进入读事件");
recBuffer.clear();
SocketChannel clientchannel=(SocketChannel)key.channel();//发生读事件
//channel.read(recBuffer);
int read = clientchannel.read(recBuffer);
System.out.println(new String(recBuffer.array(), 0, read));
sendBuffer.clear();
//服务器回复消息
String str = "client send hello ";
sendBuffer.put(str.getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
}
}
public static void main(String[] args) {
try {
new ClientNIO().startClientNIO();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ServerNIO {
private int port=5886;
private ServerSocketChannel serverSocketChannel;
private Selector selector;//suanjian
private ByteBuffer recBuffer;
private ByteBuffer sendBuffer;
private FileChannel fileChannel;
private Selector selectorc;
private ThreadPoolExecutor executor;
public ServerNIO(){
try {
selector=Selector.open();
serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);//将channel置为非阻塞模式
recBuffer=ByteBuffer.allocate(1024);
sendBuffer=ByteBuffer.allocate(1024);
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);//监听事件 先将事件连接好 事件发生之后
// fileChannel=FileChannel.open(Paths.get("F:\\newa.txt"),
// StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//客户端给服务器发送文件
executor = new ThreadPoolExecutor(5, 10,
10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000));
} catch (IOException e) {
e.printStackTrace();
}
}
public void startServerNIO() {
try {
while (true) {
int select = selector.select();//返回发生的事件个数 是阻塞方法 加上时间限制
if (select > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//发生具体的事件 对具体事件做对应处理
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
handlerKey(key);
iterator.remove(); //处理完的事件删除
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//echo回收模型
/**
* 监听事件
* @param key
* @throws IOException
*/
private void handlerKey(SelectionKey key) throws IOException {
if(key.isValid()&&key.isAcceptable()){
//判断是否是accept事件(请求链接事件)
SocketChannel clientChannel = serverSocketChannel.accept();
//肯定立马拿到返回值
clientChannel.configureBlocking(false);
//让黄牛监听读写事件
//clientChannel.register(selector,SelectionKey.OP_READ);//注册 监听写事件没有必要:
//服务器主动发数据 缓冲区不满就会进入写状态,会自动判断 如果注册,写操作会一直就绪,就会导致写操作一直就绪 Selector处理线程会占用整个CPU资源
//主从reactor
// }else if(key.isValid()&&key.isReadable()) {
//数据已经发送过来
//不能使用全局 每次置空保留的是最后一个channel
// SocketChannel channel=(SocketChannel)key.channel();//发生读事件
//
// int read= 0;
// try {
// read = channel.read(recBuffer);
// } catch (IOException e) {
// e.printStackTrace();
// }
// System.out.println(new String(recBuffer.array(),0,read));
// recBuffer.clear();
//
// sendBuffer.clear();
// //服务器回复消息
// String str="server recive your msg";
// sendBuffer.put(str.getBytes());
// sendBuffer.flip();
// try {
// channel.write(sendBuffer);
// } catch (IOException e) {
// e.printStackTrace();
// }
//多线程的实现
//new Thread(new ServerHandler(key,recBuffer,sendBuffer)).start();
//ServerHandler serverHandler=new ServerHandler(key,recBuffer,sendBuffer);
//executor.execute(serverHandler);
}
}
public static void main(String[] args) {
new ServerNIO().startServerNIO();
}
}
public class ServerFinalHandler implements Runnable {
private SelectionKey key;
private ByteBuffer recBuffer;
private ByteBuffer sendBuffer;
private Selector selector;
private SocketChannel clientChannel;
public ServerFinalHandler(SelectionKey key, ByteBuffer recBuffer, ByteBuffer sendBuffer, Selector selector, SocketChannel clientChannel) {
this.key = key;
this.recBuffer = recBuffer;
this.sendBuffer = sendBuffer;
this.selector = selector;
this.clientChannel = clientChannel;
}
public Selector getSelector() {
return selector;
}
@Override
public void run() {
try {
clientChannel.register(this.getSelector(), SelectionKey.OP_READ);
//if (key.isValid() && key.isReadable()) {
//数据已经发送过来
//不能使用全局 每次置空保留的是最后一个channel
SocketChannel channel = (SocketChannel) key.channel();//发生读事件
int read = 0;
read = channel.read(recBuffer);
System.out.println(new String(recBuffer.array(), 0, read));
recBuffer.clear();
sendBuffer.clear();
//服务器回复消息
String str = "server recive your msg";
sendBuffer.put(str.getBytes());
sendBuffer.flip();
channel.write(sendBuffer);
//}
} catch (ClosedChannelException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}