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

com.sangupta.jerry.ds.bitarray.MMapFileBackedBitArray Maven / Gradle / Ivy

/**
 *
 * jerry - Common Java Functionality
 * Copyright (c) 2012-2015, Sandeep Gupta
 * 
 * http://sangupta.com/projects/jerry
 * 
 * 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.sangupta.jerry.ds.bitarray;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import java.util.Arrays;

import com.sangupta.jerry.util.BitUtils;

import net.jcip.annotations.NotThreadSafe;

/**
 * An implementation of {@link BitArray} that uses a memory-mapped
 * file to persist all changes synchronously for the underlying bit
 * array. This is useful for stateful bit-arrays which are expensive
 * to construct yet need the best overall performance.
 * 
 * @author sangupta
 * @since 1.7
 */
@NotThreadSafe
public class MMapFileBackedBitArray implements BitArray {
	
	/**
	 * Underlying file that represents the state of the
	 * {@link BitArray}.
	 * 
	 */
	protected final RandomAccessFile backingFile;
	
	/**
	 * The maximum number of elements this file will store
	 */
	protected final int maxElements;
	
	/**
	 * The number of bytes being used for this byte-array
	 * 
	 */
	protected final int numBytes;
	
	/**
	 * The memory-mapped byte-buffer
	 */
	protected final MappedByteBuffer buffer;
	
	/**
	 * Construct a {@link BitArray} that is backed by the given file. Ensure
	 * that the file is a local file and not on a network share for performance
	 * reasons.
	 * 
	 * @param backingFile
	 *            the file that needs to store the bit-array
	 * 
	 * @param maxElements
	 *            the number of maximum elements that this {@link BitArray}
	 *            implementation will store
	 * 
	 * @throws IOException
	 *             if something fails while reading the file initially
	 * 
	 * @throws IllegalArgumentException
	 *             if the {@link #backingFile} is null, is not a
	 *             file, or the number of {@link #maxElements} are less than
	 *             equal to zero
	 */
	public MMapFileBackedBitArray(File backingFile, int maxElements) throws IOException {
		if(backingFile == null) {
			throw new IllegalArgumentException("Backing file cannot be empty/null");
		}
		
		if(backingFile.exists() && !backingFile.isFile()) {
			throw new IllegalArgumentException("Backing file does not represent a valid file");
		}
		
		if(maxElements <= 0) {
			throw new IllegalArgumentException("Max elements in array cannot be less than or equal to zero");
		}
		
		// we open in "rwd" mode, to save one i/o operation
		// than in "rws" mode
		this.backingFile = new RandomAccessFile(backingFile, "rwd");
		
		this.numBytes = (maxElements >> 3) + 1;
		extendFile(this.numBytes);
		
		// initialize the rest
		this.maxElements = maxElements;
		this.buffer = this.backingFile.getChannel().map(MapMode.READ_WRITE, 0, this.backingFile.length());
	}

	/**
	 * @see BitArray#getBit(int)
	 */
	@Override
	public boolean getBit(int index) {
		if(index > maxElements) {
			throw new IndexOutOfBoundsException("Index is greater than max elements permitted");
		}
		
		int pos = index >> 3; // div 8
		int bit = 1 << (index & 0x7);
		byte bite = this.buffer.get(pos);
		return (bite & bit) != 0;
	}

	/**
	 * @see BitArray#setBit(int)
	 */
	@Override
	public boolean setBit(int index) {
		if(index > maxElements) {
			throw new IndexOutOfBoundsException("Index is greater than max elements permitted");
		}
		
		int pos = index >> 3; // div 8
		int bit = 1 << (index & 0x7);
		byte bite = this.buffer.get(pos);
		bite = (byte) (bite | bit);
		this.buffer.put(pos, bite);
		return true;
	}

	/**
	 * @see BitArray#clear()
	 */
	@Override
	public void clear() {
		byte bite = 0;
		for(int index = 0; index < this.numBytes; index++) {
			this.buffer.put(index, bite);
		}
	}

	/**
	 * @see BitArray#clearBit(int)
	 */
	@Override
	public void clearBit(int index) {
		if(index > maxElements) {
			throw new IndexOutOfBoundsException("Index is greater than max elements permitted");
		}
		
		int pos = index >> 3; // div 8
		int bit = 1 << (index & 0x7);
		bit = ~bit;
		byte bite = this.buffer.get(pos);
		bite = (byte) (bite & bit);
		this.buffer.put(pos, bite);
	}

	/**
	 * @see BitArray#setBitIfUnset(int)
	 */
	@Override
	public boolean setBitIfUnset(int index) {
		if(!this.getBit(index)) {
			return this.setBit(index);
		}
		
		return false;
	}

	/**
	 * @see BitArray#or(BitArray)
	 */
	@Override
	public void or(BitArray bitArray) {
		if(bitArray == null) {
			throw new IllegalArgumentException("BitArray to OR with cannot be null");
		}
		
		if(this.numBytes != bitArray.numBytes()) {
			throw new IllegalArgumentException("BitArray to OR with is of different length");
		}
		
		byte[] bytes = bitArray.toByteArray();
		for(int index = 0; index < this.numBytes; index++) {
			byte bite = this.buffer.get(index);
			bite = (byte) (bite | bytes[index]);
			this.buffer.put(index, bite);
		}
	}

	/**
	 * @see BitArray#and(BitArray)
	 */
	@Override
	public void and(BitArray bitArray) {
		if(bitArray == null) {
			throw new IllegalArgumentException("BitArray to OR with cannot be null");
		}
		
		if(this.numBytes != bitArray.numBytes()) {
			throw new IllegalArgumentException("BitArray to OR with is of different length");
		}
		
		byte[] bytes = bitArray.toByteArray();
		for(int index = 0; index < this.numBytes; index++) {
			byte bite = this.buffer.get(index);
			bite = (byte) (bite & bytes[index]);
			this.buffer.put(index, bite);
		}
	}

	/**
	 * @see BitArray#bitSize()
	 */
	@Override
	public int bitSize() {
		return this.numBytes;
	}
	
	/**
	 * Extend this file to the given new length filling extra bytes with zeros.
	 * 
	 * @param newLength
	 *            the new length expected for this file
	 * 
	 * @throws IOException
	 *             if file operation fails
	 */
	protected void extendFile(final long newLength) throws IOException {
		long current = this.backingFile.length();
		int delta = (int) (newLength - current) + 1;
		if(delta <= 0) {
			return;
		}
		
		this.backingFile.setLength(newLength);
		this.backingFile.seek(current);
		byte[] bytes = new byte[delta];
		Arrays.fill(bytes, (byte) 0);
		this.backingFile.write(bytes);
	}

	@Override
	public void close() throws IOException {
		this.closeDirectBuffer(this.buffer);
		this.backingFile.close();
	}
	
	/**
	 * Method that helps unmap a memory-mapped file before being
	 * garbage-collected.
	 * 
	 * @param byteBuffer
	 *            the {@link ByteBuffer} instance to close
	 */
	protected void closeDirectBuffer(ByteBuffer byteBuffer) {
	    if (!byteBuffer.isDirect()) {
	    	return;
	    }

	    // we could use this type cast and call functions without reflection code,
	    // but static import from sun.* package is risky for non-SUN virtual machine.
	    //try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
	    try {
	        Method cleaner = byteBuffer.getClass().getMethod("cleaner");
	        cleaner.setAccessible(true);
	        Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
	        clean.setAccessible(true);
	        clean.invoke(cleaner.invoke(byteBuffer));
	    } catch(Exception ex) { 
	    	
	    }
	    
	    byteBuffer = null;
	}

	@Override
	public int numBytes() {
		return this.numBytes;
	}
	
	@Override
	public byte[] toByteArray() {
		if(this.buffer.hasArray()) {
			return this.buffer.array();
		}
		
		byte[] bytes = new byte[this.numBytes];
		for(int index = 0; index < this.numBytes; index++) {
			bytes[index]  = this.buffer.get(index);
		}
		
		return bytes;
	}

	@Override
	public int getHighestBitSet() {
		for(int index = this.numBytes - 1; index >= 0; index--) {
			byte bite = this.buffer.get(index);
			if(bite != 0) {
				// this is the highest set bit
				if(index > 0) {
					return (index * 8) + BitUtils.getHighestSetBitIndex(bite);
				}
				
				return BitUtils.getHighestSetBitIndex(bite);
			}
		}
		
		// not found
		return -1;
	}

	@Override
	public int getLowestBitSet() {
		for(int index = 0; index < this.numBytes; index++) {
			byte bite = this.buffer.get(index);
			if(bite != 0) {
				// this is the highest set bit
				if(index > 0) {
					return (index * 8) + BitUtils.getLowestSetBitIndex(bite);
				}
				
				return BitUtils.getLowestSetBitIndex(bite);
			}
		}
		
		// not found
		return -1;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy