All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pascal.taie.util.collection.SparseBitSet Maven / Gradle / Ivy

The newest version!
/*
 * Tai-e: A Static Analysis Framework for Java
 *
 * Copyright (C) 2022 Tian Tan 
 * Copyright (C) 2022 Yue Li 
 *
 * This file is part of Tai-e.
 *
 * Tai-e is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * Tai-e is distributed in the hope that it will be useful,but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Tai-e. If not, see .
 */

package pascal.taie.util.collection;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serial;
import java.io.Serializable;

/**
 * Sparse bit set. This implementation groups bits into blocks, and it could
 * avoid allocating words to represent continuous zero bits when possible.
 * This design saves memory and improves efficiency of set iterations.
 * 

* This implementation uses core design and some code from * https://github.com/brettwooldridge/SparseBitSet * We rewrite most code to support the operations that we need * and improve the readability. */ public class SparseBitSet extends AbstractBitSet implements Serializable { // TODO: unify level1/2/3 and table/area/block // Currently: // w1/level1 = table // w2/level2 = area // w3/level3 = block //============================================================================== // The critical parameters. These are set up so that the compiler may // pre-compute all the values as compile-time constants. //============================================================================== /** * The number of bits in a positive integer, and the size of permitted index * of a bit in the bit set. */ private static final int INDEX_SIZE = Integer.SIZE - 1; /** * LEVEL3 is the number of bits of the level3 address. */ private static final int LEVEL3 = 5; // Do not change! /** * LEVEL2 is the number of bits of the level2 address. */ private static final int LEVEL2 = 5; // Do not change! /** * LEVEL1 is the number of bits of the level1 address. */ private static final int LEVEL1 = INDEX_SIZE - LEVEL2 - LEVEL3 - ADDRESS_BITS_PER_WORD; /** * MAX_LENGTH1 is the maximum number of entries in the level1 set array. */ private static final int MAX_LENGTH1 = 1 << LEVEL1; /** * LENGTH2 is the number of entries in the any level2 area. */ private static final int LENGTH2 = 1 << LEVEL2; /** * LENGTH3 is the number of entries in the any level3 block. */ private static final int LENGTH3 = 1 << LEVEL3; /** * The shift to create the word index. (I.e., move it to the right end) */ static final int SHIFT3 = ADDRESS_BITS_PER_WORD; /** * MASK3 is the mask to extract the LEVEL3 address from a word index * (after shifting by SHIFT3). */ private static final int MASK3 = LENGTH3 - 1; /** * SHIFT2 is the shift to bring the level2 address (from the word index) to * the right end (i.e., after shifting by SHIFT3). */ static final int SHIFT2 = LEVEL3; /** * MASK2 is the mask to extract the LEVEL2 address from a word index * (after shifting by SHIFT3 and SHIFT2). */ private static final int MASK2 = LENGTH2 - 1; /** * SHIFT1 is the shift to bring the level1 address (from the word index) to * the right end (i.e., after shifting by SHIFT3). */ static final int SHIFT1 = LEVEL2 + LEVEL3; /** * UNIT is the greatest number of bits that can be held in one level1 entry. * That is, bits per word by words per level3 block by blocks per level2 area. */ private static final int UNIT = LENGTH2 * LENGTH3 * BITS_PER_WORD; /** * BITS_PER_WORD_SIZE is maximum index of a bit in a ADDRESS_BITS_PER_WORD word. */ private static final int WORD_SIZE = BITS_PER_WORD - 1; /** * LENGTH3_SIZE is maximum index of a LEVEL3 page. */ private static final int LENGTH3_SIZE = LENGTH3 - 1; /** * LENGTH2_SIZE is maximum index of a LEVEL2 page. */ private static final int LENGTH2_SIZE = LENGTH2 - 1; // ------------------------------------------------------------------------ // instance fields // ------------------------------------------------------------------------ /** * The storage for this SparseBitSet. The ith bit is stored in a word * represented by a long value, and is at bit position i % 64 * within that word (where bit position 0 refers to the least significant bit * and 63 refers to the most significant bit). *

* The words are organized into blocks, and the blocks are accessed by two * additional levels of array indexing. */ private transient long[][][] table; /** * For the current size of the bits array, this is the maximum possible * length of the bit set, i.e., the index of the last possible bit, plus one. * Note: this not the value returned by length(). * * @see #resize(int) * @see #length() */ private transient int bitsLength; private transient State state; public SparseBitSet() { this(1); } public SparseBitSet(int nbits) { if (nbits < 0) { throw new NegativeArraySizeException("nbits < 0: " + nbits); } resize(nbits - 1); state = new State(); updateState(); } @Override public boolean set(int bitIndex) { if ((bitIndex + 1) < 1) { throw new IndexOutOfBoundsException("bitIndex=" + bitIndex); } if (bitIndex >= bitsLength) { resize(bitIndex); } final int w = wordIndex(bitIndex); final int w1 = level1Index(w); final int w2 = level2Index(w); long[] a3 = getOrCreateBlock(w1, w2); int w3 = level3Index(w); long oldWord = a3[w3]; long newWord = oldWord | (1L << bitIndex); if (oldWord != newWord) { a3[w3] = newWord; invalidateState(); return true; } return false; } @Override public boolean clear(int bitIndex) { if ((bitIndex + 1) < 1) { throw new IndexOutOfBoundsException("bitIndex=" + bitIndex); } if (bitIndex >= bitsLength) { return false; } final int w = wordIndex(bitIndex); long[][] a2; if ((a2 = table[level1Index(w)]) == null) { return false; } long[] a3; if ((a3 = a2[level2Index(w)]) == null) { return false; } int w3 = level3Index(w); long oldWord = a3[w3]; long newWord = oldWord & ~(1L << bitIndex); // Clear the indicated bit if (oldWord != newWord) { // In the interests of speed, no check is made here on whether the // level3 block goes to all zero. This may be found and corrected // in some later operation. a3[w3] = newWord; invalidateState(); return true; } return true; } @Override public boolean get(int bitIndex) { if ((bitIndex + 1) < 1) { throw new IndexOutOfBoundsException("bitIndex=" + bitIndex); } final int w = wordIndex(bitIndex); long[][] a2; long[] a3; return bitIndex < bitsLength && (a2 = table[level1Index(w)]) != null && (a3 = a2[level2Index(w)]) != null && ((a3[level3Index(w)] & (1L << bitIndex)) != 0); } @Override public void flip(int bitIndex) { if ((bitIndex + 1) < 1) { throw new IndexOutOfBoundsException("bitIndex=" + bitIndex); } if (bitIndex >= bitsLength) { resize(bitIndex); } final int w = wordIndex(bitIndex); final int w1 = level1Index(w); final int w2 = level2Index(w); long[] a3 = getOrCreateBlock(w1, w2); a3[level3Index(w)] ^= (1L << bitIndex); // Flip the designated bit invalidateState(); } @Override public int nextSetBit(int fromIndex) { // The index value of this method is permitted to be Integer.MAX_VALUE, // as this is needed to make the loop defined above work: just in case the // bit labelled Integer.MAX_VALUE-1 is set. This case is not optimised: // but eventually -1 will be returned, as this will be included with // any search that goes off the end of the level1 array. if (fromIndex < 0) { throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); } // This is the word from which the search begins. int w = wordIndex(fromIndex); int w1 = level1Index(w); int w2 = level2Index(w); int w3 = level3Index(w); long word = 0L; final int tableLength = table.length; long[][] a2; long[] a3; // first check whether bitIndex itself (or the bits next to bitIndex // in the same word) has been set if (w1 < tableLength && ((a2 = table[w1]) == null || (a3 = a2[w2]) == null || ((word = a3[w3] & (~0L << fromIndex)) == 0L))) { // bitIndex is not set, start a search since bitIndex + 1 ++w; w1 = level1Index(w); w2 = level2Index(w); w3 = level3Index(w); outer: for (; w1 != tableLength; ++w1) { if ((a2 = table[w1]) != null) { for (; w2 != LENGTH2; ++w2) { if ((a3 = a2[w2]) != null) { for (; w3 != LENGTH3; ++w3) { if ((word = a3[w3]) != 0) { break outer; } } } w3 = 0; // reset w3 } } w2 = w3 = 0; // reset w2 and w3 } } return (w1 >= tableLength ? -1 : bitIndex(w1, w2, w3) + Long.numberOfTrailingZeros(word)); } @Override public int nextClearBit(int fromIndex) { // The index of this method is permitted to be Integer.MAX_VALUE, // as this is needed to make this method work together with the method // nextSetBit()--as might happen if a search for the next clear bit is // started after finding a set bit labelled Integer.MAX_VALUE-1. This // case is not optimised, the code will eventually return -1 (since // the Integer.MAX_VALUE-th bit does "exist," and is 0). if (fromIndex < 0) { throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); } // This is the word from which the search begins. int w = wordIndex(fromIndex); int w1 = level1Index(w); int w2 = level2Index(w); int w3 = level3Index(w); long nword = (~0L << fromIndex); final int tableLength = table.length; long[][] a2; long[] a3; // first check whether bitIndex itself (or the bits next to bitIndex // in the same word) is clear (not set). if (w1 < tableLength && (a2 = table[w1]) != null && (a3 = a2[w2]) != null && ((nword = ~a3[w3] & (~0L << fromIndex))) == 0L) { // bitIndex is clear (not set), start a search since bitIndex + 1 ++w; w1 = level1Index(w); w2 = level2Index(w); w3 = level3Index(w); nword = ~0L; outer: for (; w1 != tableLength; ++w1) { if ((a2 = table[w1]) == null) { break; } for (; w2 != LENGTH2; ++w2) { if ((a3 = a2[w2]) == null) { break outer; } for (; w3 != LENGTH3; ++w3) { if ((nword = ~a3[w3]) != 0) { break outer; } } w3 = 0; } w2 = w3 = 0; } } final int result = bitIndex(w1, w2, w3) + Long.numberOfTrailingZeros(nword); return (result == Integer.MAX_VALUE ? -1 : result); } @Override public int previousSetBit(int fromIndex) { if (fromIndex < 0) { if (fromIndex == -1) { return -1; } throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); } final long[][][] table = this.table; final int tableSize = table.length - 1; /* This is the word from which the search begins. */ final int w = wordIndex(fromIndex); int w1 = level1Index(w); int w2, w3, w4; if (w1 > tableSize) { // If the fromIndex is out of scope of table, then start from // the very end of the table. w1 = tableSize; w2 = LENGTH2_SIZE; w3 = LENGTH3_SIZE; w4 = WORD_SIZE; } else { w2 = level2Index(w); w3 = level3Index(w); w4 = fromIndex % BITS_PER_WORD; } long word; long[][] a2; long[] a3; for (; w1 >= 0; --w1) { if ((a2 = table[w1]) != null) { for (; w2 >= 0; --w2) { if ((a3 = a2[w2]) != null) { for (; w3 >= 0; --w3) { if ((word = a3[w3]) != 0) { for (int offset = w4; offset >= 0; --offset) { if ((word & (1L << offset)) != 0) { return bitIndex(w1, w2, w3) + offset; } } } w4 = WORD_SIZE; } } w3 = LENGTH3_SIZE; w4 = WORD_SIZE; } } w2 = LENGTH2_SIZE; w3 = LENGTH3_SIZE; w4 = WORD_SIZE; } return -1; } @Override public int previousClearBit(int fromIndex) { if (fromIndex < 0) { if (fromIndex == -1) { return -1; } throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); } final long[][][] table = this.table; final int tableSize = table.length - 1; int w = wordIndex(fromIndex); int w1 = level1Index(w); if (w1 > tableSize) { return fromIndex; } int w2 = level2Index(w); int w3 = level3Index(w); int w4 = fromIndex % BITS_PER_WORD; long word; long[][] a2; long[] a3; for (; w1 >= 0; --w1) { if ((a2 = table[w1]) == null) { return bitIndex(w1, w2, w3) + w4; } for (; w2 >= 0; --w2) { if ((a3 = a2[w2]) == null) { return bitIndex(w1, w2, w3) + w4; } for (; w3 >= 0; --w3) { if ((word = a3[w3]) == 0) { return bitIndex(w1, w2, w3) + w4; } for (int offset = w4; offset >= 0; --offset) { if ((word & (1L << offset)) == 0) { return bitIndex(w1, w2, w3) + offset; } } w4 = WORD_SIZE; } w3 = LENGTH3_SIZE; } w2 = LENGTH2_SIZE; } return -1; } @Override public boolean intersects(IBitSet set) { if (this.isEmpty() || set.isEmpty()) { return false; } if (this == set) { return true; } if (!(set instanceof SparseBitSet other)) { return super.intersects(set); } return iterateBlocks(this, other, new IntersectsAction(this)); } private static class IntersectsAction extends BlockAction { private boolean intersects = false; private IntersectsAction(SparseBitSet self) { super(self); } @Override boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock) { boolean isZero = true; if (selfBlock != null) { for (int w3 = 0; w3 < LENGTH3; ++w3) { long selfWord = selfBlock[w3]; if (selfWord != 0) { long iteratedWord = iteratedBlock[w3]; if ((selfWord & iteratedWord) != 0) { intersects = true; return false; } isZero = false; } } } return isZero; } @Override boolean canBreak() { // already found intersection, can break the iteration. return intersects; } @Override Boolean getResult() { return intersects; } } @Override public boolean contains(IBitSet set) { if (this == set) { return true; } if (!(set instanceof SparseBitSet other)) { return super.contains(set); } return iterateBlocks(this, other, new ContainsAction(this)); } private static class ContainsAction extends BlockAction { private boolean contains = true; private ContainsAction(SparseBitSet self) { super(self); } @Override boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock) { boolean isZero = true; if (selfBlock != null) { for (int w3 = 0; w3 < LENGTH3; ++w3) { long selfWord = selfBlock[w3]; if (selfWord != 0) { isZero = false; } long iteratedWord = iteratedBlock[w3]; if ((selfWord | iteratedWord) != selfWord) { contains = false; } } } else { contains = !isNonZeroBlock(iteratedBlock); } return isZero; } @Override boolean canBreak() { // already found not-contain bits, can break the iteration return !contains; } @Override Boolean getResult() { return contains; } } /** * Performs a logical AND of this target bit set with the * argument bit set. This operation cannot skip zero blocks in the * other set, thus we cannot implement it via {@link #iterateBlocks}. * * @param set a bit set * @return {@code true} if this bit set changed as a result of the call */ @Override public boolean and(IBitSet set) { if (this == set) { return false; } if (!(set instanceof SparseBitSet other)) { throw new UnsupportedOperationException( String.format("%s does not support AND with %s", this.getClass(), set.getClass())); } // Unlike other set operations, AND requires iteration on // non-null blocks of both this and other sets. boolean changed = false; long[][][] thisTable = this.table; long[][][] otherTable = other.table; int w1InCommon = Math.min(thisTable.length, otherTable.length); // process common part for (int w1 = 0; w1 < w1InCommon; ++w1) { long[][] otherArea = otherTable[w1]; long[][] thisArea = thisTable[w1]; if (otherArea != null) { if (thisArea != null) { // both areas are present boolean isZeroArea = true; for (int w2 = 0; w2 < LENGTH2; ++w2) { long[] thisBlock = thisArea[w2]; long[] otherBlock = otherArea[w2]; if (otherBlock != null) { if (thisBlock != null) { // both blocks are present boolean isZeroBlock = true; // perform AND on each words for (int w3 = 0; w3 < LENGTH3; ++w3) { long oldWord = thisBlock[w3]; long newWord = oldWord & otherBlock[w3]; if (oldWord != newWord) { thisBlock[w3] = newWord; changed = true; } if (newWord != 0) { isZeroBlock = false; } } if (isZeroBlock) { thisArea[w2] = null; } else { isZeroArea = false; } } } else if (isNonZeroBlock(thisBlock)) { // otherBlock is null and thisBlock is not zero, // then clear thisBlock and mark changed thisArea[w2] = null; changed = true; } } if (isZeroArea) { // iterate all thisBlocks and found they are all zero, // then clear thisArea thisTable[w1] = null; } } } else if (isNonZeroArea(thisArea)) { // otherArea is null and thisArea is not zero, // then clear thisArea and mark changed thisTable[w1] = null; changed = true; } } // process extra part of this table if (w1InCommon < thisTable.length) { if (!changed) { // check whether extra areas are zero for (int w1 = w1InCommon; w1 < thisTable.length; ++w1) { if (isNonZeroArea(thisTable[w1])) { changed = true; break; } } } clearTable(w1InCommon); } if (changed) { invalidateState(); } return changed; } private static boolean isNonZeroArea(long[][] area) { if (area != null) { for (long[] block : area) { if (isNonZeroBlock(block)) { return true; } } } return false; } private static boolean isNonZeroBlock(long[] block) { if (block != null) { for (long word : block) { if (word != 0) { return true; } } } return false; } @Override public boolean andNot(IBitSet set) { if (this == set) { boolean changed = !isEmpty(); clear(); return changed; } if (!(set instanceof SparseBitSet other)) { return super.andNot(set); } return iterateBlocks(this, other, new AndNotAction(this)); } private static class AndNotAction extends ChangeAction { private AndNotAction(SparseBitSet self) { super(self); } @Override boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock) { boolean isZero = true; boolean changed = false; if (selfBlock != null) { for (int w3 = 0; w3 < LENGTH3; ++w3) { long selfWord = selfBlock[w3]; if (selfWord != 0) { long newWord = selfWord & ~iteratedBlock[w3]; if (newWord != selfWord) { selfBlock[w3] = newWord; changed = true; } if (newWord != 0) { isZero = false; } } } } this.changed |= changed; return isZero; } } @Override public boolean or(IBitSet set) { if (this == set) { return false; } if (!(set instanceof SparseBitSet other)) { return super.or(set); } return iterateBlocks(this, other, new OrAction(this)); } private static class OrAction extends ChangeAction { private OrAction(SparseBitSet self) { super(self); } @Override boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock) { boolean isZero = true; boolean changed = false; for (int w3 = 0; w3 < LENGTH3; ++w3) { long iteratedWord = iteratedBlock[w3]; if (iteratedWord != 0) { isZero = false; if (selfBlock == null) { selfBlock = self.getOrCreateBlock(w1, w2); } long selfWord = selfBlock[w3]; long newWord = selfWord | iteratedWord; if (selfWord != newWord) { selfBlock[w3] = newWord; changed = true; } } else if (selfBlock != null && selfBlock[w3] != 0) { isZero = false; } } this.changed |= changed; return isZero; } } @Override public IBitSet orDiff(IBitSet set) { if (this == set) { return new SparseBitSet(); } if (!(set instanceof SparseBitSet other)) { return super.orDiff(set); } return iterateBlocks(this, other, new OrDiffAction(this)); } private static class OrDiffAction extends BlockAction { private SparseBitSet diff; private boolean changed; private OrDiffAction(SparseBitSet self) { super(self); } @Override void start(SparseBitSet iterated) { diff = new SparseBitSet(); changed = false; } @Override boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock) { boolean isZero = true; boolean changed = false; for (int w3 = 0; w3 < LENGTH3; ++w3) { long iteratedWord = iteratedBlock[w3]; if (iteratedWord != 0) { isZero = false; if (selfBlock == null) { selfBlock = self.getOrCreateBlock(w1, w2); } long selfWord = selfBlock[w3]; long newWord = selfWord | iteratedWord; if (selfWord != newWord) { selfBlock[w3] = newWord; changed = true; long[] diffBlock = diff.getOrCreateBlock(w1, w2); diffBlock[w3] = iteratedWord & ~selfWord; } } else if (selfBlock != null && selfBlock[w3] != 0) { isZero = false; } } this.changed |= changed; return isZero; } @Override void finish() { if (changed) { self.invalidateState(); diff.invalidateState(); } } @Override IBitSet getResult() { return diff; } } @Override public boolean xor(IBitSet set) { if (this == set) { boolean changed = !isEmpty(); clear(); return changed; } if (!(set instanceof SparseBitSet other)) { return super.xor(set); } return iterateBlocks(this, other, new XorAction(this)); } private static class XorAction extends ChangeAction { private XorAction(SparseBitSet self) { super(self); } @Override boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock) { boolean isZero = true; boolean changed = false; for (int w3 = 0; w3 < LENGTH3; ++w3) { long iteratedWord = iteratedBlock[w3]; if (iteratedWord != 0) { isZero = false; if (selfBlock == null) { selfBlock = self.getOrCreateBlock(w1, w2); } long selfWord = selfBlock[w3]; long newWord = selfWord ^ iteratedWord; if (selfWord != newWord) { selfBlock[w3] = newWord; changed = true; } } else if (selfBlock != null && selfBlock[w3] != 0) { isZero = false; } } this.changed |= changed; return isZero; } } @Override public void clear() { clearTable(0); } @Override public boolean isEmpty() { updateState(); return state.cardinality == 0; } @Override public int length() { updateState(); return state.length; } @Override public int size() { updateState(); return state.size; } @Override public int cardinality() { updateState(); return state.cardinality; } @Override public int hashCode() { updateState(); return state.hash; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SparseBitSet that = (SparseBitSet) o; return cardinality() == that.cardinality() && contains(that); } @Override public SparseBitSet copy() { SparseBitSet copy = new SparseBitSet(); copy.or(this); return copy; } // ------------------------------------------------------------------------ // utility methods and classes // ------------------------------------------------------------------------ /** * Extracts level 1 index (i.e., area index) from {@code wordIndex}. */ private static int level1Index(int wordIndex) { return wordIndex >> SHIFT1; } /** * Extracts level 2 index (i.e., block index) from {@code wordIndex}. */ private static int level2Index(int wordIndex) { return (wordIndex >> SHIFT2) & MASK2; } /** * Extracts level 3 index (i.e., word index in the block) from {@code wordIndex}. */ private static int level3Index(int wordIndex) { return wordIndex & MASK3; } /** * Combines level 1/2/3 indexes to compute the corresponding word index. */ private static int wordIndex(int w1, int w2, int w3) { return (w1 << SHIFT1) + (w2 << SHIFT2) + w3; } /** * Combines level 1/2/3 indexes to compute the corresponding bit index. * * @return index of the first bit in the word specified by the combined word index */ private static int bitIndex(int w1, int w2, int w3) { return wordIndex(w1, w2, w3) << SHIFT3; } /** * Retrieves the block of specified position in the given table. * * @param table the table * @param w1 level 1 index * @param w2 level 2 index * @return the block if it is present in the table, or {@code null} if absent. */ @Nullable private static long[] getBlock(long[][][] table, int w1, int w2) { return w1 < table.length && table[w1] != null ? table[w1][w2] : null; } /** * Retrieves the block of specified position in the table of this set. * If the block is absent (i.e., {@code null}), this method will create * the block, and resize table and create new area, if necessary. */ private long[] getOrCreateBlock(int w1, int w2) { if (w1 >= table.length) { resize(bitIndex(w1, /* only the highest one bit matters */ 0, 0)); } long[][] area; if ((area = table[w1]) == null) { area = table[w1] = new long[LENGTH2][]; } long[] block; if ((block = area[w2]) == null) { block = area[w2] = new long[LENGTH3]; } return block; } /** * Resize the bit array. Moves the entries in the bits array of this * SparseBitSet into an array whose size (which may be larger or smaller) * is the given bit size (i.e., includes the bit whose bitIndex is * one less that the given value). If the new array is smaller, the excess * entries in the set array are discarded. If the new array is bigger, * it is filled with nulls. * * @param bitIndex the desired bit index to be included in the set */ private void resize(int bitIndex) { // Find an array size that is a power of two that is as least // large enough to contain the bitIndex requested. final int w1 = level1Index(wordIndex(bitIndex)); int newSize = Integer.highestOneBit(w1); if (newSize == 0) { newSize = 1; } if (w1 >= newSize) { newSize <<= 1; } if (newSize > MAX_LENGTH1) { newSize = MAX_LENGTH1; } final int aLength1 = (table != null ? table.length : 0); if (newSize != aLength1 || table == null) { // only if the size needs to be changed final long[][][] temp = new long[newSize][][]; // Get the new array if (aLength1 != 0) { // If it exists, copy old array to the new array. System.arraycopy(table, 0, temp, 0, Math.min(aLength1, newSize)); clearTable(0); // Don't leave unused pointers around. } table = temp; // Set new array as the set array bitsLength = // Index of last possible bit, plus one. (newSize == MAX_LENGTH1 ? Integer.MAX_VALUE : newSize * UNIT); } } /** * Clears out a part of the set array with nulls, from the given * fromAreaIndex to the end of the array. If the given parameter * is beyond the end of the bits array, nothing is changed. * * @param fromAreaIndex word index at which to start (inclusive) */ private void clearTable(int fromAreaIndex) { final int aLength = table.length; if (fromAreaIndex < aLength) { for (int w = fromAreaIndex; w != aLength; ++w) { table[w] = null; } invalidateState(); } } /** * Core method for set operations. This method operates on two sets, * a self set and an iterated set. * Note that it ONLY iterates non-null blocks in the iterated set. * * @param self the self set * @param iterated the other set, whose non-null blocks are iterated * @param action the action to be taken during iteration * @param type of returned value * @return result of the action */ private static R iterateBlocks(SparseBitSet self, SparseBitSet iterated, BlockAction action) { assert self == action.self; long[][][] selfTable = self.table; long[][][] iteratedTable = iterated.table; action.start(iterated); boolean canIterateBothSets = action.isIterateBothSets(); outer: for (int w1 = 0; w1 < iteratedTable.length; ++w1) { // search for non-null areas in iteratedTable long[][] iteratedArea = iteratedTable[w1]; if (iteratedArea != null) { boolean isZeroArea = true; for (int w2 = 0; w2 < LENGTH2; ++w2) { // search for non-null blocks in iteratedTable long[] iteratedBlock = iteratedArea[w2]; if (iteratedBlock != null) { long[] selfBlock = getBlock(selfTable, w1, w2); boolean isZeroBlock = action.accept(w1, w2, selfBlock, iteratedBlock); if (isZeroBlock) { if (selfBlock != null) { // clear zero block in self selfTable[w1][w2] = null; self.invalidateState(); } } else { // found non-zero block, then the area is not zero isZeroArea = false; } if (action.canBreak()) { break outer; } } } if (isZeroArea && canIterateBothSets && // we can confirm the area is zero // only when non-null blocks of both sets were iterated w1 < selfTable.length && selfTable[w1] != null) { // clear zero area in self selfTable[w1] = null; self.invalidateState(); } } } action.finish(); return action.getResult(); } /** * Actions that are performed during iterating non-null blocks. * * @param type of return valued */ private abstract static class BlockAction { final SparseBitSet self; BlockAction(SparseBitSet self) { this.self = self; } /** * Operation needs to be performed before the iteration. */ void start(SparseBitSet iterated) { } /** * @return {@code true} if {@code selfBlock} becomes zero block after * this call. */ abstract boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock); /** * @return whether this action iterates both self set and the other set. */ boolean isIterateBothSets() { return false; } /** * @return whether the iteration can be broken. */ boolean canBreak() { return false; } /** * Operation needs to be performed after the iteration. */ void finish() { } /** * @return the result of iteration. */ R getResult() { return null; } } /** * Abstract class for the actions that may change {@code self} set. */ private abstract static class ChangeAction extends BlockAction { boolean changed; private ChangeAction(SparseBitSet self) { super(self); } @Override void start(SparseBitSet iterated) { changed = false; } @Override void finish() { if (changed) { self.invalidateState(); } } @Override Boolean getResult() { return changed; } } private void invalidateState() { state.valid = false; } /** * The entirety of the bit set is examined, and the various statistics of * the bit set (size, length, cardinality, hashCode, etc.) are computed. Level * arrays that are empty (i.e., all zero at level 3, all null at level 2) are * replaced by null references, ensuring a normalized representation. */ private void updateState() { if (!state.valid) { iterateBlocks(this, this, new UpdateAction(this)); } } @Serial private void writeObject(ObjectOutputStream s) throws IOException { updateState(); // Update structure and state if needed. /* Write any hidden stuff. */ s.defaultWriteObject(); s.writeInt(state.length); // Needed to know where last bit is /* This is the number of index/value pairs to be written. */ int count = state.count; // Minimum number of words to be written s.writeInt(count); final long[][][] a1 = table; final int aLength1 = a1.length; long[][] a2; long[] a3; long word; for (int w1 = 0; w1 != aLength1; ++w1) { if ((a2 = a1[w1]) != null) { for (int w2 = 0; w2 != LENGTH2; ++w2) { if ((a3 = a2[w2]) != null) { final int base = (w1 << SHIFT1) + (w2 << SHIFT2); for (int w3 = 0; w3 != LENGTH3; ++w3) { if ((word = a3[w3]) != 0) { s.writeInt(base + w3); s.writeLong(word); --count; } } } } } } if (count != 0) { throw new InternalError("count of entries not consistent"); } /* As a consistency check, write the hash code of the set. */ s.writeInt(state.hash); } @Serial private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { /* Read in any hidden stuff that is part of the class overhead. */ s.defaultReadObject(); final int aLength = s.readInt(); resize(aLength); // Make sure there is enough space /* Read in number of mappings. */ final int count = s.readInt(); /* Read the keys and values, them into the set array, areas, and blocks. */ long[][] a2; long[] a3; for (int n = 0; n != count; ++n) { final int w = s.readInt(); final int w3 = w & MASK3; final int w2 = (w >> SHIFT2) & MASK2; final int w1 = w >> SHIFT1; final long word = s.readLong(); if ((a2 = table[w1]) == null) { a2 = table[w1] = new long[LENGTH2][]; } if ((a3 = a2[w2]) == null) { a3 = a2[w2] = new long[LENGTH3]; } a3[w3] = word; } /* Ensure all the pieces are set up for set scanning. */ state = new State(); updateState(); if (count != state.count) { throw new InternalError("count of entries not consistent"); } final int hash = s.readInt(); // Get the hashcode that was stored if (hash != state.hash) { // An error of some kind, if not the same throw new IOException("deserialized hashCode mis-match"); } } private static class UpdateAction extends BlockAction { /** * Working space for find the size and length of the bit set. Holds the * index of the last non-empty word in the set. */ private transient int maxWordIndex; /** * Working space for find the size and length of the bit set. Holds a copy * of the last non-empty word in the set. */ private transient long maxWord; /** * Working space for find the hash value of the bit set. Holds the * current state of the computation of the hash value. This value is * ultimately transferred to the Cache object. * * @see State */ private transient long hash; /** * Working space for keeping count of the number of non-zero words in the * bit set. Holds the current state of the computation of the count. This * value is ultimately transferred to the Cache object. * * @see State */ private transient int count; /** * Number of blocks actually in use by this set to represent bit values. */ private transient int blockCount; /** * Working space for counting the number of non-zero bits in the bit set. * Holds the current state of the computation of the cardinality.This * value is ultimately transferred to the Cache object. * * @see State */ private transient int cardinality; private UpdateAction(SparseBitSet self) { super(self); } @Override void start(SparseBitSet iterated) { hash = 1234L; // Magic number maxWordIndex = 0; // index of last non-zero word maxWord = 0L; // word at that index count = 0; // count of non-zero words in whole set blockCount = 0; // count of blocks actually use in whole set cardinality = 0; // count of non-zero bits in the whole set } @Override boolean accept(int w1, int w2, long[] selfBlock, long[] iteratedBlock) { ++blockCount; boolean isZero = true; // Presumption for (int w3 = 0; w3 != LENGTH3; ++w3) { final long word = iteratedBlock[w3]; if (word != 0) { isZero = false; compute(wordIndex(w1, w2, w3), word); } } return isZero; } /** * This method does the accumulation of the statistics. It must be called * in sequential order of the words in the set for which the statistics * are being accumulated, and only for non-null values of the second * parameter. *

* Two of the values (a2Count and a3Count) are not updated here, * but are done in the code near where this method is called. * * @param index the word index of the word supplied * @param word the long non-zero word from the set */ private void compute(final int index, final long word) { // Count the number of actual words being used. ++count; // Continue to accumulate the hash value of the set. hash ^= word * (long) (index + 1); // The last non-zero word contains the last actual bit of the set. // The location of this bit is used to compute the set length. maxWordIndex = index; maxWord = word; // Count the actual bits, so as to get the cardinality of the set. cardinality += Long.bitCount(word); } @Override boolean isIterateBothSets() { return true; } @Override void finish() { State state = self.state; state.count = count; state.cardinality = cardinality; state.length = (maxWordIndex + 1) * BITS_PER_WORD - Long.numberOfLeadingZeros(maxWord); state.size = blockCount * LENGTH3 * BITS_PER_WORD; state.hash = (int) ((hash >> Integer.SIZE) ^ hash); state.valid = true; } } private static class State { /** * Boolean value indicating whether the values in current state is valid; * if not, then {@link #updateState()} should be called to update. */ private boolean valid; private transient int hash; private transient int size; private transient int cardinality; private transient int length; private transient int count; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy