基本概念

  • BIO:是堵塞I/O,无论是磁盘I/O,还是网络I/O,数据在写入OutputStream和InputStream都可能发生堵塞,一旦有堵塞,线程会失去CPU的使用权(堵塞)。

  • NIO:简单的说就是非堵塞式I/O(单个I/O堵塞时不堵塞线程),一个线程能够负责多个I/O连接(利用serverSocketChannels来接收),取一个准备好接收数据的连接(注冊到Selector轮询调度)。尽快地用连接向缓存区(利用buffer优化I/O)填充数据,然后转向下一个准备好的连接。

  • 缓存区(buffer):通信通道(channel)用来数据传输的介质,在NOI模型中,不再通向输出流写入数据或从输入流读取数据。而是在缓存区中读写数据,能够有效降低I/O中断次数,调优I/O。(我会写博客专门讲缓存区的…链接留个位置)
  • 通道(channel):负责将缓冲区的数据块移入或移出到各种I/O源(我会写博客专门讲通道的…链接留个位置)
  • 就绪选择(selector):为完毕就绪选择。要将不同的通道注冊到一个Selector对象。每一个通道分配有一个SeletionKey。

    然后程序能够通过询问Selector对象。得知哪些通道已经准备就绪。能够无堵塞地完毕I/O操作,能够向Selector对象查询对应的键集合。

通道和I/O流的差别

  • 流和通道间的关键差别是流是基于字节的,而通道是基于块的。块的单次中断传输的数据量远大于字节,所以性能是有优势。当然,出于性能考虑,流也能够传输字节数组。
  • 通常情况下(如网络通道)。通道的缓冲区支持单个通道的读写,而流是仅仅读或者仅仅写的。CDROM是仅仅读通道,但这仅仅是特例

理解I/O:性能的瓶颈

  • 现代计算机基于冯诺依曼的存储运行模型,所以数据在计算机部件间的传输速率决定了计算机的运行效率。可是各级存储(CPU寄存器、缓存、内存、硬盘)的传输速度差异巨大(数量级上的差距),在传统的BIO模式下,这导致快速计算部件的大量时间浪费在等待数据传输上。然而计算机作为节点的计算机网络中。问题依然存在。

  • 传输速度上:
    CPU>内存>磁盘>网络(一般情况下)
  • 传统的做法是通过缓存和多线程解决这一问题:
    • 快速缓存能够降低中断次数(单次传输的数据量增大,数据总量不变)和中断的时间(缓存一般比原始存储位置传输速率快)
    • 多线程能够让单个线程处理一个I/O,不会影响线程获得CPU资源。可是当I/O连接数量增多时,线程的数量随之添加,生成多个线程以及线程之间切换的开销是不容忽略的,线程管理的开销(时间+资源)极大地降低了系统性能
    • 借鉴多线程对于I/O的解决方式,我们进一步解决的就是优化掉创建线程和线程间切换带来的巨大的开销,那么也就是採用单个线程非堵塞地管理多个I/O连接,也就是我们要讲的NIO。

例讲Channel和buffer的简单使用

枯燥的解说API没有意义,也太没意思了。所以我採取更加直观的样例来说明API。不严谨但更简单直观。首先为了方便測试我们利用Channel写的Client。我们先用简单的几行代码实现一个本地的測试server:

package com.company;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; public class TestServer { public static void main(String[] args){
//server监听请求的端口
int port = 9999;
ServerSocket server=null;
try {
server = new ServerSocket(port);
}catch( IOException e ){
System.out.println("server创建失败");
e.printStackTrace();
}
Socket temp=null;
try{
temp = server.accept();
}catch( IOException e ){
System.out.println("获取连接失败");
e.printStackTrace();
} OutputStream output = null;
try{
output = temp.getOutputStream();
}catch ( Exception e ){
e.printStackTrace();
}
byte [] buffer = "llin of Tianjin University love JAVA and Alibaba!".getBytes();
while ( true ) {
try {
output.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
try {
//防止传输信息过于快,不方便我们測试
Thread.sleep(1000);
}catch ( InterruptedException e ){
e.printStackTrace();
}
}
}
}

主要作用是间隔一段时间向客户端发送一段信息,用来測试客户端的channel是否实际发挥了作用

以下是一个样例,能够用来简单熟悉一下JAVA中的Buffer和Channel接口的使用,尽管实际中这样使用的意义不大

package com.company;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel; /**
* Created by liulin on 16-4-12.
*/
public class TestClient { //默认的IP
public static final String ipAddress = "127.0.0.1";
//默认端口
public static final int DEFAULT_PORT = 9999;
//缓存的大小
public static final int BUFFER_SIZE = 200; public static void main ( String [] args ){ if ( args.length == 0 ){//假设没有给出IP地址那么没办法找到測试server啊
System.out.println( "请输入server的IP地址或域名");
} //给定端口那么使用指定端口,否则使用默认端口
int port=0;
try{
port = Integer.parseInt(args[1]);
}catch ( Exception e ){
port = DEFAULT_PORT;
e.printStackTrace();
} try{//本例仅仅是让大家熟悉JAVA提供的Buffer,Channel的API。让不熟悉Socket的同学学习下Socket机制
//所以不採用非堵塞机制,也不使用selector
SocketAddress address = new InetSocketAddress( ipAddress , port );
//通过静态工厂获得指定address的通道实例
SocketChannel client = SocketChannel.open(address);
//通过静态工厂获得指定大小的缓存
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
WritableByteChannel out = Channels.newChannel( System.out );//输出通道
//从server读取数据
while ( client.read(buffer) != -1 ){
//将缓存的position置为buffer内置数组的初始位置,当前position位置设置为limit
//position,limit,capacity的概念会在专门介绍buffer的博客中一齐解说
//简单地讲,就是告诉buffer。我们要開始从头读取/改动你了
buffer.flip();
out.write( buffer );
//为了方便查看调试信息,输出一个换行
System.out.write( "\n".getBytes());
//仅仅是改动position为buffer内置数组初始位置。limit设置为capacity
//眼下能够简单地理解为清空缓存(详情我会在专门介绍buffer的博客中一齐解说)
buffer.clear();
} }catch ( Exception e ){
e.printStackTrace();
} }
}

主要就是利用Channel连接到server而且通过buffer进行读写。输出到控制台的一个简单的样例,运行效果例如以下(測试的时候一定要先打开測试server):

用一个NIO的HTTPserver解说NIO

作为一个有着TDD思维的程序猴子。怎么能不先写測试用的客户端呢….事实上仅仅是无耻地在之前客户端添了一行代码

package com.company;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel; /**
* Created by liulin on 16-4-12.
*/
public class TestClient { //默认的IP
public static final String ipAddress = "127.0.0.1";
//默认端口
public static final int DEFAULT_PORT = 9999;
//缓存的大小
public static final int BUFFER_SIZE = 200; public static void main ( String [] args ){ if ( args.length == 0 ){//假设没有给出IP地址那么没办法找到測试server啊
System.out.println( "请输入server的IP地址或域名");
} //给定端口那么使用指定端口,否则使用默认端口
int port=0;
try{
port = Integer.parseInt(args[1]);
}catch ( Exception e ){
port = DEFAULT_PORT;
e.printStackTrace();
} try{//本例仅仅是让大家熟悉JAVA提供的Buffer,Channel的API。让不熟悉Socket的同学学习下Socket机制
//所以不採用非堵塞机制,也不使用selector
SocketAddress address = new InetSocketAddress( ipAddress , port );
//通过静态工厂获得指定address的通道实例
SocketChannel client = SocketChannel.open(address);
//通过静态工厂获得指定大小的缓存
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
WritableByteChannel out = Channels.newChannel( System.out );//输出通道
//从server读取数据
client.write( ByteBuffer.wrap("testing...".getBytes())); while ( client.read(buffer) != -1 ){
//将缓存的position置为buffer内置数组的初始位置,当前position位置设置为limit
//position,limit,capacity的概念会在专门介绍buffer的博客中一齐解说
//简单地讲。就是告诉buffer。我们要開始从头读取/改动你了
buffer.flip();
out.write( buffer );
//为了方便查看调试信息。输出一个换行
System.out.write( "\n".getBytes());
//仅仅是改动position为buffer内置数组初始位置。limit设置为capacity
//眼下能够简单地理解为清空缓存(详情我会在专门介绍buffer的博客中一齐解说)
buffer.clear();
} }catch ( Exception e ){
e.printStackTrace();
}
}
}

以下是由selector调度的HTTPserver,解说写在了凝视中,假设还有不明确的能够在评论中问

package com.company;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel; /**
* Created by liulin on 16-4-12.
*/
public class TestClient { //默认的IP
public static final String ipAddress = "127.0.0.1";
//默认端口
public static final int DEFAULT_PORT = 9999;
//缓存的大小
public static final int BUFFER_SIZE = 200; public static void main ( String [] args ){ if ( args.length == 0 ){//假设没有给出IP地址那么没办法找到測试server啊
System.out.println( "请输入server的IP地址或域名");
} //给定端口那么使用指定端口,否则使用默认端口
int port=0;
try{
port = Integer.parseInt(args[1]);
}catch ( Exception e ){
port = DEFAULT_PORT;
e.printStackTrace();
} try{//本例仅仅是让大家熟悉JAVA提供的Buffer,Channel的API,让不熟悉Socket的同学学习下Socket机制
//所以不採用非堵塞机制,也不使用selector
SocketAddress address = new InetSocketAddress( ipAddress , port );
//通过静态工厂获得指定address的通道实例
SocketChannel client = SocketChannel.open(address);
//通过静态工厂获得指定大小的缓存
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
WritableByteChannel out = Channels.newChannel( System.out );//输出通道
//从server读取数据
client.write( ByteBuffer.wrap("testing...".getBytes())); while ( client.read(buffer) != -1 ){
//将缓存的position置为buffer内置数组的初始位置,当前position位置设置为limit
//position,limit,capacity的概念会在专门介绍buffer的博客中一齐解说
//简单地讲,就是告诉buffer,我们要開始从头读取/改动你了
buffer.flip();
out.write( buffer );
//为了方便查看调试信息,输出一个换行
System.out.write( "\n".getBytes());
//仅仅是改动position为buffer内置数组初始位置。limit设置为capacity
//眼下能够简单地理解为清空缓存(详情我会在专门介绍buffer的博客中一齐解说)
buffer.clear();
} }catch ( Exception e ){
e.printStackTrace();
}
}
}

測试结果例如以下:



详细的细节,我还会在新的博客里详谈,这篇篇幅太长了

參考书目

Java网络编程(第三版)

深入分析Java Web技术内幕

Java I/O(一) NIO概述的更多相关文章

  1. Java NIO:NIO概述

    Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...

  2. (转载)Java NIO:NIO概述(一)

    Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...

  3. Java NIO学习笔记一 Java NIO概述

    Java NIO概述 Java NIO(新的IO)是Java的替代IO API(来自Java 1.4),这意味着替代标准的 java IO和java Networking API.Java NIO提供 ...

  4. Java NIO系列教程(一) Java NIO 概述

    <I/O模型之四:Java 浅析I/O模型> 一.阻塞IO与非阻塞IO 阻塞IO: 通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至数 ...

  5. Java NIO2:NIO概述

    一.概述 从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新特性,被统称为NIO(即New I/O).新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原j ...

  6. Java NIO学习笔记---I/O与NIO概述

    文章目录: 1.什么是IO 2.什么是Java NIO 3.I/O常见概念 4.为什么使用NIO 5.IO VS NIO 一.什么是IO I/O 或者输入/输出 , 指的是计算机与外部世界或者一个程序 ...

  7. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  8. Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...

  9. Java I/O and NIO [reproduced]

    Java I/O and NIO.2---Five ways to maximize Java NIO and NIO.2---Build more responsive Java applicati ...

随机推荐

  1. iOS开发多线程篇 08 —GCD的常见用法

    iOS开发多线程篇—GCD的常见用法 一.延迟执行 1.介绍 iOS常见的延时执行有2种方式 (1)调用NSObject的方法 [self performSelector:@selector(run) ...

  2. ORACLE修改列名与列类型

    --修改列名 alter table 表名 rename column 旧列名 to 新列名; --修改列类型 )); 删除表的一列: alter table 表名 drop column 列名 给表 ...

  3. C# Lock 解读

    一.Lock定义     lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断.它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行, ...

  4. CentOS 7下Java的SecureRandom种子初始化失败解决办法

    io.netty.util.internal.ThreadLocalRandom getInitialSeedUniquifierWARNING: Failed to generate a seed ...

  5. HTML5坦克大战(2)绘制坦克复习

    html代码: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head&g ...

  6. 机器学习算法( 七、AdaBoost元算法)

    一.概述 当做重要决定时,大家可能都会考虑吸取多个专家而不只是一个人的意见.机器学习处理问题时又何尝不是如此?这就是元算法(meta-algorithm)背后的思路.元算法是对其他算法进行组合的一种方 ...

  7. spring quartz定时任务 配置

    cronExpression表达式: 字段 允许值 允许的特殊字符秒 0-59 , - * /分 0-59 , - * /小时 0-23 , - * /日期 1-31 , - * ? / L W C月 ...

  8. nutch solr

    创建solr数据目录 创建目录solrData,拷贝solr-4.10.2/example/solr到solrData下     修改配置文件中数据目录路径 修改 solrData/solr/coll ...

  9. JVM Specification 9th Edition (3) Chapter 2. The Structure of the Java Virtual Machine

    Chapter 2. The Structure of the Java Virtual Machine 内容列表 2.1. The class File Format (class文件的格式) 2. ...

  10. 数据库设计(四)数据库的规范化(Normalization)

    数据库的规范化 Database Normalization is a technique of organizing the data in the database. Normalization ...