/* * Copyright (c) 2013-2017 Cinchapi Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cinchapi.concourse.util; import java.util.AbstractSet; import java.util.BitSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.NotThreadSafe; import com.cinchapi.concourse.util.ReadOnlyIterator; /** * <p> * Similar to a {@link BitSet java.util.BitSet}. The only difference is that its * keys are {@code} long instead of {@code int}. There is also support for * negative indexes. * </p> * <p> * The {@link LongBitSet} is backed by Map<Long, BitSet>. The idea is to use * high bits of a key as a key to this map and low bits as a key to a value * BitSet. Thus we will support both partitioning and long keys. Internally, * each partition can hold about a million entries, which means that they * require no more than 128K, keeping total size of a {@link LongBitSet} rather * small. * </p> * <p> * <strong>NOTE:</strong> Implementation and documentation adapted from <a * href="http://java-performance.info/bit-sets/" * >http://java-performance.info/bit-sets/</a> * </p> * * @author Mikhail Vorontsov * */ @NotThreadSafe public class LongBitSet { /** * Return a new {@link LongBitSet}. * * @return the LongBitSet */ public static LongBitSet create() { return new LongBitSet(); } /** * Number of bits allocated to a value in an index */ private static final int VALUE_BITS = 20; // 1M values per bit set /** * Mask for extracting values */ private static final long VALUE_MASK = (1 << VALUE_BITS) - 1; /** * Map from a value stored in high bits of a long index to a bit set mapped * to the lower bits of an index. * Bit sets size should be balanced - not to long (otherwise setting a * single bit may waste megabytes of memory) * but not too short (otherwise this map will get too big). Update value of * {@code VALUE_BITS} for your needs. * In most cases it is ok to keep 1M - 64M values in a bit set, so each bit * set will occupy 128Kb - 8Mb. */ private Map<Long, BitSet> m_sets = new HashMap<Long, BitSet>(20); /** * Return {@code true} if this set contains an element at {@code index}. * * @param index * @return the value of {@link #get(long)}. */ public boolean contains(long index) { return get(index); } /** * Get a value for a given index * * @param index Long index * @return Value associated with a given index */ public boolean get(final long index) { final BitSet bitSet = m_sets.get(getSetIndex(index)); return bitSet != null && bitSet.get(getPos(index)); } /** * Return an iterator that will traverse all the elements in the set (e.g. * all the bits that are turned on). * * @return the Iterator */ public Iterator<Long> iterator() { return new InternalIterator(); } /** * Set the bit at {@code index} to {@code true}, if it is currently * {@code false}. * * @param index * @return {@code true} if this operation results in a change to the * underlying bit set (e.g. the bit was previously turned off and is * not turned on), {@code false} otherwise */ public boolean set(final long index) { BitSet bs = getBitSet(index); int pos = getPos(index); if(!bs.get(pos)) { bs.set(pos, true); return true; } else { return false; } } /** * Set a given value for a given index * * @param index Long index * @param value Value to set */ public void set(final long index, final boolean value) { if(value) { getBitSet(index).set(getPos(index), value); } else { // if value shall be cleared, check first if given partition // exists final BitSet bitSet = m_sets.get(getSetIndex(index)); if(bitSet != null) bitSet.clear(getPos(index)); } } /** * Convert the bit set to an iterable that can be used to return an * iterator. * * @return the iterable */ public Iterable<Long> toIterable() { return new InternalSetView(); } /** * Helper method to get (or create, if necessary) a bit set for a given long * index * * @param index Long index * @return A bit set for a given index (always not null) */ private BitSet getBitSet(final long index) { final Long iIndex = getSetIndex(index); BitSet bitSet = m_sets.get(iIndex); if(bitSet == null) { bitSet = new BitSet(1024); m_sets.put(iIndex, bitSet); } return bitSet; } /** * Get index of a value in a bit set (bits 0-19) * * @param index Long index * @return Index of a value in a bit set */ private int getPos(final long index) { return (int) (index & VALUE_MASK); } /** * Get set index by long index (extract bits 20-63) * * @param index Long index * @return Index of a bit set in the inner map */ private long getSetIndex(final long index) { return index >> VALUE_BITS; } /** * The {@link Iterator} returned from the {@link #iterator()} method. * * @author Jeff Nelson */ private class InternalIterator extends ReadOnlyIterator<Long> { private long baseIndex = 0; private Iterator<Map.Entry<Long, BitSet>> bitIt = m_sets.entrySet() .iterator(); private BitSet bitSet = null; private int position = -1; { flip(); } @Override public boolean hasNext() { if(position >= 0) { return true; } else { flip(); return position >= 0; } } @Override public Long next() { if(position >= 0) { long next = position + baseIndex; position = bitSet.nextSetBit(position + 1); return next; } else { return -1L; } } /** * Flip to the next internal {@link BitSet} and establish the * {@link #position} of the next set bit. */ private void flip() { while (bitIt.hasNext() && position < 0) { Map.Entry<Long, BitSet> entry = bitIt.next(); baseIndex = entry.getKey() << VALUE_BITS; bitSet = entry.getValue(); position = bitSet.nextSetBit(0); } } } /** * A {@link Set} that lazily loads elements from the {@link #iterator()} * within this LongBitSet. * * @author Jeff Nelson */ @Immutable private class InternalSetView extends AbstractSet<Long> { /** * A flag that indicates if the Set is empty upon creation. */ private final boolean empty; /** * A cache of the size. We can only compute the size by iterating * through all the elements, so this is lazily computed and cached so * that this work is only done once, if necessary. */ private int size = 0; /** * Construct a new instance. * * @param iterator */ private InternalSetView() { this.empty = !iterator().hasNext(); } @Override public Iterator<Long> iterator() { return LongBitSet.this.iterator(); } @Override public int size() { if(size == 0 && !empty) { Iterator<Long> it = iterator(); while (it.hasNext()) { it.next(); ++size; } } return size; } @Override public boolean isEmpty() { return empty; } } }