不学无数——JAVA中NIO再深入

简介: JAVA中NIO再深入在上一章节的JAVA中的I/O和NIO我们学习了如何使用NIO,接下来再深入了解一下关于NIO的知识。缓冲器内部的细节Buffer由数据和可以高效地访问及操作这些数据的四个索引组成。

JAVA中NIO再深入

在上一章节的JAVA中的I/O和NIO我们学习了如何使用NIO,接下来再深入了解一下关于NIO的知识。

缓冲器内部的细节

Buffer数据可以高效地访问及操作这些数据的四个索引组成。这四个索引是

  • mark:标记,就像游戏中设置了一个存档一样,可以调用reset()方法进行回归到mark标记的地方。
  • position:位置,其实缓冲器实际上就是一个美化过的数组,从通道中读取数据就是放到了底层的数组。所以其实就像索引一样。所以positon变量跟踪已经写了多少数据。
  • limit:界限,即表明还有多少数据需要取出,或者还有多少空间能够写入。
  • capacity:容量,表明缓冲器中可以存储的最大容量。

在缓冲器中每一个读写操作都会改变缓冲器的状态,用于反应所发生的变化。通过记录和跟踪这些变化,缓冲器就能够内部地管理自己的资源。下面是用于设置和复位索引以及查询其索引值的方法

方法名 解释
capacity() 返回缓冲器的容量
clear() 清空缓冲器,将position设置为0,limit设置容量。调用此方法复写缓冲器
flip() 将limit设置为position,position设置为0.此方法用于准备从缓冲区读取已经写入的数据
limit() 返回limit值
limit(int lim) 设置limit值
mark() 将mark设置为positon
position() 返回position的值
position(int pos) 设置postion的值
remaining() 返回limit-position的值

接下来我们写个例子模拟这四个索引的变化情况,例如有一个字符串BuXueWuShu。我们交换相邻的字符。

    private static void symmetricScranble(CharBuffer buffer){
        while (buffer.hasRemaining()){
            buffer.mark();
            char c1 = buffer.get();
            char c2 = buffer.get();
            buffer.reset();
            buffer.put(c2).put(c1);
        }
    }

    public static void main(String[] args) {
        char [] data = "BuXueWuShu".toCharArray();
        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length*2);
        CharBuffer charBuffer = byteBuffer.asCharBuffer();
        charBuffer.put(data);
        System.out.println(charBuffer.rewind());
        symmetricScranble(charBuffer);
        System.out.println(charBuffer.rewind());
        symmetricScranble(charBuffer);
        System.out.println(charBuffer.rewind());
    }

rewind()方法是将position设为0 ,mark设为-1

在刚进入symmetricScranble ()方法时的各个索引如下图所示

img_9a9615573de95eebe013ccbb5a01efc9.png
1

然后第一次调用了mark()方法以后就相当于给mark赋值了,相当于在此设置了一个回档点。此时索引如下所示

img_de36fffbf948cbdae1af95c8d27c7a4c.jpe
2

然后每次调用get()方法Position索引都会改变,在第一次调用了两次get()方法以后,各个索引如下

img_ac73fe545757a67da87a4fcfd446b970.jpe
3

然后调用了reset()方法另Position=Mark,此时的索引如下

img_de36fffbf948cbdae1af95c8d27c7a4c.jpe
4

然后每次调用put()方法也会改变Position索引的值,

img_f9a5b68383db84a45174318eb184b57a.jpe
5

注意此时前两个字符已经互换了位置。然后在第二轮while开始再次改变了Mark索引的值,各个索引如下

img_8cb1d1ec142e8504def665eb97aca951.jpe
6

此时我们应该就知道前面我们说的调用clear()方法并不会清除缓冲器里面的数据的原因了,因为只是将其索引变了而已。

内存映射文件

内存映射文件不是Java引入的概念,而是操作系统提供的一种功能,大部分操作系统都支持。

内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且可以完全将其视为非常大的数组进行访问。所以对于文件的操作就变为了对于内存中的字节数组的操作,然后对于字节数组的操作会映射到文件中。这种映射可以映射整个文件,也可以只映射文件中的一部分。什么时候字节数组中的的操作会映射到文件上呢?这是由操作系统内部决定的。

内存放不下整个文件也不要紧,操作系统会自动进行处理,将需要的内容读到内存,将修改的内容保存到硬盘,将不再使用的内存释放。

如何用NIO将文件映射到内存中呢?下面有个小例子表示将文件的前1024个字节映射到内存中。

FileChannel fileChannel = new FileInputStream("").getChannel();
MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

创建一个内存映射文件只需要在通道中调用map()方法即可,MapMode有以下三个参数

  • READ_ONLY:创建一个只读的映射文件
  • READ_WRITE:创建一个既能读也能写的映射文件
  • PRIVATE:创建一个写时拷贝(copy-on-write)的映射文件

我们可以简单的对比一下用内存映射文件对文件进行读写操作和用缓存Buffer对文件进行读写操作的速度比较。

  public static void main(String[] args) throws IOException {
        String fileName="/Users/hupengfei/Downloads/a.sql";
        long t1=System.currentTimeMillis();
        FileChannel fileChannel = new RandomAccessFile(fileName,"rw").getChannel();
        IntBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size()).asIntBuffer();
        map.put(0);
        for (int i = 1; i < 50590; i++) {
            map.put(map.get(i-1));
        }
        fileChannel.close();
        long t=System.currentTimeMillis()-t1;
        System.out.println("Mapped Read/Write:"+t);

        long t2=System.currentTimeMillis();
        RandomAccessFile randomAccessFile = new RandomAccessFile(new File(fileName),"rw");
        randomAccessFile.writeInt(1);
        for (int i = 0 ; i<50590;i++){
            randomAccessFile.seek(randomAccessFile.length()-4);
            randomAccessFile.writeInt(randomAccessFile.readInt());
        }
        randomAccessFile.close();
        long t22=System.currentTimeMillis()-t2;
        System.out.println("Stream Read/Write:"+t22);
    }

发现打印如下

Mapped Read/Write:29
Stream Read/Write:2439

文件越大,那么这个差异会更明显。

文件加锁

在JDK1.4中引入了文件加锁的机制,它允许我们同步的访问某个作为共享资源的文件。对于同一文件竞争的两个线程可能是来自于不同的操作系统,也可能是不同的进程,也可能是相同的进程,例如Java中两个线程对于文件的竞争。文件锁对于其他的操作系统的进程是可见的,因为文件加锁是直接映射到了本地操作系统的加锁工具。

下面举了一个简单的关于文件加锁的例子

    public static void main(String[] args) throws IOException, InterruptedException {
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/hupengfei/Downloads/a.sql");
        FileLock fileLock = fileOutputStream.getChannel().tryLock();
        if (fileLock != null){
            System.out.println("Locked File");
            TimeUnit.MICROSECONDS.sleep(100);
            fileLock.release();
            System.out.println("Released Lock");
        }
        fileLock.close();
    }

通过对FileChannel调用tryLock()或者lock()方法,就可以获得整个文件的FileLock

  • tryLock():是非阻塞的,如果不能获得锁,那么他就会直接从方法调用中返回
  • lock():是阻塞的,它要阻塞进程直到锁可以获得为止

调用FileLock.release()可以释放锁。

当然也可以通过以下的方式对于文件的部分进行上锁

tryLock(long position,long size,boolean shared)
lock(long position,long size,boolean shared)

对于加锁的区域是通过positionsize进行限定的,而第三个参数指定是否为共享锁。无参数的加锁方法会对整个文件进行加锁,甚至文件变大以后也是如此。其中锁的类型是独占锁还是共享锁可以通过FileLock.isShared()进行查询。

参考文章

相关文章
|
4月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
16天前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
20 2
|
2月前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
3月前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
67 2
|
3月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
98 0
|
4月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
147 1
|
3月前
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
63 0
|
4月前
|
安全 Java
【Java】已解决java.nio.channels.OverlappingFileLockException异常
【Java】已解决java.nio.channels.OverlappingFileLockException异常
103 1
|
4月前
|
Java
【Java】已解决java.nio.channels.ClosedChannelException异常
【Java】已解决java.nio.channels.ClosedChannelException异常
320 1
|
4月前
|
Java
【Java】已解决java.nio.channels.FileLockInterruptionException异常
【Java】已解决java.nio.channels.FileLockInterruptionException异常
35 1