BitSet的應用實踐

1.BitSet介紹

BitSet是用於存儲二進制位和對二進制進行操作的Java數據結構,BitSet從jdk1.0開始就有了。它存儲的是二進制位在BitSet中狀態,根據對這些狀態的判斷,可以有很多應用。以前對數據的操作都是先把數據都是存儲在內存中間的,現在可以通過設置BitSet的相應位達到存儲數據信息的目的,極大的節省了內存空間。

2.BitSet應用

BitSet可以做的事情主要分爲以下幾類:

(1)大數據量的查找。

(2)大數據量的去重。

(3)大數據量的統計。

(4)大數據量的排序。

(5)求數據的並集、交集、補集等。

(6)大數據量的判別。
BitSet常見的應用是那些對海量數據進行一些統計工作,比如日誌分析、用戶數統計等等。

BitSet能夠做以上事情主要依靠BitSet的基本操作,對應的常用方法:

(1)初始化一個BitSet。使用構造函數BitSet( )或BitSet(int nbits)。

(2)設置BitSet的某一指定位。就是把指定位存放入BitSet,使用設置函數set(int bitIndex)。

(3)獲取BitSet的某一位的狀態。就是判斷指定位是否在BitSet中,boolean型的返回值,使用函數get(int bitIndex)。

(4)清空BitSet或清空BitSet的某一指定位。就是把BitSet所有位或指定位清除,使用清空函數clear( )或clear(int bitIndex)。

不過使用BitSet有一點需要注意:在沒有外部同步的情況下,多個線程操作一個BitSet是不安全的。所以在多線程環境下使用BitSet要考慮線程安全的問題,可以使用多線程安全策略確保多個線程在執行過程中的的線程安全性。也就是BitSet是非線程安全的,需要外部同步。

3.BitSet應用舉例

下面就來看一個BitSet應用的具體例子。
(1)使用BitSet查找電話號碼

從一堆數量大概在千萬級的電話號碼列表中找出所有重複的電話號碼,需要時間複雜度儘可能小。

如果這個問題使用暴力搜索時間複雜度太高,就不考慮這種解決方案。

容易想到的辦法就是建立一個標誌數組,int boolean都行,用相應的位置值來代替這個號碼是否出現,根據數組的可直接存取特性,來提高效率。比如電話號“8832061”如果存在,就把他放入數組的第8832061位設置該位的值爲1或true。

但是這樣做有一個缺點就是int型的字段太過於佔空間,我們只需要知道這個號碼存在與否,所以最簡單的0和1就夠用了,能表示0和1的最小存儲單位是什麼呢?是內存中的一位。BitSet是用於存儲二進制位和對二進制進行操作的數據結構。

之前int型的一個電話號碼的狀態要佔4個字節,現在使用BitSet存儲出現的電話號碼的位置,而且BitSet有自動去重功能。8bit是1byte,int佔用4byte,那麼使用的空間大小就縮小了4*8 = 32倍。使用了內存大大減少。

下面的簡單代碼給出了BitSet的例子:

import java.util.BitSet;
public class BitSetDemo {
 
    public static void main(String[] args) {
 
        //創建一個具有10000000位的bitset 初始所有位的值爲false
        BitSet bitSet = new BitSet(10000000);
        //將指定位的值設爲true
        bitSet.set(9999);
        //或者bitSet.set(9999,true);
        //輸出指定位的值
        System.out.println("9999:"+bitSet.get(9999));
        System.out.println("9998:"+bitSet.get(9998));
 
    }
}

程序運行結果:第一行輸入“true”,第二個輸出“false”。

(2)使用BitSet統計隨機數的個數

有1千萬個隨機數,隨機數的範圍在1到1億之間。現在要求寫出一種算法,將1到1億之間沒有在隨機數中的數求出來?

這裏以100代替1億,使用BitSet存放隨機數並進行統計,減少內存的使用,代碼如下:

import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.BitSet;
public class BitSetDemo3 {
    public static void main(String[] args)
    {
        Random random=new Random();
 
        List<Integer> list=new ArrayList<>();
        for(int i=0;i<100;i++)
        {
            int randomResult=random.nextInt(100);
            list.add(randomResult);
        }
        System.out.print("0~100之間產生的隨機數有:");
        for(int i=0;i<list.size();i++){
            System.out.print(list.get(i)+" ");
        }
        System.out.println();
        System.out.println("0~100之間的隨機數產生了"+list.size()+"個");
        BitSet bitSet=new BitSet(100);
        for(int i=0;i<100;i++)
        {
            bitSet.set(list.get(i));
        }
        //public int cardinality()方法返回此BitSet中比特設置爲true的數目
        //就是BitSet中存放的有效位的個數,如果有重複運算會進行自動去重
        System.out.println("0~100存在BitSet的隨機數有"+bitSet.cardinality()+"個");
        System.out.print("0~100不在上述隨機數中有:");
        int count = 0;
        for (int i = 0; i < 100; i++) {
            if(!bitSet.get(i))
            {
                System.out.print(i+" ");
                count++;
            }
        }
        System.out.println();
        //0~100不在產生的隨機數中的個數就是100減去存在BitSet的隨機數個數
        System.out.print("0~100不在產生的隨機數中的個數爲:"+count+"個");
    }
}

運行結果:

(3)使用BitSet查找某個範圍內的所有素數的個數

素數:一個大於1的自然數,如果除了1和它本身外,不能被其他自然數整除(除0以外)的數稱爲素數(質數) ,否則稱爲合數。

如下爲統計2000000以內的素數的個數:

import java.util.BitSet;
public class BitSetDemo3 {
    public static void main(String[] s)
    {
        int n = 2000000;
        long start = System.currentTimeMillis();
        BitSet sieve = new BitSet(n+1);
        int count=0;
        for (int i = 2; i <= n; i++){
            sieve.set(i);
        }
        int finalBit = (int) Math.sqrt(n);
 
        for (int i = 2; i < finalBit; i++){
            if (sieve.get(i)){
                for (int j = 2 * i; j < n; j += i){
                    sieve.clear(j);
                }
            }
        }
        int counter = 0;
        for (int i = 1; i < n; i++) {
            if (sieve.get(i)) {
                count++;
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(count + " primes");
        System.out.println((end - start) + " ms");
    }
}

運行結果:

(4)使用BitSet進行排序   

如統計40億個數據中沒有出現的數據,將40億個不同數據進行排序等。

統計個數和上面兩個類似,就不說了。使用BitSet對數據進行排序的代碼如下:

import  java.util.BitSet;
public class BitSetDemo {
 
    public static void main(String[] args) {
 
            int[] array = new int[] { 423, 700, 9999, 2323, 356, 6400, 1,2,3,2,2,2,2 };
            BitSet bitSet = new BitSet();
            //BitSet默認初始大小爲64
            System.out.println("BitSet size: " + bitSet.size());
 
            for (int i = 0; i < array.length; i++) {
                bitSet.set(array[i]);
            }
            //public int cardinality()方法返回此BitSet中比特設置爲true的數目
            //就是BitSet中存放的有效位的個數,如果有重複運算會進行自動去重
            int bitLen=bitSet.cardinality();
            System.out.println("bitLen = "+bitLen);
            System.out.println("Before ordering:"+bitSet);
 
            //進行排序,即把bit爲true的元素複製到另一個數組
            int[] orderedArray = new int[bitLen];
            int k = 0;
            //nextSetBit(int fromIndex)方法返回fromIndex之後的下一個值爲true的索引
            //此循環就是按照順序循環取出在BitSet存在的數,以此達到排序的目的
            for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
                orderedArray[k++] = i;
            }
 
            System.out.println("After ordering:");
            for (int i = 0; i < bitLen; i++) {
                System.out.print(orderedArray[i] + "\t");
            }
            System.out.println();
            //或者順序訪問的數據不放入數組而進行直接讀取
            System.out.println("iterate over the true bits in a BitSet");
            //或直接迭代BitSet中bit爲true的元素
            for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
                System.out.print(i+"\t");
            }
 
    }
}

運行結果:

(5)使用BitSet求並、交、補集

import  java.util.BitSet;
public class BitSetDemo {
 
    public static void main(String[] args) {
        BitSet bitSet = new BitSet(100);
        bitSet.set(1);
        bitSet.set(2);
        bitSet.set(3);
 
        BitSet bitSet2 = new BitSet(100);
        bitSet2.set(2);
        bitSet2.set(3);
        bitSet2.set(5);
 
        System.out.println("剛開始的bitSet:"+bitSet);
        System.out.println("剛開始的bitSet2:"+bitSet2);
        System.out.println("------------------------------");
        //求並集
        bitSet.or(bitSet2);
        System.out.println("求完並集之後的bitSet:"+bitSet);
        System.out.println("求完並集之後的bitSet2:"+bitSet2);
        System.out.println("------------------------------");
        //使bitSet回到剛開始的狀態
        bitSet.clear(5);
 
        //求交集
        bitSet.and(bitSet2);
        System.out.println("求完交集之後的bitSet:"+bitSet);
        System.out.println("求完交集之後的bitSet2:"+bitSet2);
        System.out.println("------------------------------");
        //使bitSet回到剛開始的狀態
        bitSet.set(1);
        //此方法返回在bitSet中不在bitSet2中的值,求的是bitSet相對於bitSet2的補集
        bitSet.andNot(bitSet2);
        System.out.println("求完補集之後的bitSet:"+bitSet);
        System.out.println("求完補集之後的bitSet2:"+bitSet2);
    }
}

程序運行結果: 

(6)垃圾郵件的識別
這裏使用到了布隆過濾器。布隆過濾是一個很長的二進制向量和一系列隨機映射函數。
假定我們存儲一億個電子郵件地址,我們先建立一個十六億二進制(比特),即兩億字節的向量,然後將這十六億個二進制全部設置爲零。對於每一個電子郵件地址 X,我們根據電子郵件地址 X 用八個不同的隨機數產生器(F1,F2, ...,F8) 產生八個信息指紋(f1, f2, ..., f8)。再用一個隨機數產生器 G 把這八個信息指紋映射到 1 到十六億中的八個自然數 g1, g2, ...,g8。現在我們把這八個位置的二進制全部設置爲一。當我們對這一億個 email 地址都進行這樣的處理後。一個針對這些 email 地址的布隆過濾器就建成了。布隆過濾器的結構類似於下圖:

假設我們把x、y、z都映射到了布隆過濾器,圖中每一個數據對應布隆過濾器,上面介紹的郵件映射的是8位。在使用布隆過濾器進行判斷之前首先把所有的數據都映射到布隆過濾器。現在需要判斷w是否存在,就根據w找到w應該映射到的布隆過濾器的所有位的位置,如果它對應的所有映射位的位置值都位true,說明w存在,如果有一個以上映射的位的值爲fasle,則說明w不存在。

那麼如何用布隆過濾器來檢測一個可疑的電子郵件地址 Y 是否在黑名單中?我們用相同的八個隨機數產生器(F1, F2, ..., F8)對這個地址產生八個信息指紋 s1,s2,...,s8,然後將這八個指紋對應到布隆過濾器的八個二進制位,分別是 t1,t2,...,t8。如果 Y 在黑名單中,顯然,t1,t2,..,t8 對應的八個二進制一定是一。這樣在遇到任何在黑名單中的電子郵件地址,我們都能準確地發現。

布隆過濾器決不會漏掉任何一個在黑名單中的可疑地址。但是,它有一條不足之處。也就是它有極小的可能將一個不在黑名單中的電子郵件地址判定爲在黑名單中,因爲有可能某個好的郵件地址正巧對應個八個都被設置成一的二進制位。好在這種可能性很小。我們把它稱爲誤識概率。在上面的例子中,誤識概率在萬分之一以下。 

布隆過濾器的好處在於快速,省空間。但是有一定的誤識別率。常見的補救辦法是在建立一個小的白名單,存儲那些可能別誤判的郵件地址。

關於BitSet的應用就講到這裏啦。

轉自:https://blog.csdn.net/kongmin_123/article/details/82257209?from=singlemessage

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章