NIO 是什么

java.nio全称java non-blocking(非阻塞) IO(实际上是 new io),是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

NIO与IO的区别

IO NIO
面向流(Stream Oriented) 面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞(Non Blocking IO)
选择器(Selectors)

NIO系统的核心是:通道(Channel)和缓冲区(Buffer)

缓冲区(Buffer)

位于 java.nio 包,所有缓冲区都是 Buffer 抽象类的子类,使用数组对数据进行缓冲。

除了 boolean 类型,Buffer 对每种基本数据类型都有针对的实现类:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

创建缓冲区通过 xxxBuffer.allocate(int capacity)方法

ByteBuffer buf1 = ByteBuffer.allocate(512);
LongBuffer buf2 = LongBuffer.allocate(1024);
……

缓冲区的属性

容量(capacity):表示缓冲区存储数据的最大容量,不能为负数,创建后不可修改。

限制:第一个不可以读取或写入的数据的索引,即位于 limit 后的数据不能读写。不能为负数,不能大于容量。

位置(position):下一个要读取或写入的数据的索引,位置不能为负数,不能大于 limit

标记(mark):标记是一个索引,通过 Buffer 中的 mark() 方法指 Buffer 中一个特定的 position,之后可以通过 reset() 方法回到这个 postion。

Buffer 的常用方法

方法名称 说明
Buffer clear() 清空缓冲区并返回对缓冲区的引用
Buffer flip() 将缓冲区的 limit 设置为当前位置,并将当前位置重置为0
int capacity() 返回 Buffer 的容量大小
boolean hasRemaining() 判断缓冲区是否还有元素
int limit() 返回 限制的位置
Buffer limit(int n) 将设置缓冲区界限为 n,并返回一个具有新 limit 的缓冲区对象
Buffer mark() 对缓冲区设置标记
int position() 返回缓冲区的当前位置 position
Buffer position(int n) 将设置缓冲区的当前位置为 n,并返回修改后的 Buffer 对象
int remaining() 返回 position 和 limit 之间的元素个数
Buffer reset() 将位置 position 转到以前设置的 mark 所在的位置
Buffer rewind() 将位置设置为 0,取消设置的 mark

Buffer 所有子类提供了两个操作的数据的方法:get() 方法和 put() 方法

缓冲区存取数据操作

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer1 {

	public static void main(String[] args) {
testuse();
} public static void testuse() {
//1.分配一个指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024); System.out.println("---------------allocate()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity()); //2.利用 put() 存入数据到缓冲区中
String str="hello";
//将字符串转为 byte 数组存入缓冲区
buf.put(str.getBytes());
System.out.println("---------------put()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity()); //3.切换读取数据模式
buf.flip();
System.out.println("---------------flip()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity()); //4.利用get() 读取缓冲区中的数据
byte[] data=new byte[buf.limit()];
System.out.println("---------------get()----------------");
buf.get(data);
System.out.println(new String(data,0,data.length));
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity()); //5.rewind() 重复读
buf.rewind();
System.out.println("---------------rewind()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity()); //6.clear() 清空缓冲区,但缓冲区中的数据依然存在
buf.clear();
System.out.println("---------------clear()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
System.out.println((char)buf.get());
}
}

使用 mark()方法标记

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer2 {

	public static void main(String[] args) {
testmark();
} public static void testmark() {
String str="jikedaquan.com";
//创建缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
//存入数据
buf.put(str.getBytes());
//切换模式
buf.flip();
//临时数组用于接收缓冲区获取的数据,长度与缓冲区 limit 一值
byte[] data=new byte[buf.limit()];
//获取缓冲区的数据从0开始获取4个,存入 data 数组中
buf.get(data, 0, 4);
//将数组转为字符串打印
System.out.println(new String(data,0,4));
//打印 position
System.out.println(buf.position()); //标记
buf.mark();
System.out.println("---------------再次获取----------------");
//从索引4开始,获取6个字节(余下数据)
buf.get(data, 4, 6);
System.out.println(new String(data,4,6));
System.out.println(buf.position()); //恢复到标记位置
buf.reset();
System.out.println("---------------reset()----------------");
System.out.println(buf.position()); //判断缓冲区是是有还有剩余数据
if (buf.hasRemaining()) {
//获取缓冲区中可以操作的数量
System.out.println("可操作数量:"+buf.remaining());
}
}
}

mark <= position <= limit <= capacity

虽然使用了缓冲区提高了一定的IO速度,但这样的效率仍然不是最高的。非直接缓冲区在与物理磁盘操作中需要经过内核地址空间copy操作,直接缓冲区不经过copy操作,直接操作物理内存映射文件,

使用直接缓冲区将大大提高效率。

直接缓冲区进行分配和取消分配所需成本工厂高于非直接缓冲区,一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。

直接缓冲区可以通过调用此类的 allocateDirect()工厂方法创建

创建直接缓冲区

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer3 {

	public static void main(String[] args) {
testAllocateDirect();
} public static void testAllocateDirect() {
//创建直接缓冲区
ByteBuffer buf=ByteBuffer.allocateDirect(1024); //是否是直接缓冲区
System.out.println(buf.isDirect());
}
}

通道(Channel)

缓冲区仅是运载数据的容器,需要对数据读写还需要有一条通道,这两者是密不可分的。

Channel 接口的主要实现类:

  • FileChannel:用于读取、写入、映射和操作文件的通道
  • DatagramChannel:通过 UDP 读写网络中的数据通道
  • ScoketChannel:通过 TCP 读写网络中的数据
  • ServerScoketChannel:可以监听新进来的 TCP 链接,对每一个新进来的连接都会创建一个 SocketChannel

如何获取通道?

1、通过支持通道的对象调用 getChannel() 方法

支持通道的类:

  • FileInputStream
  • FileOutputStream
  • RandomAccessFile
  • DatagramScoket
  • Socket
  • ServerScoket

使用通道和缓冲区实现文件读和写

package testnio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public class TestChannel { public static void main(String[] args) {
FileInputStream fis=null;
FileOutputStream fos=null; FileChannel inChannel=null;
FileChannel outChannel=null; try {
//创建输入流
fis=new FileInputStream("F:/1.jpg");
//创建输出流
fos=new FileOutputStream("F:/2.jpg");
//获取通道
inChannel=fis.getChannel();
outChannel=fos.getChannel(); //分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024); //将通道中的数据存入缓存区
while(inChannel.read(buf)!=-1) {
//切换读取数据的模式
buf.flip();
//将读入的缓冲区存入写数据的管道
outChannel.write(buf);
//清空缓存区(清空才能再次读入)
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fis!=null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos!=null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inChannel!=null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outChannel!=null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} }

2、通过通道类的静态方法 open()

package testnio;

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; public class TestOpenAndMapped { public static void main(String[] args) {
FileChannel inChannel=null;
FileChannel outChannel=null;
try {
//通过open创建通道
inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE); //内存映射文件 直接缓冲区
MappedByteBuffer inMappedBuf=inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf=outChannel.map(MapMode.READ_WRITE, 0, inChannel.size()); //直接对缓冲区进行数据的读写操作
byte[] data=new byte[inMappedBuf.limit()];
inMappedBuf.get(data);//读
outMappedBuf.put(data);//写
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inChannel!=null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outChannel!=null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

JDK 1.7 新增的方法 open(),参数 path 通常代表一个依赖系统的文件路径,通过Paths.get()获取。

参数 StandardOpenOption 是一个枚举类型,常用值如下:

  • READ :打开读访问
  • WRITE:打开写访问
  • APPEND:向后追加
  • CREATE:创建新文件,存在则覆盖
  • CREATE_NEW:创建新文件,存在则报错

MappedByteBuffer:直接字节缓冲区,其内容是文件的内存映射区域。

通道数据传输

将数据从源通道传输到其他 Channel 中,transferTo() 和 transferFrom()

package testnio;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; public class TestChannelTransfer { public static void main(String[] args) {
FileChannel inChannel=null;
FileChannel outChannel=null;
try {
//通过open创建管道
inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
//将inChannel中所有数据发送到outChannel
//inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inChannel!=null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outChannel!=null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

Channel 负责传输,Buffer 负责存储

Java入门系列-23-NIO(使用缓冲区和通道对文件操作)的更多相关文章

  1. Java入门系列Java NIO

    jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小.

  2. java io系列23之 BufferedReader(字符缓冲输入流)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_23.html 更多内容请参考:java io系列01之 "目录" Buffere ...

  3. Java入门系列-25-NIO(实现非阻塞网络通信)

    还记得之前介绍NIO时对比传统IO的一大特点吗?就是NIO是非阻塞式的,这篇文章带大家来看一下非阻塞的网络操作. 补充:以数组的形式使用缓冲区 package testnio; import java ...

  4. Java入门系列-26-JDBC

    认识 JDBC JDBC (Java DataBase Connectivity) 是 Java 数据库连接技术的简称,用于连接常用数据库. Sun 公司提供了 JDBC API ,供程序员调用接口和 ...

  5. Java入门系列-22-IO流

    File类的使用 Java程序如何访问文件?通过 java.io.File 类 使用File类需要先创建文件对象 File file=new File(String pathname);,创建时在构造 ...

  6. Java入门系列-19-泛型集合

    集合 如何存储每天的新闻信息?每天的新闻总数是不固定的,太少浪费空间,太多空间不足. 如果并不知道程序运行时会需要多少对象,或者需要更复杂方式存储对象,可以使用Java集合框架. Java 集合框架提 ...

  7. NIO的缓冲区、通道、选择器关系理解

    Buffer的数据存取    一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类.   Java NIO中的Buffer主要用于与NIO通道进行交互,数 ...

  8. Python学习入门基础教程(learning Python)--5.6 Python读文件操作高级

    前文5.2节和5.4节分别就Python下读文件操作做了基础性讲述和提升性介绍,但是仍有些问题,比如在5.4节里涉及到一个多次读文件的问题,实际上我们还没有完全阐述完毕,下面这个图片的问题在哪呢? 问 ...

  9. Java入门系列(十)Java IO

    概述 总体而言,java的读写操作又分为两种:字符流和字节流. 实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件. 什么是流? ...

随机推荐

  1. docker容器中安装vi

    容器中输入vi提示 root@e36f8029c9f2:/# vi bash: vi: command not found 解决办法: 1.通过命令获取最新的软件包 apt-get update ap ...

  2. docker-compsoe & .netcore & nginx

    1.引言 紧接上篇.NET Core容器化@Docker,这一节我们先来介绍如何使用Nginx来完成.NET Core应用的反向代理,然后再介绍多容器应用的部署问题. 2. Why Need Ngin ...

  3. 构建针对 iOS 和 Android 的原生扩展

    第一部分:音量控制入门 http://www.adobe.com/cn/devnet/air/articles/building-ane-ios-android-pt1.html 第二部分: 开发 A ...

  4. 【bzoj4888】: [Tjoi2017]异或和 BIT-乱搞

    [bzoj4888]: [Tjoi2017]异或和 题目大意:给定一个序列,求这个序列所有的连续和的异或值.(n<=1e5 ai<=1e6) 想了各种奇怪的方法就是不会做啊啊啊.. Orz ...

  5. ubuntu 12.0.4 下python3.x web环境搭建

    ubuntu 12.0.4 安装python3.x 1. $ sudo add-apt-repository ppa:fkrull/deadsnakes$ sudo apt-get update$ s ...

  6. ThinkPHP 3.2.x 集成极光推送指北

    3.2版本已经过了维护生命周期,官方已经不再维护,请及时更新至5.0版本 -- ThinkPHP 官方仓库 以上,如果有条件,请关闭这个页面,然后升级至 ThinkPHP 5,如果由于各种各样的原因无 ...

  7. Docker部署常见问题

    一.删除容器和镜像 在删除镜像之前要先用 docker rm 删掉依赖于这个镜像的所有容器(哪怕是已经停止的容器),否则无法删除该镜像. 停止容器 # docker stop $(docker ps ...

  8. editplus 编辑 php双击选中变量问题

    windows下,在很多地方双击鼠标左键可以选中一个连续的英文字符串. 在editplus 编辑器里可以双击选中一个变量,方便了编程,但是使用phptools(php.stx)增强语法插件后,在一个变 ...

  9. HTML基础信息笔记

    HTML 是什么 HTML 指的是超文本标记语言 (Hyper Text Markup Language) HTML 标签(tag) HTML 标签是由尖括号包围的关键词,比如 <html> ...

  10. mysql 设置默认时间为now()

    TIMESTAMP的变体1,TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP在创建新记录和修改现有记录的时候都对这个数据列 ...