1. 程式人生 > >Java位操作全面總結

Java位操作全面總結

在計算機中所有資料都是以二進位制的形式儲存的。位運算其實就是直接對在記憶體中的二進位制資料進行操作,因此處理資料的速度非常快。在實際程式設計中,如果能巧妙運用位操作,完全可以達到四兩撥千斤的效果,正因為位操作的這些優點,所以位操作在各大IT公司的筆試面試中一直是個熱點問題。

位操作基礎

基本的位操作符有與、或、異或、取反、左移、右移這6種,它們的運算規則如下所示:

注意以下幾點:

  1. 在這6種操作符,只有~取反是單目操作符,其它5種都是雙目操作符。
  2. 位操作只能用於整形資料,對float和double型別進行位操作會被編譯器報錯。
  3. 位操作符的運算優先順序比較低,因為儘量使用括號來確保運算順序,否則很可能會得到莫明其妙的結果。比如要得到像1,3,5,9這些2^i+1的數字。寫成int a = 1 « i + 1;是不對的,程式會先執行i + 1,再執行左移操作。應該寫成int a = (1 « i) + 1;
  4. 另外位操作還有一些複合操作符,如&=、|=、 ^=、«=、»=。
package com.king.bit;

/**
 * @author taomk
 * @version 1.0
 * @since 15-5-10 下午2:23
 */
public class BitMain {

    public static void main(String [] args) {
        int a = -15, b = 15;
        System.out.println(a >> 2); // -4:-15 = 1111 0001(二進位制),右移二位,最高位由符號位填充將得到1111 1100即-4
        System.out.println(b >> 2); // 3:15=0000 1111(二進位制),右移二位,最高位由符號位填充將得到0000 0011即3
    }
}

常用位操作小技巧

下面對位操作的一些常見應用作個總結,有判斷奇偶、交換兩數、變換符號及求絕對值。這些小技巧應用易記,應當熟練掌握。

判斷奇偶

只要根據最未位是0還是1來決定,為0就是偶數,為1就是奇數。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)來判斷a是不是偶數。下面程式將輸出0到100之間的所有偶數:

    for (int i = 0; i < 100; i ++) {
        if ((i & 1) == 0) { // 偶數
            System.out.println(i);
        }
    }

交換兩數

int c = 1, d = 2;
c ^= d;
d ^= c;
c ^= d;
System.out.println("c=" + c);
System.out.println("d=" + d);

可以這樣理解:
第一步 a=b 即a=(ab);
第二步 b=a 即b=b(ab),由於運算滿足交換律,b(ab)=bba。由於一個數和自己異或的結果為0並且任何數與0異或都會不變的,所以此時b被賦上了a的值;
第三步 a=b 就是a=ab,由於前面二步可知a=(ab),b=a,所以a=ab即a=(ab)a。故a會被賦上b的值;

變換符號

變換符號就是正數變成負數,負數變成正數。
如對於-11和11,可以通過下面的變換方法將-11變成11

1111 0101(二進位制) –取反-> 0000 1010(二進位制) –加1-> 0000 1011(二進位制)

同樣可以這樣的將11變成-11

0000 1011(二進位制) –取反-> 0000 0100(二進位制) –加1-> 1111 0101(二進位制)

因此變換符號只需要取反後加1即可。完整程式碼如下:

int a = -15, b = 15;
System.out.println(~a + 1);
System.out.println(~b + 1);

求絕對值

位操作也可以用來求絕對值,對於負數可以通過對其取反後加1來得到正數。對-6可以這樣:

1111 1010(二進位制) –取反->0000 0101(二進位制) -加1-> 0000 0110(二進位制)

來得到6。

因此先移位來取符號位,int i = a » 31;要注意如果a為正數,i等於0,為負數,i等於-1。然後對i進行判斷——如果i等於0,直接返回。否之,返回~a+1。完整程式碼如下:

int i = a >> 31;
System.out.println(i == 0 ? a : (~a + 1));

現在再分析下。對於任何數,與0異或都會保持不變,與-1即0xFFFFFFFF異或就相當於取反。因此,a與i異或後再減i(因為i為0或-1,所以減i即是要麼加0要麼加1)也可以得到絕對值。所以可以對上面程式碼優化下:

int j = a >> 31;
System.out.println((a ^ j) - j);

注意這種方法沒用任何判斷表示式,而且有些筆面試題就要求這樣做,因此建議讀者記住該方法(_講解過後應該是比較好記了)。

位操作與空間壓縮

篩素數法在這裡不就詳細介紹了,本文著重對篩素數法所使用的素數表進行優化來減小其空間佔用。要壓縮素數表的空間佔用,可以使用位操作。下面是用篩素數法計算100以內的素數示例程式碼(注2):

// 列印100以內素數:
// (1)對每個素數,它的倍數必定不是素數;
// (2)有很多重複訪問如flag[10]會在訪問flag[2]和flag[5]時各訪問一次;
int max = 100;
boolean[] flags = new boolean[max];
int [] primes = new int[max / 3 + 1];
int pi = 0;

for (int m = 2; m < max ; m ++) {
    if (!flags[m]) {
        primes[pi++] = m;
        for(int n = m; n < max; n += m) {
            flags[n] = true;
        }
    }
}

System.out.println(Arrays.toString(primes));

執行結果如下:

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0]

在上面程式是用bool陣列來作標記的,bool型資料佔1個位元組(8位),因此用位操作來壓縮下空間佔用將會使空間的佔用減少八分之七。

下面考慮下如何在陣列中對指定位置置1,先考慮如何對一個整數在指定位置上置1。對於一個整數可以通過將1向左移位後與其相或來達到在指定位上置1的效果,程式碼如下所示:

// 在一個數指定位上置1
int e = 0;
e |=  1 << 10;
System.out.println(e);

同樣,可以1向左移位後與原數相與來判斷指定位上是0還是1(也可以將原數右移若干位再與1相與)。

//判斷指定位上是0還是1
if ((e & (1 << 10)) != 0)
    System.out.println("指定位上為1");
else
    System.out.println("指定位上為0");

擴充套件到陣列上,我們可以採用這種方法,因為陣列在記憶體上也是連續分配的一段空間,完全可以“認為”是一個很長的整數。先寫一份測試程式碼,看看如何在陣列中使用位操作:

int[] bits = new int[40];
for (int m = 0; m < 40; m += 3) {
    bits[m / 32] |= (1 << (m % 32));
}
// 輸出整個bits
for (int m = 0; m < 40; m++) {
    if (((bits[m / 32] >> (m % 32)) & 1) != 0)
        System.out.print('1');
    else
        System.out.print('0');
}

執行結果如下:

1001001001001001001001001001001001001001

可以看出該陣列每3個就置成了1,證明我們上面對陣列進行位操作的方法是正確的。因此可以將上面篩素數方法改成使用位操作壓縮後的篩素數方法:

int[] flags2 = new int[max / 32 + 1];
pi = 0;
for (int m = 2; m < max ; m ++) {
    if ((((flags2[m / 32] >> (m % 32)) & 1) == 0)) {
        primes[pi++] = m;
        for(int n = m; n < max; n += m) {
            flags2[n / 32] |= (1 << (n % 32));
        }
    }
}

System.out.println();
System.out.println(Arrays.toString(primes));

執行結果如下:

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0]

位操作工具類

package com.king.bit;
/**
 * Java 位運算的常用方法封裝<br>
 */
public class BitUtils {
    /**
     * 獲取運算數指定位置的值<br>
     * 例如: 0000 1011 獲取其第 0 位的值為 1, 第 2 位 的值為 0<br>
     * 
     * @param source
     *            需要運算的數
     * @param pos
     *            指定位置 (0<=pos<=7)
     * @return 指定位置的值(0 or 1)
     */
    public static byte getBitValue(byte source, int pos) {
        return (byte) ((source >> pos) & 1);
    }

    /**
     * 將運算數指定位置的值置為指定值<br>
     * 例: 0000 1011 需要更新為 0000 1111, 即第 2 位的值需要置為 1<br>
     * 
     * @param source
     *            需要運算的數
     * @param pos
     *            指定位置 (0<=pos<=7)
     * @param value
     *            只能取值為 0, 或 1, 所有大於0的值作為1處理, 所有小於0的值作為0處理
     * 
     * @return 運算後的結果數
     */
    public static byte setBitValue(byte source, int pos, byte value) {

        byte mask = (byte) (1 << pos);
        if (value > 0) {
            source |= mask;
        } else {
            source &= (~mask);
        }

        return source;
    }

    /**
     * 將運算數指定位置取反值<br>
     * 例: 0000 1011 指定第 3 位取反, 結果為 0000 0011; 指定第2位取反, 結果為 0000 1111<br>
     * 
     * @param source
     * 
     * @param pos
     *            指定位置 (0<=pos<=7)
     * 
     * @return 運算後的結果數
     */
    public static byte reverseBitValue(byte source, int pos) {
        byte mask = (byte) (1 << pos);
        return (byte) (source ^ mask);
    }

    /**
     * 檢查運算數的指定位置是否為1<br>
     * 
     * @param source
     *            需要運算的數
     * @param pos
     *            指定位置 (0<=pos<=7)
     * @return true 表示指定位置值為1, false 表示指定位置值為 0
     */
    public static boolean checkBitValue(byte source, int pos) {

        source = (byte) (source >>> pos);

        return (source & 1) == 1;
    }

    /**
     * 入口函式做測試<br>
     * 
     * @param args
     */
    public static void main(String[] args) {
        // 取十進位制 11 (二級制 0000 1011) 為例子
        byte source = 11;
        // 取第2位值並輸出, 結果應為 0000 1011
        for (byte i = 7; i >= 0; i--) {
            System.out.printf("%d ", getBitValue(source, i));
        }
        // 將第6位置為1並輸出 , 結果為 75 (0100 1011)
        System.out.println("\n" + setBitValue(source, 6, (byte) 1));
        // 將第6位取反並輸出, 結果應為75(0100 1011)
        System.out.println(reverseBitValue(source, 6));
        // 檢查第6位是否為1,結果應為false
        System.out.println(checkBitValue(source, 6));
        // 輸出為1的位, 結果應為 0 1 3
        for (byte i = 0; i < 8; i++) {
            if (checkBitValue(source, i)) {
                System.out.printf("%d ", i);
            }
        }
    }
}

BitSet類

BitSet類:大小可動態改變, 取值為true或false的位集合。用於表示一組布林標誌。 此類實現了一個按需增長的位向量。位 set 的每個元件都有一個 boolean 值。用非負的整數將 BitSet 的位編入索引。可以對每個編入索引的位進行測試、設定或者清除。通過邏輯與、邏輯或和邏輯異或操作,可以使用一個 BitSet 修改另一個 BitSet 的內容。預設情況下,set 中所有位的初始值都是 false。

每個位 set 都有一個當前大小,也就是該位 set 當前所用空間的位數。注意,這個大小與位 set 的實現有關,所以它可能隨實現的不同而更改。位 set 的長度與位 set 的邏輯長度有關,並且是與實現無關而定義的。

除非另行說明,否則將 null 引數傳遞給 BitSet 中的任何方法都將導致 NullPointerException。 在沒有外部同步的情況下,多個執行緒操作一個 BitSet 是不安全的。

建構函式: BitSet() or BitSet(int nbits),預設初始大小為64。

public void set(int pos): 位置pos的字位設定為true。

public void set(int bitIndex, boolean value): 將指定索引處的位設定為指定的值。

public void clear(int pos): 位置pos的字位設定為false。

public void clear(): 將此 BitSet 中的所有位設定為 false。

public int cardinality(): 返回此 BitSet 中設定為 true 的位數。

public boolean get(int pos): 返回位置是pos的字位值。

public void and(BitSet other): other同該字位集進行與操作,結果作為該字位集的新值。

public void or(BitSet other): other同該字位集進行或操作,結果作為該字位集的新值。

public void xor(BitSet other): other同該字位集進行異或操作,結果作為該字位集的新值。

public void andNot(BitSet set): 清除此 BitSet 中所有的位,set - 用來遮蔽此 BitSet 的 BitSet

public int size(): 返回此 BitSet 表示位值時實際使用空間的位數。

public int length(): 返回此 BitSet 的“邏輯大小”:BitSet 中最高設定位的索引加 1。

public int hashCode(): 返回該集合Hash 碼, 這個碼同集合中的字位值有關。

public boolean equals(Object other): 如果other中的字位同集合中的字位相同,返回true。

public Object clone(): 克隆此 BitSet,生成一個與之相等的新 BitSet。

public String toString(): 返回此位 set 的字串表示形式。

例1:標明一個字串中用了哪些字元

package com.king.bit;

import java.util.BitSet;

public class WhichChars {

    private BitSet used = new BitSet();

    public WhichChars(String str) {
        for (int i = 0; i < str.length(); i++)
            used.set(str.charAt(i));  // set bit for char
    }

    public String toString() {
        String desc = "[";
        int size = used.size();
        for (int i = 0; i < size; i++) {
            if (used.get(i))
                desc += (char) i;
        }
        return desc + "]";
    }

    public static void main(String args[]) {
        WhichChars w = new WhichChars("How do you do");
        System.out.println(w);
    }
}

例2:

package com.king.bit;

import java.util.BitSet;

public class MainTestThree {

    /**
     * @param args
     */
    public static void main(String[] args) {
        BitSet bm = new BitSet();
        System.out.println(bm.isEmpty() + "--" + bm.size());
        bm.set(0);
        System.out.println(bm.isEmpty() + "--" + bm.size());
        bm.set(1);
        System.out.println(bm.isEmpty() + "--" + bm.size());
        System.out.println(bm.get(65));
        System.out.println(bm.isEmpty() + "--" + bm.size());
        bm.set(65);
        System.out.println(bm.isEmpty() + "--" + bm.size());
    }

}

例3:

package com.king.bit;

import java.util.BitSet;

public class MainTestFour {

    /**
     * @param args
     */
    public static void main(String[] args) {
        BitSet bm1 = new BitSet(7);
        System.out.println(bm1.isEmpty() + "--" + bm1.size());

        BitSet bm2 = new BitSet(63);
        System.out.println(bm2.isEmpty() + "--" + bm2.size());

        BitSet bm3 = new BitSet(65);
        System.out.println(bm3.isEmpty() + "--" + bm3.size());

        BitSet bm4 = new BitSet(111);
        System.out.println(bm4.isEmpty() + "--" + bm4.size());
    }

}

位操作技巧

// 1. 獲得int型最大值
System.out.println((1 << 31) - 1);// 2147483647, 由於優先順序關係,括號不可省略
System.out.println(~(1 << 31));// 2147483647

// 2. 獲得int型最小值
System.out.println(1 << 31);
System.out.println(1 << -1);

// 3. 獲得long型別的最大值
System.out.println(((long)1 << 127) - 1);

// 4. 乘以2運算
System.out.println(10<<1);

// 5. 除以2運算(負奇數的運算不可用)
System.out.println(10>>1);

// 6. 乘以2的m次方
System.out.println(10<<2);

// 7. 除以2的m次方
System.out.println(16>>2);

// 8. 判斷一個數的奇偶性
System.out.println((10 & 1) == 1);
System.out.println((9 & 1) == 1);

// 9. 不用臨時變數交換兩個數(面試常考)
a ^= b;
b ^= a;
a ^= b;

// 10. 取絕對值(某些機器上,效率比n>0 ? n:-n 高)
int n = -1;
System.out.println((n ^ (n >> 31)) - (n >> 31));
/* n>>31 取得n的符號,若n為正數,n>>31等於0,若n為負數,n>>31等於-1
若n為正數 n^0-0數不變,若n為負數n^-1 需要計算n和-1的補碼,異或後再取補碼,
結果n變號並且絕對值減1,再減去-1就是絕對值 */

// 11. 取兩個數的最大值(某些機器上,效率比a>b ? a:b高)
System.out.println(b&((a-b)>>31) | a&(~(a-b)>>31));

// 12. 取兩個數的最小值(某些機器上,效率比a>b ? b:a高)
System.out.println(a&((a-b)>>31) | b&(~(a-b)>>31));

// 13. 判斷符號是否相同(true 表示 x和y有相同的符號, false表示x,y有相反的符號。)
System.out.println((a ^ b) > 0);

// 14. 計算2的n次方 n > 0
System.out.println(2<<(n-1));

// 15. 判斷一個數n是不是2的冪
System.out.println((n & (n - 1)) == 0);
/*如果是2的冪,n一定是100... n-1就是1111....
所以做與運算結果為0*/

// 16. 求兩個整數的平均值
System.out.println((a+b) >> 1);

// 17. 從低位到高位,取n的第m位
int m = 2;
System.out.println((n >> (m-1)) & 1);

// 18. 從低位到高位.將n的第m位置為1
System.out.println(n | (1<<(m-1)));
/*將1左移m-1位找到第m位,得到000...1...000
n在和這個數做或運算*/

// 19. 從低位到高位,將n的第m位置為0
System.out.println(n & ~(0<<(m-1)));
/* 將1左移m-1位找到第m位,取反後變成111...0...1111
n再和這個數做與運算*/

相關推薦

Java操作全面總結

在計算機中所有資料都是以二進位制的形式儲存的。位運算其實就是直接對在記憶體中的二進位制資料進行操作,因此處理資料的速度非常快。在實際程式設計中,如果能巧妙運用位操作,完全可以達到四兩撥千斤的效果,正因為位操作的這些優點,所以位操作在各大IT公司的筆試面試中一直是個熱點問題。 位操作基礎 基本的位操作符有與、

C++:操作基礎篇之操作全面總結

位操作篇共分為基礎篇和提高篇,基礎篇主要對位操作進行全面總結,幫助大家梳理知識。提高篇則針對各大IT公司如微軟、騰訊、百度、360等公司的筆試面試題作詳細的解答,使大家能熟練應對在筆試面試中位操作題目。       下面就先來對位操作作個全面總結,歡迎大家補充。 在計

java操作總結

int a = -2; //1111 1111 1111 1111 1111 1111 1111 1110 a = a<<3; System.out.println(a); //a=-16 1111 1111 1111 1111 1111 1111 1111 0000 in

JAVA類的全面總結

java是面向物件: 下面解釋什麼面向物件 JAVA操作都是類中操作如上圖Cat類,例項化可以在main函式中 那麼java本身就是類操作,為什麼還要學習繼承多型介面包? 官方回答:可讀性強,安全性高,程式碼簡化,維護性高,方便查詢錯誤等 下面我要從現實生活的角度

[Java] 操作

Java中的位操作及強制型別轉換 進製表示 Java中提供了表示8,16進位制的方法 int a=0b11; int b=011; int

可能是最通俗易懂的 Java 操作運算講解

Java 位操作這是一項很基礎很基礎的知識內容,在所有 Android 和 Java 開發者的學習之路上,大家都接觸過,但是實際運用的場景卻很少見,很多人估計都忘記有這個知識點了。事實上,在 C/C++ 開發領域因為與硬體的聯絡更緊密,所以位操作運算應用的更普遍

轉: 【Java並發編程】之十七:深入Java內存模型—內存操作規則總結

tle 沒有 article 類型 javase 感知 執行引擎 要求 lock 轉載請註明出處:http://blog.csdn.net/ns_code/article/details/17377197 主內存與工作內存 Java內存模型的主要目標是定義程序中

java中的操作

ble 其他 自動 byte 有符號 cnblogs 運算 編譯 位移操作 之前做項目的時候使用位操作不是很多,今天在刷leetcode上題目的時候用到了位操作,是leetcode中的第29題Divide Two Integers。 一、java的位操作: 位運算表達式由操

Java運算符使用總結(重點:自增自減、運算和邏輯運算)

運算 計算器 可讀性 過多 移位運算 style avi 學會 new Java運算符共包括這幾種:算術運算符、比較運算符、位運算符、邏輯運算符、賦值運算符和其他運算符。(該圖來自網絡) 簡單的運算符,就不過多介紹使用了,可自行測試。關於賦值運算,可以結合算術運

JAVA 集合操作總結

dna arr -o2 inter map 實現 void array 遍歷 1.Collection 1.基本操作 對集合的基礎操作 1.boolean add(Object o) //添加對象到集合 2.boolean remove(Object o) //刪除指定的

java Integer中的方法解析(操作

方法 描述 static int bitCount(int i) 返回i的二進位制中1的個數. static int compare(int x, int y)

Java IO流常用操作方法總結

一、簡介 在實際工作中,基本上每個專案難免都會有檔案相關的操作,比如檔案上傳、檔案下載等,這些操作都是使用IO流進行操作的,本文將通過簡單的示例對常用的一些IO流進行總結。 二、使用詳解 【a】FileInputStream與FileOutputStream 首先通過檢視jdk文件,

jsp頁面中JSTL/EL標籤引用java後臺靜態static欄的方法總結

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Selenium Java Web 自動化實踐總結(六)元素操作例項

先上介面和html程式碼,頁面包含按鈕,單選框,複選框,下拉框,輸入框這幾種頁面常用元素 html程式碼如下 <html> <head>演示用html</head> <body> <br><br><br>

老鳥的JAVA面試和基礎總結

前言   畢業至今已有4年的時間,近兩年期間陸續面試了不少的求職的前(JAVA)、後(WEB)端開發人員,包括實習生、應屆畢業生、一兩年工作經驗的、也有三四年工作經驗的,也算見過了比較多的開發人員,想在這裡做個總結,本次主要講一講面試和後端(java)相關的東西; 關於面試準備

JAVA中常用的二進位制操作

一,計算某個正數的二進位制表示法中 1 的個數 1 //求解正數的二進位制表示法中的 1 的位數 2 private static int countBit(int num){ 3 int count = 0; 4 for

Java檔案操作及編碼總結

編碼/解碼 編碼:getBytes(); 按照預設編碼表編碼 字串-------->位元組 解碼:new String(); 按照預設編碼表解碼 位元組------->>字串 GBK 碼錶:漢字的儲存,第一個一定是負的。如果轉換器讀到的

2018全面總結阿里巴巴Java開發手冊

其實早在多年前,Google就已經把公司內部採用的所有語言的編碼規範(其稱為 Style Guide )都開源在Github上。這份清單中包括了 C++ 、 Objective-C 、 Java 、 Python 、 R 、 Shell 、 HTML/CSS 、 JavaScript 、 A

java操作mongodb總結

新需求需要用到Mongodb去存APP過來的資料,所以就簡單學了一下。 專案是用Spring搭建的,本來直接用spring-data-mongodb這個模組就很簡單地實現操作mongodb的功能,但是,經理硬是不讓,必須使用mongodb原生的驅動去實現。給我