Skip to content

Commit

Permalink
Introduce BitArray, a simple BitSet clone that supports shifting.
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeWharton committed Jan 17, 2014
1 parent fb21058 commit ce40dc7
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2014 Square 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.squareup.okhttp.internal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** A simple bitset which supports left shifting. */
public final class BitArray {
/** Create a bit array from a bit-packed long. */
public static BitArray fromValue(long bitLong) {
return new BitArray(bitLong);
}

long[] data;

// Start offset which allows for cheap shifting. Data is always kept on 64-bit bounds but we
// offset the outward facing index to support shifts without having to move the underlying bits.
private int start; // Valid values are [0..63]

BitArray() {
data = new long[1];
}

private BitArray(long bitLong) {
data = new long[] { bitLong, 0 };
}

private void growToSize(int size) {
long[] newData = new long[size];
if (data != null) {
System.arraycopy(data, 0, newData, 0, data.length);
}
data = newData;
}

private int offsetOf(int index) {
index += start;
int offset = index / 64;
if (offset > data.length - 1) {
growToSize(offset + 1);
}
return offset;
}

private int shiftOf(int index) {
return (index + start) % 64;
}

public void clear() {
Arrays.fill(data, 0);
}

public void set(int index) {
if (index < 0) {
throw new IllegalArgumentException("index < 0: " + index);
}
int offset = offsetOf(index);
data[offset] |= 1L << shiftOf(index);
}

public void unset(int index) {
if (index < 0) {
throw new IllegalArgumentException("index < 0: " + index);
}
int offset = offsetOf(index);
data[offset] &= ~(1L << shiftOf(index));
}

public boolean get(int index) {
if (index < 0) {
throw new IllegalArgumentException("index < 0: " + index);
}
int offset = offsetOf(index);
return (data[offset] & (1L << shiftOf(index))) != 0;
}

public void shiftLeft(int count) {
if (count < 0) {
throw new IllegalArgumentException("count < 0: " + count);
}
start -= count;
if (start < 0) {
int arrayShift = (start / -64) + 1;
long[] newData = new long[data.length + arrayShift];
System.arraycopy(data, 0, newData, arrayShift, data.length);
data = newData;
start = 64 + (start % 64);
}
}

@Override public String toString() {
StringBuilder builder = new StringBuilder("{");
List<Integer> ints = toIntegerList();
for (int i = 0, count = ints.size(); i < count; i++) {
if (i > 0) {
builder.append(',');
}
builder.append(ints.get(i));
}
return builder.append('}').toString();
}

List<Integer> toIntegerList() {
List<Integer> ints = new ArrayList<Integer>();
for (int i = 0, count = data.length * 64 - start; i < count; i++) {
if (get(i)) {
ints.add(i);
}
}
return ints;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright 2014 Square 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.squareup.okhttp.internal;

import java.math.BigInteger;
import org.junit.Test;

import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class BitArrayTest {
@Test public void initializeFromLong() {
long bits = 1L | (1L << 3) | (1L << 7);
BitArray b = BitArray.fromValue(bits);
assertTrue(b.get(0));
assertTrue(b.get(3));
assertTrue(b.get(7));
}

@Test public void initializedFullFromLong() {
long bits = -1;
BitArray b = BitArray.fromValue(bits);
for (int i = 0; i < 64; i++) {
assertTrue(b.get(i));
}
}

@Test public void hpackUseCase() {
long bits = 1L | (1L << 63);
BitArray b = BitArray.fromValue(bits);
assertTrue(b.get(0));
assertFalse(b.get(1));
assertTrue(b.get(63));
assertFalse(b.get(64));
b.set(64);
assertTrue(b.get(64));
}

@Test public void setExpandsData() {
BitArray b = new BitArray();
b.set(64);
assertEquals(asList(64), b.toIntegerList());
}

@Test public void clearBit() {
BitArray b = new BitArray();
b.set(100);
b.unset(100);
assertTrue(b.toIntegerList().isEmpty());
}

@Test public void shiftLeftExpandsData() {
BitArray b = new BitArray();
b.set(0);
b.shiftLeft(64);
assertEquals(asList(64), b.toIntegerList());
}

@Test public void shiftLeftFromZero() {
BitArray b = new BitArray();
b.set(0);
b.shiftLeft(1);
assertEquals(asList(1), b.toIntegerList());
}

@Test public void shiftLeftAcrossOffset() {
BitArray b = new BitArray();
b.set(63);
assertEquals(1, b.data.length);
b.shiftLeft(1);
assertEquals(asList(64), b.toIntegerList());
assertEquals(2, b.data.length);
}

@Test public void multipleShiftsLeftAcrossOffset() {
BitArray b = new BitArray();
b.set(1000);
b.shiftLeft(67);
assertEquals(asList(1067), b.toIntegerList());
b.shiftLeft(69);
assertEquals(asList(1136), b.toIntegerList());
}

@Test public void clearBits() {
BitArray b = new BitArray();
b.set(10);
b.set(100);
b.set(1000);
b.clear();
assertTrue(b.toIntegerList().isEmpty());
}

@Test public void bigIntegerSanityCheck() {
BitArray a = new BitArray();
BigInteger b = BigInteger.ZERO;

a.set(64);
b = b.setBit(64);
assertEquals(bigIntegerToString(b), a.toString());

a.set(1000000);
b = b.setBit(1000000);
assertEquals(bigIntegerToString(b), a.toString());

a.shiftLeft(100);
b = b.shiftLeft(100);
assertEquals(bigIntegerToString(b), a.toString());

a.set(0xF00D);
b = b.setBit(0xF00D);
a.set(0xBEEF);
b = b.setBit(0xBEEF);
a.set(0xDEAD);
b = b.setBit(0xDEAD);
assertEquals(bigIntegerToString(b), a.toString());

a.shiftLeft(0xB0B);
b = b.shiftLeft(0xB0B);
assertEquals(bigIntegerToString(b), a.toString());

a.unset(64280);
b = b.clearBit(64280);
assertEquals(bigIntegerToString(b), a.toString());
}

private static String bigIntegerToString(BigInteger b) {
StringBuilder builder = new StringBuilder("{");
for (int i = 0, count = b.bitLength(); i < count; i++) {
if (b.testBit(i)) {
builder.append(i).append(',');
}
}
builder.setCharAt(builder.length() - 1, '}');
return builder.toString();
}
}

0 comments on commit ce40dc7

Please sign in to comment.