diff options
author | Lasse Collin <lasse.collin@tukaani.org> | 2017-10-12 19:36:03 +0300 |
---|---|---|
committer | Lasse Collin <lasse.collin@tukaani.org> | 2017-12-16 17:34:25 +0200 |
commit | c9977ef12b482d621622d14dd3e75e7416c2bd7c (patch) | |
tree | 1a4c17176a44e0312729165b20d598b04dc4ffff | |
parent | d534dc50d8d2bbe134881385b634cc4ff1160215 (diff) | |
download | xz-java-c9977ef12b482d621622d14dd3e75e7416c2bd7c.tar.gz |
Add ArrayCache, ResettableArrayCache, and CloseIgnoringInputStream.
-rw-r--r-- | fileset-src.txt | 3 | ||||
-rw-r--r-- | src/org/tukaani/xz/ArrayCache.java | 172 | ||||
-rw-r--r-- | src/org/tukaani/xz/CloseIgnoringInputStream.java | 48 | ||||
-rw-r--r-- | src/org/tukaani/xz/ResettableArrayCache.java | 120 |
4 files changed, 343 insertions, 0 deletions
diff --git a/fileset-src.txt b/fileset-src.txt index 3850458..4651750 100644 --- a/fileset-src.txt +++ b/fileset-src.txt @@ -6,12 +6,14 @@ src/XZSeekDecDemo.java src/XZSeekEncDemo.java src/org/tukaani/xz/ARMOptions.java src/org/tukaani/xz/ARMThumbOptions.java +src/org/tukaani/xz/ArrayCache.java src/org/tukaani/xz/BCJCoder.java src/org/tukaani/xz/BCJDecoder.java src/org/tukaani/xz/BCJEncoder.java src/org/tukaani/xz/BCJOptions.java src/org/tukaani/xz/BlockInputStream.java src/org/tukaani/xz/BlockOutputStream.java +src/org/tukaani/xz/CloseIgnoringInputStream.java src/org/tukaani/xz/CorruptedInputException.java src/org/tukaani/xz/CountingInputStream.java src/org/tukaani/xz/CountingOutputStream.java @@ -40,6 +42,7 @@ src/org/tukaani/xz/LZMAOutputStream.java src/org/tukaani/xz/MemoryLimitException.java src/org/tukaani/xz/PowerPCOptions.java src/org/tukaani/xz/RawCoder.java +src/org/tukaani/xz/ResettableArrayCache.java src/org/tukaani/xz/SPARCOptions.java src/org/tukaani/xz/SeekableFileInputStream.java src/org/tukaani/xz/SeekableInputStream.java diff --git a/src/org/tukaani/xz/ArrayCache.java b/src/org/tukaani/xz/ArrayCache.java new file mode 100644 index 0000000..c940d77 --- /dev/null +++ b/src/org/tukaani/xz/ArrayCache.java @@ -0,0 +1,172 @@ +/* + * ArrayCache + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +package org.tukaani.xz; + +/** + * Caches large arrays for reuse (base class and a dummy cache implementation). + * <p> + * When compressing or decompressing many (very) small files in a row, the + * time spent in construction of new compressor or decompressor objects + * can be longer than the time spent in actual compression or decompression. + * A large part of this initialization overhead comes from allocation and + * garbage collection of large arrays. + * <p> + * The {@code ArrayCache} API provides a way to cache large array allocations + * for reuse. It can give a major performance improvement when compressing or + * decompressing many tiny files. If you are only (de)compressing one or two + * files or the files a very big, array caching won't improve anything, + * although it won't make anything slower either. + * <p> + * <b>Important: The users of ArrayCache don't return the allocated arrays + * back to the cache in all situations.</b> + * This a reason why it's called a cache instead of a pool. + * If it is important to be able to return every array back to a cache, + * {@link ResettableArrayCache} can be useful. + * <p> + * In compressors (OutputStreams) the arrays are returned to the cache + * when a call to {@code finish()} or {@code close()} returns + * successfully (no exceptions are thrown). + * <p> + * In decompressors (InputStreams) the arrays are returned to the cache when + * the decompression is successfully finished ({@code read} returns {@code -1}) + * or {@code close()} or {@code close(boolean)} is called. This is true even + * if closing throws an exception. + * <p> + * Raw decompressors don't support {@code close(boolean)}. With raw + * decompressors, if one wants to put the arrays back to the cache without + * closing the underlying {@code InputStream}, one can wrap the + * {@code InputStream} into {@link CloseIgnoringInputStream} when creating + * the decompressor instance. Then one can use {@code close()}. + * <p> + * Different cache implementations can be extended from this base class. + * All cache implementations must be thread safe. + * <p> + * This class also works as a dummy cache that simply calls {@code new} + * to allocate new arrays and doesn't try to cache anything. A statically + * allocated dummy cache is available via {@link #getDummyCache()}. + * <p> + * If no {@code ArrayCache} is specified when constructing a compressor or + * decompressor, the default {@code ArrayCache} implementation is used. + * See {@link #getDefaultCache()} and {@link #setDefaultCache(ArrayCache)}. + * <p> + * This is a class instead of an interface because it's possible that in the + * future we may want to cache other array types too. New methods can be + * added to this class without breaking existing cache implementations. + * + * @since 1.7 + * + * @see BasicArrayCache + */ +public class ArrayCache { + /** + * Global dummy cache instance that is returned by {@code getDummyCache()}. + */ + private static final ArrayCache dummyCache = new ArrayCache(); + + /** + * Global default {@code ArrayCache} that is used when no other cache has + * been specified. + */ + private static volatile ArrayCache defaultCache = dummyCache; + + /** + * Returns a statically-allocated {@code ArrayCache} instance. + * It can be shared by all code that needs a dummy cache. + */ + public static ArrayCache getDummyCache() { + return dummyCache; + } + + /** + * Gets the default {@code ArrayCache} instance. + * This is a global cache that is used when the application + * specifies nothing else. The default is a dummy cache + * (see {@link #getDummyCache()}). + */ + public static ArrayCache getDefaultCache() { + // It's volatile so no need for synchronization. + return defaultCache; + } + + /** + * Sets the default {@code ArrayCache} instance. + * Use with care. Other libraries using this package probably shouldn't + * call this function as libraries cannot know if there are other users + * of the xz package in the same application. + */ + public static void setDefaultCache(ArrayCache arrayCache) { + if (arrayCache == null) + throw new NullPointerException(); + + // It's volatile so no need for synchronization. + defaultCache = arrayCache; + } + + /** + * Creates a new {@code ArrayCache} that does no caching + * (a dummy cache). If you need a dummy cache, you may want to call + * {@link #getDummyCache()} instead. + */ + public ArrayCache() {} + + /** + * Allocates a new byte array. + * <p> + * This implementation simply returns {@code new byte[size]}. + * + * @param size the minimum size of the array to allocate; + * an implementation may return an array that + * is larger than the given {@code size} + * + * @param fillWithZeros if true, the caller expects that the first + * {@code size} elements in the array are zero; + * if false, the array contents can be anything, + * which speeds things up when reusing a cached + * array + */ + public byte[] getByteArray(int size, boolean fillWithZeros) { + return new byte[size]; + } + + /** + * Puts the given byte array to the cache. The caller must no longer + * use the array. + * <p> + * This implementation does nothing. + */ + public void putArray(byte[] array) {} + + /** + * Allocates a new int array. + * <p> + * This implementation simply returns {@code new int[size]}. + * + * @param size the minimum size of the array to allocate; + * an implementation may return an array that + * is larger than the given {@code size} + * + * @param fillWithZeros if true, the caller expects that the first + * {@code size} elements in the array are zero; + * if false, the array contents can be anything, + * which speeds things up when reusing a cached + * array + */ + public int[] getIntArray(int size, boolean fillWithZeros) { + return new int[size]; + } + + /** + * Puts the given int array to the cache. The caller must no longer + * use the array. + * <p> + * This implementation does nothing. + */ + public void putArray(int[] array) {} +} diff --git a/src/org/tukaani/xz/CloseIgnoringInputStream.java b/src/org/tukaani/xz/CloseIgnoringInputStream.java new file mode 100644 index 0000000..db68ddb --- /dev/null +++ b/src/org/tukaani/xz/CloseIgnoringInputStream.java @@ -0,0 +1,48 @@ +/* + * CloseIgnoringInputStream + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +package org.tukaani.xz; + +import java.io.InputStream; +import java.io.FilterInputStream; + +/** + * An {@code InputStream} wrapper whose {@code close()} does nothing. + * This is useful with raw decompressors if you want to call + * {@code close()} to release memory allocated from an {@link ArrayCache} + * but don't want to close the underlying {@code InputStream}. + * For example: + * <p><blockquote><pre> + * InputStream rawdec = new LZMA2InputStream( + * new CloseIgnoringInputStream(myInputStream), + * myDictSize, null, myArrayCache); + * doSomething(rawdec); + * rawdec.close(); // This doesn't close myInputStream. + * </pre></blockquote> + * <p> + * With {@link XZInputStream}, {@link SingleXZInputStream}, and + * {@link SeekableXZInputStream} you can use their {@code close(boolean)} + * method to avoid closing the underlying {@code InputStream}; with + * those classes {@code CloseIgnoringInputStream} isn't needed. + * + * @since 1.7 + */ +public class CloseIgnoringInputStream extends FilterInputStream { + /** + * Creates a new {@code CloseIgnoringInputStream}. + */ + public CloseIgnoringInputStream(InputStream in) { + super(in); + } + + /** + * This does nothing (doesn't call {@code in.close()}). + */ + public void close() {} +} diff --git a/src/org/tukaani/xz/ResettableArrayCache.java b/src/org/tukaani/xz/ResettableArrayCache.java new file mode 100644 index 0000000..2f89c1d --- /dev/null +++ b/src/org/tukaani/xz/ResettableArrayCache.java @@ -0,0 +1,120 @@ +/* + * ResettableArrayCache + * + * Author: Lasse Collin <lasse.collin@tukaani.org> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + +package org.tukaani.xz; + +import java.util.ArrayList; +import java.util.List; + +/** + * An ArrayCache wrapper that remembers what has been allocated + * and allows returning all allocations to the underlying cache at once. + * + * @since 1.7 + */ +public class ResettableArrayCache extends ArrayCache { + private final ArrayCache arrayCache; + + // Lists of arrays that have been allocated from the arrayCache. + private final List<byte[]> byteArrays; + private final List<int[]> intArrays; + + /** + * Creates a new ResettableArrayCache based on the given ArrayCache. + */ + public ResettableArrayCache(ArrayCache arrayCache) { + this.arrayCache = arrayCache; + + // Treat the dummy cache as a special case since it's a common case. + // With it we don't need to put the arrays back to the cache and + // thus we don't need to remember what has been allocated. + if (arrayCache == ArrayCache.getDummyCache()) { + byteArrays = null; + intArrays = null; + } else { + byteArrays = new ArrayList<byte[]>(); + intArrays = new ArrayList<int[]>(); + } + } + + public byte[] getByteArray(int size, boolean fillWithZeros) { + byte[] array = arrayCache.getByteArray(size, fillWithZeros); + + if (byteArrays != null) { + synchronized(byteArrays) { + byteArrays.add(array); + } + } + + return array; + } + + public void putArray(byte[] array) { + if (byteArrays != null) { + // The array is more likely to be near the end of the list so + // start the search from the end. + synchronized(byteArrays) { + int i = byteArrays.lastIndexOf(array); + if (i != -1) + byteArrays.remove(i); + } + + arrayCache.putArray(array); + } + } + + public int[] getIntArray(int size, boolean fillWithZeros) { + int[] array = arrayCache.getIntArray(size, fillWithZeros); + + if (intArrays != null) { + synchronized(intArrays) { + intArrays.add(array); + } + } + + return array; + } + + public void putArray(int[] array) { + if (intArrays != null) { + synchronized(intArrays) { + int i = intArrays.lastIndexOf(array); + if (i != -1) + intArrays.remove(i); + } + + arrayCache.putArray(array); + } + } + + /** + * Puts all allocated arrays back to the underlying ArrayCache + * that haven't already been put there with a call to + * {@code putArray}. + */ + public void reset() { + if (byteArrays != null) { + // Put the arrays to the cache in reverse order: the array that + // was allocated first is returned last. + synchronized(byteArrays) { + for (int i = byteArrays.size() - 1; i >= 0; --i) + arrayCache.putArray(byteArrays.get(i)); + + byteArrays.clear(); + } + + synchronized(intArrays) { + for (int i = intArrays.size() - 1; i >= 0; --i) + arrayCache.putArray(intArrays.get(i)); + + intArrays.clear(); + } + } + } +} |