java NIO的两大特性:非阻塞与直接缓冲
java NIO中所要注意的点:
1、 FileChannel 的read函数,write函数中,如果传入的参数是HeapBuffer类型,则会临时申请一块DirectBuffer,进行数据拷贝,而不是直接进行数据传输,这是出于什么原因?
一、Buffer(capaticy、limit、position、mark)(Buffer类不是线程安全的)
0<=mark<=position<=limit<=capaticy
capaticy:指的是缓冲区的容器的容量,就是数组的length的值
limit:和容器的size()是一个意思
position:指的是当前的索引
mark:标志索引的起始位置或者是多阶段时各个阶段的基点
一旦创建了缓冲区,其capaticy就不能改变,所以要手动对缓冲区进行扩容
直接缓冲区的回收和使用
1、buffer分类主要讲三个:
(1)ByteBuffer
(2)CharBuffer
(3)MappedByteBuffer
2、Buffer的关键的方法
(1)创建Buffer的三种方式
①ByteBuffer bb = ByteBuffer.allocate(10);//分配缓冲区,实际上创建了HeapByteBuffer
②byte[] array = new byte[10]; ByteBuffer bbf = ByteBuffer.wrap(array);
③ByteBuffer bb = ByteBuffer.allocateDirect(10);//分配直接缓冲区(这种方式只有ByteBuffer可以用)直接缓冲区善于保存那些易受操作系统本机IO操作影响的大量、长时间保存的数据,因为system.gc会移动对象。
(2)常用的方法
添加数据:put(byte x),执行这个方法时向当前位置添加数据,position++
put(int i, byte x) ,这个方法是给定索引复制,但是position不变。
put(byte[] src,int offset,int,length),这个方法相当于循环执行put(byte x),执行完后位置将增加length
put(byte[] src)
put(ByteBuffer src)将给定源缓冲区的剩余字节传输到此缓冲区当前位置中,n=src.remaining(),两个缓冲区的位置都增加n
ByteBuffer类中putType()方法的使用,位置会相应地增加
获取数据:get(),执行这个方法时从当前位置获取数据,position++
get(int i),这个方法是从给定索引获取数据,但是position不变
get(byte[] dst,int offset,int length),这个方法相当于循环执行get(),执行完后位置将增加length
get(byte[] dst)
ByteBuffer类中getType()方法的使用,位置会相应地增加
剩余空间元素数:remaining()->limit-position;
是否有剩余元素:hasRemaining()
缓冲区是否只读:isReadOnly()
是否是直接缓冲区:isDirect()
判断是否有底层实现的数组:hasArray()
让position回到基点:reset()->position=mark;
还原缓冲区的状态:clear()->limit=capacity;position=0;mark=-1;主要是用在对缓冲区进行存取数据之前
对缓冲区进行反转:flip()->limit=position;position=0;mark=-1;主要是用在写完数据,然后进行读数据的时候。
重绕缓冲区:rewind()->position=0;mark=-1;主要使用在重新读取缓冲区
获取偏移量:arrayOffset()这个值在slice()的情况下才会用到
把数组包装到缓冲区中:wrap(byte[] bytes)
创建子缓冲区:slice()新缓冲区的内容将从当前位置开始,两个缓冲区的位置限制和标志值是相互独立的。在这里就体现除了arrayOffset()的作用
CharBuffer类的缓冲区:注意中文的转码问题 :
//第一种方法
ByteBuffer buffer = ByteBuffer.wrap("我是中国人".getBytes("UTF-16BE")); CharBuffer charBuffer = buffer.asCharBuffer(); charBuffer.clear(); while(charBuffer.hasRemaining()) { System.out.print(charBuffer.get()); } System.out.println();
//第二种方法 ByteBuffer buffer2 = ByteBuffer.wrap("我是中国人".getBytes("UTF-8")); CharBuffer charBuffer2 = Charset.forName("UTF-8").decode(buffer2); charBuffer2.clear(); while(charBuffer2.hasRemaining()) { System.out.print(charBuffer2.get()); }
ByteBuffer类的视图缓冲区:asCharBuffer(),asFloatBuffer(),asDoubleBuffer(),asIntBuffer(),asLongBuffer(),asShortBuffer(),但是他们仍然是存储在ByteBuffer里面的。
//直接缓冲区IntBuffer ByteBuffer buffer = ByteBuffer.allocateDirect(102400); buffer.clear(); IntBuffer intBuffer = buffer.asIntBuffer(); int i = 1; long startTime = System.currentTimeMillis(); while(intBuffer.hasRemaining()) { intBuffer.put(i++); intBuffer.get(i-2); } long endTime = System.currentTimeMillis(); System.out.println(intBuffer.limit()); System.out.println(endTime-startTime); System.out.println(); //非直接缓冲区IntBuffer IntBuffer intBuffer2 = IntBuffer.allocate(25600); intBuffer2.clear(); int j = 1; long startTime2 = System.currentTimeMillis(); while(intBuffer2.hasRemaining()) { intBuffer2.put(j++); intBuffer2.get(j-2); } long endTime2 = System.currentTimeMillis(); System.out.println(intBuffer2.limit()); System.out.println(endTime2-startTime2);
设置字节序的方法:(默认大字节序)order()
缓冲区转成数组的方法:array()
创建只读缓冲区:asReadOnlyBuffer()
压缩缓冲区:compact()这里相当于是整理缓冲区
比较相等:equals()比较两个缓冲区的position和limit之间的内容是否完全相等
比较大小:compareTo()比较两个缓冲区的position和limit之间的内容
复制缓冲区:duplicate()注意在这里是没有重新创建一个缓冲区的,引用的还是原来的数组
对缓冲区进行扩容:
ByteBuffer buffer = ByteBuffer.allocateDirect(1024); ByteBuffer buffer2 = ByteBuffer.allocate(2048); buffer2.put(buffer);
(3)CharBuffer类常用的API
append方法的使用:将指定的字符或者字符序列添加到缓冲区
读取当前位置给定索引的字符:charAt(int index)
将给定字符串的所有内容传输到缓冲区的当前位置:put(String src)
将当前缓冲区的内容写入指定的字符缓冲区:read(CharBuffer target)和put(CharBuffer src)
创建一个子缓冲区:subsequence(int start,int end)
创建缓冲区:wrap(CharSequence csq,int start,int end)
获取字符串缓冲区的长度:length()
3、直接缓冲区手动释放内存
public static void recycleDirectBuffer() throws Exception { ByteBuffer buffer = ByteBuffer.allocateDirect(10); Field cleaner = buffer.getClass().getDeclaredField("cleaner"); cleaner.setAccessible(true); Cleaner value = (Cleaner)cleaner.get(buffer); value.clean(); }
二、通道(Channel)
异步通道在多线程并发的情况下是线程安全的
1、AutoCloseable接口
实现这个接口的资源可以和try()语句结合使用,实现自动关闭资源的功能
2、AsynchronousChannel接口
(1)Future方法
(2)回调
3、InterruptibleChannel接口
4、AbstructInterruptibleChannel类
5、FileChannel类(该通道永远是阻塞的操作)(多个并发线程可以安全地使用FileChannel)
获取它的四种方式:FileInputStream、FileOutputStream、RandomAccessFile、FileChannelImpl.open
(1)write(ByteBuffer src)
`` 从通道的当前位置开始写入
将缓冲区的remaining写入通道
方法具有同步性
(2)read(ByteBuffer dest)
返回值表示从通道的当前位置向ByteBuffer缓冲区中读取的字节个数
是从通道的当前位置开始读取的
将字节放入buffer的当前位置
方法具有同步性
当通道的数据大于缓冲区容量的时候,ByteBuffer缓冲区中remaining为多少,就从通道中读取多少数据
从通道中读取的数据放入缓冲的remaining空间中
(3)write(ByteBuffer[] srcs)
方法是从通道的当前位置开始写入的
方法将通道的remaining写入通道
这个方法具有同步性
(4)read(ByteBuffer[] dests)
这个方法的返回值代表从通道的当前位置向缓冲区读取了字节的个数
方法是从通道的当前位置开始读取的
将字节放入ByteBuffer的当前位置
方法具有同步性
如果从通道中读取的数据大于缓冲区的容量,那么reamaining是多少,就从同道中读取多少字节
从通道中读取的字节放入缓冲的remaining空间中
(5)write(ByteBuffer[] srcs,int offset,int length)
是从通道的当前位置开始写入的
将ByteBuffer的remaining写入通道
方法具有同步性
(6)read(ByteBuffer[] dests,int offset,int length)
返回值的含义是从通道的当前位置读入缓冲区的字节的数量
方法是从通道的当前位置开始读取的
方法是将字节放入ByteBuffer的当前位置
方法具有同步性
如果方法从通道中读取的数据大于缓冲区的绒量,remianing剩余多少,就读入多少字节
从通道中读取的数据放入缓冲区的remaining空间中
(7)write(ByteBuffer src,int position)向通道中的指定位置写入数据
从通道的指定位置开始写入
将ByteBuffer的remaining写入通道
方法具有同步的特性
这个方法是不会影响通道的position的值的,就是说此position非比position
(8)read(ByteBuffer dest,int position)读取通道指定位置的数据
返回值的意义就是从通道指定位置向缓冲区读取数据的字节个数
方法将数据放入ByteBuffer的当前位置
方法具有同步性
如果从通道读取的数据大于缓冲区的容量时,只读remaining的数据在缓冲区中
从通道读取的字节放入缓冲区的remaining空间中
(9)position(long newPosition)设置此通道的文件位置
(10)truncate(long size)截断缓冲区
(11)transferTo(position,count,WritableByteChannel dest)从通道的文件中给定position开始的count个字节,并将其写入目标通道的当前位置
此方法不修改此通道的位置
如果给定的位置大于该文件的当前大小,则不传输任何字节
如果count的字节个数大于position到size的字节个数,则传输通道的size-position个字节数到dest通道的当前位置
如果count的字节个数小于position到size的字节个数,则传输通道的count个字节数到dest通道的当前位置
(零拷贝技术、kafka)
①传统IO拷贝技术:
代码:
File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);两次系统调用,四次上下文切换,四次拷贝
②零拷贝技术:(windows不支持)
transferTo底层调用的就是sendFIle;一次系统调用,两次上下文切换,两次拷贝
③mmap拷贝技术
mmap和write两次系统调用,四次上下文切换,三次拷贝
④直接内存拷贝技术
原理是(Linux直接IO),直接跳过内核缓冲区,用户缓冲区直接和文件系统进行操作。但这里的用户缓冲区在堆外,不在JVM内。
用于自己做缓冲管理,不需要操作系统做缓冲管理
(12)transferFrom(ReadableByteChannel src,position.count)将字节从给定的可读取字节通道传输到此通道的文件中
此方法不修改通道的位置
position是当前通道的位置,而不是src通道的位置
如果给定的位置大于给文件的当前大小,则不传输任何字节
如果count的字节数大于src.remaining,则通道的src.remaining的字节数传输到当前通道的position位置
如果count的字节数小于src.remaining,则通道的count的字节数传输到当前通道的position位置
??与transferTo不同,transferFrom不能使FileChannel通道对应的文件大小增长
(13)FileLock lock(long position,long size,boolean shared)通道文件给定区域上的锁定;position是指从哪个位置开始上锁,size是指锁的范围
方法是同步的(用于进程之间的同步)
lock方法调用期间,如果另一个线程关闭了此通道,则抛出AsynchronousCloseException的一场
如果调用lock方法之前,已设置线程为中断状态,那么当调用lock方法时,将会抛出FileLockInterruptionException的异常,不更改该线程的中断状态。
共享锁自己不能写,自己能读
共享锁别人不能写,别人能读
独占所自己可以写
独占所别人不能写
独占所自己可以读
独占所别人不能读
lock方法可以实现提前锁定,也就是当文件大小小于指定的position时,是可以提前在position位置处加锁的
共享锁与共享锁之间是非互斥关系
共享锁和独占锁之间是互斥关系
独占锁和共享锁之间是互斥关系
独占锁和独占锁之间是互斥关系
文件锁定以整个java虚拟机来保持,但他们不适应与同一虚拟机内多个线程对文件的访问
lock()相当于lock(0L,Long.MAX_VALUE,false)
(14)tryLock(long position,long size,boolean shared)获取通道文件给定区域的锁定
是独占的。
文件锁定以整个java虚拟机来保持。但他们不适用于控制同一虚拟机内多个线程对文件的访问。
tryLock(非租塞的);lock(阻塞的)
tryLock()相当于tryLock(0L,Long.MAX_VALUE,false)
close()方法在内部调用的是release()方法
acquiredBy()返回当前锁所属的FileChannel
overlaps(long position,long size)判断此锁定是否与给定的锁定区域重叠
(15)force(boolean metaData)强制将所有对通道文件的更新写入包含文件的存储设备(强制将内核缓冲区的数据,刷新到磁盘中)
这个方法会严重影响性能
(16)MappedByteBuffer map(FileChannel.MapMode mode,long position,long size)将此通道的文件区域直接映射到内存中
三种模式:MapMode.READ_ONLY(只读模式);MapMode.READ_WRITE(读写模式);MapMode.PRIVATE(专用模式)对缓冲区的更改不会传播到文件;
关闭通道对映射关系的有效性没有任何的影响
如果需要map映射那么文件的大小至少要上了(MB或者GB)的级别;太小的内存映射用普通的方法反而性能更好。
MappedByteBuffer 通过fileChannel.map()获得
force()方法,把map缓冲区的内容强制刷新到磁盘中
load()方法将此缓冲区的内容加载到物理内存
isLoaded()方法,判断此缓冲区的内容是否为与物理内存中
(16)open(Path path,OpenOptions ... options)打开一个文件
path可以通过File类的toPath()方法进行获取
StandardOpenOption(CREATE,WRITE,APPEND,READ,TRUNCATE_EXISTING,CREATE_NEW,DELETE_ON_CLOSE,SPARSE,SYNC,DSYNC)
isOpen()判断当前通道是否处于打开的状态
三、socket(套接字)
1、ServerSocket的accept()的方法时阻塞的
2、socket中的inputstream的read方法也具有阻塞性
3、
4、readFully
5、调用stream的close()方法,造成socket关闭。
6、三次握手建立的时机
7、serverSocket类的应用
①接收accept和Timeout(accept将只阻塞timeout的时间长度)
②构造方法的backlog的参数意义,这个参数的默认值为50
③ServerSocket(int port,int backlog,InetAddress bindAddr)这个方法用于一个主机有多块网卡或者多个IP地址的情况。如果bindAddr为null,则默认接受任何/所有本地地址上的链接。
④InetAddress类代表IP地址;InetSocketAddress类代表socket地址
他们之间的关系:InetSocketAddress(InetAddress addr,int port)
⑤两种创建ServerSocket的方法
第一种:
ServerSocket serverSocket = new ServerSocket(8080);
第二种:
ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(8080));
⑥getLocalSocketAddress()和getLocalPort()方法
⑦getHostName()(这个是获取域名)和getHostString()(这个是获取IP地址String)方法的区别
⑧获取InetAddress地址对象(public final InetAddress getAddress())
⑨public final boolean isUnresolved()是否不能对域名进行解析
⑩
⑪public boolean isBound()查看socket的绑定状态
⑫serverSocket.getInetAddress()获取IP地址
⑬Socket选项ReuseAddress
服务端端口不能被复用
服务端端口可以被复用
客户端端口不能被复用
客户端端口可以被复用
MSL是报文在网络上生存的最大时间
⑭Socket选项ReceiveBuffersize
8、Socket类的使用
①连接与超时
public void connect(SocketAddress endpoint,int timeout)连接到服务端并设置一个超时值
②获得远程端口与本地端口
public int getPort()和public int getLocalPort()
③获取本地地址getLocalAddress和getLocalSocketAddress
④获取远程地址getInetAddress和getRemoteSocketAddress
⑤套接字状态的判断
isBound()、isConnected()、isClosed()、
⑥开启半读半写状态
shutdownInput和shutdownOutput
isInputShutdown和isOutputShutdown
⑦Socket的TcpNoDelay(Nagle算法)
⑧Socket选项SendBufferSize
设置发送缓冲区的大小
⑨Socket选项Linger
用来控制socket的close()方法时的行为
⑩Socket选项timeout
与此Socket关联的InputStream的read只阻塞此时间长度
⑪Socket选项OOBInline(关于紧急数据)
⑫Socket选项KeepAlive
⑬Socket选项TrafficClass
9、客户端连接服务端的两种方法
第一种:
Socket socket = new Socket("127.0.0.1",8080);
第二种:
10、基于UDP的Socket通信
四、IO多路复用
1、Selector
他是SelectableChannel对象的多路复用器
2、SectionKey
3、SelectableChannel
SelectableChannel 在多线程并发环境下是安全的。
向选择器注册某个通道前, 必须将该通道置于非阻塞模式, 并且在注销之前可能无法返回到阻塞模式。
在将通道注册到选择器之前,必须将通道设置成非阻塞模式
public final SelectionKey keyFor(Selector sel)
register(Selector sel,int ops)
(1)子类DatagramChannel
(2)子类ServerSocketChannel
(3)子类SocketChannel
4、AbstractlnterruptibleChannel
4、SocketChannel
2、ServerSocketChannel
3、DatagramChannel
五、SocketChannel的注意点
1、close方法(two-step处理:preclose+close)
protected void implCloseSelectableChannel() throws ClosedChannelException { /* 828 */ synchronized (this.stateLock) { /* 829 */ this.isInputOpen = false; /* 830 */ this.isOutputOpen = false; /* */ /* 837 */ if (this.state != 4) { /* 838 */ nd.preClose(this.fd); /* */ } /* */ /* 844 */ if (this.readerThread != 0L) { /* 845 */ NativeThread.signal(this.readerThread); /* */ } /* 847 */ if (this.writerThread != 0L) { /* 848 */ NativeThread.signal(this.writerThread); /* */ } /* */ /* 860 */ if (!isRegistered()) /* 861 */ kill(); /* */ } /* */ }
java的nio的方法:
一、Path
1、resolve 拼接path的接口
2、getFileName返回此路径作为路径对象表示的文件或目录的名称(即返回文件名或者目录名)
二、Files
1、isDirectory
2、exists
3、newDirectoryStream 扫描某个目录下的文件,正则表达式为过滤条件
4、isHidden
三、Paths
四、FileSystem
五、FileSystemProvider
六、Charset.forName("UTF-8")
七、DirectoryStream目录流
Path dir = ... try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { for (Path entry: stream) { ... } }
1、Filter这是一个接口,如果实现的话需要实现它的accept方法,作为文件过滤用的
八、LinkOption检查链接文件的选项
参考文献:
JAVA NIO技术详解:https://www.jianshu.com/p/713af3a13bde
JAVA transferTo:https://www.cnblogs.com/Jack-Blog/p/12078767.html
JAVA mmap和直接内存:https://blog.csdn.net/a724888/article/details/74779324
零拷贝技术详解:https://www.cnblogs.com/yibutian/p/9482640.html
Direct IO:https://www.ibm.com/developerworks/cn/linux/l-cn-directio/
JAVA IO:https://blog.csdn.net/u013076044/article/details/78306800