Java Nio 系列
Java Nio 之Buffer
Java Nio 之直接内存
Java Nio 之高级搬砖工(FileChannel) 一

前言

 大家对搬砖都很熟悉吧;小绿和小蓝是搬砖工,小绿比小蓝早一点开始搬砖,小绿搬砖的方式:一块砖一块砖搬,一直勤勤恳恳;小蓝的性格比较懒,一开始和小绿一起搬砖的方式 一样,但是发现这样搬砖很累,于是小蓝就想着怎么减轻自己的活然后又能干多一点,于是想到了一个好方法:将砖放到小推车里然后运输到目的地,这样小蓝干多的活不仅多了,看起来也不是那么累了。
这里的小推车就是我们的缓冲区而小蓝我们的高级搬砖工就是今天的主题FileChannel

概念

注释原文 :A channel for reading, writing, mapping, and manipulating a file.
翻译:用于读、写、映射、操作一个文件的通道。
相比java io 的读和写以及操作文件,它多了一个功能 就是映射,什么是映射呢,我们下一个专题再说。

详细的概念

 FileChannel 是一个连接一个文件的字节通道,它可以提供position() 来查询当前读或写的位置以及position(long)来设置当前读或者写的位置。文件自身包含了可读可写的可变长度字节序列,当前的字节序列长度可以通过size() 方法 获取。当写的字节数超过当前大小时 ,文件的size 增加;当调用 truncate() 方法时,文件的大小减小。文件还可能具有一些相关的元数据,如访问权限、内容类型和最后修改时间;该类不定义用于元数据访问的方法。

如何生成FileChannel

FileChannel##open(Path path, OpenOption… options)

Path 是一个可以定位某个文件在文件系统中位置的对象,依赖于文件系统。Path 类是从JDK7开始有的,大家可以去了解下,这里就不做过多解释。
OpenOption 表示 如何打开或者创建一个文件,这是一个接口我们来看看它的主要的实现枚举StandardOpenOption:代码清单4-1:

    //只读,与WRITE 和Append 不能在一起使用,否则会抛出异常READ,// 打开后可写,从文件开始位置写,可能会覆盖文件中以前的数据WRITE,// 从文件结尾追加写APPEND,// 当和WRITE 选项并存的时候,该文件的长度将被清为零,与READ选项并存的 时候该选项将被忽略TRUNCATE_EXISTING,// 创建一个不存在的文件//与CREATE_NEW 并存时 该选项配置将被忽略CREATE,//创建新的文件,文件存在则失败//相对于其他系统操作检查文件存在和创建文件是原子性的CREATE_NEW

上面有三个 枚举没有列举出来这里用不到。
演示一波 代码清单4-2:

public class FileChannelOpenStudy {public static final String JAVA_NIO = "java NIO";static String createAndWriteAndReadPath = "createAndWriteAndReadPath.txt";public static void main(String[] args) {FileChannel readChannel = null;FileChannel createAndWriteChannel = null;FileChannel appendChannel = null;
//        FileChannel dataSyncChannel = null;try{//create and writecreateAndWriteChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.CREATE, StandardOpenOption.WRITE);ByteBuffer writeBuffer = ByteBuffer.allocate(6);writeBuffer.put("hello,".getBytes(Charset.forName("UTF-8")));//切换 读写模式writeBuffer.flip();int writed1 = createAndWriteChannel.write(writeBuffer);System.out.println("createAndWriteChannel write in " + writed1 + " bytes");readChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.READ);ByteBuffer readBuffer = ByteBuffer.allocate(writed1);int readed1 = readChannel.read(readBuffer);readBuffer.flip();System.out.println("readChannel read " + readed1 + " bytes:" + new String(readBytesFrromBuffer(readBuffer)));createAndWriteChannel.close();appendChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.APPEND);ByteBuffer appendBuffer = ByteBuffer.allocate(JAVA_NIO.length());appendBuffer.put(JAVA_NIO.getBytes("UTF-8"));appendBuffer.flip();int writed2 = appendChannel.write(appendBuffer);System.out.println("appendChannel writed in " + writed2 + " bytes");ByteBuffer readBuffer1 = ByteBuffer.allocate(writed2);int readed2 = readChannel.read(readBuffer1, readed1);readBuffer1.flip();System.out.println("readChannel readed " + readed2 + " bytes:" +new String(readBytesFrromBuffer(readBuffer1)));appendChannel.close();} catch (IOException e) {e.printStackTrace();} finally {}}private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {byte[] result = new byte[byteBuffer.limit()];byteBuffer.get(result);return result;}
}

结果如下:

createAndWriteChannel write in 6 bytes
readChannel read 6 bytes:hello,
appendChannel writed in 8 bytes
readChannel readed 8 bytes:java NIO

FileInputStream##getChannel()

调用FileInputStream 的getChannel 返回一个与这个文件输入流关联的唯一的文件通道,记住是唯一的,如果已创建与之关联的文件通道则直接返回已创建的。
该方法返回的FileChannel 是只读的,调用read会抛出异常

来个demo 极度舒适一下

public class FileInputStreamGetChannel {static String filePath = "F:\\idea_two\\demo\\createAndWriteAndReadPath.txt";public static void main(String[] args) {try {FileInputStream fileInputStream = new FileInputStream(filePath);FileChannel channel = fileInputStream.getChannel();ByteBuffer readBuffer = ByteBuffer.allocate((int) channel.size());int readed = channel.read(readBuffer);readBuffer.flip();System.out.println(" fileInputStream get Channel read from " + readed + " bytes:" +new String(readBytesFrromBuffer(readBuffer)));//这里关闭的时候会先判断文件通道的parent 属性是否为空,如果不为空则会调用parent属性的关闭,这里的parent 就是FileInputStream对象channel.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {}}private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {byte[] result = new byte[byteBuffer.limit()];byteBuffer.get(result);return result;}
}

看看结果再来极度舒适一下

fileInputStream get Channel read from 14 bytes:hello,java NIO

相对于普通文件读写的一些特殊方法

read 方法 重载系列

参数 返回值类型及说明 说明
ByteBuffer dst int 返回读取的字节数 从文件的当前位置读取 一系列字节到给定的
buffer中,然后根据读的字节数更新下文件
的当前位置
ByteBuffer[] dsts,
int offset,
int length
long 返回读取的字节数 从文件读取字节到给定的字节缓冲区数组里,
offset 指的是字节写到缓冲区数组中第一个缓冲区的
偏移量,不能为负,且不大于dsts.length;qilength指的
是从offset 开始能 写几个缓冲区,不能为负且不能大于dsts.length-offset
ByteBuffer[] dsts long 返回读取的字节数 从该通道读取字节到给定的字节缓冲区数组里

撸一 撸 代码

代码清单5-1

public class FileChannelReadTest {//该文件内容自己脑补static String testFilePath = "testFileChannelReadTest.txt";public static void main(String[] args) {Path path = Paths.get(testFilePath);try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {//readByteBuffer read1 = ByteBuffer.allocate(4);int readed1 = fileChannel.read(read1, 0);System.out.println("read ByteBuffer " + readed1 + " bytes,內容:" + new String(read1.array()));ByteBuffer[] byteBuffersRead = new ByteBuffer[3];IntStream.rangeClosed(0, 2).forEach(i -> {byteBuffersRead[i] = ByteBuffer.allocate(5);});System.out.println("alfter read fileChannel position:" + fileChannel.position());//很关键 要不然又从头开始读,如下面的结果AfileChannel.position(readed1);//scatter read 分散读取 从channnel 中分散地读取到buffer数组中long readed2 = fileChannel.read(byteBuffersRead, 0, byteBuffersRead.length);IntStream.rangeClosed(0, 2).forEach(i -> {byteBuffersRead[i].flip();});System.out.println(String.format("read ByteBuffers %d bytes,content:%s\n%s\n%s",readed2,new String(readBytesFrromBuffer(byteBuffersRead[0])),new String(readBytesFrromBuffer(byteBuffersRead[1])),new String(readBytesFrromBuffer(byteBuffersRead[2]))));} catch (IOException e) {}}private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {byte[] result = new byte[byteBuffer.limit()];byteBuffer.get(result);return result;}
}

结果A(错误结果)

read ByteBuffer 4 bytes,內容:hell
read ByteBuffers 15 bytes,content:helln
iha0n
iha1n

结果C(正确结果)

read ByteBuffer 4 bytes,內容:hell
alfter read fileChannel position:0
read ByteBuffers 15 bytes,content:niha0
niha1
niha2

write 方法重载系列

参数 返回值类型及说明 说明
ByteBuffer src int 写入的字节数,可能为0 从给定缓冲区里读取数据向通道中写
ByteBuffer[] srcs,
int offset,
int length
long 写入的字节数,可能为零 从给定的缓冲区数组读取数据向通道中
写;offset 指的是从缓冲区数组中读的第
一个缓冲区的下标,不能为负,且不大于
dsts.length;length 指的是从offset 起
能读的几个缓冲区,不能为负不能大于dsts.length-offset
ByteBuffer[] srcs long 写入的字节数,可能为零 从给定缓冲区数组向该通道里写

秀一波操作

代码清单5-2

public class FileChannelWriteTest {static String testFilePath = "testFileChannelReadTest.txt";public static void main(String[] args) {Path path = Paths.get(testFilePath);//使用FileChannel #open 方法 创建 FileChannel 比较推荐这种try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {ByteBuffer write1 = ByteBuffer.allocate(8);write1.put("hell".getBytes(Charset.forName("UTF-8")));//缓冲区切换读写模式 详见第一节内容write1.flip();//写入int writed1 = fileChannel.write(write1);System.out.println("write  ByteBuffer " + writed1 + " bytes");ByteBuffer[] byteBuffers = new ByteBuffer[3];IntStream.rangeClosed(0, 2).forEach(i -> {byteBuffers[i] = ByteBuffer.allocate(8);byteBuffers[i].put(("niha" + i).getBytes());//切换读写模式 一定别忘了byteBuffers[i].flip();});//聚合写入 将多个缓冲写入到一个通道里long writed2 = fileChannel.write(byteBuffers, 0, byteBuffers.length);System.out.println(" write ByteBuffers "+writed2 +" bytes");} catch (IOException e) {}}}

撸的结果:

write  ByteBuffer 4 bytes
write ByteBuffers 15 bytes

force(boolean metaData)

 强制此通道文件的任何更新写入到包含它的存储设备。我们调用上面的write 方法只是将数据写入到 系统缓存中,然后后由pdflush线程异步刷新到硬盘上 ,当然是根据过期脏页的内存占用工作内存的百分比、脏页占工作内存的百分比决定是否开启pdflush线程。调用该方法可以确保我们写入的数据保存到磁盘上,这样不会再突然断电的时候丢失关键的信息。
 该方法包含一个参数metaData 布尔型,表示是否将文件的元数据信息一起刷新到磁盘,文件的元数据信息包括文件的访问权限、文件的最后更新时间等,与操作系统有关;如果metaData 为true 则会多一个io 操作,可以通过设置该值来限制io 操作的数量。
用法: 每次调用write 完就调用该方法则会降低应用的响应时间以及吞吐率

  • 如果写入的数据不希望在断电的情况丢失,则可以每次写每次force 一下,牺牲性能保证写入数据的完整性。
  • 如果对写入的数据在断电时可以丢失极小部分数据,可以采用异步刷盘的策略,有个定时任务定时调用force 而保证数据只会在断电时候丢失几秒的数据,这在许多应用中都是可以接受的

话不多说先撸个代码为敬

及时写及时刷 演示代码: 代码清单5-3

public class FileChannelForceInTime {static String testFilePath = "testFileChannelForceInTime.txt";static byte[] imageBytes;final static String PICTUREE_PATH = "meimei.jpg";static {try {imageBytes = Files.readAllBytes(Paths.get(PICTUREE_PATH));} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args)  {try  {Path path = Paths.get(testFilePath);FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND);//每次写都调用forceByteBuffer imageBytesBuffer = ByteBuffer.allocateDirect(imageBytes.length);imageBytesBuffer.put(imageBytes);long now = System.currentTimeMillis();for (int i = 0; i <= 9; i++) {imageBytesBuffer.flip();fileChannel.write(imageBytesBuffer);fileChannel.force(false);//这里为了和异步刷盘的例子保持一致,因为异步刷盘有个定时任务所以 可能时间需要长点,所以这里模拟一下Thread.sleep( 1000);}System.out.println("write in time force cost " + ((System.currentTimeMillis() - now)-10 * 1000) + "ms");fileChannel.close();} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}

输出结果

write in time force cost 922ms

定时任务异步刷盘 代码演示:代码清单5-4

public class FileChannelTimelyForce {static String testFilePath = "testFileChannelTimelyForce.txt";static byte[] imageBytes;final static String PICTUREE_PATH = "meimei.jpg";static {try {imageBytes = Files.readAllBytes(Paths.get(PICTUREE_PATH));} catch (IOException e) {e.printStackTrace();}}private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);public static void main(String[] args) {try {Path path = Paths.get(testFilePath);;FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND);executorService.scheduleAtFixedRate(() -> {try {if (fileChannel.isOpen()) {fileChannel.force(false);}} catch (IOException e) {}}, 1, 1, TimeUnit.SECONDS);long now = System.currentTimeMillis();ByteBuffer imageBytesBuffer = ByteBuffer.allocateDirect(imageBytes.length);imageBytesBuffer.put(imageBytes);for (int i = 0; i <= 9; i++) {imageBytesBuffer.flip();fileChannel.write(imageBytesBuffer);//异步刷盘有s个定时任务所以 可能时间需要长点,所以这里模拟一下Thread.sleep(1000);}System.out.println("fileChannnel write timely foce cost " + (System.currentTimeMillis() - now - 10 * 1000) + "ms");fileChannel.close();} catch (IOException e) {} catch (InterruptedException e) {}finally {executorService.shutdown();}}
}

运行结果如下:

fileChannnel write timely foce cost 127ms

两种用法的耗时也是非常明显的,可以根据大家的场景去使用,当然有更好的方法大家也可以在下面评论下,共同进步。

后记

下一节我还会讨论FileChannel,会涉及到该类的一些高级用法以及一些底层原理,还有Kafka消息中间件是怎么用的,可能需要一段时间,麻烦大家给博主我一个支持,点个赞加个关注先。顺便吐槽下又要开始找工作了,公司发不起工资了,衰。
Java Nio 系列
Java Nio 之Buffer
Java Nio 之直接内存
Java Nio 之高级搬砖工(FileChannel) 一

前言

 大家对搬砖都很熟悉吧;小绿和小蓝是搬砖工,小绿比小蓝早一点开始搬砖,小绿搬砖的方式:一块砖一块砖搬,一直勤勤恳恳;小蓝的性格比较懒,一开始和小绿一起搬砖的方式 一样,但是发现这样搬砖很累,于是小蓝就想着怎么减轻自己的活然后又能干多一点,于是想到了一个好方法:将砖放到小推车里然后运输到目的地,这样小蓝干多的活不仅多了,看起来也不是那么累了。
这里的小推车就是我们的缓冲区而小蓝我们的高级搬砖工就是今天的主题FileChannel

概念

注释原文 :A channel for reading, writing, mapping, and manipulating a file.
翻译:用于读、写、映射、操作一个文件的通道。
相比java io 的读和写以及操作文件,它多了一个功能 就是映射,什么是映射呢,我们下一个专题再说。

详细的概念

 FileChannel 是一个连接一个文件的字节通道,它可以提供position() 来查询当前读或写的位置以及position(long)来设置当前读或者写的位置。文件自身包含了可读可写的可变长度字节序列,当前的字节序列长度可以通过size() 方法 获取。当写的字节数超过当前大小时 ,文件的size 增加;当调用 truncate() 方法时,文件的大小减小。文件还可能具有一些相关的元数据,如访问权限、内容类型和最后修改时间;该类不定义用于元数据访问的方法。

如何生成FileChannel

FileChannel##open(Path path, OpenOption… options)

Path 是一个可以定位某个文件在文件系统中位置的对象,依赖于文件系统。Path 类是从JDK7开始有的,大家可以去了解下,这里就不做过多解释。
OpenOption 表示 如何打开或者创建一个文件,这是一个接口我们来看看它的主要的实现枚举StandardOpenOption:代码清单4-1:

    //只读,与WRITE 和Append 不能在一起使用,否则会抛出异常READ,// 打开后可写,从文件开始位置写,可能会覆盖文件中以前的数据WRITE,// 从文件结尾追加写APPEND,// 当和WRITE 选项并存的时候,该文件的长度将被清为零,与READ选项并存的 时候该选项将被忽略TRUNCATE_EXISTING,// 创建一个不存在的文件//与CREATE_NEW 并存时 该选项配置将被忽略CREATE,//创建新的文件,文件存在则失败//相对于其他系统操作检查文件存在和创建文件是原子性的CREATE_NEW

上面有三个 枚举没有列举出来这里用不到。
演示一波 代码清单4-2:

public class FileChannelOpenStudy {public static final String JAVA_NIO = "java NIO";static String createAndWriteAndReadPath = "createAndWriteAndReadPath.txt";public static void main(String[] args) {FileChannel readChannel = null;FileChannel createAndWriteChannel = null;FileChannel appendChannel = null;
//        FileChannel dataSyncChannel = null;try{//create and writecreateAndWriteChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.CREATE, StandardOpenOption.WRITE);ByteBuffer writeBuffer = ByteBuffer.allocate(6);writeBuffer.put("hello,".getBytes(Charset.forName("UTF-8")));//切换 读写模式writeBuffer.flip();int writed1 = createAndWriteChannel.write(writeBuffer);System.out.println("createAndWriteChannel write in " + writed1 + " bytes");readChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.READ);ByteBuffer readBuffer = ByteBuffer.allocate(writed1);int readed1 = readChannel.read(readBuffer);readBuffer.flip();System.out.println("readChannel read " + readed1 + " bytes:" + new String(readBytesFrromBuffer(readBuffer)));createAndWriteChannel.close();appendChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.APPEND);ByteBuffer appendBuffer = ByteBuffer.allocate(JAVA_NIO.length());appendBuffer.put(JAVA_NIO.getBytes("UTF-8"));appendBuffer.flip();int writed2 = appendChannel.write(appendBuffer);System.out.println("appendChannel writed in " + writed2 + " bytes");ByteBuffer readBuffer1 = ByteBuffer.allocate(writed2);int readed2 = readChannel.read(readBuffer1, readed1);readBuffer1.flip();System.out.println("readChannel readed " + readed2 + " bytes:" +new String(readBytesFrromBuffer(readBuffer1)));appendChannel.close();} catch (IOException e) {e.printStackTrace();} finally {}}private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {byte[] result = new byte[byteBuffer.limit()];byteBuffer.get(result);return result;}
}

结果如下:

createAndWriteChannel write in 6 bytes
readChannel read 6 bytes:hello,
appendChannel writed in 8 bytes
readChannel readed 8 bytes:java NIO

FileInputStream##getChannel()

调用FileInputStream 的getChannel 返回一个与这个文件输入流关联的唯一的文件通道,记住是唯一的,如果已创建与之关联的文件通道则直接返回已创建的。
该方法返回的FileChannel 是只读的,调用read会抛出异常

来个demo 极度舒适一下

public class FileInputStreamGetChannel {static String filePath = "F:\\idea_two\\demo\\createAndWriteAndReadPath.txt";public static void main(String[] args) {try {FileInputStream fileInputStream = new FileInputStream(filePath);FileChannel channel = fileInputStream.getChannel();ByteBuffer readBuffer = ByteBuffer.allocate((int) channel.size());int readed = channel.read(readBuffer);readBuffer.flip();System.out.println(" fileInputStream get Channel read from " + readed + " bytes:" +new String(readBytesFrromBuffer(readBuffer)));//这里关闭的时候会先判断文件通道的parent 属性是否为空,如果不为空则会调用parent属性的关闭,这里的parent 就是FileInputStream对象channel.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {}}private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {byte[] result = new byte[byteBuffer.limit()];byteBuffer.get(result);return result;}
}

看看结果再来极度舒适一下

fileInputStream get Channel read from 14 bytes:hello,java NIO

相对于普通文件读写的一些特殊方法

read 方法 重载系列

参数 返回值类型及说明 说明
ByteBuffer dst int 返回读取的字节数 从文件的当前位置读取 一系列字节到给定的
buffer中,然后根据读的字节数更新下文件
的当前位置
ByteBuffer[] dsts,
int offset,
int length
long 返回读取的字节数 从文件读取字节到给定的字节缓冲区数组里,
offset 指的是字节写到缓冲区数组中第一个缓冲区的
偏移量,不能为负,且不大于dsts.length;qilength指的
是从offset 开始能 写几个缓冲区,不能为负且不能大于dsts.length-offset
ByteBuffer[] dsts long 返回读取的字节数 从该通道读取字节到给定的字节缓冲区数组里

撸一 撸 代码

代码清单5-1

public class FileChannelReadTest {//该文件内容自己脑补static String testFilePath = "testFileChannelReadTest.txt";public static void main(String[] args) {Path path = Paths.get(testFilePath);try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {//readByteBuffer read1 = ByteBuffer.allocate(4);int readed1 = fileChannel.read(read1, 0);System.out.println("read ByteBuffer " + readed1 + " bytes,內容:" + new String(read1.array()));ByteBuffer[] byteBuffersRead = new ByteBuffer[3];IntStream.rangeClosed(0, 2).forEach(i -> {byteBuffersRead[i] = ByteBuffer.allocate(5);});System.out.println("alfter read fileChannel position:" + fileChannel.position());//很关键 要不然又从头开始读,如下面的结果AfileChannel.position(readed1);//scatter read 分散读取 从channnel 中分散地读取到buffer数组中long readed2 = fileChannel.read(byteBuffersRead, 0, byteBuffersRead.length);IntStream.rangeClosed(0, 2).forEach(i -> {byteBuffersRead[i].flip();});System.out.println(String.format("read ByteBuffers %d bytes,content:%s\n%s\n%s",readed2,new String(readBytesFrromBuffer(byteBuffersRead[0])),new String(readBytesFrromBuffer(byteBuffersRead[1])),new String(readBytesFrromBuffer(byteBuffersRead[2]))));} catch (IOException e) {}}private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {byte[] result = new byte[byteBuffer.limit()];byteBuffer.get(result);return result;}
}

结果A(错误结果)

read ByteBuffer 4 bytes,內容:hell
read ByteBuffers 15 bytes,content:helln
iha0n
iha1n

结果C(正确结果)

read ByteBuffer 4 bytes,內容:hell
alfter read fileChannel position:0
read ByteBuffers 15 bytes,content:niha0
niha1
niha2

write 方法重载系列

参数 返回值类型及说明 说明
ByteBuffer src int 写入的字节数,可能为0 从给定缓冲区里读取数据向通道中写
ByteBuffer[] srcs,
int offset,
int length
long 写入的字节数,可能为零 从给定的缓冲区数组读取数据向通道中
写;offset 指的是从缓冲区数组中读的第
一个缓冲区的下标,不能为负,且不大于
dsts.length;length 指的是从offset 起
能读的几个缓冲区,不能为负不能大于dsts.length-offset
ByteBuffer[] srcs long 写入的字节数,可能为零 从给定缓冲区数组向该通道里写

秀一波操作

代码清单5-2

public class FileChannelWriteTest {static String testFilePath = "testFileChannelReadTest.txt";public static void main(String[] args) {Path path = Paths.get(testFilePath);//使用FileChannel #open 方法 创建 FileChannel 比较推荐这种try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {ByteBuffer write1 = ByteBuffer.allocate(8);write1.put("hell".getBytes(Charset.forName("UTF-8")));//缓冲区切换读写模式 详见第一节内容write1.flip();//写入int writed1 = fileChannel.write(write1);System.out.println("write  ByteBuffer " + writed1 + " bytes");ByteBuffer[] byteBuffers = new ByteBuffer[3];IntStream.rangeClosed(0, 2).forEach(i -> {byteBuffers[i] = ByteBuffer.allocate(8);byteBuffers[i].put(("niha" + i).getBytes());//切换读写模式 一定别忘了byteBuffers[i].flip();});//聚合写入 将多个缓冲写入到一个通道里long writed2 = fileChannel.write(byteBuffers, 0, byteBuffers.length);System.out.println(" write ByteBuffers "+writed2 +" bytes");} catch (IOException e) {}}}

撸的结果:

write  ByteBuffer 4 bytes
write ByteBuffers 15 bytes

force(boolean metaData)

 强制此通道文件的任何更新写入到包含它的存储设备。我们调用上面的write 方法只是将数据写入到 系统缓存中,然后后由pdflush线程异步刷新到硬盘上 ,当然是根据过期脏页的内存占用工作内存的百分比、脏页占工作内存的百分比决定是否开启pdflush线程。调用该方法可以确保我们写入的数据保存到磁盘上,这样不会再突然断电的时候丢失关键的信息。
 该方法包含一个参数metaData 布尔型,表示是否将文件的元数据信息一起刷新到磁盘,文件的元数据信息包括文件的访问权限、文件的最后更新时间等,与操作系统有关;如果metaData 为true 则会多一个io 操作,可以通过设置该值来限制io 操作的数量。
用法: 每次调用write 完就调用该方法则会降低应用的响应时间以及吞吐率

  • 如果写入的数据不希望在断电的情况丢失,则可以每次写每次force 一下,牺牲性能保证写入数据的完整性。
  • 如果对写入的数据在断电时可以丢失极小部分数据,可以采用异步刷盘的策略,有个定时任务定时调用force 而保证数据只会在断电时候丢失几秒的数据,这在许多应用中都是可以接受的

话不多说先撸个代码为敬

及时写及时刷 演示代码: 代码清单5-3

public class FileChannelForceInTime {static String testFilePath = "testFileChannelForceInTime.txt";static byte[] imageBytes;final static String PICTUREE_PATH = "meimei.jpg";static {try {imageBytes = Files.readAllBytes(Paths.get(PICTUREE_PATH));} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args)  {try  {Path path = Paths.get(testFilePath);FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND);//每次写都调用forceByteBuffer imageBytesBuffer = ByteBuffer.allocateDirect(imageBytes.length);imageBytesBuffer.put(imageBytes);long now = System.currentTimeMillis();for (int i = 0; i <= 9; i++) {imageBytesBuffer.flip();fileChannel.write(imageBytesBuffer);fileChannel.force(false);//这里为了和异步刷盘的例子保持一致,因为异步刷盘有个定时任务所以 可能时间需要长点,所以这里模拟一下Thread.sleep( 1000);}System.out.println("write in time force cost " + ((System.currentTimeMillis() - now)-10 * 1000) + "ms");fileChannel.close();} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}

输出结果

write in time force cost 922ms

定时任务异步刷盘 代码演示:代码清单5-4

public class FileChannelTimelyForce {static String testFilePath = "testFileChannelTimelyForce.txt";static byte[] imageBytes;final static String PICTUREE_PATH = "meimei.jpg";static {try {imageBytes = Files.readAllBytes(Paths.get(PICTUREE_PATH));} catch (IOException e) {e.printStackTrace();}}private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);public static void main(String[] args) {try {Path path = Paths.get(testFilePath);;FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND);executorService.scheduleAtFixedRate(() -> {try {if (fileChannel.isOpen()) {fileChannel.force(false);}} catch (IOException e) {}}, 1, 1, TimeUnit.SECONDS);long now = System.currentTimeMillis();ByteBuffer imageBytesBuffer = ByteBuffer.allocateDirect(imageBytes.length);imageBytesBuffer.put(imageBytes);for (int i = 0; i <= 9; i++) {imageBytesBuffer.flip();fileChannel.write(imageBytesBuffer);//异步刷盘有s个定时任务所以 可能时间需要长点,所以这里模拟一下Thread.sleep(1000);}System.out.println("fileChannnel write timely foce cost " + (System.currentTimeMillis() - now - 10 * 1000) + "ms");fileChannel.close();} catch (IOException e) {} catch (InterruptedException e) {}finally {executorService.shutdown();}}
}

运行结果如下:

fileChannnel write timely foce cost 127ms

两种用法的耗时也是非常明显的,可以根据大家的场景去使用,当然有更好的方法大家也可以在下面评论下,共同进步。

后记

下一节我还会讨论FileChannel,会涉及到该类的一些高级用法以及一些底层原理,还有Kafka消息中间件是怎么用的,可能需要一段时间,麻烦大家给博主我一个支持,点个赞加个关注先。顺便吐槽下又要开始找工作了,公司发不起工资了,衰。

Java Nio 之高级搬砖工(FileChannel) 一相关推荐

  1. Java Nio 之高级搬砖工(FileChannel)二

    Java Nio 系列 Java Nio 之Buffer Java Nio 之直接内存 Java Nio 之高级搬砖工(FileChannel) 一 Java Nio 之高级搬砖工(FileChann ...

  2. 又到年底了,想知道你在互联网圈混到什么级别了吗?初级搬砖工还是极品精英?...

    又到年底了,想知道你在互联网圈混到什么级别了吗?初级搬砖工还是极品精英? 我测试出来竟然是IT名媛-- 亲们,手机扫码块测测,还有机会获得免费套餐邀请码,就是可以免费开通30余款云产品的那个邀请码哦! ...

  3. 从搬砖工到亿万富豪,这些年他经历了什么?

    在福建,有个人从工地搬砖工做到了拥有亿万财富的老板.而这其中的缘由竟然是因为茶油. 2015年,林龙生转行回老家,接手一个破产的工厂,还承包了8000亩山地.林龙生看中的这块地,其实里面种着一种别人都 ...

  4. 搬砖工php什么意思,醒工砖是什么意思什么梗 醒工砖是醒醒工头喊你起来搬砖的意思...

    醒工砖是什么意思什么梗?醒工砖,网络流行词,原句是"醒醒,工头喊你起来搬砖","醒工砖"是缩写形式.下面就跟360常识网一起具体看看醒工砖等相关内容. 醒工砖词 ...

  5. JAVA多线程终止线程、退出线程、Interrupt()方法、苦逼的搬砖工

    方式一: 设置标志位退出 public class Interrupt1 {public static void main(String[] args) throws InterruptedExcep ...

  6. Java NIO系列教程(七) FileChannel

    Java NIO中的FileChannel是一个连接到文件的通道.可以通过文件通道读写文件. FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下. 打开FileChannel 在使用F ...

  7. 普通二/三本学校程序员(搬砖工)的出路?

    突然很羡慕那些985,211学校的学生,敲门砖是那么的响,机会是那么的多,路是那么的明确. 我上大学以来,努力的路就没停过,先是狂刷ACM,然后自学用cocos2d开发PC课件,然后赶上jsp,疯狂加 ...

  8. 搬砖工php什么意思,我要去搬砖了是什么意思什么梗? 搬砖的几种含义了解一下...

    川北在线核心提示:原标题:我要去搬砖了是什么意思什么梗? 搬砖的几种含义了解一下 网络流行语,是现如今网络时代最常见的一种语言,随着网络使用人数越来越多,网络流行语也变得越来越丰富. 最近,在网上评论 ...

  9. CPU:别再拿我当搬砖工!

    来源 | 编程技术宇宙 责编 | 晋兆雨 封图 | CSDN 下载自视觉中国 数据搬运工 Hi,我是CPU一号车间的阿Q,有段日子没见面了. 还记得上回说到咱们厂里用上了DMA技术(太慢不能忍!CPU ...

最新文章

  1. python在日常工作处理中的应用-Python在工作中的应用
  2. Linux运维之常见命令
  3. mysql目录下没有配置文件_MySQL没有my.cnf配置文件如何解决
  4. 深入 Adobe Reader 保护模式 —— 第一部分 —— 设计
  5. P3357 最长k可重线段集问题(网络流/串联/拆点)
  6. C++中读取文件乱码问题
  7. Linux系统文件误删除恢复方法;宿主机windows与Linux文件共享!
  8. Crackme015
  9. 黑马vue实战项目-(一)项目初始化登录功能开发
  10. nagios监控php使用情况,给nagios增加监控当前php进程数的插件,并用pnp出图
  11. MEMS传感器工作原理总结
  12. input标签 各属性解释
  13. 都在说CI/CD,到底什么是CI/CD
  14. 豚鼠学习HTML前端第一周
  15. 关于yum repo-pkgs命令使用测试
  16. opencv图像合成
  17. LOB类型的学习、总结
  18. 非常适合新手的一个Python爬虫项目: 打造一个英文词汇量测试脚本!
  19. 如何利用开关量信号传输装置实现工厂智能化技改?
  20. 计算机日期函数公式大全,常用的Excel日期函数大全

热门文章

  1. 勤哲软件登录后未响应/卡死的一种情况
  2. 未经专业销密的计算机设备,对计算机硬盘、U盘等存储部件进行删除或格式化处理,只是对__进行了删除操作,对于__并没有做任 - 普法考试题库问答...
  3. 代码里的世界观——通往架构师之路
  4. 知乎 运动规划和路径规划_运动路径–过去,现在和未来
  5. unity3d鼠标点击,获取世界坐标
  6. 传说中的小品:手机时代
  7. JDBC核心技术六(数据库连接池)
  8. 【Python基础进阶笔记】函数调用数据探索
  9. python自动化报表-Python报表自动化
  10. Android系统目录结构详解