您的位置:首页 > 编程语言 > Java开发

java8--NIO(java疯狂讲义3复习笔记)

2017-05-31 18:45 435 查看
NIO采用内存映射文件的方式处理输入输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念),通过这种方式来进行输入输出比传统的输入输出要快得多.

Java中与NIO相关的包如下:

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

java.nio.channels包:与Channle和Selector相关的类.

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

java.nio.channels.spi包:与Channel相关的服务提供编程接口

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

NIO中的新特点:Channel(通道)和Buffer(缓冲),Channle与传统的InputStream,OutputStream最大的区别在于它提供了一个map()方法,通过该方法可以海子街将"一块数据"映射到内存中.如果说传统的输入/输出系统是面向流的处理,则NIO则是面向块的处理.

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

除了ByteBuffer之外,它们都采用相同或相似的方法来管理数据,只是各自管理的数据类型不同而已.

NIO还提供了加工Unicode字符串映射成字节序列以及逆映射操作的Charset类, 也提供了用于支持费阻塞式输入输出的Selector类.

15.9.2使用Buffer

Buffer就像是一个数组,可以保存多个类型相同的数据.Buffer是一个抽象类,最常用的子类是ByteBuffer,它可以在底层字节数组上进行get/set操作. 也有CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer.

这些实现类都没提供构造器,使用如下方式是构造:

ByteBuffer byteBuffer = ByteBuffer.allocate(160);

或者

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(160);//ByteBuffer 独有的创建方法,称为直接BUffer

直接Buffer的创建成本比普通的Buffer创建成本高,但效率也高,所以只适用于长生存期的Buffer,不适用于短生存期,一次用完就丢弃的Buffer.

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

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

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

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

Buffer包含的一些常用方法:

这些方法大概可以分类

1.标记功能: mark()和reset()

2.position操作: position() position(int newPs) rewind() reset()

3.limit操作: limit() limit(int newLt)

4.剩余元素操作: hasRemaining() remaining()

5.容器大小: capacity()

重点:

put()方法:用于向Buffer中放入数据

get()方法:用于从Buffer中取出数据

这两个方法,既支持对单个数据的访问,也支持对批量数据的访问.

而且,分为相对和绝对两种.

相对(relative):从Buffer的当前position处开始读取或写入数据, 然后将位置(position)的值按处理的元素的个数增加.

绝对(Absolute):直接根据索引向Buffer中读取或写入数据,使用绝对方式访问Buffer里的数据时,并不会影响位置(position)的值.

public static void bufferTest(){
CharBuffer buff = CharBuffer.allocate(8);    // ①
System.out.println("capacity: "    + buff.capacity());
System.out.println("limit: " + buff.limit());
System.out.println("position: " + buff.position());
// 放入元素
buff.put('a');
buff.put('b');
buff.put('c');      // ②
System.out.println("加入三个元素后,position = "
+ buff.position());
// 调用flip()方法
buff.flip();      // ③
System.out.println("执行flip()后,limit = " + buff.limit());
System.out.println("position = " + buff.position());
// 取出第一个元素
System.out.println("第一个元素(position=0):" + buff.get());  // ④
System.out.println("取出一个元素后,position = "
+ buff.position());
// 调用clear方法
buff.clear();     // ⑤
System.out.println("执行clear()后,limit = " + buff.limit());
System.out.println("执行clear()后,position = "
+ buff.position());
System.out.println("执行clear()后,缓冲区内容并没有被清除:"
+ "第三个元素为:" +  buff.get(2));    // ⑥
System.out.println("执行绝对读取后,position = "
+ buff.position());
}


15.9.3 使用Channel

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

1. Channel可以直接将指定文件的部分或全部直接映射成Buffer.

2. 程序不能直接访问Channel中的数据,包括读取, 写入,都不行,Channel中取出一些数据,然后让程序从Buffer中取出这些数据:如果要将程序中的数据写入Channel, 一样先让程序将数据放入Buffer中,程序再将Buffer里的数据写入Channel中.

Channle接口的实现类很多,这里只介绍FileChannel的用法.

所有的Channle都不应该通过构造器来直接创建,而是通过传统的节点InputStream,OutputStream的getChannle()方法来返回对应的Channle.

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

public static void fileChannelTest(){
File f = new File(".project");
try(
// 创建FileInputStream,以该文件输入流创建FileChannel
FileChannel inChannel = new FileInputStream(f).getChannel();
// 以文件输出流创建FileBuffer,用以控制输出
FileChannel outChannel = new FileOutputStream("aw.txt")
.getChannel())
{
// 将FileChannel里的全部数据映射成ByteBuffer
MappedByteBuffer buffer = inChannel.map(FileChannel
.MapMode.READ_ONLY , 0 , f.length());   // ①
// 直接将buffer里的数据全部输出
outChannel.write(buffer);     // ②
// 再次调用buffer的clear()方法,复原limit、position的位置
buffer.clear();

// 使用GBK的字符集来创建解码器
Charset charset = Charset.forName("UTF-8");
// 创建解码器(CharsetDecoder)对象
CharsetDecoder decoder = charset.newDecoder();
// 使用解码器将ByteBuffer转换成CharBuffer
CharBuffer charBuffer =  decoder.decode(buffer);
// CharBuffer的toString方法可以获取对应的字符串
System.out.println(charBuffer);
}
catch (IOException ex)
{
ex.printStackTrace();
}
}


下面给一个出问题的例子,这个是

RandomAccessFile生成的FileChannle,可是与疯狂讲义里说的有偏差,主要问题是当插入的数据超出FileChannle的范围时,FileChannle不会自动扩大,反而变成等待状态,
所访问的文件直接挂掉,一点那个文件,eclipse也挂掉,最后Mac都无法关机,只能强制关机。也不知道哪里没用对。


public static void randomFileChannelTest() throws FileNotFoundException, IOException{
File f = new File("write.txt");
try(
// 创建一个RandomAccessFile对象

FileChannel randomChannel = new RandomAccessFile(f, "rw").getChannel();
)
{
// 将Channel中所有数据映射成ByteBuffer
randomChannel.position(1000);
System.out.println(f.length());
System.out.println(randomChannel.size());
ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0 ,1);
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
// 使用GBK的字符集来创建解码器
Charset charset = Charset.forName("UTF-8");
// 创建解码器(CharsetDecoder)对象
CharsetDecoder decoder = charset.newDecoder();
// 使用解码器将ByteBuffer转换成CharBuffer
CharBuffer charBuffer = decoder.decode(buffer);
// CharBuffer的toString方法可以获取对应的字符串
System.out.println(charBuffer);
// 把Channel的记录指针移动到最后
randomChannel.position(10);
System.out.println("randomChannel.position():"+randomChannel.position());
// 将buffer中所有数据输出
buffer.flip();
randomChannel.write(buffer);
buffer.clear();
randomChannel.close();
}catch(Exception e){
e.printStackTrace();
}
}


编码解码(字符集和Charset),(在String类里也提供了一个getBytes(String charset)方法,该方法返回byte[],也是使用指定的字符集将字符串转换成字节序列,跟这个Charset的功能类似).

public class CharsetTransform
{
public static void main(String[] args)
throws Exception
{
// 创建简体中文对应的Charset
Charset cn = Charset.forName("GBK");
// 获取cn对象对应的编码器和解码器
CharsetEncoder cnEncoder = cn.newEncoder();
CharsetDecoder cnDecoder = cn.newDecoder();
// 创建一个CharBuffer对象
CharBuffer cbuff = CharBuffer.allocate(8);
cbuff.put('孙');
cbuff.put('悟');
cbuff.put('空');
cbuff.flip();
// 将CharBuffer中的字符序列转换成字节序列
ByteBuffer bbuff = cnEncoder.encode(cbuff);
// 循环访问ByteBuffer中的每个字节
for (int i = 0; i < bbuff.capacity() ; i++)
{
System.out.print(bbuff.get(i) + " ");
}
// 将ByteBuffer的数据解码成字符序列
System.out.println("\n" + cnDecoder.decode(bbuff));
}
}


15.9.5 文件锁
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: