java流与文件——内存映射文件

【0】README

0.1) 本文描述转自 core java volume 2, 旨在理解 java流与文件——内存映射文件 的相关知识;
0.2)内存映射文件的目的是: 提高访问速度, 缓冲区Buffer;
0.3) 本文干货源代码均为原创, for source code , please visit https://github.com/pacosonTang/core-java-volume/blob/master/coreJavaAdvanced/chapter1/MemoryMapTest.java


【1】intro to 内存映射文件

1.1)大多数os 都利用 虚拟内存实现来将 一个文件或文件的一部分映射到 内存中;
1.2)java.nio 包使内存映射变得简单, 下面是我们需要做的:

  • 1.2.1)首先,从文件中获得一个通道, 通道是对 磁盘文件的一种抽象, 它使我们可以访问诸如内存映射, 文件加锁机制以及文件间快速数据传递等操作系统特性;(干货——通道的定义,即对磁盘文件的抽象)
    FileChannel channel = FIleChannel.open(path , options);
  • 1.2.2) 然后, 调用FileChannel 类的map方法从这个通道中获得一个 ByteBuffer。 你可以指定想要映射的文件区域与映射模式, 支持的模式有三种(Model):
    • M1)FileChannel.MapMode.READ_ONLY: 所产生的缓冲区是只读的, 任何对该缓冲区写入都会产生异常;
    • M2)FileChannel.MapMode.READ_WRITE:所产生的缓冲区是可写的;任何修改都会在这个时刻写回到文件中;
    • M3)FileChannel.MapMode.PRIVATE: 所产生的缓冲区是可写的, 但是任何修改对这个缓冲区来说都是私有的,不会传播到文件中;

1.3)一旦有了缓冲区,就可以使用 ByteBuffer 类 和 Buffer 超类的方法读写数据了;

  • 1.3.1)缓冲区支持顺序和随机数据访问, 它有一个可以通过get 和 put 操作来移动的位置;如, 像下面这样遍历缓冲区中的所有字节:
    while(buffer.hasRemaining)
    {
    byte b = buffer.get();
    }
  • 1.3.2)或者像下面这样进行随机访问:
    for(i=0;i
CRC32 crc = new CRC32();
while(more bytes)
    crc.update(next byte)
long checksum = crc.getValue();

这里写图片描述


【2】缓冲区数据结构

2.1)在使用内存映射时, 我们创建了单一的缓冲区横跨整个文件或我们感兴趣的文件区域。我们还可以使用更多的缓冲区来读写大小适度的信息块;
2.2)本节将简要介绍Buffer 对象上的基本操作。

  • 2.2.1)缓冲区定义:缓冲区是由具有相同类型的数值构成的数组,Buffer 类是一个抽象类, 它有众多的具体子类,包括ByteBuffer, CharBuffer, DoubleBuffer, IntBuffer, LongBuffer, ShortBuffer ; (干货——缓冲区定义)
    Attention) StringBufffer 类与这些缓冲区没有关系;
  • 2.2.2)最常用的是ByteBuffer 和 CharBuffer; (干货——最常用Buffer的是ByteBuffer 和 CharBuffer)
  • 2.2.3)每个缓冲区都具有: (干货——缓冲区的功能)

    • 2.2.3.1)一个容量: 它永远不能改变;
    • 2.2.3.2)一个读写位置: 下一个值将在此进行读写;
    • 2.2.3.3)一个界限: 超过它进行读写是没有意义的;
    • 2.2.3.4)一个可选标记:用于重复一个读入或写出操作;
    • 2.2.3.5)这些值满足下面的条件: 0 <= 标记<=读写位置 <=界限 <=容量; (干货——缓冲区中给定标记,读写位置,界限,容量的大小关系)
      这里写图片描述
  • 2.2.4)使用缓冲区的主要目的是执行写, 然后读入循环。

    • 2.2.4.1)put方法:将值添加到缓冲区;
    • 2.2.4.2)flip方法:将界限设置到当前位置,并把位置复位到0;
    • 2.2.4.3)remaining方法: 现在在 remaining 方法返回整数时(它返回的值是 “界限—位置”),不断地调用get方法;
    • 2.2.4.4)clear方法: 在我们将缓冲区的所有值都读入后, 调用clear 使缓冲区为下一次写循环做好准备。 clear 方法将位置复位到0, 并将界限复位到容量;
    • 2.2.4.5)rewind 或 mark/reset方法: 如果你想重读缓冲区, 可以使用 rewind或 mark/reset 方法;
    • 2.2.4.6)ByteBuffer.allocate 或 ByteBuffer.wrap : 要获得缓冲区,调用它们;
    • 2.2.4.7)用来自某个通道的数据填充缓冲区, 或用缓冲区的数据写出到通道: (干货代码——用来自某个通道的数据填充缓冲区, 或用缓冲区的数据写出到通道)
ByteBuffer buffer = ByteBuffer.allocate(RECORD_SIZE);
channel.read(buffer);
channel.position(newpos);
buffer.flip();
channel.write(buffer);

这里写图片描述


【3】文件加锁机制(多个程序同时修改同一个文件的情形)

3.1)problem + solution

  • 3.1.1)problem: 多个程序同时修改同一个文件的情形, 这些程序需要以某种方式进行通信, 不然文件很容易损坏;
  • 3.1.2)solution: 文件锁可以解决这个问题, 它可以控制对文件或文件中的某个范围的字节 的访问; (干货——文件锁的功能)

3.2)看个荔枝:假设应用程序将用户个人信息存储在一个配置文件中, 当用户同时调用两个线程操作该文件时;

  • 3.2.1)第一个线程,应该锁定该文件, 而第二个线程发现这个文件被锁定了, 它必须决策是等待直至解锁还是跳过这个写操作过程;(干货——文件加锁机制后线程如何决策)
FileChannel channel = FileChannel.open(path);
FileLock lock = channle.lock();
或
FileLock lock = channel.tryLock();

Attention): 通道是对磁盘文件的一种抽象; (干货——再次提醒通道定义,即通道是对磁盘文件的一种抽象)
这里写图片描述
这里写图片描述

对上述代码的分析(Analysis):

  • A1)第一个调用会阻塞直至可获得锁, 而第二个调用将立即返回, 要么返回锁,要么在所不可获得的情况下返回null;
  • A2)这个文件将保持锁定状态,直至这个通道被关闭, 或者在锁上调用 release 方法;

3.3)你还可以通过下面的调用锁定文件的一部分:

FileLock lock(long start, long size, boolean shared)
或
FileLock tryLock(long start, long size, boolean shared)
  • 3.3.1)如果shared 标志为 false, 则锁定文件的目的是读写, 而如果为 true, 则这是一个共享锁, 它允许多个进程从文件中读入, 并组织任何进程获得独占锁; (干货——tryLock方法中shared数据域的含义)
  • 3.3.2)并非所有的os 都支持共享锁, 因此你可能会请求共享锁的时候得到的是独占的锁。调用 FileLock 类的 isShared 方法可以查询你所持有的锁的类型; (干货——共享锁和独占锁的定义)

Attention)如果你锁定了文件的尾部, 而这个文件的长度随后增长超过了锁定的部分, 那么增长出来的额外区域是未锁定的, 要想锁定所有的字节,可以用 Long.MAX_VALUE 来表示尺寸;

3.4)要确保在操作完成时释放锁,与往常一样, 最好在一个 try 语句中执行释放锁的操作:

try(FileLock lock = channle.lock())
{
    access th elocked file or segment
}

对以上代码的分析(Analysis):

  • A1)查看 tryLock API, 你会发现(以下内容转自 java SE 8 API): (干货中的干货——shared标识的排他锁或共享锁与StandardOpenOption的对应关系)
public abstract FileLock tryLock(long position,
                                 long size,
                                 boolean shared)
                          throws IOException
Parameters:
position - The position at which the locked region is to start; must be non-negative
size - The size of the locked region; must be non-negative, and the sum position + size must be non-negative
shared - true to request a shared lock, false to request an exclusive lock
  • A2)shared 属性值(false=排他锁, 而true=共享锁)
    • NonReadableChannelException - If shared is true but this channel was not opened for reading
    • NonWritableChannelException - If shared is false but this channel was not opened for writing
  • 也就是说,
    • 共享锁(shared = true): 对应的是 FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)
    • 排他锁(shared = false): 对应的是FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE)
    • 以上对应关系出错的话,会抛出异常;

Attention) 文件加锁机制是依赖于os的, 下面是需要注意的:

  • A1)在某些系统中,文件加锁仅仅是建议性的, 如果一个应用未能获得锁,它仍旧可以向被另一个应用并发锁定的文件执行写操作;
  • A2)在某些系统中, 不能在锁定一个文件的同时将其映射到内存中;
  • A3)文件锁是由整个 java 虚拟机持有的。 如果有两个程序是由同一个虚拟机启动的, 那么他们不可能每一个都获得一个在同一个文件上的锁。当调用lock 或 tryLock 方法时, 如果虚拟机已经在同一个文件上持有另一个重叠的锁,那么这两个方法将抛出 OverlappingFileLockException;
  • A4)在一些系统中, 关闭一个通道会释放由 java 虚拟机持有的底层文件上的所有锁。 因此, 在同一个锁定文件上应该避免使用多个通道;(干货——在同一个锁定文件上应该避免使用多个通道)
  • A5)在网络文件系统上锁定文件时高度依赖于系统 的, 因此应该尽量避免;
    这里写图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值