相關概念
基礎類型
在java中:
byte -> 8 bits -->1字節
char -> 16 bit -->2字節
short -> 16 bits -->2字節
int -> 32 bits -->4字節
float -> 32 bits -->4字節
long -> 64 bits -->8字節
位運算符
在java中,int數據底層以補碼形式存儲。int型變量使用32bit存儲數據,其中最高位是符號位,0表示正數,1表示負數,可通過Integer.toBinaryString()
轉換爲bit字符串,
// 若最高的幾位爲0則不輸出這幾位,從爲1的那一位開始輸出
System.out.println(Integer.toBinaryString(10));
System.out.println(Integer.toBinaryString(-10));
// 會輸出(手工排版過,以下的輸出均會被手工排版):
1010
11111111111111111111111111110110
左移<<
5<<2=20
首先會將5轉爲2進製表示形式: 0000 0000 0000 0000 0000 0000 0000 0101
然後左移2位後,低位補0: 0000 0000 0000 0000 0000 0000 0001 0100
換算成10進製爲20
右移>>
5>>2=1
還是先將5轉爲2進製表示形式:0000 0000 0000 0000 0000 0000 0000 0101
然後右移2位,高位補0: 0000 0000 0000 0000 0000 0000 0000 0001
換算成十進制後是1
無符號右移>>>
5>>>3
我們知道在Java中int類型佔32位,可以表示一個正數,也可以表示一個負數。正數換算成二進制後的最高位爲0,負數的二進制最高爲爲1。對於2進制補碼的加法運算,和平常的計算一樣,而且符號位也參與運算,不過最後只保留32位
-5換算成二進制: 1111 1111 1111 1111 1111 1111 1111 1011
-5右移3位: 1111 1111 1111 1111 1111 1111 1111 1111 // (用1進行補位,結果爲-1)
-5無符號右移3位: 0001 1111 1111 1111 1111 1111 1111 1111 // (用0進行補位,結果536870911 )
位與&
第一個操作數的的第n位於第二個操作數的第n位如果都是1,那麼結果的第n爲也爲1,否則爲0
5轉換爲二進制:0000 0000 0000 0000 0000 0000 0000 0101
3轉換爲二進制:0000 0000 0000 0000 0000 0000 0000 0011
------------------------------------------------------------
1轉換爲二進制:0000 0000 0000 0000 0000 0000 0000 0001
位或|
第一個操作數的的第n位於第二個操作數的第n位只要有一個爲1則爲1,否則爲0
5轉換爲二進制:0000 0000 0000 0000 0000 0000 0000 0101
3轉換爲二進制:0000 0000 0000 0000 0000 0000 0000 0011
-------------------------------------------------------------------------------------
6轉換爲二進制:0000 0000 0000 0000 0000 0000 0000 0111
對於移位運算,例如將x左移/右移n位,如果x是byte、short、char、int,n會先模32(即n=n%32),然後再進行移位操作。可以這樣解釋:int類型爲32位,移動32位(或以上)沒有意義。
同理若x是long,n=n%64。
左移和右移代替乘除
a=a*4;
b=b/4;
可以改爲
a=a<<2;
b=b>>2;
說明: 除2 = 右移1位 乘2 = 左移1位 除4 = 右移2位 乘4 = 左移2位 除8 = 右移3位 乘8 = 左移3位 … …
類比十進制中的滿十進一,向左移動小數點後,數字就會縮小十倍,在二進制中滿二進一,進行右移一次相當於縮小了2兩倍,右移兩位相當於縮小了4倍,右移三位相當於縮小了8倍。通常如果需要乘以或除以2的n次方,都可以用移位的方法代替。
實際上,只要是乘以或除以一個整數,均可以用移位的方法得到結果如:
a=a*9
分析a9可以拆分成a(8+1)即a8+a1, 因此可以改爲: a=(a<<3)+a
a=a*7
分析a7可以拆分成a(8-1)即a8-a1, 因此可以改爲: a=(a<<3)-a
關於除法讀者可以類推, 此略。
【注意】由於+/-運算符優先級比移位運算符高,所以在寫公式時候一定要記得添加括號,不可以 a = a*12 等價於 a = a<<3 +a <<2; 要寫成a = (a<<3)+(a <<2 )。
與運算代替取餘
31轉換爲二進制:011111,0,31
32轉換爲二進制:100010 與31取交集的結果是:10轉換爲十進制爲2
31轉換爲二進制:100001 與31取交集的結果是:01轉換爲十進制爲1
30轉換爲二進制:011110 與31取交集的結果是:11110轉換爲十進制爲30
29轉換爲二進制:011101 與31取交集的結果是:11101轉換爲十進制爲29
33轉換爲二進制:100001 與31取交集的結果是:1轉換爲十進制爲1
31轉換爲二進制後,低位值全部爲1,高位全爲0。所以和其進行與運算,高位和0與,結果是0,相當於將高位全部截取,截取後的結果肯定小於等於31,地位全部爲1,與1與值爲其本身,所以相當於對數進行了取餘操作。
進制轉換
0x
開頭表示16進制,例如:0x2表示:2,0x2f表示480
開頭表示8進制,例如:02表示:2,010表示:8
Integer.toHexString(int i) // 十進制轉成十六進制
Integer.toOctalString(int i) // 十進制轉成八進制
Integer.toBinaryString(int i)// 十進制轉成二進制
Integer.valueOf(m,n).toString() // 把n進制的m轉換爲10進制
BitMap實現原理
在java中,一個int類型佔32個字節,我們用一個int數組來表示時未new int[32],總計佔用內存32*32bit,現假如我們用int字節碼的每一位表示一個數字的話,那麼32個數字只需要一個int類型所佔內存空間大小就夠了,這樣在大數據量的情況下會節省很多內存。
具體思路:
1個int佔4字節即4*8=32位,那麼我們只需要申請一個int數組長度爲 int tmp[1+N/32]即可存儲完這些數據,其中N代表要進行查找的總數,tmp中的每個元素在內存在佔32位可以對應表示十進制數0~31,所以可得到BitMap表:
tmp[0]:可表示0~31
tmp[1]:可表示32~63
tmp[2]可表示64~95
.......
那麼接下來就看看十進制數如何轉換爲對應的bit位:
假設這40億int數據爲:6,3,8,32,36,......,那麼具體的BitMap表示爲:
如何判斷int數字在tmp數組的哪個下標,這個其實可以通過直接除以32取整數部分,例如:整數8除以32取整等於0,那麼8就在tmp[0]上。另外,我們如何知道了8在tmp[0]中的32個位中的哪個位,這種情況直接mod上32就ok,又如整數8,在tmp[0]中的第8 mod上32等於8,那麼整數8就在tmp[0]中的第八個bit位(從右邊數起)。
BitMap源碼
private long length;
private static int[] bitsMap;
private static final int[] BIT_VALUE = {0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020,
0x00000040, 0x00000080, 0x00000100, 0x00000200, 0x00000400, 0x00000800, 0x00001000, 0x00002000, 0x00004000,
0x00008000, 0x00010000, 0x00020000, 0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, 0x00800000,
0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000};
public BitMap2(long length) {
this.length = length;
/**
* 根據長度算出,所需數組大小
* 當 length%32=0 時大小等於
* = length/32
* 當 length%32>0 時大小等於
* = length/32+l
*/
bitsMap = new int[(int) (length >> 5) + ((length & 31) > 0 ? 1 : 0)];
}
/**
* @param n 要被設置的值爲n
*/
public void setN(long n) {
if (n < 0 || n > length) {
throw new IllegalArgumentException("length value "+n+" is illegal!");
}
// 求出該n所在bitMap的下標,等價於"n/5"
int index = (int) n>>5;
// 求出該值的偏移量(求餘),等價於"n%31"
int offset = (int) n & 31;
/**
* 等價於
* int bits = bitsMap[index];
* bitsMap[index]=bits| BIT_VALUE[offset];
* 例如,n=3時,設置byte第4個位置爲1 (從0開始計數,bitsMap[0]可代表的數爲:0~31,從左到右每一個bit位表示一位數)
* bitsMap[0]=00000000 00000000 00000000 00000000 | 00000000 00000000 00000000 00001000=00000000 00000000 00000000 00000000 00001000
* 即: bitsMap[0]= 0 | 0x00000008 = 3
*
* 例如,n=4時,設置byte第5個位置爲1
* bitsMap[0]=00000000 00000000 00000000 00001000 | 00000000 00000000 00000000 00010000=00000000 00000000 00000000 00000000 00011000
* 即: bitsMap[0]=3 | 0x00000010 = 12
*/
bitsMap[index] |= BIT_VALUE[offset];
}
/**
* 獲取值N是否存在
* @return 1:存在,0:不存在
*/
public int isExist(long n) {
if (n < 0 || n > length) {
throw new IllegalArgumentException("length value illegal!");
}
int index = (int) n>>5;
int offset = (int) n & 31;
int bits = (int) bitsMap[index];
// System.out.println("n="+n+",index="+index+",offset="+offset+",bits="+Integer.toBinaryString(bitsMap[index]));
return ((bits & BIT_VALUE[offset])) >>> offset;
}
BitMap應用
- BitMap小小變種:2-BitMap。
看個小場景:在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的整數輸出即可。
- 已知某個文件內包含一些電話號碼,每個號碼爲8位數字,統計不同號碼的個數。
8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。 (可以理解爲從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==1.2MBytes,這樣,就用了小小的1.2M左右的內存表示了所有的8位數的電話)
BitMap問題
BitMap 的思想在面試的時候還是可以用來解決不少問題的,然後在很多系統中也都會用到,算是一種不錯的解決問題的思路。
但是 BitMap 也有一些侷限,因此會有其它一些基於 BitMap 的算法出現來解決這些問題。
- 數據碰撞。比如將字符串映射到 BitMap 的時候會有碰撞的問題,那就可以考慮用 Bloom Filter 來解決,Bloom Filter 使用多個 Hash 函數來減少衝突的概率。
- 數據稀疏。又比如要存入(10,8887983,93452134)這三個數據,我們需要建立一個 99999999 長度的 BitMap ,但是實際上只存了3個數據,這時候就有很大的空間浪費,碰到這種問題的話,可以通過引入 Roaring BitMap 來解決。
參考鏈接