aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLasse Collin <lasse.collin@tukaani.org>2017-10-12 19:36:03 +0300
committerLasse Collin <lasse.collin@tukaani.org>2017-12-16 17:34:25 +0200
commitc9977ef12b482d621622d14dd3e75e7416c2bd7c (patch)
tree1a4c17176a44e0312729165b20d598b04dc4ffff
parentd534dc50d8d2bbe134881385b634cc4ff1160215 (diff)
downloadxz-java-c9977ef12b482d621622d14dd3e75e7416c2bd7c.tar.gz
Add ArrayCache, ResettableArrayCache, and CloseIgnoringInputStream.
-rw-r--r--fileset-src.txt3
-rw-r--r--src/org/tukaani/xz/ArrayCache.java172
-rw-r--r--src/org/tukaani/xz/CloseIgnoringInputStream.java48
-rw-r--r--src/org/tukaani/xz/ResettableArrayCache.java120
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();
+ }
+ }
+ }
+}