Java入门系列-23-NIO(使用缓冲区和通道对文件操作)
Posted 享智同行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java入门系列-23-NIO(使用缓冲区和通道对文件操作)相关的知识,希望对你有一定的参考价值。
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(使用缓冲区和通道对文件操作)的主要内容,如果未能解决你的问题,请参考以下文章