大白話講解 BitSet


原理

BitSet是位操作的對象,值只有0或1即false和true,內部維護了一個long數組,初始只有一個long,所以BitSet最小的size是64,當隨着存儲的元素越來越多,BitSet內部會動態擴充,最終內部是由N個long來存儲,這些針對操作都是透明的。
用1位來表示一個數據是否出現過,0為沒有出現過,1表示出現過。使用用的時候既可根據某一個是否為0表示,此數是否出現過。
一個1G的空間,有 8102410241024=8.5810^9bit,也就是可以表示85億個不同的數。
注意:在沒有外部同步的情況下,多個線程操作一個BitSet是不安全的。

例子

比如有一堆數字,需要存儲,source=[3,5,6,9]
用int就需要4*4個字節。
java.util.BitSet可以存true/false。
如果用java.util.BitSet,則會少很多,其原理是:
1,先找出數據中最大值maxvalue=9
2,聲明一個BitSet bs,它的size是maxvalue+1=10
3,遍歷數據source,bs[source[i]]設置成true.
最后的值是:
(0為false;1為true)
bs [0,0,0,1,0,1,1,0,0,1]
3, 5,6, 9
這樣一個本來要int型需要占4字節共32位的數字現在只用了1位!
比例32:1
這樣就省下了很大空間
通常用在數據統計、分析的領域。

初始化邏輯

初始化大小 默認就一個long元素,邏輯如下:

private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;	

//用來開辟bit位空間
private long[] words;

//當前long數組的大小
private transient int wordsInUse = 0;

public BitSet() {
    initWords(BITS_PER_WORD);
    sizeIsSticky = false;
}

private void initWords(int nbits) {
    words = new long[wordIndex(nbits-1) + 1];
}

//bitIndex除去64(bitIndex >> 6 )得到會落到long數組的index;
private static int wordIndex(int bitIndex) {
    return bitIndex >> ADDRESS_BITS_PER_WORD;
}

BitSet的基本運算

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的應用一——排序

/**
 *  問題重述:一個最多包含n個正整數的文件,每個數都小於n,其中n=107,並且沒有重復。
 *  最多有1MB內存可用。要求用最快方式將它們排序並按升序輸出。
 */

import java.util.BitSet;
import java.util.Scanner;

/**
 * 解決思路
 * 將文件中的數讀入,把數字對應的bit位設置為1,最后,將bit位為1的按序輸出。
 */
public class SortByBit {


    public static void main(String args[]) {

        //輸入數字
        int n;
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();

        BitSet bitSet = new BitSet();
        for (int i = n; i>0;i--) {
            bitSet.set(sc.nextInt());
        }

        //輸出
        for (int i = bitSet.size(); i>0; i--) {
            if (bitSet.get(i))
                System.out.print(i + " ");
        }

    }
}

//輸出
3
1 20 2
20 2 1

應用二——字符串判重

BitSet bitSet = new BitSet(Integer.MAX_VALUE);//hashcode的值域  
//0x7FFFFFFF  (int類型的最大值,第一位是符號位,可用Integer.MAX_VALUE代替)
String url = "http://baidu.com/a";  
int hashcode = url.hashCode() & 0x7FFFFFFF;  
bitSet.set(hashcode);  
System.out.println(bitSet.cardinality()); //狀態為true的個數
System.out.println(bitSet.get(hashcode)); //檢測存在性  
bitSet.clear(hashcode); //清除狀態

為什么使用long,不用int?

JDK選擇long數組作為BitSet的內部存儲結構是出於性能的考慮,因為BitSet提供and和or這種操作,需要對兩個BitSet中的所有bit位做and或者or,實現的時候需要遍歷所有的數組元素。使用long能夠使得循環的次數降到最低,所以Java選擇使用long數組作為BitSet的內部存儲結構。

從數據在棧上的存儲來說,使用long和byte基本是沒有什么差別的,除了編譯器強制地址對齊的時候,使用byte最多會浪費7個字節(強制按照8的倍數做地址對其),另外從內存讀數組元素的時候,也是沒有什么區別的,因為匯編指令有對不同長度數據的mov指令。所以說,JDK選擇使用long數組作為BitSet的內部存儲結構的根本原因就是在and和or的時候減少循環次數,提高性能。

Java1.8-BitSet源碼分析

https://www.jianshu.com/p/91d75bf588b8


免責聲明!

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



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