java NIO 学习笔记

java NIO 概述:

java NIO 是 java jdk1.4 java.nio.* 包中新引入的io库,目的是提高速度。
旧的io包已由nio重新实现。
nio主要应用于两个场景:文件io,网络io
java NIO 由三个核心部分组成: Channels Buffers Selectors

channel 类似于流 数据可以从channel读到buffer 也可以从buffer 写到channel。
常用的channel实现有: FileChannel(从文件中读写), DatagramChannel (通过UDP读写网络中数据),SocketChannel (通过TCP读写网络中数据),
ServerSocketChannel(监听TCP链接,为每个连接创建socketChannel)

Buffers关键实现有: ByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer
LongBuffer ShortBuffer MappedByteBuffer(表示内存映射文件)

Selector可以使单线程处理多个channel, 向selector注册channel后调用select()
方法,该方法会阻塞直到某个通道有事件就绪

java NiO 与 java IO 的区别:

1.java NIO 面向缓冲,java IO面向流:
    IO读取流中的数据不能进行缓存,不能控制流中数据的前后移动。
    NIO中读取数据到缓冲区中并可也进行前后移动,但要注意保证数据的正确性
    以及避免数据被覆盖。
    
2. java NIO 有非阻塞模式,java IO流是阻塞的
    线程用io流的read write方法读取写入数据时会被阻塞。
    NIO从通道(channel)读取数据,若无可用数据则线程不会阻塞,直到数据可用再读取。
    NIO向通道写数据,无需等待完全写入,线程可以去做其他操作。一个线程可管理多个
    输入输出通道。
    
3. java NIO 有选择器(selector) java IO 没有
    NIO选择器可以注册多个通道,然后用一个线程选择通道,因此单独线程可以管理多个
    通道。

channel:

io流的读写是单向的,channel是双向的(channel->buffer或者 buffer->channel),
可以异步读写。
channel可以从多个buffer中读取数据,调用channel.read(bufArray),
传入一个buffer数组,将会按照buffer在数组中的顺序读入channel中。
 一个buffer读满后再读另一个.
 
也可以将数据从多个buffer写入到同一个channel中:channel.write(buffArray)
    写入方法类似于读取,也是按buffer数组元素顺序写入,
    只写入buffer的position,limit之间的数据.
    
 两个channel之间也可以直接传递数据,但两个channel必须有一个为FileChannel。
 调用fileChannel的transferFrom() 和transferTo() 方法:
 
 transferFrom():  将fromchannel中的数据传输到FileChannel中:
  RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
  FileChannel toChannel = toFile.getChannel();
  toChannel.transferFrom(fromChannel, position, count);
  从position处开始向目标文件写入数据,
  count是最多传输的字节数,若源通道空间小于count,则传输数量也小于count
  另外SocketChannel只会传输已准备好的数据,传输数量也可能小于count
  
 transferTo(): 将filechannel中数据传输至其他channel中:
  fromChannel.transferTo(position, count, toChannel);
  transferTo方法中同样需要注意SocketChannel的问题


channel的常见类实现如下:
FileChannel:一个连接到文件的通道。可以通过FileChannel读写文件。FileChannel
    无法设置为非阻塞模式。需通过InputStream、OutputStream或RandomAccessFile
    来获取一个FileChannel实例:
    RandomAccessFile file= new RandomAccessFile("data/tst.txt", "rw");
    FileChannel inChannel = file.getChannel();
    FileInputStream fileInputStream = new FileInputStream("F:\\tst.txt");
    jdk1.7之后通过open()方法 传递Path对象来创建:
    FileChannel.open(Paths.get("F:\\tst.txt"), StandardOpenOption.WRITE);
    
    调用 int channel.read(buf)从FileChannel中读取数据到buffer,返回值表示读到
    的元素数量。读到文件末尾则返回-1
    
    向FileChannel写数据:
    while(buf.hasRemaining()) {
		channel.write(buf);
    }		// 需在循环中写入,因为不能保证write()方法一次向FileChannel写入多少字节
    channel.close()  		//关闭FileChannel
    filechannel.position(): 获取channel当前位置。
    filechannel.position(pos): 设置当前channel位置。
    将position设置在文件结束符之后,从channel读取时返回-1,向channel写数据时
    文件将撑大并写入数据,导致文件空洞。
    filechannel.size(): 返回关联的文件大小
    filechannel.truncate(1024): 截取文件前1024个字节
    fileChannel.force():强制将channel中剩余的数据写到磁盘上。
    
Socketchannel:是一个连接到TCP网络套接字的通道。
    两种创建方式:
      打开一个socketchannel并连接到某台服务器上 
      SocketChannel.open()
      一个新连接到达ServerSocketChannel时,创建一个SocketChannel
      socketChannel.connect(new InetSocketAddress("http://xxx.com", 80))
    关闭socketchannel: socketChannel.close();
    
    socketChannel.read(buf): 从socketChannel读数据 返回值表述读取的元素数
      返回-1表示读到了流的末尾
    
    SocketChannel.write(): 向socketchannel写入数据,同Filechannel一样需要循环写入
    设置非阻塞模式:socketChannel.configureBlocking(false);
    判断连接是否建立:boolean socketChannel.finishConnect()
    
ServerSocketChannel:一个可以监听新进来的TCP连接的通道
    打开ServerSocketChannel: ServerSocketChannel.open() 
    关闭:ServerSocketChannel.close()
    监听连接: ServerSocketChannel.accept() 返回包含新进
      来的连接的 SocketChannel 该方法一直阻塞到有连接进来
    可以设置非阻塞模式:该模式下监听方法立刻返回,需检查返回的
    SocketChannel是否是null
    
DatagramChannel:一个能收发UDP包的通道,UDP是无连接的网络协议,因此不能像其它
    通道一样读取和写入。只能发送和接收数据包。
    打开channel:
      DatagramChannel channel = DatagramChannel.open();
      channel.socket().bind(new InetSocketAddress(8888));  
      //可以在UDP端口8888上接收数据包
      
    channel.receive(buf): 将接收到的数据包复制到指定buffer,若buffer容量不够
      多余的数据会丢失
      
    channel.send(buf, new InetSocketAddress("xxx.com", 80)):
      用send方法发送buffer中的数据到”xxx.com”服务器的UDP端口80
    
    channel.connect(new InetSocketAddress("xxx.com", 80));
      将DatagramChannel“连接”到网络中的特定地址,连接后可以使用
      read()和write()方法,类似传统的通道一样。但在数据传送方面没有保证
      channel.read(buf); channel.write(but);
      
AsynchronousFileChannel: 在Java 7中被添加到NIO中。可以实现异步读取
    和写入文件数据
    打开AsynchronousFileChannel: 
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);
	StandardOpenOption.READ表示对path代表的文件进行读操作
	
	使用future读取数据:
	    Future future= fileChannelread(buffer, 0);	
	    //从文件0位置处读取数据到buffer中,异步读取使用:futrue.isDone()
	    来确认是否读完
	    
	使用CompletionHandler读取数据:
	    fileChannel.read(buffer, position, buffer, 
	    new CompletionHandler<Integer, ByteBuffer>(){
		    @Override
		    public void completed(Integer result, ByteBuffer attachment) {
		        System.out.println("result = " + result);
		        attachment.flip();
		        byte[] data = new byte[attachment.limit()];
		        attachment.get(data);
		        System.out.println(new String(data));
		        attachment.clear();
		    }
		    @Override
		    public void failed(Throwable exc, ByteBuffer attachment) {
		    }
        }
        CompletionHandler 作为参数,读取完成时调用completed方法,result为
        读取的数据量,attachment存储读取的数据。读取失败时 failed()方法会被调用。
        
     使用Future写入数据:
         打开channel时open()方法需设定StandardOpenOption.WRITE参数
         表示对文件进行写操作
         Future<Integer> future = fileChannel.write(buffer, position);
         future .isDone()
         将buffer中的数据写入文件,需调用isDone()判断是否写入完毕。
         目标文件需提前创建否则会抛异常
         
使用CompletionHandler写入数据:
    fileChannel.write(buffer, position, buffer, 
    new CompletionHandler<Integer, ByteBuffer>() {。。。}
    类似于CompletionHandler读取数据,需重写completed和falied方法

Buffer

buffer 本质上是一块包装过的内存,用于和nio channel进行数据读写交互。
读写步骤:
 inchannel.read(buf);
 buf.flip();  //切换为读模式
 outchannel.write(buf);
 buf.clear();  //切换到写模式,清空缓冲区 buf.compact() 清除读过的数据,
 未读数据放置到buf起始区,新写入的数据放在未读数据后面。
 
 buffer.capacity : 表示buf的大小。capacity不能改变,因为buffer底层实现是数组
 buffer.position : 表示buf当前位置,写入数据后position向前移动,范围:0~capaci-1
 							切换为读模式时position置零。
 buffer.limit : 写模式下等于capacity,读模式下表示最多能读到的数据等于写模式下的position
 注:切换到读模式本质上是改变了position(设为上次开始写入时的起始位置)和
     limit(上次写模式的position)
      读完后需要clear()缓冲区,将position变为开始读取时的position位置,
      并且limit变为 capacity-position的值
 
 创建buffer:ByteBuffer.allocate();
 写入数据到buffer 的两种方法:channel.read(buf), buf.put();
 从buffer 中读取数据两种方法:channel.write(buf), buf.get();
 buffer.rewind() : position设为0,limit不变;
 buffer.mark() : 标记buf中特定的position
 buffer.reset() : 回复到之前设定的position
 buffer.equals() : 若两个buf 中数据类型相同,各个类型元素的数量和值都相同则表示两个buf相等
 buffer.compareTo() : 比较两个buf中第一个不同的元素,若数据都相同则比较元素个数

Selector

selector 可以检测到多个channel,并收集channel的读写状态。使得单线程可以管理多
个channel。使用Selector时 channel必须处在非阻塞模式。FileChannel不能切换到
非阻塞模式
创建Selector:Selector.open();
注册channel:  channel.register(selector, Selectionkey.OP_READ);
register方法返回SelectionKey对象包含 监听事件集合(interest集合),ready集合,
Channel,Selector

Selector 监听channel事件:Selectionkey.OP_READ(读就绪),    
                         Selectionkey.OP_WRITE(写就绪),
                         SelectionKey.OP_ACCEPT(接收就绪)
                         SelectionKey.OP_CONNECT(链接就绪)
  用 ‘|’操作符来监听多个事件 SelectionKey.OP_READ | SelectionKey.OP_WRITE
 
 interest集合包含Selector感兴趣的集合,判断interest集合是否包含某事件:
     boolean isAccept  = (interestSet & SelectionKey.OP_ACCEPT)
      == SelectionKey.OP_ACCEPT;
      
ready集合包含channel已就绪的事件,访问ready集合: selectionKey.readyOps();
    检测channel中某事件是否就绪:
    selectionKey.isAcceptable() isConnectable() ....
  
从SelectionKey访问Channel和Selector:
    selectionKey.channel();  获取channel后需强制转换为对应的channel实现类
    selectionKey.selector();

在SelectionKey上附加其他对象:selectionKey.attach(obj); 
                             Object obj2= selectionKey.attachment();
Selector选择channel过程:
    首先确定感兴趣的事件是否有channel就绪: 调用 int select()方法,返回就绪
        channel数的int值,select() 会阻塞到至少有一个channel就绪。
        select(long timeout):该方法设置最长阻塞时间。selectNow():不会阻塞
        没有channel就绪则返回0
    然后 调用Set selectedKeys = selector.selectedKeys() 方法,selectedKeys 
        中包含就绪的channel对应的SelectionKey对象,可以遍历selectedKeys来
        访问:
        Iterator keyIterator = selectedKeys.iterator();
        SelectionKey key = keyIterator.next();
        遍历完成后需将遍历对象移除:keyIterator.remove();
        
Selector.wakeup():用另外一个线程调用该方法,使得原来因为调用select()
    而阻塞的线程醒来。

Selector.close(): 该方法关闭selector 并使得注册的SelectionKey 无效,注册的
    channel不会关闭。

Pipe(管道)

Pipe是2个线程之间的单向数据连接,pipe有两个通道:sourceChannel和sinkchannel
数据从sinkchannel写入,从source通道读出
创建pipe:Pipe.open();
向pipe写数据:先访问sinkChannel:sinkChannel  = pipe.sink();
             然后 sinkChannel.write(buffer) 将buffer中数据写入sinkChannel
从pipe读数据:首先访问sourceChannel: sourceChannel = pipe.source();
		     然后 int bytesRead= sourceChannel.read(buf); 返回ing值为读入
		     buffer的元素数量

Path

Path接口在Java7 中被添加到Java NIO,是Java NIO2 的一部分
一个path代表一个文件系统路径,包括绝对路径和相对路径
nio path 类似于java.io.File类。但有几个不同点,在很多情况下可以使用可以使用
Path 接口替换 file 类

创建绝对路径:
  Path path = Paths.get("d:\\data\\tst.txt")   //注意路径‘\’需要转义
创建相对路径:Paths.get(basePath, relativePath)
  创建指向文件夹路径的实例:
  Path ideaprojs= Paths.get("d:\\data", "ideaprojs");
  创建指向文件路径的实例:
  Path file = Paths.get("d:\\data", "ideaprojs\\proj1\\txt.txt");
  "." 代表当前路径:
  currentDir = Paths.get("."); 
  currentDir.toAbsolutePath() //获取当前绝对路径
  ".."代表上一级目录:
  Paths.get("..");  //获取当前目录的父目录
  可以用 Path.normalize()方法来解析路径中的. 和 .. 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值