Java中的NIO

1、Java新IO概述

       新IO和传统IO有相同的目的,都是用于进行输入输出,但新IO使用了不同的方式来处理输入输出,新IO采用内存映射文件的方式来处理输入输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了,通过这种方式来进行输入输出比传统的输入输出要快得多。Java中与新IO相关的包:

              java.nio 主要包含各种与Buffer相关的类

              java.nio.channels  主要包含与Channel和Selector相关的类

              java.nio.charset  主要包含与字符集相关的类

              java.nio.channels.spi  主要包含与Channel相关的服务提供者编程接口

              java.nio.charset.spi  包含与字符集相关的服务提供者编程接口

       Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,Channel是对传统的输入输出系统的模拟,在新IO系统中所有的数据都需要通过通道传输;Channel与传统的InputStream、OutputStream最大的区别在于它提供了一个map()方法,通过该方法可以直接将“一块数据”映射到内存中。如果说传统的IO是面向流的处理,则新IO则是面向块的处理。

       Buffer可以理解为一个容器,它的本质是一个数组,发送到Channel中所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。

2、使用Buffer

       Buffer是一个抽象类,其最常用的子类是ByteBuffer,它可以在底层字节数组上进行get/set操作。除了ByteBuffer之外,对应于其他基本数据类型(boolean除外)都有相应的Buffer类:CharBuffer...

                static xxxBuffer allocate(int capacity) 

       实际上使用较多的是ByteBuffer和CharBuffer,其他用得少。其中ByteBuffer类还有一个子类:MappedByteBuffer,它用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回。

       在Buffer中有三个重要的概念:容量(capacity)、界限(limit)、位置(position)

                                  

                                                                        0<=mark<=position<=limit<=capacity

       Buffer的主要作用就是装入数据,然后输出数据。开始时Buffer的position为0,limit为capacity,程序可以通过put()方法向Buffer中放入一些数据(或从Channel中获取一些数据),每放入一些数据。Buffer的position相应地向后移动一些位置。

       当Buffer装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position所在的位置,并将position设为0,这就使得Buffer的读写指针又移到了开始位置。也就是说,Buffer调用flip()方法之后,Buffer为输出数据做好准备;当Buffer输出数据结束后,Buffer调用clear()方法,clear()方法将position置0,将limit置capacity,这样为再次向Buffer中装入数据做好准备。

       Buffer包含以下常用的方法:

              int capacity()

              boolean hasRemaining() 判断当前位置和界限之间是否还有元素可供处理

              int limit()

              Buffer limit(int newLt) 重新设置界限的值,并返回一个具有新的limit的缓冲区对象。

              Buffer mark() 设置Buffer的mark位置,它只能在0和位置之间做mark

              int position()

              Buffer position(int newPs) 设置Buffer的position,并返回position被修改后的Buffer对象

              int remaining() 返回当前位置和界限之间的元素个数

              Buffer reset() 将position转到mark所在的位置

              Buffer rewind() 将position设置为0,取消设置的mark

       此外,Buffer还提供两个重要的方法:put()、get(),用于向Buffer中放入数据和从Buffer中取数据。其既可以单个数据访问,也可以进行批量数据操作。

       当使用put()和get()来访问Buffer中的数据时,分为相对和绝对两种:

              相对:从Buffer的当前位置处开始处理,然后将位置移动

              绝对:直接根据索引向Buffer中读取或写入数据,position不变

       通过allocate()方法创建的Buffer对象时普通Buffer,ByteBuffer还提供了一个allocateDirect()方法来创建直接Buffer。直接Buffer的创建成本比普通Buffer的创建成本高,但直接Buffer的读取效率更高。

3、使用Channel

       Channel类似于传统的流对象,但与传统的流对象有两个区别:

     (1)Channel可以直接将制定文件的部分或全部直接映射成Buffer

     (2)程序不能直接访问Channel中的数据,包括读取、写入都不行,Channel只能与Buffer进行交互。即如果从Channel中去数据,必须先用Buffer从Channel中取出数据,然后让程序从Buffer中取出这些数据;如果要向Channel中写数据,同样先往Buffer中放入数据,再将Buffer中的数据写入Channel中。

       Java为Channel接口提供了DatagramChannel、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、ServerSocketChannel、SocketChannel等实现类。

       所有Channel都不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel,不同的节点流获得的Channel不一样。

       Channel中最常用的三类方法是map()、read()和write(),其中map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;而read()或write()方法都有一系列重载形式,这些方法用于从Buffer中读取或写入数据。

       map()方法的签名为:MappedByBuffer map(FileChannel.MapMode mode, long position, long size),第一个参数为执行映射时的模式,分别有只读、写等模式;而第二、第三个参数用于控制将Channel的哪些数据映射成ByteBuffer。

4、字符集和Charset

       JDK 1.4提供了Charset来处理字节序列和字符序列之间的转换,该类包含了用于创建解码器和编码器的方法,还提供了获取Charset所支持的字符集的方法,Charset类是不可变的。

       Charset类提供了一个availableCharsets()静态方法来获取当前JDK所支持的所有字符集。

       一旦知道了字符集的别名后,就可以调用Charset的forName()方法来创建对应的Charset对象,forName()方法的参数就是相应字符集的别名:

              Charset cs = Charset.forName(“utf-8”)

       获取Charset对象之后,就可以通过该对象的newDecoder()、newEncoder()两个方法分别返回CharsetDecoder和CharsetEncoder对象,代表该Charset对象的解码器和编码器。调用CharsetDecoder的decode()方法就可以将ByteBuffer(字节序列)转换为CharBuffer(字符序列),调用CharsetEncoder的encode()方法就可以将CharBuffer或String(字符序列)转换为ByteBuffer(字节序列)。

       实际上,Charset类提供了如下三个方法:

              CharBuffer decode(ByteBuffer bb)

              ByteBuffer encode(CharBuffer cb)

              ByteBuffer encode(String str)

5、文件锁

       在NIO中,Java提供了FileLock来支持文件锁定功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁的FileLock对象,从而锁定文件。

       lock()和tryLock()方法的区别为:当lock()试图锁定某个文件时,如果无法获得文件锁,程序将一直阻塞;而tryLock()方法是尝试获取文件锁,它将直接返回而不阻塞,如果获得文件锁,该方法则返回该文件锁,否则返回null。

       如果FileChannel只想锁定文件的部分内容,而不是锁定文件的全部内容,则可以使用如下的lock()或tryLock()方法:

              lock(long position, long size, boolean shared)

              tryLock(long position, long size, boolean shared)

       参数shared为true时,表示该锁为一个共享锁,它将允许多个进程来读取文件,但阻止其他进程获得对该文件的排它锁。当shared为false时,表明该锁是一个排它锁,它将锁住对该文件的读写。程序可以通过调用FileLock的isShared来判断它获得的锁是否为共享锁。

       处理完文件后需要调用release()方法来释放文件锁。

       关于文件锁需要注意:

       (1)在某些平台上,文件锁仅仅是建议性的,并不是强制性的。这意味着即使一个程序不能获得文件锁,它可以对该文件进行读写。

       (2)在某些平台上,不能同步地锁定一个文件并把它映射到内存中

       (3)文件锁由Java虚拟机所支持,如果两个Java程序使用同一个Java虚拟机允许,则它们不能对同一个文件加锁

       (4)在某些平台上关闭FileChannel时,会释放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel。

下面给出NIO数据读写的代码:

(1)读数据

package cn.edu.hust.io.nio;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIORead {

	public static void main(String[] args) throws IOException {
		FileInputStream inputStream = new FileInputStream("test.txt");
		FileChannel channel = inputStream.getChannel();
		
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		channel.read(buffer);
		buffer.flip();
		StringBuffer stringBuffer = new StringBuffer();
		while (buffer.remaining() > 0) {
			byte b = buffer.get();
			stringBuffer.append((char) b);
		}
		System.out.println(stringBuffer);
		channel.close();
		inputStream.close();
	
	}
}

(2)写数据

package cn.edu.hust.io.nio;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOWrite {

	public static void main(String[] args) throws IOException {
		File file = new File("test.txt");
		FileOutputStream outputStream = new FileOutputStream(file);
		FileChannel channel = outputStream.getChannel();
		ByteBuffer buffer =  ByteBuffer.allocate(1024);
		String data = "Benz Audi BMW Toyota Honda Nissan Mazda Volvo";
		buffer.put(data.getBytes());
		buffer.flip(); //准备好数据
		channel.write(buffer);
		channel.close();
		outputStream.close();
		
	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值