1、問題
問題提出:
M(如10億)個int整數,只有其中N個數重復出現過,讀取到內存中並將重復的整數刪除。
2、解決方案
問題分析:
我們肯定會先想到在計算機內存中開辟M個int整型數據數組,來one bye one讀取M個int類型數組, 然后在一一比對數值,最后將重復數據的去掉。當然這在處理小規模數據是可行的。
我們考慮大數據的情況:例如在java語言下,對10億個int類型數據排重。
java中一個int類型在內存中占4byte。那么10億個int類型數據共需要開辟10^9次方*4byte≈4GB的連續內存空間。以32位操作系統電腦為例,最大支持內存為4G,可用內存更是小於4G。所以上述方法在處理大數據時根本行不通。
思維轉化:
既然我們不能為所有 int 類型的數據開辟 int 類型數組,那么可以采取更小的數據類型來讀取緩存 int 類型數據。考慮到計算機內部處理的數據都是 01 序列的bit,那么我們是否可以用 1bit 來表示一個 int 類型數據。
位映射的引出:
使用較小的數據類型指代較大的數據類型。如上所說的問題,我們可以用1個 bit 來對應一個int 整數。假如對應的 int 類型的數據存在,就將其對應的 bit 賦值為1,否則,賦值為0(boolean類型)。java中 int 范圍為 -2^31 到 2^31-1. 那么所有可能的數值組成的長度為2^32. 對應的 bit 長度也為 2^32. 那么可以用這樣處理之后只需要開辟2^32 bit = 2^29 byte = 512M 大小的 內存空間 。顯然,這樣處理就能滿足要求了,雖然對內存的消耗也不太小。
問題解決方案:
首先定義如下圖的int - byte 映射關系,當然,映射關系可以自定義。但前提要保證你的數組上下標不能越界。
但如上定義的bit[]數組顯然在計算機中是不存在的,所我們需要將其轉化為 java 中的一個基本數據類型存儲。顯然,byte[] 是最好的選擇。
將其轉化為byte[] 數組方案:
自定義的映射關系表,每個bit對應一個 int 數值,將 int 的最大值,最小值與數組的最大最小索引相對應。從上圖可以看出來 int 數值與bit索引相差 2^31次方。當然,你也可以定義其他的映射關系,只是注意不要發生數組越界的情況。
bit[]索引:由於最大值可能是2^32,故用long接收: long bitIndex = num + (1l << 31);
byte[]索引: int index = (int) (bitIndex / 8); ,在字節byte[index]中的具體位置: int innerIndex = (int) (bitIndex % 8);
更新值: dataBytes[index] = (byte) (dataBytes[index] | (1 << innerIndex));
3、代碼

1 import java.util.Random; 2 3 /** 4 * 問題:M(如10億)個int整數,只有其中N個數重復出現過,讀取到內存中並將重復的整數刪除。<br/> 5 * 使用位映射來進行海量數據的去重排序,原先一個元素用一個int現在只用一個bit, 內存占比4*8bit:1bit=32:1<br/> 6 * 亦可用java語言提供的BitSet,不過其指定bit index的參數為int類型,因此在此例中將輸入數轉為bit index時對於較大的數會越界<br><br/> 7 */ 8 public class BigDataSort { 9 10 private static final int CAPACITY = 1_000_000;// 數據容量 11 12 public static void main(String[] args) { 13 14 testMyFullBitMap(); 15 16 } 17 18 public static void testMyFullBitMap() { 19 MyFullBitMap ms = new MyFullBitMap(); 20 21 byte[] bytes = null; 22 23 Random random = new Random(); 24 long startTime = System.currentTimeMillis(); 25 for (int i = 0; i < CAPACITY; i++) { 26 int num = random.nextInt(); 27 // System.out.println("讀取了第 " + (i + 1) + "\t個數: " + num); 28 bytes = ms.setBit(num); 29 } 30 long endTime = System.currentTimeMillis(); 31 System.out.printf("存入%d個數,用時%dms\n", CAPACITY, endTime - startTime); 32 33 startTime = System.currentTimeMillis(); 34 ms.output(bytes); 35 endTime = System.currentTimeMillis(); 36 System.out.printf("取出%d個數,用時%dms\n", CAPACITY, endTime - startTime); 37 } 38 } 39 40 class MyFullBitMap { 41 // 定義一個byte數組表示所有的int數據,一bit對應一個,共2^32b=2^29B=512MB 42 private byte[] dataBytes = new byte[1 << 29]; 43 44 /** 45 * 讀取數據,並將對應數數據的 到對應的bit中,並返回byte數組 46 * 47 * @param num 48 * 讀取的數據 49 * @return byte數組 dataBytes 50 */ 51 public byte[] setBit(int num) { 52 53 long bitIndex = num + (1l << 31); // 獲取num數據對應bit數組(虛擬)的索引 54 int index = (int) (bitIndex / 8); // bit數組(虛擬)在byte數組中的索引 55 int innerIndex = (int) (bitIndex % 8); // bitIndex 在byte[]數組索引index 中的具體位置 56 57 // System.out.println("byte[" + index + "] 中的索引:" + innerIndex); 58 59 dataBytes[index] = (byte) (dataBytes[index] | (1 << innerIndex)); 60 return dataBytes; 61 } 62 63 /** 64 * 輸出數組中的數據 65 * 66 * @param bytes 67 * byte數組 68 */ 69 public void output(byte[] bytes) { 70 int count = 0; 71 for (int i = 0; i < bytes.length; i++) { 72 for (int j = 0; j < 8; j++) { 73 if (((bytes[i]) & (1 << j)) != 0) { 74 count++; 75 int number = (int) ((((long) i * 8 + j) - (1l << 31))); 76 // System.out.println("取出的第 " + count + "\t個數: " + number); 77 } 78 } 79 } 80 } 81 }