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 ...
随机推荐
- NiftyNet 项目了解
1. NiftyNet项目概述 NiftyNet项目对tensorflow进行了比较好的封装,实现了一整套的DeepLearning流程.将数据加载.模型加载,网络结构定义等进行了很好的分离,抽象封装 ...
- IntelliJ IDEA代码编码区提示库源不匹配字节码解决办法
在使用IntelliJ IDEA进行开发时,可能会在代码编辑区出现此提示:library source does not match the bytecode for class HelloWorld ...
- CCNA2.0笔记_OSPF v3
OSPF v3 是可以在ipv6上实现路由的一种路由协议 OSPF v2(for IPv4),OSPF v3(for IPv6)在一台路由器中互相独立运行 OSPF v3与v2有很多类似的功能: - ...
- linux学习笔记7---命令cp
cp命令用来复制文件或者目录,是Linux系统中最常用的命令之一. cp命令用来将一个或多个源文件或者目录复制到指定的目的文件或目录.它可以将单个源文件复制成一个指定文件名的具体的文件或一个已经存在的 ...
- libcgi库安装
官网:https://boutell.com/cgic/#build 1. 可直接tar包安装 tar xvf libcgi-1.0.tar.gzcd libcgi-1.0./configuremak ...
- jQuery设置和获取HTML、文本和值
jQuery设置和获取HTML.文本和值 按 Ctrl+C 复制代码 <script type="text/javascript"> //<![CDATA[ $( ...
- 【BZOJ】3399: [Usaco2009 Mar]Sand Castle城堡(贪心)
http://www.lydsy.com/JudgeOnline/problem.php?id=3399 贪心就是将两组排序,然后直接模拟即可.. 如果我们用a去匹配一个绝对值和它差不多的值,那么去匹 ...
- centos7 安装kvm虚拟机
准备工作 centos7 光盘文件 物理机(>=4 Cores; >= 4GB memory; >= 40G disk size) 参考文档:KVM Virtualization i ...
- Java逍遥游记读书笔记<二>
Abstract抽象类 1.抽象类不能被实例化 2.抽象方法没有方法体 如: public abstract class Weapen { public abstract void attack(); ...
- PHP urlencode
url get传参时,对参数值需要用urlencode()处理,防止参数中含有特殊字符&等 例如: 一产品名称为A&T Plastic,在产品列表中就产生了这样的一个联接<a h ...