1. 程式人生 > >用少於2MB內存存下5百萬個介於0到1千萬之間的整數

用少於2MB內存存下5百萬個介於0到1千萬之間的整數

public 了解 需要 tail 十進制數 可能 light return style

使用bitmap,對每個bit為進行映射、

把每個bit位映射成一個整數,

在2.5億個整數中找出不重復的整數,註,內存不足以容納這2.5億個整數。

轉載自https://itimetraveler.github.io/2017/07/13/%E3%80%90%E7%AE%97%E6%B3%95%E3%80%9110%E4%BA%BFint%E5%9E%8B%E6%95%B0%EF%BC%8C%E7%BB%9F%E8%AE%A1%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0/

10億int整型數,以及一臺可用內存為1GB的機器,時間復雜度要求O(n),統計只出現一次的數?

首先分析多大的內存能夠表示10億的數呢?一個int型占4字節,10億就是40億字節(很明顯就是4GB),也就是如果完全讀入內存需要占用4GB,而題目只給1GB內存,顯然不可能將所有數據讀入內存。

  1. 位圖法:用一個bit位來標識一個int整數。
  2. 分治法:分批處理這10億的數。

int整型數是4字節(Byte),也就是32位(bit),如果能用一個bit位來標識一個int整數那麽存儲空間將大大減少。

另一種是分治法,內存有限,分批讀取處理。

int型數據是 -2^31~2^31-1個數,共有2^32個數。接近43億

位圖法:int型是4字節(byte)=32位(bit)。每個int用1bit來表示,表示所有的int型數字需要2^32bit的空間,也就是2^32/8=512MB,即用512M就可以存儲所有int型的數。

具體做法:因為每個int型數有32bit,所以申請int num[N/32+1]大小的數組即可,N=2^32為要查找的總數。

num中的每個元素在內存中都有32bit可以表示對應的十進制數0-32,所以相應的bitmap表如下:

num[0]:0~31

num[1]:32~63

num[2]:64~95

~~

假設這10億int數據為:6,3,8,32,36,……,那麽具體的BitMap表示為:

技術分享圖片

(1):判斷int型數字target在哪個位置:將數字target/32,取整數部分,來得到在數組中的位置,然後target%32來得到在具體哪個bit上

如:8 8/32=0 即在num[0]的內部,8%32=8,則它在num[0]的第8個bit位

如何統計只出現一次的數:

可以分為3種情況:出現0次,出現1次,多於1次,需要使用2個bit位來表示,分別表示為00,01, 10, 11(無效)

然後順序掃描10億個數,在對應的雙bit位標記該數出現的次數,最後取出所有雙bit位為01的就是了

優點:

  1. 運算效率高,不許進行比較和移位;2

  2. 占用內存少,比如N=10000000;只需占用內存為N/8=1250000Byte=1.25M

缺點:所有的數據不能重復。即不可對重復的數據進行排序和查找。

Bit-Map可作為數據的查找、去重、排序等操作。比如以下幾個例子:

1、在3億個整數中找出重復的整數個數,限制內存不足以容納3億個整數

對於這種場景可以采用2-BitMap來解決,即為每個整數分配2bit,用不同的0、1組合來標識特殊意思,如00表示此整數沒有出現過,01表示出現一次,11表示出現過多次,就可以找出重復的整數了,其需要的內存空間是正常BitMap的2倍,為:3億*2/8/1024/1024=71.5MB。

具體的過程如下:掃描著3億個整數,組BitMap,先查看BitMap中的對應位置,如果00則變成01,是01則變成11,是11則保持不變,當將3億個整數掃描完之後也就是說整個BitMap已經組裝完畢。最後查看BitMap將對應位為11的整數輸出即可。、

2、對沒有重復元素的整數進行排序

對於非重復的整數排序BitMap有著天然的優勢,它只需要將給出的無重復整數掃描完畢,組裝成為BitMap之後,那麽直接遍歷一遍Bit區域就可以達到排序效果了。

舉個例子:對整數4、3、1、7、6進行排序:

技術分享圖片

直接按Bit位輸出就可以得到排序結果了。

3、已知某個文件內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數

8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。可以理解為從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的內存表示了所有的8位數的電話。

4、2.5億個整數中找出不重復的整數的個數,內存空間不足以容納這2.5億個整數

將bit-map擴展一下,用2bit表示一個數即可:0表示未出現;1表示出現一次;2表示出現2次及以上,即重復,在遍歷這些數的時候,如果對應位置的值是0,則將其置為1;如果是1,將其置為2;如果是2,則保持不變。或者我們不用2bit來進行表示,我們用兩個bit-map即可模擬實現這個2bit-map,都是一樣的道理。

最後放一個使用Byte[]數組存儲、讀取bit位的示例代碼,來自利用位映射原理對大數據排重:

技術分享圖片

由於最大值可能是2^32,故用long接收。

long bitIndex = num + (1l << 31);

計算在轉化為byte[]數組的索引,由於上面定義的bitIndex 索引是非負數,故無需引入位運算去符號。

int index = (int) (bitIndex / 8);

計算bitIndex 在byte[]數組索引index 中的具體位置。

int innerIndex = (int) (bitIndex % 8);

引入位運算將byte[]數組索引index 的各個位按權值相加

dataBytes[index] = (byte) (dataBytes[index] | (1 << innerIndex));

這樣就解決了整個大數據讀取排重的問題。

http://yacare.iteye.com/blog/1969931

class BitmapTest {

    private static final int CAPACITY = 1000000000;//數據容量

    // 定義一個byte數組緩存所有的數據
    private byte[] dataBytes = new byte[1 << 29];

    public static void main(String[] args) {
        BitmapTest ms = new BitmapTest();

        byte[] bytes = null;

        Random random = new Random();
        for (int i = 0; i < CAPACITY; i++) {
            int num = random.nextInt();
            System.out.println("讀取了第 " + (i + 1) + "\t個數: " + num);
            bytes = ms.splitBigData(num);
        }
        System.out.println("");
        ms.output(bytes);
    }


    /**
     * 讀取數據,並將對應數數據的 到對應的bit中,並返回byte數組
     * @param num 讀取的數據
     * @return byte數組  dataBytes
     */
    private byte[] splitBigData(int num) {

        long bitIndex = num + (1l << 31);         //獲取num數據對應bit數組(虛擬)的索引
        int index = (int) (bitIndex / 8);         //bit數組(虛擬)在byte數組中的索引
        int innerIndex = (int) (bitIndex % 8);    //bitIndex 在byte[]數組索引index 中的具體位置

        System.out.println("byte[" + index + "] 中的索引:" + innerIndex);

        dataBytes[index] = (byte) (dataBytes[index] | (1 << innerIndex));
        return dataBytes;
    }

    /**
     * 輸出數組中的數據
     * @param bytes byte數組
     */
    private void output(byte[] bytes) {
        int count = 0;
        for (int i = 0; i < bytes.length; i++) {
            for (int j = 0; j < 8; j++) {
                if (!(((bytes[i]) & (1 << j)) == 0)) {
                    count++;
                    int number = (int) ((((long) i * 8 + j) - (1l << 31)));
                    System.out.println("取出的第  " + count + "\t個數: " +  number);
                }
            }
        }
    }

}

2、分治法

分治法目前看到的解決方案有哈希分桶(Hash Buckets)歸並排序兩種方案。

哈希分桶的思想是先遍歷一遍,按照hash分N桶(比如1000桶),映射到不同的文件中。這樣平均每個文件就10MB,然後分別處理這1000個文件,找出沒有重復的即可。一個相同的數字,絕對不會誇文件,有hash做保證。因為算法具體還不甚了解,這裏先不做詳細介紹。

用少於2MB內存存下5百萬個介於0到1千萬之間的整數