Java I/O(一) NIO概述
基本概念
- 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概述的更多相关文章
- Java NIO:NIO概述
Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...
- (转载)Java NIO:NIO概述(一)
Java NIO:NIO概述 在上一篇博文中讲述了几种IO模型,现在我们开始进入Java NIO编程主题.NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题.本文下面分别从Java ...
- Java NIO学习笔记一 Java NIO概述
Java NIO概述 Java NIO(新的IO)是Java的替代IO API(来自Java 1.4),这意味着替代标准的 java IO和java Networking API.Java NIO提供 ...
- Java NIO系列教程(一) Java NIO 概述
<I/O模型之四:Java 浅析I/O模型> 一.阻塞IO与非阻塞IO 阻塞IO: 通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至数 ...
- Java NIO2:NIO概述
一.概述 从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新特性,被统称为NIO(即New I/O).新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原j ...
- Java NIO学习笔记---I/O与NIO概述
文章目录: 1.什么是IO 2.什么是Java NIO 3.I/O常见概念 4.为什么使用NIO 5.IO VS NIO 一.什么是IO I/O 或者输入/输出 , 指的是计算机与外部世界或者一个程序 ...
- Java网络编程和NIO详解9:基于NIO的网络编程框架Netty
Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- 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 ...
随机推荐
- [Egret]长按截屏分享、分享截屏图片、本地存储
egret 分享有API可以把一个显示对象树渲染成一个位图纹理,我把它赋值给 HTML 的 Image 元素,就实现了图片的显示,在微信中,通过长按图片可以分享出去.当然在其他浏览器可以保存在本地. ...
- Centos安装ruby--jekyll
想要使用gem install 安装jekyll,执行发现没有gem这个命令,那就需要先安装gem了 yum list '*ruby*' 修改gem源,使用淘宝的源: gem sources --re ...
- Powershell对象条件查询筛选
在 Windows PowerShell 中,与所需的对象数量相比,通常生成的对象数量以及要传递给管道的对象数量要多得多.可以使用 Format cmdlet 来指定要显示的特定对象的属性,但这并不能 ...
- uinty实现玩家尾随鼠标位置平滑旋转角度
首先我们要在场景中加入一个quad平面作为地板, 然后指定Layer为Floor,然后移除mesh renderer组件 然后加入脚本 脚本主要思想是从屏幕中心投出一条射线到地板, 然后获取相应坐标, ...
- ARM与X86架构的对决[整编]
CISC(复杂指令集计算机)和RISC(精简指令集计算机)是当前CPU的两种架构.它们的区别在于不同的CPU设计理念和方法.早期的CPU全部是CISC架构,它的设计目的是 CISC要用最少的机器语言 ...
- select 自定义样式插件 selectize.js
[特别推荐]几款极好的 JavaScript 下拉列表插件 表单元素让人爱恨交加.作为网页最重要的组成部分,表单几乎无处不在,从简单的邮件订阅.登陆注册到复杂的需要多页填写的信息提交功能,表单都让 ...
- SQL语句大全2
SQL 语句大全 --语 句 功 能 --数据操作 SELECT --从数据库表中检索数据行和列 INSERT --向数据库表添加新数据行 DELETE --从数据库表中删除数据行 UPDATE -- ...
- SQLite 连接两个字符串
SQLite中,连接字符串不是使用+,而是使用|| 示例: SELECT 'I''M '||'Chinese.' 将输出 I'M Chinese. 特别说明:1. SELECT 'I''M '+'Ch ...
- HDU2059 龟兔赛跑 【DP】
龟兔赛跑 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submi ...
- 对IOS设备中UDID的一些思考
本文转载至 http://blog.csdn.net/happyrabbit456/article/details/11565209 http://blog.csdn.net/xiaoguan2008 ...