Java NIO读书笔记

Java NIO读书笔记

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

简单介绍

NIO的作用就是改进程序的性能。由于有时候程序的性能瓶颈不再是CPU,而是IO。这时候NIO就派上用场了。NIO的原理就是尽量利用系统底层的资源来提高效率,比方利用DMA硬件减小CPU负荷,利用操作系统的epoll机制避免线程频繁切换。通过底层资源提高系统的吞吐量。

缓冲区

缓冲区就是一个固定大小的一组数据。缓冲区有四个很重要的属性:容量,限制,位置,标记。容量就是一个缓冲区最大能容量的元素数量,限制就是对容量进行逻辑上的限制,位置用于跟踪get或者put方法的位置,标记用于reset函数返回上次固定的位置。

put()方法用于往缓冲区中存入数据,get()方法用于从缓冲区中读取数据。写入操作详细的API有put(byte)、put(index, byte)、put(byte[])、put(byte[], int start, int length),有单个元素的写入,也有批量的写入。读取操作也一样拥有这四种API。

flip()用于交换未写入的和写入的数据。也就是将limit设为position,将position设为0。一般先存入一组数据之后,经过翻转,再从中读取原本写入的数据。

compact()将已经读过的数据进行压缩,将未读过的数据拷贝到缓冲区索引號为0的位置。复制之后,原来的数据不会被擦除。

mark()方法用于标记,reset()方法用于返回上次标记的位置。rewind()、clear()、flip()都会重置标记,position()、limit()、看情况,假设小于标记时也会重置标记。

缓冲区之间能够比較。比較一定要同样的类型。比較的根据是缓冲区剩余的内容,与标记、位置、容量、限制等无关。

创建缓冲区能够有两种方法。一种是创建新的缓冲区,调用xxBuffer.allocate,第二是将现有的数组进行封装,缓冲区写入的数据都会写入到原来的数组中。

缓冲区是能够复制的。调用duplicate()。复制出来的缓冲区事实上是一个视图。复制出来的缓冲区和原来的缓冲区拥有同样的数据,可是每一个缓冲区都有各自的属性,限制、位置、标记都是独立的。复制的时候也能够取缓冲区的一部分,调用slice()。

缓冲区还分为big-endian和little-endian。java.nio.ByteOrder能够获取本机的字节顺序。

另一种缓冲区称之为直接缓冲区,能够通过xxBuffer.allocateDirect获取。直接缓冲区就是操作性能比普通的缓冲区要高。

ByteBuffer提供了asXXBuffer。比方asShortBuffer、asCharBuffer等。这些缓冲区称之为视图缓冲区。就是将字节缓冲区以第二种行为提供给其它程序。ByteBuffer还提供了getInt、getLong、getDouble等方法,这些方法称之为视图操作,好像就在操作第二种类型的缓冲区。写入操作也是一样,也有视图操作。视图缓冲区和视图操作和字节顺序有关,所以在操作之前先设置字节顺序,默认的是BigEndian。

Java不支持无符号的数据类型。可是总是有解决的方法的。以下就是一种解决的方法。

package com.ronsoft.books.nio.buffers;


import java.nio.ByteBuffer;
/**
 * Utility class to get and put unsigned values to a ByteBuffer object.
 * All methods here are static and take a ByteBuffer argument.
 * Since java does not provide unsigned primitive types, each unsigned
 * value read from the buffer is promoted up to the next bigger primitive
 * data type. getUnsignedByte() returns a short, getUnsignedShort() returns
 * an int and getUnsignedInt() returns a long.
 There is no getUnsignedLong()
 * since there is no primitive type to hold the value returned. If needed,
 * methods returning BigInteger could be implemented.
 * Likewise, the put methods take a value larger than the type they will
 * be assigning. putUnsignedByte takes a short argument, etc.
 *
 * @author Ron Hitchens (ron@ronsoft.com)
 */
public class Unsigned
{
    public static short getUnsignedByte (ByteBuffer bb)
    {
        return ((short)(bb.get() & 0xff));
    }
    public static void putUnsignedByte (ByteBuffer bb, int value)
    {
        bb.put ((byte)(value & 0xff));
    }
    public static short getUnsignedByte (ByteBuffer bb, int position)
    {
        return ((short)(bb.get (position) & (short)0xff));
    }
    public static void putUnsignedByte (ByteBuffer bb, int position,
                                        int value)
    {
        bb.put (position, (byte)(value & 0xff));
    }
    // ---------------------------------------------------------------
    public static int getUnsignedShort (ByteBuffer bb)
    {
        return (bb.getShort() & 0xffff);
    }
    public static void putUnsignedShort (ByteBuffer bb, int value)
    {
        bb.putShort ((short)(value & 0xffff));
    }
    public static int getUnsignedShort (ByteBuffer bb, int position)
    {
        return (bb.getShort (position) & 0xffff);
    }
    public static void putUnsignedShort (ByteBuffer bb, int position,
                                         int value)
    {
        bb.putShort (position, (short)(value & 0xffff));
    }
    // ---------------------------------------------------------------
    public static long getUnsignedInt (ByteBuffer bb)
    {
        return ((long)bb.getInt() & 0xffffffffL);
    }
    public static void putUnsignedInt (ByteBuffer bb, long value)
    {
        bb.putInt ((int)(value & 0xffffffffL));
    }
    public static long getUnsignedInt (ByteBuffer bb, int position)
    {
        return ((long)bb.getInt (position) & 0xffffffffL);
    }
    public static void putUnsignedInt (ByteBuffer bb, int position,
                                       long value)
    {
        bb.putInt (position, (int)(value & 0xffffffffL));
    }
}

最后另一种映射缓冲区,这样的缓冲区一定是直接缓冲区,仅仅能由FileChannel创建。

通道

通道和缓冲区不同,每一个操作系统都有不同的实现方式,因此通道的代码一般都是接口或者抽象类。

通道分为堵塞通道和非堵塞通道。非堵塞通道不能在文件通道上使用。

通道类似于一种连接,所以通道是不能循环使用的。通道能够被关闭。关闭能够通过close方法和中断,对通道发送中断信号通道就会关闭。这样的设计初看认为非常别扭,可是这样设计是为了便于在不同的操作系统中实现。

通道还支持批量写入或读取多个缓冲区。一般的操作系统都从底层支持批量写入或读取缓冲区,因此Java会将批量操作翻译成系统底层的API调用,让操作系统来完毕批量操作,因此速度很快。

文件通道仅仅能是堵塞通道。比起FileStream,FileChannel还提供了很多其它的操作,比方指定在某个位置写入数据。文件通道的创建须要FileStream或者RandomAccessFile,文件通道的状态和创建时传入的參数状态是保持一致的,文件的位置是同步的。文件通道还提供了force操作,将文件的改动马上写入文件。文件通道提供了truncate操作,用于设置文件的大小。

文件系统中有个文件洞(File Hole)的概念,就是文件的大小比占用的空间少。比方在文件的1GB位置写入10K数据,那么文件实际善用的空间是10K,而不是1G。

文件锁一个常见的误区是,每一个文件仅仅能有一个文件锁,不是每一个文件通道对象有一个文件锁,也不是每一个线程有一个文件锁,而是每一个文件有一个文件锁。因此,在同一个JVM中,假设对一个文件创建了两个文件通道,在同一个地方都加上相互排斥锁,是不会堵塞的。也就是说,在JVM内部,文件锁是不起作用的。文件锁要记得释放,最好就是将释放的代码放在finally块中。

文件映射缓冲区。这样的缓冲区和普通的缓冲区一样,可是数据的内容是放在磁盘上的。映射缓冲区有三种模式,一种是仅仅读,一种是读写,一种是私有。私有模式下,文件的改动是不会写入到文件的,仅仅是保存到缓冲区中。私有模式下,文件的内容会与其它普通的文件通道同步。可是同步的单位是分页,也就是说,私有模式下是否同步跟操作系统的分页大小有关。假设在私有模式下改动文件,那么相应的分页将不再和其它文件通道同步。

通道之间还能够直接传输,相关的方法是transferTo和transferFrom。有些操作系统内核就支持通道之间的传输,因此性能很高。

文件映射的load()方法能够将整个文件载入到操作系统的文件缓存中,同一时候文件的内容和磁盘保持同步。

套接字通道和文件通道不同,支持非堵塞模式。每一个套接字通道相应了一个套接字。这样的通道不能从现有的套接字中创建。

blockingLock()方法会返回一个Object对象,能够用Java中的synchronizedkeyword对这个对象进行锁定,防止其它的线程对该对象进行改动。套接字通道分为SocketChannel和ServerSocketChannel。ServerSocketChannel仅仅是提供了非堵塞的accept方法。

数据报通道使用UDP协议进行通信。注意,在接收数据的时候,假设缓冲区的容量不够了,那么多出的数据会被\textbf{丢弃},不会有不论什么现象。发送数据的时候,假设缓冲区太大,超过了系统的发送队列,那么不会有不论什么数据会被发送。数据报通道也有connect方法,该方法仅仅是指定发送对象,并非真正的连接。

管道通道(PipeChannel)和Unix中的管道通信不是同一个概念。NIO中的管道通道仅仅能在一个JVM内部进行通信,而不是进程间的通信。进程间通信能够通过套接字。管道通信在创建的时候通过Pipe.open()就可以创建一对通道,SinkChannel和SourceChannel。SinkChannel用于写入,SourceChannel用于读取。通过管道能够实现一个线程仅仅顾写入数据,另外一个线程仅仅顾读取数据,有点类似于Python中的generator对象。管道通道最大的用处就是封装。将一个文件通道或者套接字通道封装成管道通道,提高代码的复用程度。经过实验,发现管道内部存在缓冲,就算另外一边没有读取,写入的一边也能够写入大于1K的数据。

选择器

选择器的详细实现仅仅能是通过操作系统来完毕,因此性能比較高。

有关选择器部分的有三个类,Selector、SelectionKey、SelectableChannel。

Selector用于管理多个可选通道和一堆SelectionKey。select方法会堵塞,返回的不是已经就绪的通道数量,而是在这次调用中成为就绪状态的通道数量。selectedKeys()返回的事实上是一个Set,而Set不支持多线程,所以假设selectedKeys放在另外的线程迭代,那么在迭代的过程中可能会产生ConcurrentModificationException。

Selector中有三种集合:注冊集合、选择集合、取消集合。选择集合仅仅会添加�不会降低,降低须要通过迭代器手动删除,每处理一次请求就删除相应的SelectionKey。

选择模式有两种一种是select第二种是epoll。Select是POSIX标准,而Epoll是Linux特有的。Select最多仅仅能监听1024个通道,而Epoll则没有这样的限制。Select每次调用时会扫描全部的通道,因此通道越多性能越差,而Epoll中有一个可用队列,这个队列由操作系统内核来维护的,当一个通道可用时,操作系统就会往队列中添加�通道,因此性能不会随着通道数量的添加�而变差。

Epoll有两种工作模式,一种是Level Trigger水平触发,还有一种是Edge Trigger边缘触发。默认是水平触发,这样的模式当通道的数据还没有读取完时,下一次选择之后selectionKeys会立即返回没有读完的通道,而边缘触发则不会,边缘触发的性能更高可是程序出错的可能性更大。

SelectionKey就是通道和选择器的相应关系。提供了readyOps()方法,这种方法返回通道已经就绪的操作。也能够通过isWritable()、isReadable()等方法推断通道是否支持某个操作,这两种方法是等价的。选择键还能够带上一个附件,便于通道获取參数。须要注意的是,附件假设不再使用,应该立即清除掉,否则会造成内存泄露。

SelectableChannel就是可选通道,它能够在多个Selector中注冊,注冊的时候要提供须要监听的事件,比方OP\_READ、OP\_WRITE。validOps()方法返回这个通道能够监听的操作。JDK中定义了4种兴趣:读、写、连接、接受。SocketChannel是不能接受连接的,所以validOps不会返回接受动作。注冊通道能够反复注冊,可是第二次注冊时仅仅会改动兴趣集,并返回同一个SelectionKey。假设第二次注冊的时候已经调用了cancel()方法,然而Selector还没有来得及更新,就会发生CancelledKeyException。

关闭通道应该是一个很高速的操作,没有不论什么堵塞。这是JavaNIO的设计目标。这种设计称为异步关闭。

一般编写代码的时候模板例如以下:

while(true) {
    selector.select();


    Iterator<SelectionKey> keys = selector.selectedKeys();
    while(keys.hasNext()) {
        SelectionKey key = keys.next();


        // 处理事件
        ...


        // 处理完成之后删除,这样就表示 这次事件已经处理过了
        keys.remove();
    }
}

对于多核的计算机而言,仅仅有一个线程在工作是很低效的,为了在多核计算机上提升性能,必须引入多核线程和多个选择器。每一个线程一个选择器,每次接受连接的时候随机分配给一个线程。这是一种方法,第二种方法是当中一个线程用于接受连接,其余的线程专门负责处理业务。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/118369.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • rwx权限详解

    rwx权限详解

  • C语言-链表排序_单链表的排序c语言

    C语言-链表排序_单链表的排序c语言C语言-链表排序题目描述已有a、b两个链表,每个链表中的结点包括学号、成绩。要求把两个链表合并,按学号升序排列。输入第一行,a、b两个链表元素的数量N、M,用空格隔开。接下来N行是a的数据然后M行是b的数据每行数据由学号和成绩两部分组成输出按照学号升序排列的数据样例输入235100689382495210样例输出210382…

    2022年10月11日
  • 互联网测试面试题及答案(软件测试面试题及答案2019)

    很多软件测试工程师在面试互联网企业的时候都会遇到考官给的几道面试题,这也反应了测试工程师对企业的重要性,今天传智播客整理了一份2019年的互联网企业软件测试面试题,希望能帮助到大家。2019年互联网企业软件测试面试题(常考)1、什么是兼容性测试?答:兼容性测试是检查软件在不同软件平台,硬件平台上是否可以正常运行的测试。主要查看软件在不同操作系统、浏览器、数据库中运行是否正常。2、你能不能…

  • sop流程图模板_这是一份标准作业流程SOP详解,附流程图绘制规范,不愁不会画!…「建议收藏」

    sop流程图模板_这是一份标准作业流程SOP详解,附流程图绘制规范,不愁不会画!…「建议收藏」注:资料来源百度、档即用网,品质人生质量开讲平台搜集、整理、编辑,仅供学习交流所用,请勿做其他用途!小编辛苦整理,转载请注明出处。什么是SOP?StandardOperationProcedure所谓SOP,是StandardOperationProcedure三个单词中首字母的大写,即标准作业程序。是以文件的形式描述作业员在生产作业过程中的操作步骤和应遵守的事项;是作业员的作业指导书…

  • getchar的使用

    1.从缓冲区读走一个字符,相当于清除缓冲区2.前面的scanf()在读取输入时会在缓冲区中留下一个字符’\n’(输入完s[i]的值后按回车键所致),所以如果不在此加一个getchar()把这个回车符取走的话,gets()就不会等待从键盘键入字符,而是会直接取走这个“无用的”回车符,从而导致读取有误3.getchar()是在输入缓冲区顺序读入一个字符(包括空格、回车和…

  • 惠普电脑如何设置u盘启动_惠普笔记本电脑怎么用u盘重装系统

    惠普电脑如何设置u盘启动_惠普笔记本电脑怎么用u盘重装系统惠普笔记本现在算是比较普遍,很多用户都会想给自己的惠普笔记本重装系统,那么惠普怎么重装系统呢?下面介绍一下惠普笔记本u盘系统安装步骤。惠普u盘启动系统安装步骤阅读1、将U盘插在USB接口,开机并不断按下启动U盘快捷键。2、在进入系统启动菜单中选择有USB字样的选项并回车。3、系统启动后会进入PE界面,老机型选择Win2003PE,选择完后回车进入。4、等待进入系统后,无需操作,云骑士会自动打开进行…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号