一、JAVA NIO 与IO区别
IO NIO
面向流 面向缓冲区
阻塞IO 非阻塞IO
无 选择器 Selector
二、缓冲区和通道
Java NIO系统的核心在于:通道(Channnel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区,然后操作缓冲区,对数据进行处理。简言之:Channel负责传输,Buffer负责存储。
缓冲区:一个用于特定基本数据类型的容器,由java.nio定义的,所有缓冲区都是Buffer抽象的子类,JAVA NIO 中的Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道
2.1 直接缓冲区和非直接缓冲区
字符缓冲区要么是直接,要么是非直接。如果为直接字节缓冲区,则java虚拟机会尽最大努力直接在此缓冲区傻瓜执行I/O操作,也就是说,在每次调用基础操作系统的一个本金I/O操作前(或后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区(或从中间缓冲区中复制内容)
直接字节缓冲区通过调此类的allocateDirect()工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区,直接缓冲区的内容可以驻留在常规的垃圾回收堆之外。因此,他们对应用程序的内存需求两年造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些容易受基础系统的本机I/O操作影响大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配他们
直接字节缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建,该方法返回MappedByteBuffer。Java平台的实现有助于通过JNI从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的时不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常
字节缓冲区时是直接缓冲区还是非直接缓冲区可以通过isDirect()方法来确定,提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理
/**
* 一、缓冲区(Buffer):再Java NIO中负责数据的存储,缓冲区就是数组,用于存储不同数据类型
* 根据数据类型不同(boolean)除外,提供了相应类型的缓冲区
* ByteBuffer
* CharBuffer
* ShortBuffer
* IntBuffer
* LongBuffer
* FloatBuffer
* DoubleBuffer
* 上述缓冲区的管理方式一致,通过allocate()获取缓冲区
*
* 二、缓冲区存取数据的两个核心方法:
* put():存入数据到缓冲区
* get():获取缓冲区的数据
*
* 三、缓冲区的四个核心参数
* capacity:容量,表示缓冲区最大存储数据的容量,一旦声明不能改变
* limit:界限,表示缓冲区可以操作数据的大小,(limit后数据不能进行读写)
* position:位置,表示缓冲区中正在操作数据的位置
* mark: 标记,表示记录当前position的位置,可以通过reset() 恢复到mark的位置
* 0 <= position <= limit <= capacity
*
* 四、直接缓冲区和 非直接缓冲区
* 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存
* 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,
* 可以提高效率
*
**/
public class TestBuffer{
@Test
public void test(){
//1.分配一个指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
System.out.println("-----------allocate()-----------");
System.out.println(buf.position()); //0
System.out.println(buf.limit()); //1024
System.out.println(buf.capacity()); //1024
//2.利用put()存入数据到缓冲区
String str = "abcde";
buf.put(str.getBytes());
System.out.println("-----------put()-----------");
System.out.println(buf.position()); //5
System.out.println(buf.limit()); //1024
System.out.println(buf.capacity()) //1024
//3.切换读取数据模式
buf.flip() //切换读取数据模式
System.out.println(buf.position()); //0 从头开始读
System.out.println(buf.limit()); //5 可以读取的数据是5个
System.out.println(buf.capacity()); //1024
//4.利用get()读取缓冲区的数据
byte[] dst = new Byte[buf.limit()];
buf.get(dst); //读取数据到字节数组中
System.out.println(new String(dst,0,dst.length));
System.out.println(-----------get()-----------);
System.out.println(buf.position()); //5
System.out.println(buf.limit()); //5
System.out.println(buf.capacity()); //1024
//5.rewind() 可重复读
System.out.println("-----------rewind()-----------");
System.out.println(buf.position()); //0 可以重新读刚才的数据
System.out.println(buf.limit()); //5
System.out.println(buf.capacity()); //1024
//6.clear()清空缓冲区 但是缓冲区的数据依然存在,但是处于“被遗忘”状态
buf.clear();
System.out.println("-----------clear()-----------");
System.out.println(buf.position()); //0
System.out.println(buf.limit()); //1024
System.out.println(buf.capacity()); //1024
//验证缓冲区中是否有数据
System.out.println((Char)buf.get()); //a
}
@Test
public void test1(){
String str = "abcde";
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(str);
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst,0,2);
System.out.println(new String(dst,0,2)); //ab
System.out.println(buf.position()); //2
//mark 标记
buf.mark(); //标记position 位置
buf.get(dst,2,2) //position 从2 读2个
System.out.println(new String(dst,2,2)); //cd
System.out.println(buf.position()); //4
//reset() 恢复到mark标记的位置
buf.reset();
System.out.println(buf.position()); //2
if(buf.hasRemaining()){
//获取缓冲区可以操作的数量
System.out.println(buf.remaining());
}
}
@Test
public void test2(){
//分配直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
//判断缓冲区的类型
System.out.println(buf.isDirect());
}
}
通道(Channel)
由 java.nio.channels包定义的,Channel表示IO源与目标打开的连接,channe类似于传统的“流”,只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互
public class TestNio{
/**
* 一、通道(Channel) 用于源节点与目标节点的连接,在Java NIO中负责缓冲区中的数据传输
* Channel本身不存储数据,因此需要配合缓冲区进行传输
*
* 二、通道的主要实现类
* java.nio.channel.Channel 接口:
* FileChannel
* SocketChannel
* ServerSocketChannel
* DatagramChannel
*
* 三、获取通道
* 1.Java 针对支持通道的类提供了getChannel()方法
* 本地IO
* FileInputStream/FileOutputStream
* RandomAccessFile
*
* 网络IO
* Socket
* ServerSocket
* DatagramSocket
*
* 2.在JDK 1.7中的NIO.2针对各个通道提供了静态方法open()
*
* 3.在JDK 1.7中的NIO.2的Files 工具类newByteChannel()
*
* 四、通道直接的数据传输
* transferFrom();
* tramsferTo();
*
* 五、分散(Scatter)和聚集(Gather)
* 分散读取(Scattering Reads):将通道分散到多个缓冲区
* 聚集写入(Gathering writes):将多个缓冲区中的数据聚集到缓冲区
*
* 六、字符集Charset
* 编码:字符串 -> 字符数组
* 解码:字节数组-> 字符串
**/
@Test //1.利用通道完成文件的复制(非直接缓冲区)
public void test1(){
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try{
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
//①获取通道
inChannel =fis.getChannel();
outChannel = fos.getChannel();
//② 分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//③ 将通道中的数据存入缓冲区
while(inChannle.read(buf) != -1){
buf.flip();//切换读取数据的模式
//④将缓冲区的数据写入通道
outChannel.write(buf);
buf.clear(); //清空缓冲区
}
}catch(IOException e){
e.printStackTrace()
}finally{
if (outChanel != null){
try {
outChanel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChannel != null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//2.使用直接缓冲区完成文件的复制(内存映射文件)
public void test2() throws IOException{
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"),StandaradOpenOpetion.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"),StandaradOpenOption.WRITE,
StandaradOpenOption.READ,StandaradOpenOption.CREATE_NEW);
//内存映射文件
MappedByteBuffer inMapperBuf = inChannel.map(FileChanle.MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMapperBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE,
0,inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
outMapperBuffer.put(dst);
inChannel.close();
outChannel.close();
}
//通道之间的数据传输,(直接缓冲区)
@Test
public void test3() throws IOException{
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"),StandaradOpenOption.READ);
FileChanel outChannel = FileChannel.open(Paths.get("2.jpg"),StandaradOpenOption.WRITE,StandaradOpenOption.READ, StandOpenOption.CREATE_NEW);
//第一种方式
//inChannel.transferTo(0,inChannel.size(),outChannel);
//第二种方式
outChannel.transferFrom(inChannel,0,inChannel.size());
inChannel.close();
outChannle.close();
}
@Test
public void test4(){
RandomAccessFile raf1 = new RandomAccessFile("1.txt","rw");
//1.获取通道
FileChannel channel1 = raf1.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
//3.分散读取
ByteBuffer[] bufs = {buf1,buf2};
channel1.read(bufs);
for(ByteBuffer byteBuffer: bufs){
byteBuffer.filp();
}
System.out.println(new String(buf[0].arrays(),0,bufs[0].limit()));
System.out.println("-------------------------");
System.out.println(new String(buf[1].arrays(),0,bufs[1].limit()));
//4.聚集写入
RandomAccessFile raf2 = new RandomAccessFile("2.txt","rw");
FileChannel channel2 = raf.getChannel();
channel2.write();
}
@Test
public void test5(){
Map<String,Charset> map = Charset.availableCharsets();
Set<Map.Entry<String,Charset>> set = map.entrySet();
for(Map.Entry<String,Charset> entry : set){
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
@Test
public void test6(){
Charset cs1 = Charset.forName("GBK");
//获取编码器
CharsetEncoder ce = cs1.newEncoder();
//获取解码器
CharsetDecoder cd = cs1.newDecoder();
CharBuffer cBuf = CharBuffer.allocate(1024);
cBuf.put("Hello World!");
cBuf.filp();
//编码
ByteBuffer bBuffer = ce.encode(cBuf);
for(int i = 0 ;i < 12 ; i++){
System.out.println(bBuffer.get());
}
//解码
bBuffer.flip();
CharBuffer cBuf2 = cd.decode(bBuffer);
System.out.println(cBuf.toString());
}
}