最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java位集合之BitMap、BitSet和布隆過濾器示例解析

 更新時間:2024年12月27日 11:11:47   作者:愛吃牛肉的大老虎  
這篇文章主要介紹了Java中位集合的基本概念、實現(xiàn)方法以及應(yīng)用場景,包括Bit-Map、BitSet和BloomFilter,Bit-Map通過位操作高效地存儲和查詢元素狀態(tài),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

1 Java位集合

前幾天剛學(xué)習(xí)了Redis中位操作命令,今天順便學(xué)下java中位集合

1.1 Bit-Map

1.1.1 簡介

Bit-map的基本思想就是用一個bit位來標(biāo)記某個元素對應(yīng)的Value,而Key即是該元素。由于采用了Bit為單位來存儲數(shù)據(jù),因此在存儲空間方面,可以大大節(jié)省。(即:節(jié)省存儲空間 

Bitmap主要用于快速檢索關(guān)鍵字狀態(tài),通常要求關(guān)鍵字是一個連續(xù)的序列(或者關(guān)鍵字是一個連續(xù)序列中的大部分), 最基本的情況,使用1bit表示一個關(guān)鍵字的狀態(tài)(可標(biāo)示兩種狀態(tài)),根據(jù)需要也可以使用2bit(表示4種狀態(tài)),3bit(表示8種狀態(tài))。

Bitmap的主要應(yīng)用場合:表示連續(xù)(或接近連續(xù),即大部分會出現(xiàn))的關(guān)鍵字序列的狀態(tài)(狀態(tài)數(shù)/關(guān)鍵字個數(shù) 越小越好)。
32位機(jī)器上,對于一個整型數(shù),比如int a=1 在內(nèi)存中占32bit位(一個字寬4Byte),這是為了方便計算機(jī)的運(yùn)算。但是對于某些應(yīng)用場景而言,這屬于一種巨大的浪費(fèi),因為我們可以用對應(yīng)的32bit位對應(yīng)存儲十進(jìn)制的0-31個數(shù),而這就是Bit-map的基本思想。Bit-map算法利用這種思想處理大量數(shù)據(jù)的排序、查詢以及去重。

假設(shè)有這樣一個需求:

在20億個隨機(jī)整數(shù)中找出某個數(shù)m是否存在其中,并假設(shè)32位操作系統(tǒng),4G內(nèi)存
Java中,int占4字節(jié),1字節(jié)=8位(1 byte = 8 bit)
如果每個數(shù)字用int存儲,那就是20億個int,因而占用的空間約為 (2000000000*4/1024/1024/1024)≈7.45 G如果按位存儲就不一樣了,20億個數(shù)就是20億位,占用空間約為 (2000000000/8/1024/1024/1024)≈0.233 G

Bit-Map的每一位表示一個數(shù),0表示不存在,1表示存在,這正符合二進(jìn)制,這樣我們可以很容易表示{1,2,4,6}這幾個數(shù):

計算機(jī)內(nèi)存分配的最小單位是字節(jié),也就是8位,那如果要表示{12,13,15}怎么辦呢,是在另一個8位上表示了:

這樣的話,好像變成一個二維數(shù)組了

1個int占32位,那么我們只需要申請一個int數(shù)組長度為 int tmp[1+N/32] 即可存儲,其中N表示要存儲的這些數(shù)中的最大值,于是:

tmp[0]:可以表示0~31
tmp[1]:可以表示32~63
tmp[2]:可以表示64~95
。。。

如此一來,給定任意整數(shù)M,那么M/32就得到下標(biāo),M%32就知道它在此下標(biāo)的哪個位置

1.1.2 添加

這里有個問題,我們怎么把一個數(shù)放進(jìn)去呢?例如,想把5這個數(shù)字放進(jìn)去,怎么做呢?
首先,5/32=0,5%32=5,也是說它應(yīng)該在tmp[0]的第5個位置,那我們把1向左移動5位,然后按位或

換成二進(jìn)制就是

這就相當(dāng)于

86 | 32 = 118
86 | (1<<5) = 118
b[0] = b[0] | (1<<5)

也就是說,要想插入一個數(shù),將1左移帶代表該數(shù)字的那一位,然后與原數(shù)進(jìn)行按位或操作

化簡一下,就是 86 + (5/8) | (1<<(5%8))
因此,公式可以概括為:p + (i/8)|(1<<(i%8)) 其中,p表示現(xiàn)在的值,i表示待插入的數(shù)

1.1.3 清除

以上是添加,那如果要清除該怎么做呢?

還是上面的例子,假設(shè)我們要6移除,該怎么做呢?

從圖上看,只需將該數(shù)所在的位置為0即可

首先把1左移6位,就到達(dá)6這個數(shù)字所代表的位,然后按位取反,最后與原數(shù)按位與,這樣就把該位置為0了

b[0] = b[0] & (~(1<<6))
b[0] = b[0] & (~(1<<(i%8)))

1.1.4 查找

前面我們也說了,每一位代表一個數(shù)字,1表示有(或者說存在),0表示無(或者說不存在)。通過把該為置為1或者0來達(dá)到添加和清除的效果,那么判斷一個數(shù)存不存在就是判斷該數(shù)所在的位是0還是1

假設(shè),我們想知道3在不在,那么只需判斷 b[0] & (1<<3) 如果這個值是0,則不存在,如果是1,就表示存在

1.2 Bitmap應(yīng)用

大量數(shù)據(jù)的快速排序、查找、去重

1.2.1 快速排序

假設(shè)我們要對0-7內(nèi)的5個元素(4,7,2,5,3)排序(這里假設(shè)這些元素沒有重復(fù)),我們就可以采用Bit-map的方法來達(dá)到排序的目的。

要表示8個數(shù),我們就只需要8個Bit(1Bytes),首先我們開辟1Byte的空間,將這些空間的所有Bit位都置為0,然后將對應(yīng)位置為1。

最后,遍歷一遍Bit區(qū)域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達(dá)到了排序的目的,時間復(fù)雜度O(n)。

優(yōu)點:

運(yùn)算效率高,不需要進(jìn)行比較和移位;
占用內(nèi)存少,比如N=10000000;只需占用內(nèi)存為N/8=1250000Byte=1.25M

缺點:

所有的數(shù)據(jù)不能重復(fù)。即不可對重復(fù)的數(shù)據(jù)進(jìn)行排序和查找。
只有當(dāng)數(shù)據(jù)比較密集時才有優(yōu)勢

1.2.2 快速去重

20億個整數(shù)中找出不重復(fù)的整數(shù)的個數(shù),內(nèi)存不足以容納這20億個整數(shù)。

首先,根據(jù)內(nèi)存空間不足以容納這20億個整數(shù)我們可以快速的聯(lián)想到Bit-map。下邊關(guān)鍵的問題就是怎么設(shè)計我們的Bit-map來表示這20億個數(shù)字的狀態(tài)了。其實這個問題很簡單,一個數(shù)字的狀態(tài)只有三種,分別為不存在,只有一個,有重復(fù)。因此,我們只需要2bits就可以對一個數(shù)字的狀態(tài)進(jìn)行存儲了,假設(shè)我們設(shè)定一個數(shù)字不存在為00,存在一次01,存在兩次及其以上為11。那我們大概需要存儲空間2G左右。

接下來的任務(wù)就是把這20億個數(shù)字放進(jìn)去(存儲),如果對應(yīng)的狀態(tài)位為00,則將其變?yōu)?1,表示存在一次;如果對應(yīng)的狀態(tài)位為01,則將其變?yōu)?1,表示已經(jīng)有一個了,即出現(xiàn)多次;如果為11,則對應(yīng)的狀態(tài)位保持不變,仍表示出現(xiàn)多次。

最后,統(tǒng)計狀態(tài)位為01的個數(shù),就得到了不重復(fù)的數(shù)字個數(shù),時間復(fù)雜度為O(n)。

1.2.3 快速查找

這就是我們前面所說的了,int數(shù)組中的一個元素是4字節(jié)占32位,那么除以32就知道元素的下標(biāo),對32求余數(shù)(%32)就知道它在哪一位,如果該位是1,則表示存在。

1.3 BitSet

BitSet實現(xiàn)了一個位向量,它可以根據(jù)需要增長。每一位都有一個布爾值。一個BitSet的位可以被非負(fù)整數(shù)索引(意思就是每一位都可以表示一個非負(fù)整數(shù))。可以查找、設(shè)置、清除某一位。通過邏輯運(yùn)算符可以修改另一個BitSet的內(nèi)容。默認(rèn)情況下,所有的位都有一個默認(rèn)值false。

public class BitSet implements Cloneable, java.io.Serializable {
    /*
     * BitSets are packed into arrays of "words."  Currently a word is
     * a long, which consists of 64 bits, requiring 6 address bits.
     * The choice of word size is determined purely by performance concerns.
     */
    private final static int ADDRESS_BITS_PER_WORD = 6;
    private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
    private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1;

    /* Used to shift left or right for a partial word mask */
    private static final long WORD_MASK = 0xffffffffffffffffL;

    /**
     * @serialField bits long[]
     *
     * The bits in this BitSet.  The ith bit is stored in bits[i/64] at
     * bit position i % 64 (where bit position 0 refers to the least
     * significant bit and 63 refers to the most significant bit).
     */
    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("bits", long[].class),
    };

    /**
     * The internal field corresponding to the serialField "bits".
     */
    private long[] words;

    /**
     * The number of words in the logical size of this BitSet.
     */
    private transient int wordsInUse = 0;

    /**
     * Given a bit index, return word index containing it.
     */
    private static int wordIndex(int bitIndex) {
        return bitIndex >> ADDRESS_BITS_PER_WORD;
    }
    /**
     * Creates a new bit set. All bits are initially {@code false}.
     */
    public BitSet() {
        initWords(BITS_PER_WORD);
        sizeIsSticky = false;
    }

    /**
     * Creates a bit set whose initial size is large enough to explicitly
     * represent bits with indices in the range {@code 0} through
     * {@code nbits-1}. All bits are initially {@code false}.
     *
     * @param  nbits the initial size of the bit set
     * @throws NegativeArraySizeException if the specified initial size
     *         is negative
     */
    public BitSet(int nbits) {
        // nbits can't be negative; size 0 is OK
        if (nbits < 0)
            throw new NegativeArraySizeException("nbits < 0: " + nbits);

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

用一個long數(shù)組來存儲,初始長度64,set值的時候首先右移6位(相當(dāng)于除以64)計算在數(shù)組的什么位置,然后更改狀態(tài)位

別的看不懂不要緊,看懂這兩句就夠了:

int wordIndex = wordIndex(bitIndex);
words[wordIndex] |= (1L << bitIndex);

1.4 Bloom Filters

1.4.1 簡介

Bloom filter 是一個數(shù)據(jù)結(jié)構(gòu),它可以用來判斷某個元素是否在集合內(nèi),具有運(yùn)行快速,內(nèi)存占用小的特點。
而高效插入和查詢的代價就是,Bloom Filter 是一個基于概率的數(shù)據(jù)結(jié)構(gòu):它只能告訴我們一個元素絕對不在集合內(nèi)或可能在集合內(nèi)。
Bloom filter 的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)是一個 比特向量(可理解為數(shù)組)。
主要應(yīng)用于大規(guī)模數(shù)據(jù)下不需要精確過濾的場景,如檢查垃圾郵件地址,爬蟲URL地址去重,解決緩存穿透問題等

如果想判斷一個元素是不是在一個集合里,一般想到的是將集合中所有元素保存起來,然后通過比較確定。鏈表、樹、散列表(哈希表)等等數(shù)據(jù)結(jié)構(gòu)都是這種思路,但是隨著集合中元素的增加,需要的存儲空間越來越大;同時檢索速度也越來越慢,檢索時間復(fù)雜度分別是O(n)、O(log n)、O(1)。

布隆過濾器的原理是:當(dāng)一個元素被加入集合時,通過 K 個散列函數(shù)將這個元素映射成一個位數(shù)組(Bit array)中的 K 個點,把它們置為 1 。檢索時,只要看看這些點是不是都是1就知道元素是否在集合中;如果這些點有任何一個 0,則被檢元素一定不在;如果都是1,則被檢元素很可能在(之所以說可能是誤差的存在)。

1.4.2 BloomFilter 流程

BloomFilter 流程:

  • 首先需要 k 個 hash 函數(shù),每個函數(shù)可以把 key 散列成為 1 的整數(shù);
  • 初始化時,需要一個長度為 n 比特的數(shù)組,每個比特位初始化為 0;
  • 某個 key 加入集合時,用 k 個 hash 函數(shù)計算出 k 個散列值,并把數(shù)組中對應(yīng)的比特位置為 1;
  • 判斷某個 key 是否在集合時,用 k 個 hash 函數(shù)計算出 k 個散列值,并查詢數(shù)組中對應(yīng)的比特位,如果所有的比特位都是1,認(rèn)為在集合中。

1.4.3 應(yīng)用場景

布隆過濾器因為他的效率非常高,所以被廣泛的使用,比較典型的場景有以下幾個:

  • 網(wǎng)頁爬蟲: 爬蟲程序可以使用布隆過濾器來過濾掉已經(jīng)爬取過的網(wǎng)頁,避免重復(fù)爬取和浪費(fèi)資源。
  • 緩存系統(tǒng): 緩存系統(tǒng)可以使用布隆過濾器來判斷一個查詢是否可能存在于緩存中,從而減少查詢緩存的次數(shù),提高查詢效率。布隆過濾器也經(jīng)常用來解決緩存穿透的問題。
  • 分布式系統(tǒng): 在分布式系統(tǒng)中,可以使用布隆過濾器來判斷一個元素是否存在于分布式緩存中,避免在所有節(jié)點上進(jìn)行查詢,減少網(wǎng)絡(luò)負(fù)載。
  • 垃圾郵件過濾: 布隆過濾器可以用于判斷一個郵件地址是否在垃圾郵件列表中,從而過濾掉垃圾郵件。
  • 黑名單過濾: 布隆過濾器可以用于判斷一個IP地址或手機(jī)號碼是否在黑名單中,從而阻止惡意請求。

1.4.4 如何使用

Java中可以使用第三方庫來實現(xiàn)布隆過濾器,常見的有Google Guava庫和Apache Commons庫以及Redis。

如Guava:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterExample {
    public static void main(String[] args) {
        // 創(chuàng)建布隆過濾器,預(yù)計插入100個元素,誤判率為0.01
        BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 100, 0.01);
        // 插入元素
        bloomFilter.put("Lynn");
        bloomFilter.put("666");
        bloomFilter.put("八股文");
        // 判斷元素是否存在
        System.out.println(bloomFilter.mightContain("Lynn")); // true
        System.out.println(bloomFilter.mightContain("張三"));  // false
    }
}

Apache Commons:

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.collections4.BloomFilter;
import org.apache.commons.collections4.functors.HashFunctionIdentity;
public class BloomFilterExample {
    public static void main(String[] args) {
        // 創(chuàng)建布隆過濾器,預(yù)計插入100個元素,誤判率為0.01
        BloomFilter<String> bloomFilter = new BloomFilter<>(HashFunctionIdentity.hashFunction(StringUtils::hashCode), 100, 0.01);
        // 插入元素
        bloomFilter.put("Lynn");
        bloomFilter.put("666");
        bloomFilter.put("八股文");
        // 判斷元素是否存在
        System.out.println(bloomFilter.mightContain("Lynn")); // true
        System.out.println(bloomFilter.mightContain("張三"));  // false
    }
}

Redis中可以通過Bloom模塊來使用,使用Redisson可以:

首先創(chuàng)建一個RedissonClient對象,然后通過該對象獲取一個RBloomFilter對象,使用tryInit方法來初始化布隆過濾器,指定了最多能添加的元素數(shù)量為100,誤判率為0.01。

然后,使用add方法將元素"犬小哈"、"666"和"八股文"添加到布隆過濾器中,使用contains方法來檢查元素是否存在于布隆過濾器中。

Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("myfilter");
bloomFilter.tryInit(100, 0.01);
bloomFilter.add("Lynn");
bloomFilter.add("666");
bloomFilter.add("八股文");
System.out.println(bloomFilter.contains("Lynn"));
System.out.println(bloomFilter.contains("張三"));
redisson.shutdown();

或者Jedis也可以:

Jedis jedis = new Jedis("localhost");
jedis.bfCreate("myfilter", 100, 0.01);
jedis.bfAdd("myfilter", "Lynn");
jedis.bfAdd("myfilter", "666");
jedis.bfAdd("myfilter", "八股文");
System.out.println(jedis.bfExists("myfilter", "Lynn"));
System.out.println(jedis.bfExists("myfilter", "張三"));
jedis.close();

總結(jié) 

到此這篇關(guān)于Java位集合之BitMap、BitSet和布隆過濾器的文章就介紹到這了,更多相關(guān)Java位集合BitMap、BitSet和布隆過濾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java正則表達(dá)式驗證固定電話號碼符合性

    Java正則表達(dá)式驗證固定電話號碼符合性

    這篇文章主要介紹了Java正則表達(dá)式驗證固定電話號碼符合性的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-09-09
  • SpringCloud中Sentinel基礎(chǔ)場景和異常處理方式

    SpringCloud中Sentinel基礎(chǔ)場景和異常處理方式

    這篇文章主要介紹了SpringCloud中Sentinel基礎(chǔ)場景和異常處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • SpringBoot中實現(xiàn)多文件打包下載的兩種方案

    SpringBoot中實現(xiàn)多文件打包下載的兩種方案

    在Spring Boot中實現(xiàn)多文件打包下載,一般是將多個文件壓縮成一個ZIP文件再進(jìn)行下載,以下是兩種典型實現(xiàn)方案以及代碼示例,需要的朋友可以參考下
    2025-09-09
  • 淺談JVM垃圾回收有哪些常用算法

    淺談JVM垃圾回收有哪些常用算法

    今天給大家?guī)淼氖顷P(guān)于Java虛擬機(jī)的相關(guān)知識,文章圍繞著JVM垃圾回收有哪些常用算法展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • Java深入淺出講解String類常見方法

    Java深入淺出講解String類常見方法

    在C語言中,如果要表示字符串而且對字符串進(jìn)行操作的話,依靠的是數(shù)組和指針,而Java中提供了String類用來專門表示字符串,String類中常見的方法,以及一些細(xì)節(jié)是本篇重點
    2022-04-04
  • Springboot使用redisson?+?自定義注解實現(xiàn)消息的發(fā)布訂閱(解決方案)

    Springboot使用redisson?+?自定義注解實現(xiàn)消息的發(fā)布訂閱(解決方案)

    Redisson是一個基于Redis的Java駐留內(nèi)存數(shù)據(jù)網(wǎng)格(In-Memory?Data?Grid)和分布式鎖框架,它提供了一系列的分布式Java對象和服務(wù),可以幫助開發(fā)者更方便地使用Redis作為數(shù)據(jù)存儲和分布式鎖的解決方案,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • MyBatis-Plus通用中等、大量數(shù)據(jù)分批查詢和處理方法

    MyBatis-Plus通用中等、大量數(shù)據(jù)分批查詢和處理方法

    文章介紹MyBatis-Plus分頁查詢處理,通過函數(shù)式接口與Lambda表達(dá)式實現(xiàn)通用邏輯,方法抽象但功能強(qiáng)大,建議擴(kuò)展分批處理及流式查詢以優(yōu)化大數(shù)據(jù)量處理,感興趣的朋友一起看看吧
    2025-07-07
  • SpringSecurity詳解整合JWT實現(xiàn)全過程

    SpringSecurity詳解整合JWT實現(xiàn)全過程

    JWT作為一個開放的標(biāo)準(zhǔn)(?RFC?7519?),定義了一種簡潔的,自包含的方法用于通信雙方之間以Json對象的形式安全的傳遞信息。接下來通過本文給大家介紹springSecurity+jwt實現(xiàn)互踢功能,需要的朋友可以參考下
    2022-07-07
  • springMVC實現(xiàn)前臺帶進(jìn)度條文件上傳的示例代碼

    springMVC實現(xiàn)前臺帶進(jìn)度條文件上傳的示例代碼

    本篇文章主要介紹了springMVC實現(xiàn)前臺帶進(jìn)度條文件上傳的示例代碼,具有一定的參考價值,有興趣的可以了解一下。
    2017-01-01
  • SpringBoot接入支付寶支付的方法步驟

    SpringBoot接入支付寶支付的方法步驟

    這篇文章主要介紹了SpringBoot接入支付寶支付的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12

最新評論

宝兴县| 肇州县| 调兵山市| 噶尔县| 富宁县| 石狮市| 仁布县| 日喀则市| 苏尼特左旗| 类乌齐县| 武定县| 九江县| 左权县| 桑植县| 静宁县| 潞西市| 浮山县| 洞头县| 始兴县| 金川县| 乌拉特中旗| 白朗县| 乐东| 洪湖市| 甘德县| 闸北区| 商河县| 明光市| 沅江市| 尤溪县| 普陀区| 平潭县| 金秀| 霍城县| SHOW| 邢台市| 介休市| 黎城县| 曲周县| 阆中市| 上饶县|