Útmutató a Java BitSet-hez
1. Áttekintés
Ebben az oktatóanyagban megnézzük, hogyan használhatjuk BitSets a bitek vektorának képviseletére.
Először azzal kezdjük, hogy miért nem használjuk a logikai []. Majd miután megismerte a BitSet belsőleg, közelebbről megvizsgáljuk az API-ját.
2. Bit tömb
A bittömbök tárolására és kezelésére azt lehet mondani, hogy használnunk kell logikai [] mint az adatstruktúránk. Első pillantásra ez ésszerű javaslatnak tűnhet.
Mindazonáltal logikai tag a logikai [] általában csak egy bit helyett egy bájtot fogyaszt. Tehát, ha szűk memóriaigényünk van, vagy csak a memóriaterület csökkentésére törekszünk, logikai [] messze nem ideálisak.
A dolgok konkrétabbá tétele érdekében nézzük meg, mennyi hely van a logikai [] 1024 elemmel fogyaszt:
logikai [] bitek = új logikai [1024]; System.out.println (ClassLayout.parseInstance (bits) .toPrintable ());
Ideális esetben 1024 bites memóriaméretet várunk ettől a tömbtől. A Java Object Layout (JOL) azonban egy teljesen más valóságot tár fel:
[Z objektum belső részei: OFFSET MÉRET TÍPUS LEÍRÁS ÉRTÉK 0 4 (objektum fejléc) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (objektum fejléc) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (objektum fejléc) 7b 12 07 00 (01111011 00010010 00000111 00000000) (463483) 12 4 (objektum fejléc) 00 04 00 00 (00000000 00000100 00000000 00000000) (1024) 16 1024 logikai érték [Z. N / A Példányméret: 1040 bájt
Ha figyelmen kívül hagyjuk az objektum fejlécének fejlécét, a tömb elemek 1024 bájtot fogyasztanak a várt 1024 bit helyett. Ez 700% -kal több memóriát jelent, mint amire számítottunk.
Aa címezhetőség kérdése és a szószakadás a legfőbb oka annak logikais nem csak egyetlen bit.
A probléma megoldásához numerikus adattípusok kombinációját használhatjuk (például hosszú) és bitenkénti műveletek. Ott a BitSet bejön.
3. Hogyan BitSet Művek
Amint azt korábban említettük, a zászlónkénti egy bit eléréséhez a BitSet Az API az alapvető numerikus adattípusok és a bitenkénti műveletek kombinációját használja.
Tegyük fel, hogy az egyszerűség kedvéért nyolc zászlót fogunk képviselni byte. Eleinte inicializáljuk a kislemez összes bitjét byte nullával:

Most, ha a bitet a harmadik pozícióba akarjuk állítani igaz, először az első számot kell balra tolnunk hárommal:

És akkor vagy eredménye az árammal byte érték:

Ugyanez történik, ha úgy dönt, hogy a bitet a hét indexbe állítja:

Amint a fentiekből látható, bal bit eltolást hajtunk végre hét bittel, és az eredményt kombináljuk az előzővel byte érték a vagy operátor.
3.1. Bit index megszerzése
Annak ellenőrzése, hogy egy adott bit index értéke-e igaz vagy sem, használjuk a és operátor. Például a következőképpen ellenőrizzük, hogy a három index be van-e állítva:
- Hajtsa végre a bal eltolást három bittel az első értéken
- Anding az eredmény az árammal byte érték
- Ha az eredmény nagyobb, mint nulla, akkor találtunk egyezést, és ez a bit index valóban be van állítva. Ellenkező esetben a kért index egyértelmű vagy egyenlő hamis

A fenti ábra a get index művelet lépéseit mutatja a harmadik indexhez. Ha egyértelmű indexről érdeklődünk, akkor az eredmény más lesz:

Mivel a és az eredmény nulla, a négy mutató egyértelmű.
3.2. A tároló termesztése
Jelenleg csak 8 bites vektort tárolhatunk. Ezen a korláton túllépve, csak egy tömböt kell használnunk bytes, egyetlen helyett byte, ez az!
Most, amikor egy adott indexet kell beállítanunk, megszereznünk vagy törölnünk kell, először meg kell találnunk a megfelelő tömb elemet. Tegyük fel például, hogy a 14. indexet állítjuk be:

Amint a fenti ábra mutatja, miután megtaláltuk a megfelelő tömb elemet, beállítottuk a megfelelő indexet.
Továbbá, ha itt 15-nél nagyobb indexet akarunk beállítani, akkor a BitSet először belső tömbjét bővíti. Csak a tömb kibővítése és az elemek másolása után állítja be a kért bitet. Ez némileg hasonlít ahhoz, hogy hogyan Tömb lista belsőleg működik.
Eddig a byte adattípus az egyszerűség kedvéért. A BitSet Az API azonban egy tömböt használ hosszú értékeket belsőleg.
4. A BitSet API
Most, hogy eleget tudunk az elméletről, itt az ideje, hogy lássuk, mi az BitSet Az API úgy néz ki.
Kezdőként hasonlítsuk össze a memória lábnyomát BitSet például 1024 bitrel a logikai [] láttuk korábban:
BitSet bitSet = új BitSet (1024); System.out.println (GraphLayout.parseInstance (bitSet) .toPrintable ());
Ezzel kinyomtatja a BitSet példány és a belső tömb mérete:
[e-mail védett] objektum külsők: CÍM MÉRET TÍPUS PATH VALUE 70f97d208 24 java.util.BitSet (object) 70f97d220 144 [J .words [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0, 0]
A fentiek szerint a hosszú[] 16 elemmel (16 * 64 bit = 1024 bit) belülről. Egyébként is, ez a példány összesen 168 bájtot használ, míg a logikai [] 1024 bájtot használtak.
Minél több bitünk van, annál jobban nő a lábnyomkülönbség. Például 1024 * 1024 bit tárolásához a logikai [] 1 MB-ot fogyaszt, és a BitSet példány körülbelül 130 KB-ot fogyaszt.
4.1. Konstrukció BitSets
A legegyszerűbb módszer a BitSet Például a no-arg konstruktort kell használni:
BitSet bitSet = new BitSet ();
Ez létrehozza a BitSet például egy hosszú[] egy méretű. Természetesen automatikusan növelheti ezt a tömböt, ha szükséges.
Lehetőség van a BitSet kezdeti bitszámmal:
BitSet bitSet = új BitSet (100_000);
Itt a belső tömbben elegendő elem lesz 100 000 bit tárolására. Ez a konstruktor akkor hasznos, ha már van ésszerű becslésünk a tárolandó bitek számáról. Ilyen használati esetekben megakadályozhatja vagy csökkentheti a tömb elemek felesleges másolását, miközben növekszik.
Még lehetséges a BitSet egy létezőtől hosszú[], byte[], LongBuffer, és ByteBuffer. Például itt létrehozunk egy BitSet egy adott példány hosszú[]:
BitSet bitSet = BitSet.valueOf (új hosszú [] {42, 12});
A programnak még három túlterhelt változata van értéke() statikus gyári módszer a többi említett típus támogatására.
4.2. Bitek beállítása
Beállíthatjuk egy adott index értékét igaz használni a készlet (index) módszer:
BitSet bitSet = new BitSet (); bitSet.set (10); assertThat (bitSet.get (10)). isTrue ();
Az indexek szokás szerint nulla alapúak. Még egy bit tartományt is beállíthat igaz használni a készlet (fromInclusive, toExclusive) módszer:
bitSet.set (20, 30); mert (int i = 20; i <= 29; i ++) {assertThat (bitSet.get (i)). isTrue (); } assertThat (bitSet.get (30)). isFalse ();
Amint a metódus aláírásából kitűnik, a kezdő index inkluzív, a vége pedig exkluzív.
Amikor azt mondjuk, hogy indexet állítunk be, akkor általában azt értjük, hogy azt állítsuk be igaz. E terminológia ellenére beállíthatunk egy adott bit indexet hamis használni a készlet (index, logikai érték) módszer:
bitSet.set (10, hamis); assertThat (bitSet.get (10)). isFalse ();
Ez a verzió támogatja az értéktartomány beállítását is:
bitSet.set (20, 30, hamis); mert (int i = 20; i <= 29; i ++) {assertThat (bitSet.get (i)). isFalse (); }
4.3. Bit törlése
Ahelyett, hogy egy adott bit indexet állítana hamis, egyszerűen törölhetjük a tiszta (index) módszer:
bitSet.set (42); assertThat (bitSet.get (42)). isTrue (); bitSet.clear (42); assertThat (bitSet.get (42)). isFalse ();
Sőt, a bitekkel egy sor bitet is törölhetünk tiszta (fromInclusive, toExclusive) túlterhelt verzió:
bitSet.set (10, 20); mert (int i = 10; i <20; i ++) {assertThat (bitSet.get (i)). isTrue (); } bitSet.clear (10, 20); mert (int i = 10; i <20; i ++) {assertThat (bitSet.get (i)). isFalse (); }
Érdekes módon, ha argumentum nélkül adjuk meg ezt a módszert, akkor az összes beállított bitet törli:
bitSet.set (10, 20); bitSet.clear (); mert (int i = 0; i <100; i ++) {assertThat (bitSet.get (i)). isFalse (); }
A fentiek szerint, miután felhívtuk a egyértelmű() módszerrel az összes bit nullára van állítva.
4.4. Bitek megszerzése
Eddig a get (index) módszer elég széles körben. Ha a kért bit index be van állítva, akkor ez a módszer visszatér igaz. Ellenkező esetben visszatér hamis:
bitSet.set (42); assertThat (bitSet.get (42)). isTrue (); assertThat (bitSet.get (43)). isFalse ();
Hasonló készlet és egyértelmű, a bit használatával számos indexet kaphatunk get (fromInclusive, toExclusive) módszer:
bitSet.set (10, 20); BitSet newBitSet = bitSet.get (10, 20); mert (int i = 0; i <10; i ++) {assertThat (newBitSet.get (i)). isTrue (); }
Amint az fent látható, ez a módszer egy másikat ad vissza BitSet a jelenlegi [20, 30) tartományában. Vagyis a bitSet változó egyenlő a newBitSet változó.
4.5. Megfordított bitek
Az aktuális bitindex-érték tagadására használhatjuk a flip (index) módszer. Vagyis megfordul igaz értékeket hamis és fordítva:
bitSet.set (42); bitSet.flip (42); assertThat (bitSet.get (42)). isFalse (); bitSet.flip (12); assertThat (bitSet.get (12)). isTrue ();
Hasonlóképpen elérhetjük ugyanazt az értéktartományra a flip (fromInclusive, toExclusive) módszer:
bitSet.flip (30, 40); mert (int i = 30; i <40; i ++) {assertThat (bitSet.get (i)). isTrue (); }
4.6. Hossz
Három hossz-szerű módszer létezik a BitSet. A méret() metódus adja vissza a belső tömb által képviselt bitek számát. Például, mivel a no-arg konstruktor lefoglal egy hosszú[] tömb egy elemmel, majd a méret() 64-et ad vissza érte:
BitSet defaultBitSet = new BitSet (); assertThat (alapértelmezettBitSet.size ()). isEqualTo (64);
Egy 64 bites számmal csak 64 bitet tudunk képviselni. Természetesen ez megváltozik, ha kifejezetten átadjuk a bitek számát:
BitSet bitSet = új BitSet (1024); assertThat (bitSet.size ()). isEqualTo (1024);
Sőt, a kardinalitás () metódus az a-ban megadott bitek számát jelenti BitSet:
assertThat (bitSet.cardinality ()). isEqualTo (0); bitSet.set (10, 30); assertThat (bitSet.cardinality ()). isEqualTo (30-10);
Eleinte ez a módszer nullát ad vissza, mint minden bit hamis. Miután beállította a [10, 30) tartományt igaz, aztán a kardinalitás () metódus hívás 20
Továbbá a hossz() metódus az utolsó beállított bit indexe utáni egy indexet adja vissza:
assertThat (bitSet.length ()) isEqualTo (30); bitSet.set (100); assertThat (bitSet.length ()). isEqualTo (101);
Eleinte az utolsó beállított index 29, tehát ez a módszer 30-at ad vissza. Amikor a 100 indexet igazra állítjuk, akkor a hossz() metódus 101-et ad vissza. Érdemes megemlíteni azt is, hogy ez a módszer nullát ad vissza, ha minden bit tiszta.
Végül a üres() metódus visszatér hamis amikor legalább egy beállított bit van a BitSet. Ellenkező esetben visszatér igaz:
assertThat (bitSet.isEmpty ()). isFalse (); bitSet.clear (); assertThat (bitSet.isEmpty ()). isTrue ();
4.7. Kombinálás másokkal BitSets
A metszi (BitSet) A módszer egy másikat igényel BitSet és visszatér igaz amikor kettő BitSetvan valami közös. Vagyis legalább egy készlet bit van ugyanabban az indexben:
BitSet first = új BitSet (); első.halmaz (5, 10); BitSet second = új BitSet (); második készlet (7, 15); assertThat (első. közbeiktatja (második)). igaz ();
A [7, 9] tartomány mindkettőben be van állítva BitSets, így ez a módszer visszatér igaz.
Lehetséges a logikai műveletek végrehajtása is és kettőn végzett művelet BitSets:
első.és (második); assertThat (first.get (7)). isTrue (); assertThat (first.get (8)). isTrue (); assertThat (first.get (9)). isTrue (); assertThat (first.get (10)). isFalse ();
Ez logikusan fog végrehajtani és a kettő között BitSets módosítja a első változó az eredménnyel. Hasonlóképpen elvégezhetünk egy logikai műveletet is xor kettőn BitSets is:
első.tiszta (); első.halmaz (5, 10); első.xor (második); mert (int i = 5; i <7; i ++) {assertThat (first.get (i)). is True (); } for (int i = 10; i <15; i ++) {assertThat (first.get (i)). isTrue (); }
Vannak más módszerek is, például a andNot (BitSet) vagy a vagy (BitSet),amely kettőn más logikai műveletet is képes végrehajtani BitSets.
4.8. Vegyes
A Java 8-tól kezdve van egy folyam() módszer az a BitSet. Például:
BitSet bitSet = new BitSet (); bitSet.set (15, 25); bitSet.stream (). forEach (System.out :: println);
Ez kinyomtatja az összes beállított bitet a konzolra. Mivel ez egy IntStream, elvégezhetünk olyan általános numerikus műveleteket, mint az összegzés, az átlag, a számlálás stb. Például itt számoljuk a beállított bitek számát:
assertThat (bitSet.stream (). count ()). isEqualTo (10);
Is, a nextSetBit (fromIndex) metódus a következő beállított bit indexet adja vissza a fromIndex:
assertThat (bitSet.nextSetBit (13)). isEqualTo (15);
A fromIndex maga is benne van ebben a számításban. Amikor nincs igaz kicsit maradt a BitSet, -1-re tér vissza:
assertThat (bitSet.nextSetBit (25)). isEqualTo (-1);
Hasonlóképpen, a nextClearBit (fromIndex) a következő tiszta indexet adja vissza a fromIndex:
assertThat (bitSet.nextClearBit (23)). isEqualTo (25);
Másrészt a előzőClearBit (fromIndex) a legközelebbi tiszta index indexét adja vissza az ellenkező irányba:
assertThat (bitSet.previousClearBit (24)). isEqualTo (14);
Ugyanez vonatkozik előzőSetBit (fromIndex):
assertThat (bitSet.previousSetBit (29)). isEqualTo (24); assertThat (bitSet.previousSetBit (14)). isEqualTo (-1);
Sőt, átalakíthatjuk a BitSet a byte[] vagy a hosszú[] használni a toByteArray () vagy toLongArray () módszerek, ill.
bájt [] bájt = bitSet.toByteArray (); long [] longs = bitSet.toLongArray ();
5. Következtetés
Ebben az oktatóanyagban láttuk, hogyan használhatjuk BitSets a bitek vektorának képviseletére.
Eleinte megismertük a nem használat indokait logikai [] bitek vektorának képviseletére. Aztán láttuk, hogy a BitSet belül működik, és hogyan néz ki az API.
Szokás szerint az összes példa elérhető a GitHubon.