/* (c) 2014 LinkedIn Corp. All rights reserved. * * 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. */ package com.linkedin.cubert.memory; import java.util.ArrayList; import java.util.List; /** * PagedByteArray * is a paged extension of a byte array which substiantially reduces data copy and memory reallocation. * * Created by spyne on 7/29/14. */ public class PagedByteArray { /** * Data is store in chunks of byte arrays stored in an ArrayList. */ final List<byte[]> chunks = new ArrayList<byte[]>(); /** * The size of each chunk. This is fixed once the object is initialized. */ final int chunkSize; /** * This maintains the max index that was written * which is used to determine the size of the data = maxIndex + 1 */ int maxIndex = -1; /** * Create a PagedByteArray object. * * @param chunkSize Size of each chunk in bytes */ public PagedByteArray(int chunkSize) { if (chunkSize < 0) { throw new IllegalArgumentException("Negative chunk size: " + chunkSize); } this.chunkSize = chunkSize; } /** * Read a byte at a specific index * * @param index index of the element in the array * @return value of the byte */ public byte read(final int index) { final int chunkIdx = getChunkIdx(index); final int pos = index % chunkSize; return chunks.get(chunkIdx)[pos]; } /** * Read a sequence of bytes. * * @param dest The destination byte array where the data is to be copied into * @param destOffset The start position in the destination array where the data is copied to * @param length The number of bytes to be copied * @param index The start position in the PagedByteArray where the read starts * @return the number of bytes copied */ public int read(final byte[] dest, final int destOffset, final int length, final int index) { final int lastIndex = index + length - 1; final int lastChunkIdx = getChunkIdx(lastIndex); int chunkIdx = getChunkIdx(index); int pos = index % chunkSize; if (chunkIdx == lastChunkIdx) { System.arraycopy(chunks.get(chunkIdx), pos, dest, destOffset, length); } else { /* Read the current chunk completely */ int read = chunkSize - pos; System.arraycopy(chunks.get(chunkIdx++), pos, dest, destOffset, read); /* Read intermediate complete chunks */ int rem = length - read; while (rem > chunkSize) { System.arraycopy(chunks.get(chunkIdx++), 0, dest, destOffset + read, chunkSize); read += chunkSize; rem -= chunkSize; } /* Read the last portion left */ System.arraycopy(chunks.get(chunkIdx), 0, dest, destOffset + read, rem); } return length; } /** * API to write a byte at position * * @param b the value of the byte * @param index position 0..(size - 1) */ public void write(final byte b, final int index) { final int chunkIdx = getChunkIdx(index); final int pos = index % chunkSize; ensureCapacity(chunkIdx); chunks.get(chunkIdx)[pos] = b; if (maxIndex < index) maxIndex = index; } /** * API to write a chunk of bytes * * @param bArray the source byte array * @param srcOffset the offset from which data is to be copied from * @param length number of bytes to write * @param index position 0..(size - 1) */ public void write(final byte bArray[], final int srcOffset, final int length, final int index) { final int lastIndex = index + length - 1; final int lastChunkIdx = getChunkIdx(lastIndex); ensureCapacity(lastChunkIdx); int chunkIdx = getChunkIdx(index); int pos = index % chunkSize; if (chunkIdx == lastChunkIdx) { System.arraycopy(bArray, srcOffset, chunks.get(chunkIdx), pos, length); } else { /* Fill up the current chunk */ int written = chunkSize - pos; System.arraycopy(bArray, srcOffset, chunks.get(chunkIdx++), pos, written); /* If data spills over multiple chunks. */ int rem = length - written; while (rem > chunkSize) { System.arraycopy(bArray, srcOffset + written, chunks.get(chunkIdx++), 0, chunkSize); rem -= chunkSize; written += chunkSize; } /* Write the last portion */ System.arraycopy(bArray, srcOffset + written, chunks.get(chunkIdx), 0, rem); } if (maxIndex < lastIndex) maxIndex = lastIndex; } /** * Get the Chunk Index for a given offset * * @param offset index of the element in the array * @return the Chunk Index */ int getChunkIdx(int offset) { return offset / chunkSize; } /** * Ensures that the PagedByteArray can store at least chunkIdx + 1 chunks * @param chunkIdx the chunk Index */ private void ensureCapacity(int chunkIdx) { /* allocate till data.size() == chunkIdx + 1 */ while (chunks.size() <= chunkIdx) { chunks.add(new byte[chunkSize]); } } /** * Since the size of a pagedByteArray is determined by the bytes written as opposed to the total * memory allocated. The size is computed by observing the max index that was written to. * * @return the size of t */ public int size() { return maxIndex + 1; } /** * Interface to reuse the data structure. */ public void clear() { chunks.clear(); maxIndex = -1; } }