MappedByteBuffer 詳解(圖解+秒懂+史上最全)


文章很長,而且持續更新,建議收藏起來,慢慢讀!瘋狂創客圈總目錄 博客園版 為您奉上珍貴的學習資源 :

免費贈送 :《尼恩Java面試寶典》 持續更新+ 史上最全 + 面試必備 2000頁+ 面試必備 + 大廠必備 +漲薪必備
免費贈送 經典圖書:《Java高並發核心編程(卷1)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高並發核心編程(卷2)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高並發核心編程(卷3)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:尼恩Java面試寶典 最新版 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取


背景:

下一個視頻版本,從架構師視角,尼恩為大家徹底介紹 rocketmq 高可用、高並發中間件的原理與實操。

給大家底層的解讀清楚 rocketmq 架構設計、源碼設計、工業級高可用實操,含好多復雜度非常高、又非常核心的概念,比如 零復制、延遲容錯、工業級RPC框架 ,以橫掃全網和史無前例的方式,幫助大家徹底掌握、深入骨髓的掌握 rocketmq, 成為明年3月份征服面試官的神器

在這里插入圖片描述

問題:why 高可用、高並發中間件的原理、源碼與實操:

  • 實際的開發過程中,很多小伙伴聚焦crud開發,環境出了問題,都不能啟動。

  • 作為開發人員,未來的高級開發、架構師,或者未來想走向高端開發,必須掌握高可用、高並發中間件的原理,掌握其實操。

本系列博客的具體內容,請參見 Java 高並發 發燒友社群:瘋狂創客圈

MappedByteBuffer(圖解+秒懂+史上最全)

這里 作為 rocketmq 高可用、高並發中間件的原理、源碼與實操的前置知識,以博文的方式: 給大家介紹一下 MappedByteBuffer

java nio中引入了一種基於MappedByteBuffer操作大文件的方式,其讀寫性能極高,本文會介紹其性能如此高的內部實現原理。

說明:本文會以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲取:語雀 或者 碼雲

內存管理

在深入MappedByteBuffer之前,先看看計算機內存管理的幾個術語:

  • MMU:CPU的內存管理單元。
  • 物理內存:即內存條的內存空間。
  • 虛擬內存:計算機系統內存管理的一種技術。它使得應用程序認為它擁有連續的可用的內存(一個連續完整的地址空間),而實際上,它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換。
  • 交換空間:操作系統反映構建並使用虛擬內存的硬盤空間大小而創建的文件,在windows下,即pagefile.sys文件,其存在意味着物理內存被占滿后,將暫時不用的數據移動到硬盤上。
  • 缺頁中斷:當程序試圖訪問已映射在虛擬地址空間中但未被加載至物理內存的一個分頁時,由MMC發出的中斷。如果操作系統判斷此次訪問是有效的,則嘗試將相關的頁從虛擬內存文件中載入物理內存。

為什么會有虛擬內存和物理內存的區別?

如果正在運行的一個進程,它所需的內存是有可能大於內存條容量之和的,如內存條是256M,程序卻要創建一個2G的數據區,那么所有數據不可能都加載到內存(物理內存),必然有數據要放到其他介質中(比如硬盤),待進程需要訪問那部分數據時,再調度進入物理內存。

什么是虛擬內存地址和物理內存地址?

假設你的計算機是32位,那么它的地址總線是32位的,也就是它可以尋址00xFFFFFFFF(4G)的地址空間,但如果你的計算機只有256M的物理內存0x0x0FFFFFFF(256M),同時你的進程產生了一個不在這256M地址空間中的地址,那么計算機該如何處理呢?

回答這個問題前,先說明計算機的內存分頁機制。

計算機會對虛擬內存地址空間(32位為4G)進行分頁(page),對物理內存地址空間(假設256M)進行分幀(page frame),頁和頁幀的大小一樣,所以虛擬內存頁的個數勢必要大於物理內存頁幀的個數。

在計算機上有一個頁表(page table),就是映射虛擬內存頁到物理內存頁的,更確切的說是頁號到頁幀號的映射,而且是一對一的映射。
問題來了,虛擬內存頁的個數 > 物理內存頁幀的個數,豈不是有些虛擬內存頁的地址永遠沒有對應的物理內存地址空間?

不是的,操作系統是這樣處理的。操作系統有個頁面失效(page fault)功能。操作系統找到一個最少使用的頁幀,使之失效,並把它寫入磁盤,隨后把需要訪問的頁放到頁幀中,並修改頁表中的映射,保證了所有的頁都會被調度。

現在來看看什么是虛擬內存地址和物理內存地址:

  • 虛擬內存區域:由頁號(與頁表中的頁號關聯)和偏移量(頁的小大,即這個頁能存多少數據)組成。

舉個例子,有一個虛擬地址它的頁號是4,偏移量是20,那么他的尋址過程是這樣的:

首先到頁表中找到頁號4對應的頁幀號(比如為8),如果頁不在內存中,則用失效機制調入頁,接着把頁幀號和偏移量傳給MMC組成一個物理上真正存在的地址,最后就是訪問物理內存的數據了。

Java中基礎MMap的使用

MappedByteBuffer是什么?從繼承結構上看,MappedByteBuffer繼承自ByteBuffer,內部維護了一個邏輯地址address。

將共享內存和磁盤文件建立聯系的是文件通道類:FileChannel。

該類的加入是JDK為了統一對外部設備(文件、網絡接口等)的訪問方法,並且加強了多線程對同一文件進行存取的安全性。

這里只是用它來建立共享內存用,它建立了共享內存和磁盤文件之間的一個通道。

FileChannel提供了map方法把文件映射到虛擬內存,通常情況可以映射整個文件,如果文件比較大,可以進行分段映射。

大致的步驟:

  • 首先通過 RandomAccessFile獲取文件通道。

  • 然后,通過channel進行內存映射,獲取一個虛擬內存區域VMA

//通過RandomAccessFile獲取FileChannel。
try (FileChannel channel = new RandomAccessFile(decodePath, "rw").getChannel();) {

    //通過channel進行內存映射,獲取一個虛擬內存區域VMA
    MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length);
    ....

channel.map方法的參數:

  • 映射類型

MapMode mode:內存映像文件訪問的方式,FileChannel中的幾個常量定義,共三種:

  1. MapMode.READ_ONLY:只讀,試圖修改得到的緩沖區將導致拋出異常。
  2. MapMode.READ_WRITE:讀/寫,對得到的緩沖區的更改最終將寫入文件;但該更改對映射到同一文件的其他程序不一定是可見的。
  3. MapMode.PRIVATE:私用,可讀可寫,但是修改的內容不會寫入文件,只是buffer自身的改變,這種能力稱之為”copy on write”。
  • position:文件映射時的起始位置。
  • length:映射區的長度。長度單位為字節。長度單位為字節

說明:本文會以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲取:語雀 或者 碼雲

示例1:通過MappedByteBuffer讀取文件

package com.crazymakercircle.iodemo.fileDemos;

import com.crazymakercircle.NioDemoConfig;
import com.crazymakercircle.util.IOUtil;
import com.crazymakercircle.util.Logger;

import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Created by 尼恩@ 瘋創客圈
 */
public class FileMmapDemo {

    /**
     * 演示程序的入口函數
     *
     * @param args
     */
    public static void main(String[] args) {
        doMmapDemo();
    }

    /**
     * 讀取
     */
    public static void doMmapDemo() {
        String sourcePath = NioDemoConfig.MMAP_FILE_RESOURCE_SRC_PATH;
        String decodePath = IOUtil.getResourcePath(sourcePath);

        Logger.debug("decodePath=" + decodePath);
        mmapWriteFile(decodePath);
    }


    /**
     * 讀取文件內容並輸出
     *
     * @param fileName 文件名
     */
    public static void mmapWriteFile(String fileName) {

        //向文件中存1M的數據
        int length = 1024;//
        try (FileChannel channel = new RandomAccessFile(fileName, "rw").getChannel();) {

            //一個整數4個字節
            MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
            for (int i = 0; i < length; i++) {
                mapBuffer.put((byte) (Integer.valueOf('a') + i % 26));
            }
            for (int i = 0; i < length; i++) {
                if (i % 50 == 0) System.out.println("");
                //像數組一樣訪問
                System.out.print((char) mapBuffer.get(i));
            }

            mapBuffer.force();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

輸出的結果

decodePath=/E:/refer/crazydemo/netty_redis_zookeeper_source_code/NioDemos/target/classes//mmap.demo.log 

abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij
klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefgh
ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef
ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd
efghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab
cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx
yzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
wxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst
uvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr
stuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop
qrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmn
opqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
mnopqrstuvwxyzabcdefghijDisconnected from the target VM, address: '127.0.0.1:50970', transport: 'socket'

Process finished with exit code 0

示例2:通過MappedByteBuffer讀取私用映射

私用,可讀可寫,但是修改的內容不會寫入文件,只是buffer自身的改變,這種能力稱之為”copy on write”。

   /**
     * 讀取文件內容並輸出
     *
     */
    public static void mmapPrivate() {

        String sourcePath = NioDemoConfig.MMAP_FILE_RESOURCE_SRC_PATH;
        String decodePath = IOUtil.getResourcePath(sourcePath);

        Logger.debug("decodePath=" + decodePath);

        //向文件中存1M的數據
        int length = 1024;//
        try (FileChannel channel = new RandomAccessFile(decodePath, "rw").getChannel();) {

            //一個整數4個字節
            MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length);
            for (int i = 0; i < length; i++) {
                mapBuffer.put((byte) (Integer.valueOf('a') + i % 26));
            }
            for (int i = 0; i < length; i++) {
                if (i % 50 == 0) System.out.println("");
                //像數組一樣訪問
                System.out.print((char) mapBuffer.get(i));
            }

           mapBuffer.force();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

執行程序,可以看到文件並沒有寫入的內容。

說明:本文會以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲取:語雀 或者 碼雲

實例3:通過MMap共享內存

共享內存對應應用開發的意義

對熟知UNIX系統應用開發的程序員來說,IPC(InterProcess Communication)機制是非常熟悉的,

IPC基本包括共享內存、信號燈操作、消息隊列、信號處理等部分,是開發應用中非常重要的必不可少的工具。

在所有的IPC中, 其中共享內存是關鍵,對於數據共享、系統快速查詢、動態配置、減少資源耗費等均有獨到的優點。

對應UNIX系統來說,共享內存分為一般普通共享內存和文件映射共享內存兩種,而對應 Windows,實際上只有映像文件共享內存一種。

所以java應用中也是只能創建映像文件共享內存。

Java中的共享內存場景

在java語言中,基本上沒有提及共享內存這個概念,但是,在某一些應用中,共享內存確實非常有用。

例如采用java語言的分布式應用系統中,存在着大量的分布式共享對象,很多時候需要查詢這些對象的狀態,以查看系統是否運行正常或者了解這些對象的目前的一些統計數據和狀態。

如果采用網絡通信的方式,顯然會增加應用的額外負擔,也增加了一些不必要的應用編程。

而如果采用共享內存的方式,則可以直接通過共享內存查看對象的狀態數據和統計數據,從而減少了一些不必要的麻煩。

共享內存的使用有如下幾個特點:

  • 可以被多個進程打開訪問;
  • 讀寫操作的進程在執行讀寫操作時其他進程不能進行寫操作;
  • 多個進程可以交替對某一共享內存執行寫操作;
  • 一個進程執行了內存的寫操作后,不影響其他進程對該內存的訪問。同時其他進程對更新后的內存具有可見性。
  • 在進程執行寫操作時如果異常退出,對其他進程寫操作禁止應自動解除。
  • 相對共享文件,數據訪問的方便性和效率有

共享內存在java中的實現

在jdk1.4中提供的類MappedByteBuffer為我們實現共享內存提供了較好的方法。

該緩沖區實際上是一個磁盤文件的內存映像。二者的變化將保持同步,即內存數據發生變化會立刻反映到磁盤文件中,這樣會有效的保證共享內存的實現。

將共享內存和磁盤文件建立聯系的是文件通道類:FileChannel。

該類的加入是JDK為了統一對外部設備(文件、網絡接口等)的訪問方法,並且加強了多線程對同一文件進行存取的安全性。

這里只是用它來建立共享內存用,它建立了共享內存和磁盤文件之間的一個通道。

打開一個文件建立一個文件通道可以用RandomAccessFile類中的方法getChannel。

該方法將直接返回一個文件通道。

該文件通道由於對應的文件設為隨機存取文件,一方面可以進行讀寫兩種操作,另一方面使用它不會破壞映像文件的內容(如果用FileOutputStream直接打開一個映像文件會將該文件的大小置為0,當然數據會全部丟失)。

為什么用 FileOutputStream和FileInputStream則不能理想的實現共享內存的要求呢?

因為這兩個類同時實現自由的讀寫操作要困難得多。

如何保障寫入的互斥性

由於只有一個文件能擁有寫的權限,可以通過分布式鎖的方式,保障排他性。

如果在同一個機器上有一種簡單的互斥方式:

  • 采用文件鎖的方式。

共享內存在java中的應用的參考代碼

package com.crazymakercircle.iodemo.sharemem;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Properties;

import com.crazymakercircle.NioDemoConfig;
import com.crazymakercircle.util.IOUtil;


/**
 * 共享內存操作類
 */
public class ShareMemory {
    String sourcePath = NioDemoConfig.MEM_SHARE_RESOURCE_SRC_PATH;
    String decodePath = IOUtil.getResourcePath(sourcePath);

    int fsize = 1024;                          //文件的實際大小  
    MappedByteBuffer mapBuf = null;         //定義共享內存緩沖區
    FileChannel fc = null;                  //定義相應的文件通道  
    FileLock fl = null;                     //定義文件區域鎖定的標記。        
    Properties p = null;
    RandomAccessFile randomAccessFile = null;         //定義一個隨機存取文件對象


    public ShareMemory() {


        try {
            // 獲得一個只讀的隨機存取文件對象   "rw" 打開以便讀取和寫入。如果該文件尚不存在,則嘗試創建該文件。    
            randomAccessFile = new RandomAccessFile(decodePath, "rw");
            //獲取相應的文件通道  
            fc = randomAccessFile.getChannel();
             //將此通道的文件區域直接映射到內存中。
            mapBuf = fc.map(FileChannel.MapMode.READ_WRITE, 0, fsize);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param pos  鎖定區域開始的位置;必須為非負數
     * @param len  鎖定區域的大小;必須為非負數
     * @param buff 寫入的數據
     * @return
     */
    public synchronized int write(int pos, int len, byte[] buff) {
        if (pos >= fsize || pos + len >= fsize) {
            return 0;
        }
        //定義文件區域鎖定的標記。  
        FileLock fl = null;
        try {
            //獲取此通道的文件給定區域上的鎖定。   
            fl = fc.lock(pos, len, false);
            if (fl != null) {

                mapBuf.position(pos);
                ByteBuffer bf1 = ByteBuffer.wrap(buff);
                mapBuf.put(bf1);
                //釋放此鎖定。  
                fl.release();

                return len;
            }
        } catch (Exception e) {
            if (fl != null) {
                try {
                    fl.release();
                } catch (IOException e1) {
                    System.out.println(e1.toString());
                }
            }
            return 0;
        }

        return 0;
    }

    /**
     * @param pos  鎖定區域開始的位置;必須為非負數
     * @param len  鎖定區域的大小;必須為非負數
     * @param buff 要取的數據
     * @return
     */
    public synchronized int read(int pos, int len, byte[] buff) {
        if (pos >= fsize) {
            return 0;
        }
        //定義文件區域鎖定的標記。  
        FileLock fl = null;
        try {
            fl = fc.lock(pos, len, false);
            if (fl != null) {
                //System.out.println( "pos="+pos );  
                mapBuf.position(pos);
                if (mapBuf.remaining() < len) {
                    len = mapBuf.remaining();
                }

                if (len > 0) {
                    mapBuf.get(buff, 0, len);
                }

                fl.release();

                return len;
            }
        } catch (Exception e) {
            if (fl != null) {
                try {
                    fl.release();
                } catch (IOException e1) {
                    System.out.println(e1.toString());
                }
            }
            return 0;
        }

        return 0;
    }

    /**
     * 完成,關閉相關操作
     */
    protected void finalize() throws Throwable {
        if (fc != null) {
            try {
                fc.close();
            } catch (IOException e) {
                System.out.println(e.toString());
            }
            fc = null;
        }

        if (randomAccessFile != null) {
            try {
                randomAccessFile.close();
            } catch (IOException e) {
                System.out.println(e.toString());
            }
            randomAccessFile = null;
        }
        mapBuf = null;
    }

    /**
     * 關閉共享內存操作
     */
    public synchronized void closeSMFile() {
        if (fc != null) {
            try {
                fc.close();
            } catch (IOException e) {
                System.out.println(e.toString());
            }
            fc = null;
        }

        if (randomAccessFile != null) {
            try {
                randomAccessFile.close();
            } catch (IOException e) {
                System.out.println(e.toString());
            }
            randomAccessFile = null;
        }
        mapBuf = null;
    }


}  

map過程核心原理

接下去通過分析源碼,了解一下map過程的內部實現。

  1. 通過RandomAccessFile獲取FileChannel。
public final FileChannel getChannel() {
    synchronized (this) {
        if (channel == null) {
            channel = FileChannelImpl.open(fd, path, true, rw, this);
        }
        return channel;
    }
}

上述實現可以看出,由於synchronized ,只有一個線程能夠初始化FileChannel。

通過FileChannel.map方法,把文件映射到虛擬內存,並返回邏輯地址address,實現如下:

**只保留了核心代碼**
public MappedByteBuffer map(MapMode mode, long position, long size)  throws IOException {
        int pagePosition = (int)(position % allocationGranularity);
        long mapPosition = position - pagePosition;
        long mapSize = size + pagePosition;
        try {
            addr = map0(imode, mapPosition, mapSize);
        } catch (OutOfMemoryError x) {
            System.gc();
            try {
                Thread.sleep(100);
            } catch (InterruptedException y) {
                Thread.currentThread().interrupt();
            }
            try {
                addr = map0(imode, mapPosition, mapSize);
            } catch (OutOfMemoryError y) {
                // After a second OOME, fail
                throw new IOException("Map failed", y);
            }
        }
        int isize = (int)size;
        Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
        if ((!writable) || (imode == MAP_RO)) {
            return Util.newMappedByteBufferR(isize,
                                             addr + pagePosition,
                                             mfd,
                                             um);
        } else {
            return Util.newMappedByteBuffer(isize,
                                            addr + pagePosition,
                                            mfd,
                                            um);
        }
}

上述代碼可以看出,最終map通過native函數map0完成文件的映射工作。

  1. 如果第一次文件映射導致OOM,則手動觸發垃圾回收,休眠100ms后再次嘗試映射,如果失敗,則拋出異常。
  2. 通過newMappedByteBuffer方法初始化MappedByteBuffer實例,不過其最終返回的是DirectByteBuffer的實例,實現如下:
static MappedByteBuffer newMappedByteBuffer(int size, long addr, FileDescriptor fd, Runnable unmapper) {
    MappedByteBuffer dbb;
    if (directByteBufferConstructor == null)
        initDBBConstructor();
    dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance(
          new Object[] { new Integer(size),
                         new Long(addr),
                         fd,
                         unmapper }
    return dbb;
}
// 訪問權限
private static void initDBBConstructor() {
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            Class<?> cl = Class.forName("java.nio.DirectByteBuffer");
                Constructor<?> ctor = cl.getDeclaredConstructor(
                    new Class<?>[] { int.class,
                                     long.class,
                                     FileDescriptor.class,
                                     Runnable.class });
                ctor.setAccessible(true);
                directByteBufferConstructor = ctor;
        }});
}

由於FileChannelImpl和DirectByteBuffer不在同一個包中,所以有權限訪問問題,通過AccessController類獲取DirectByteBuffer的構造器進行實例化。

DirectByteBuffer是MappedByteBuffer的一個子類,其實現了對內存的直接操作。

get過程

MappedByteBuffer的get方法最終通過DirectByteBuffer.get方法實現的。

public byte get() {
    return ((unsafe.getByte(ix(nextGetIndex()))));
}
public byte get(int i) {
    return ((unsafe.getByte(ix(checkIndex(i)))));
}
private long ix(int i) {
    return address + (i << 0);
}

map0()函數返回一個地址address,這樣就無需調用read或write方法對文件進行讀寫,通過address就能夠操作文件。底層采用unsafe.getByte方法,通過(address + 偏移量)獲取指定內存的數據。

  1. 第一次訪問address所指向的內存區域,導致缺頁中斷,中斷響應函數會在交換區中查找相對應的頁面,如果找不到(也就是該文件從來沒有被讀入內存的情況),則從硬盤上將文件指定頁讀取到物理內存中(非jvm堆內存)。
  2. 如果在拷貝數據時,發現物理內存不夠用,則會通過虛擬內存機制(swap)將暫時不用的物理頁面交換到硬盤的虛擬內存中。

性能分析

從代碼層面上看,從硬盤上將文件讀入內存,都要經過文件系統進行數據拷貝,並且數據拷貝操作是由文件系統和硬件驅動實現的,理論上來說,拷貝數據的效率是一樣的。
但是通過內存映射的方法訪問硬盤上的文件,效率要比read和write系統調用高,這是為什么?

  • read()是系統調用,首先將文件從硬盤拷貝到內核空間的一個緩沖區,再將這些數據拷貝到用戶空間,實際上進行了兩次數據拷貝;
  • map()也是系統調用,但沒有進行數據拷貝,當缺頁中斷發生時,直接將文件從硬盤拷貝到用戶空間,只進行了一次數據拷貝。

所以,采用內存映射的讀寫效率要比傳統的read/write性能高。

總結

  1. MappedByteBuffer使用虛擬內存,因此分配(map)的內存大小不受JVM的-Xmx參數限制,但是也是有大小限制的。
  2. 如果當文件超出1.5G限制時,可以通過position參數重新map文件后面的內容。
  3. MappedByteBuffer在處理大文件時的確性能很高,但也存在一些問題,如內存占用、文件關閉不確定,被其打開的文件只有在垃圾回收的才會被關閉,而且這個時間點是不確定的。
    javadoc中也提到:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.

說明:本文會以pdf格式持續更新,更多最新尼恩3高pdf筆記,請從下面的鏈接獲取:語雀 或者 碼雲


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM