]> granicus.if.org Git - icu/commitdiff
ICU-10932 merge branches/markus/lowmemres to trunk: use ByteBuffer to read .res files...
authorMarkus Scherer <markus.icu@gmail.com>
Fri, 6 Jun 2014 20:52:10 +0000 (20:52 +0000)
committerMarkus Scherer <markus.icu@gmail.com>
Fri, 6 Jun 2014 20:52:10 +0000 (20:52 +0000)
X-SVN-Rev: 35815

icu4j/main/classes/core/src/com/ibm/icu/impl/ICUBinary.java
icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundle.java
icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleImpl.java
icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleReader.java
icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceTableAccess.java
icu4j/main/classes/core/src/com/ibm/icu/impl/ZoneMeta.java
icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java
icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java

index 8856516bd38b948623e4574cbffe9cac3d65c5f8..155f7712eb5e5d1672731b78a1a4436d98f74dc6 100644 (file)
@@ -242,6 +242,14 @@ public final class ICUBinary
         }
     }
 
+    /**
+     * Returns a VersionInfo for the bytes in the compact version integer.
+     */
+    public static VersionInfo getVersionInfoFromCompactInt(int version) {
+        return VersionInfo.getInstance(
+                version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
+    }
+
     // private variables -------------------------------------------------
   
     /**
index 979840bb60f07e8559dab8c00a549d8eb292e57f..5ea67c3f4ec87fc7762cf24c92bf203a1a415b03 100644 (file)
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.StringTokenizer;
 
 import com.ibm.icu.impl.URLHandler.URLVisitor;
+import com.ibm.icu.util.Output;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.UResourceBundle;
 import com.ibm.icu.util.UResourceBundleIterator;
@@ -335,11 +336,10 @@ public  class ICUResourceBundle extends UResourceBundle {
      * @exception MissingResourceException If a resource was not found.
      */
     public ICUResourceBundle getWithFallback(String path) throws MissingResourceException {
-        ICUResourceBundle result = null;
         ICUResourceBundle actualBundle = this;
 
         // now recurse to pick up sub levels of the items
-        result = findResourceWithFallback(path, actualBundle, null);
+        ICUResourceBundle result = findResourceWithFallback(path, actualBundle, null, null);
 
         if (result == null) {
             throw new MissingResourceException(
@@ -348,8 +348,8 @@ public  class ICUResourceBundle extends UResourceBundle {
                 path, getKey());
         }
 
-        if ( result.getType() == ICUResourceBundle.STRING && result.getString().equals(NO_INHERITANCE_MARKER)) {
-            throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER",path,getKey());
+        if (result.getType() == STRING && result.getString().equals(NO_INHERITANCE_MARKER)) {
+            throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey());
         }
 
         return result;
@@ -384,12 +384,33 @@ public  class ICUResourceBundle extends UResourceBundle {
      * @return the resource, or null
      */
     public ICUResourceBundle findWithFallback(String path) {
-        return findResourceWithFallback(path, this, null);
+        return findResourceWithFallback(path, this, null, null);
+    }
+    public String findStringWithFallback(String path) {
+        Output<String> outString = new Output<String>();
+        findResourceWithFallback(path, this, null, outString);
+        return outString.value;
     }
 
     // will throw type mismatch exception if the resource is not a string
     public String getStringWithFallback(String path) throws MissingResourceException {
-        return getWithFallback(path).getString();
+        // Optimized form of getWithFallback(path).getString();
+        ICUResourceBundle actualBundle = this;
+        Output<String> outString = new Output<String>();
+        findResourceWithFallback(path, actualBundle, null, outString);
+        String result = outString.value;
+
+        if (result == null) {
+            throw new MissingResourceException(
+                "Can't find resource for bundle "
+                + this.getClass().getName() + ", key " + getType(),
+                path, getKey());
+        }
+
+        if (result.equals(NO_INHERITANCE_MARKER)) {
+            throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey());
+        }
+        return result;
     }
 
     /**
@@ -776,7 +797,8 @@ public  class ICUResourceBundle extends UResourceBundle {
     }
 
     protected static final ICUResourceBundle findResourceWithFallback(String path,
-            UResourceBundle actualBundle, UResourceBundle requested) {
+            UResourceBundle actualBundle, UResourceBundle requested,
+            Output<String> outString) {
         ICUResourceBundle sub = null;
         if (requested == null) {
             requested = actualBundle;
@@ -788,8 +810,19 @@ public  class ICUResourceBundle extends UResourceBundle {
 
         while (base != null) {
             if (path.indexOf('/') == -1) { // skip the tokenizer
+                if (outString != null && base instanceof ICUResourceBundleImpl.ResourceTable) {
+                    String s = ((ICUResourceBundleImpl.ResourceTable)base).getStringOrNull(path);
+                    if (s != null) {
+                        outString.value = s;
+                        return null;
+                    }
+                }
                 sub = (ICUResourceBundle) base.handleGet(path, null, requested);
                 if (sub != null) {
+                    if (outString != null && sub.getType() == STRING) {
+                        outString.value = sub.getString();  // string from alias handling
+                        return null;
+                    }
                     break;
                 }
             } else {
@@ -797,8 +830,12 @@ public  class ICUResourceBundle extends UResourceBundle {
                 StringTokenizer st = new StringTokenizer(path, "/");
                 while (st.hasMoreTokens()) {
                     String subKey = st.nextToken();
-                    sub = ICUResourceBundle.findResourceWithFallback(subKey, currentBase, requested);
+                    sub = ICUResourceBundle.findResourceWithFallback(subKey, currentBase, requested,
+                            st.hasMoreTokens() ? null : outString);
                     if (sub == null) {
+                        if (outString != null && outString.value != null) {
+                            return null;
+                        }
                         break;
                     }
                     currentBase = sub;
@@ -1182,7 +1219,7 @@ public  class ICUResourceBundle extends UResourceBundle {
             // Get the top bundle of the requested bundle
             bundle = (ICUResourceBundle)getBundleInstance(bundleName, locale, loaderToUse, false);
             if (bundle != null) {
-                sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null);
+                sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null, null);
                 // TODO
                 // The resPath of the resolved bundle should reflect the resource path
                 // requested by caller. However, overwriting resPath here will affect cached
index e7ff03266448b5474a727772f7f2fbd18e32ee5d..db2a7b20a484c8dd3fef13143cae73819d069e32 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *******************************************************************************
- * Copyright (C) 2004-2011, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
+ * Copyright (C) 2004-2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
  *******************************************************************************
  */
 package com.ibm.icu.impl;
@@ -12,7 +12,6 @@ import java.util.Set;
 import java.util.TreeSet;
 
 import com.ibm.icu.util.UResourceBundle;
-import com.ibm.icu.util.UResourceBundleIterator;
 import com.ibm.icu.util.UResourceTypeMismatchException;
 
 class ICUResourceBundleImpl extends ICUResourceBundle {
@@ -85,23 +84,28 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
     private static final class ResourceString extends ICUResourceBundleImpl {
         private String value;
         public String getString() {
-            return value;
+            if (value != null) {
+                return value;
+            }
+            return reader.getString(resource);
         }
         ResourceString(ICUResourceBundleReader reader, String key, String resPath, int resource,
                        ICUResourceBundleImpl container) {
             super(reader, key, resPath, resource, container);
-            value = reader.getString(resource);
+            String s = reader.getString(resource);
+            // Allow the reader cache's SoftReference to do its job.
+            if (s.length() < ICUResourceBundleReader.LARGE_SIZE / 2) {
+                value = s;
+            }
         }
     }
     private static final class ResourceIntVector extends ICUResourceBundleImpl {
-        private int[] value;
         public int[] getIntVector() {
-            return value;
+            return reader.getIntVector(resource);
         }
         ResourceIntVector(ICUResourceBundleReader reader, String key, String resPath, int resource,
                           ICUResourceBundleImpl container) {
             super(reader, key, resPath, resource, container);
-            value = reader.getIntVector(resource);
         }
     }
 
@@ -113,8 +117,20 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
         public int getSize() {
             return value.getSize();
         }
+        @Override
+        public String getString(int index) {
+            int res = value.getContainerResource(reader, index);
+            if (res == RES_BOGUS) {
+                throw new IndexOutOfBoundsException();
+            }
+            String s = reader.getString(res);
+            if (s != null) {
+                return s;
+            }
+            return super.getString(index);
+        }
         protected int getContainerResource(int index) {
-            return value.getContainerResource(index);
+            return value.getContainerResource(reader, index);
         }
         protected UResourceBundle createBundleObject(int index, String resKey, HashMap<String, String> table,
                                                      UResourceBundle requested, boolean[] isAlias) {
@@ -131,11 +147,14 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
     }
     private static class ResourceArray extends ResourceContainer {
         protected String[] handleGetStringArray() {
-            String[] strings = new String[value.getSize()];
-            UResourceBundleIterator iter = getIterator();
-            int i = 0;
-            while (iter.hasNext()) {
-                strings[i++] = iter.next().getString();
+            int length = value.getSize();
+            String[] strings = new String[length];
+            for (int i = 0; i < length; ++i) {
+                String s = reader.getString(value.getContainerResource(reader, i));
+                if (s == null) {
+                    throw new UResourceTypeMismatchException("");
+                }
+                strings[i] = s;
             }
             return strings;
         }
@@ -167,18 +186,18 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
     }
     static class ResourceTable extends ResourceContainer {
         protected String getKey(int index) {
-            return ((ICUResourceBundleReader.Table)value).getKey(index);
+            return ((ICUResourceBundleReader.Table)value).getKey(reader, index);
         }
         protected Set<String> handleKeySet() {
             TreeSet<String> keySet = new TreeSet<String>();
             ICUResourceBundleReader.Table table = (ICUResourceBundleReader.Table)value;
             for (int i = 0; i < table.getSize(); ++i) {
-                keySet.add(table.getKey(i));
+                keySet.add(table.getKey(reader, i));
             }
             return keySet;
         }
         protected int getTableResource(String resKey) {
-            return ((ICUResourceBundleReader.Table)value).getTableResource(resKey);
+            return ((ICUResourceBundleReader.Table)value).getTableResource(reader, resKey);
         }
         protected int getTableResource(int index) {
             return getContainerResource(index);
@@ -186,7 +205,7 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
         protected UResourceBundle handleGetImpl(String resKey, HashMap<String, String> table,
                                                 UResourceBundle requested,
                                                 int[] index, boolean[] isAlias) {
-            int i = ((ICUResourceBundleReader.Table)value).findTableItem(resKey);
+            int i = ((ICUResourceBundleReader.Table)value).findTableItem(reader, resKey);
             if(index != null) {
                 index[0] = i;
             }
@@ -197,12 +216,58 @@ class ICUResourceBundleImpl extends ICUResourceBundle {
         }
         protected UResourceBundle handleGetImpl(int index, HashMap<String, String> table,
                                                 UResourceBundle requested, boolean[] isAlias) {
-            String itemKey = ((ICUResourceBundleReader.Table)value).getKey(index);
+            String itemKey = ((ICUResourceBundleReader.Table)value).getKey(reader, index);
             if (itemKey == null) {
                 throw new IndexOutOfBoundsException();
             }
             return createBundleObject(index, itemKey, table, requested, isAlias);
         }
+        @Override
+        protected Object handleGetObject(String key) {
+            // Fast path for common cases: Avoid creating UResourceBundles if possible.
+            // It would be even better if we could override getString(key)/getStringArray(key),
+            // so that we know the expected object type,
+            // but those are final in java.util.ResourceBundle.
+            int index = ((ICUResourceBundleReader.Table)value).findTableItem(reader, key);
+            if (index >= 0) {
+                int res = value.getContainerResource(reader, index);
+                // getString(key)
+                String s = reader.getString(res);
+                if (s != null) {
+                    return s;
+                }
+                // getStringArray(key)
+                ICUResourceBundleReader.Container array = reader.getArray(res);
+                if (array != null) {
+                    int length = array.getSize();
+                    String[] strings = new String[length];
+                    for (int j = 0;; ++j) {
+                        if (j == length) {
+                            return strings;
+                        }
+                        s = reader.getString(array.getContainerResource(reader, j));
+                        if (s == null) {
+                            // Equivalent to resolveObject(key, requested):
+                            // If this is not a string array,
+                            // then build and return a UResourceBundle.
+                            break;
+                        }
+                        strings[j] = s;
+                    }
+                }
+            }
+            return super.handleGetObject(key);
+        }
+        /**
+         * Returns a String if found, or null if not found or if the key item is not a string.
+         */
+        String getStringOrNull(String key) {
+            int index = ((ICUResourceBundleReader.Table)value).findTableItem(reader, key);
+            if (index < 0) {
+                return null;
+            }
+            return reader.getString(value.getContainerResource(reader, index));
+        }
         ResourceTable(ICUResourceBundleReader reader, String key, String resPath, int resource,
                       ICUResourceBundleImpl container) {
             super(reader, key, resPath, resource, container);
index 4a8b00e657c7a9ec795f111a8a63253477622aff..72591fdae1854aec09370f9a478d422ef366b03e 100644 (file)
@@ -1,17 +1,18 @@
 /*
  *******************************************************************************
- * Copyright (C) 2004-2014, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
+ * Copyright (C) 2004-2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
  *******************************************************************************
  */
 package com.ibm.icu.impl;
 
-import java.io.BufferedInputStream;
-import java.io.DataInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.SoftReference;
 import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
 
+import com.ibm.icu.util.ICUException;
 import com.ibm.icu.util.ICUUncheckedIOException;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.UResourceBundle;
@@ -203,13 +204,19 @@ import com.ibm.icu.util.VersionInfo;
  *                     (no value restrictions, all values 0..ffff allowed!).
  * - Vectors of 32-bit words stored as type Integer Vector.
  */
-public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
+public final class ICUResourceBundleReader {
     /**
      * File format version that this class understands.
      * "ResB"
      */
-    private static final byte DATA_FORMAT_ID[] = {(byte)0x52, (byte)0x65, 
-                                                     (byte)0x73, (byte)0x42};
+    private static final int DATA_FORMAT = 0x52657342;
+    private static final class IsAcceptable implements ICUBinary.Authenticate {
+        // @Override when we switch to Java 6
+        public boolean isDataVersionAcceptable(byte formatVersion[]) {
+            return (formatVersion[0] == 1 || formatVersion[0] == 2);
+        }
+    }
+    private static final IsAcceptable IS_ACCEPTABLE = new IsAcceptable();
 
     /* indexes[] value names; indexes are generally 32-bit (Resource) indexes */
     private static final int URES_INDEX_LENGTH           = 0;   /* contains URES_INDEX_TOP==the length of indexes[];
@@ -223,7 +230,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
     //ivate static final int URES_INDEX_RESOURCES_TOP    = 2;   /* contains the top of all resources */
     private static final int URES_INDEX_BUNDLE_TOP       = 3;   /* contains the top of the bundle, */
                                                                 /* in case it were ever different from [2] */
-    //ivate static final int URES_INDEX_MAX_TABLE_LENGTH = 4;   /* max. length of any table */
+    private static final int URES_INDEX_MAX_TABLE_LENGTH = 4;   /* max. length of any table */
     private static final int URES_INDEX_ATTRIBUTES       = 5;   /* attributes bit set, see URES_ATT_* (new in formatVersion 1.2) */
     private static final int URES_INDEX_16BIT_TOP        = 6;   /* top of the 16-bit units (UTF-16 string v2 UChars, URES_TABLE16, URES_ARRAY16),
                                                                  * rounded up (new in formatVersion 2.0, ICU 4.4) */
@@ -252,26 +259,35 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
     private static final int URES_ATT_IS_POOL_BUNDLE = 2;
     private static final int URES_ATT_USES_POOL_BUNDLE = 4;
 
+    private static final CharBuffer EMPTY_16_BIT_UNITS = CharBuffer.wrap("\0");  // read-only
+
+    /**
+     * Objects with more value bytes are stored in SoftReferences.
+     * Smaller objects (which are not much larger than a SoftReference)
+     * are stored directly, avoiding the overhead of the reference.
+     */
+    static final int LARGE_SIZE = 24;
+
     private static final boolean DEBUG = false;
-    
-    private byte[] /* formatVersion, */ dataVersion;
+
+    private int /* formatVersion, */ dataVersion;
 
     // See the ResourceData struct in ICU4C/source/common/uresdata.h.
-    private String s16BitUnits;
-    private byte[] poolBundleKeys;
-    private String poolBundleKeysAsString;
+    /**
+     * Buffer of all of the resource bundle bytes after the header.
+     * (equivalent of C++ pRoot)
+     */
+    private ByteBuffer bytes;
+    private CharBuffer b16BitUnits;
+    private ByteBuffer poolBundleKeys;
     private int rootRes;
     private int localKeyLimit;
     private boolean noFallback; /* see URES_ATT_NO_FALLBACK */
     private boolean isPoolBundle;
     private boolean usesPoolBundle;
+    private int poolCheckSum;
 
-    // Fields specific to the Java port.
-    private int[] indexes;
-    private byte[] keyStrings;
-    private String keyStringsAsString;  // null except if isPoolBundle
-    private byte[] resourceBytes;
-    private int resourceBottom;  // File offset where the mixed-type resources start.
+    private ResourceCache resourceCache;
 
     private static ReaderCache CACHE = new ReaderCache();
     private static final ICUResourceBundleReader NULL_READER = new ICUResourceBundleReader();
@@ -321,27 +337,15 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
     }
 
     /*
-     * Sole constructor, just used for NULL_READER
+     * Default constructor, just used for NULL_READER.
      */
     private ICUResourceBundleReader() {
     }
 
     private ICUResourceBundleReader(InputStream stream, String baseName, String localeID, ClassLoader loader) {
-        BufferedInputStream bs = new BufferedInputStream(stream);
         try {
-            if (DEBUG)  {
-                System.out.println("The InputStream class is: " + stream.getClass().getName());
-                System.out.println("The BufferedInputStream class is: " + bs.getClass().getName());
-                System.out.println("The bytes avialable in stream before reading the header: " + bs.available());
-            }
-
-            dataVersion = ICUBinary.readHeader(bs, DATA_FORMAT_ID, this);
-
-            if (DEBUG) System.out.println("The bytes available in stream after reading the header: " + bs.available());
-
-            readData(bs);
-            stream.close();
-
+            ByteBuffer inBytes = ICUBinary.getByteBufferFromInputStream(stream);
+            init(inBytes);
         } catch (IOException ex) {
             String fullName = ICUResourceBundleReader.getFullName(baseName, localeID);
             throw new ICUUncheckedIOException("Data file " + fullName + " is corrupt - " + ex.getMessage(), ex);
@@ -353,11 +357,10 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
             if (!poolBundleReader.isPoolBundle) {
                 throw new IllegalStateException("pool.res is not a pool bundle");
             }
-            if (poolBundleReader.indexes[URES_INDEX_POOL_CHECKSUM] != indexes[URES_INDEX_POOL_CHECKSUM]) {
+            if (poolBundleReader.poolCheckSum != poolCheckSum) {
                 throw new IllegalStateException("pool.res has a different checksum than this bundle");
             }
-            poolBundleKeys = poolBundleReader.keyStrings;
-            poolBundleKeysAsString = poolBundleReader.keyStringsAsString;
+            poolBundleKeys = poolBundleReader.bytes;
         }
     }
 
@@ -371,103 +374,95 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
     }
 
     // See res_init() in ICU4C/source/common/uresdata.c.
-    private void readData(InputStream stream) throws IOException {
-        DataInputStream ds = new DataInputStream(stream);
+    private void init(ByteBuffer inBytes) throws IOException {
+        dataVersion = ICUBinary.readHeader(inBytes, DATA_FORMAT, IS_ACCEPTABLE);
+        boolean isFormatVersion10 = inBytes.get(16) == 1 && inBytes.get(17) == 0;
+        bytes = inBytes.slice();
+        int dataLength = bytes.remaining();
+
+        if(DEBUG) System.out.println("The ByteBuffer is direct (memory-mapped): " + bytes.isDirect());
+        if(DEBUG) System.out.println("The available bytes in the buffer before reading the data: " + dataLength);
 
-        if(DEBUG) System.out.println("The DataInputStream class is: " + ds.getClass().getName());
-        if(DEBUG) System.out.println("The available bytes in the stream before reading the data: "+ds.available());
+        rootRes = bytes.getInt(0);
 
-        rootRes = ds.readInt();
+        if(isFormatVersion10) {
+            localKeyLimit = 0x10000;  /* greater than any 16-bit key string offset */
+            resourceCache = new ResourceCache(dataLength / 4 - 1);
+            return;
+        }
 
         // read the variable-length indexes[] array
-        int indexes0 = ds.readInt();
+        int indexes0 = getIndexesInt(URES_INDEX_LENGTH);
         int indexLength = indexes0 & 0xff;
-        indexes = new int[indexLength];
-        indexes[URES_INDEX_LENGTH] = indexes0;
-        for(int i=1; i<indexLength; i++){
-            indexes[i] = ds.readInt();   
+        if(indexLength <= URES_INDEX_MAX_TABLE_LENGTH) {
+            throw new ICUException("not enough indexes");
+        }
+        int bundleTop;
+        if(dataLength < ((1 + indexLength) << 2) ||
+                dataLength < ((bundleTop = getIndexesInt(URES_INDEX_BUNDLE_TOP)) << 2)) {
+            throw new ICUException("not enough bytes");
         }
-        resourceBottom = (1 + indexLength) << 2;
+        int maxOffset = bundleTop - 1;
 
         if(indexLength > URES_INDEX_ATTRIBUTES) {
             // determine if this resource bundle falls back to a parent bundle
             // along normal locale ID fallback
-            int att = indexes[URES_INDEX_ATTRIBUTES];
+            int att = getIndexesInt(URES_INDEX_ATTRIBUTES);
             noFallback = (att & URES_ATT_NO_FALLBACK) != 0;
             isPoolBundle = (att & URES_ATT_IS_POOL_BUNDLE) != 0;
             usesPoolBundle = (att & URES_ATT_USES_POOL_BUNDLE) != 0;
         }
 
-        int length = indexes[URES_INDEX_BUNDLE_TOP]*4;
-        if(DEBUG) System.out.println("The number of bytes in the bundle: "+length);
+        // Read the array of 16-bit units.
+        if(indexLength > URES_INDEX_16BIT_TOP) {
+            int keysTop = getIndexesInt(URES_INDEX_KEYS_TOP);
+            int _16BitTop = getIndexesInt(URES_INDEX_16BIT_TOP);
+            if(_16BitTop > keysTop) {
+                int num16BitUnits = (_16BitTop - keysTop) * 2;
+                bytes.position(keysTop << 2);
+                b16BitUnits = bytes.slice().asCharBuffer();
+                b16BitUnits.limit(num16BitUnits);
+                maxOffset |= num16BitUnits - 1;
+            } else {
+                b16BitUnits = EMPTY_16_BIT_UNITS;
+            }
+        } else {
+            b16BitUnits = EMPTY_16_BIT_UNITS;
+        }
 
-        // Read the local key strings.
-        // The keyStrings include NUL characters corresponding to the bytes
-        // up to the end of the indexes.
-        if(indexes[URES_INDEX_KEYS_TOP] > (1 + indexLength)) {
-            int keysBottom = (1 + indexLength) << 2;
-            int keysTop = indexes[URES_INDEX_KEYS_TOP] << 2;
-            resourceBottom = keysTop;
+        if(indexLength > URES_INDEX_POOL_CHECKSUM) {
+            poolCheckSum = getIndexesInt(URES_INDEX_POOL_CHECKSUM);
+        }
+
+        // Handle key strings last:
+        // If this is a pool bundle, then we shift all bytes down,
+        // and getIndexesInt() will not work any more.
+        if(getIndexesInt(URES_INDEX_KEYS_TOP) > (1 + indexLength)) {
             if(isPoolBundle) {
                 // Shift the key strings down:
                 // Pool bundle key strings are used with a 0-based index,
                 // unlike regular bundles' key strings for which indexes
                 // are based on the start of the bundle data.
-                keysTop -= keysBottom;
-                keysBottom = 0;
+                bytes.position((1 + indexLength) << 2);
+                bytes = bytes.slice();
             } else {
-                localKeyLimit = keysTop;
-            }
-            keyStrings = new byte[keysTop];
-            ds.readFully(keyStrings, keysBottom, keysTop - keysBottom);
-            if(isPoolBundle) {
-                // Overwrite trailing padding bytes so that the conversion works.
-                while(keysBottom < keysTop && keyStrings[keysTop - 1] == (byte)0xaa) {
-                    keyStrings[--keysTop] = 0;
-                }
-                keyStringsAsString = new String(keyStrings, "US-ASCII");
+                localKeyLimit = getIndexesInt(URES_INDEX_KEYS_TOP) << 2;
             }
         }
 
-        // Read the array of 16-bit units.
-        // We are not using
-        //   new String(keys, "UTF-16BE")
-        // because the 16-bit units may not be well-formed Unicode.
-        if( indexLength > URES_INDEX_16BIT_TOP &&
-            indexes[URES_INDEX_16BIT_TOP] > indexes[URES_INDEX_KEYS_TOP]
-        ) {
-            int num16BitUnits = (indexes[URES_INDEX_16BIT_TOP] -
-                                 indexes[URES_INDEX_KEYS_TOP]) * 2;
-            char[] c16BitUnits = new char[num16BitUnits];
-            // Note: Calling readFully() to read data into byte[] and copy
-            // the data to char[] is faster than calling readChar() one by one
-            // for large data
-            byte[] c16BitUnitsBytes = new byte[num16BitUnits * 2];
-            ds.readFully(c16BitUnitsBytes);
-            for (int i = 0; i < num16BitUnits; i++) {
-                c16BitUnits[i] = (char)((c16BitUnitsBytes[i*2] << 8) | (c16BitUnitsBytes[i*2 + 1] & 0xFF));
-            }
-            s16BitUnits = new String(c16BitUnits);
-            resourceBottom = indexes[URES_INDEX_16BIT_TOP] << 2;
-        } else {
-            s16BitUnits = "\0";
+        if(!isPoolBundle) {
+            resourceCache = new ResourceCache(maxOffset);
         }
-
-        // Read the block of bytes for the mixed-type resources.
-        resourceBytes = new byte[length - resourceBottom];
-        ds.readFully(resourceBytes);
     }
 
-    VersionInfo getVersion(){
-        return VersionInfo.getInstance(dataVersion[0],dataVersion[1],dataVersion[2],dataVersion[3]);   
+    private int getIndexesInt(int i) {
+        return bytes.getInt((1 + i) << 2);
     }
-    public boolean isDataVersionAcceptable(byte version[]){
-        // while ICU4C can read formatVersion 1.0 and up,
-        // ICU4J requires 1.1 as a minimum
-        // formatVersion = version;
-        return ((version[0] == 1 && version[1] >= 1) || version[0] == 2);
+
+    VersionInfo getVersion() {
+        return ICUBinary.getVersionInfoFromCompactInt(dataVersion);
     }
-    
+
     int getRootResource() {
         return rootRes;
     }
@@ -485,7 +480,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
         return res & 0x0fffffff;
     }
     private int getResourceByteOffset(int offset) {
-        return (offset << 2) - resourceBottom;
+        return offset << 2;
     }
     /* get signed and unsigned integer values directly from the Resource handle */
     static int RES_GET_INT(int res) {
@@ -494,46 +489,55 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
     static int RES_GET_UINT(int res) {
         return res & 0x0fffffff;
     }
+    private static boolean URES_IS_ARRAY(int type) {
+        return type == UResourceBundle.ARRAY || type == ICUResourceBundle.ARRAY16;
+    }
     static boolean URES_IS_TABLE(int type) {
         return type==UResourceBundle.TABLE || type==ICUResourceBundle.TABLE16 || type==ICUResourceBundle.TABLE32;
     }
 
-    private static byte[] emptyBytes = new byte[0];
-    private static ByteBuffer emptyByteBuffer = ByteBuffer.allocate(0).asReadOnlyBuffer();
-    private static char[] emptyChars = new char[0];
-    private static int[] emptyInts = new int[0];
-    private static String emptyString = "";
+    private static final byte[] emptyBytes = new byte[0];
+    private static final ByteBuffer emptyByteBuffer = ByteBuffer.allocate(0).asReadOnlyBuffer();
+    private static final char[] emptyChars = new char[0];
+    private static final int[] emptyInts = new int[0];
+    private static final String emptyString = "";
+    private static final Container EMPTY_ARRAY = new Container();
+    private static final Table EMPTY_TABLE = new Table();
 
     private char getChar(int offset) {
-        return (char)((resourceBytes[offset] << 8) | (resourceBytes[offset + 1] & 0xff));
+        return bytes.getChar(offset);
     }
     private char[] getChars(int offset, int count) {
         char[] chars = new char[count];
         for(int i = 0; i < count; offset += 2, ++i) {
-            chars[i] = (char)(((int)resourceBytes[offset] << 8) | (resourceBytes[offset + 1] & 0xff));
+            chars[i] = bytes.getChar(offset);
         }
         return chars;
     }
     private int getInt(int offset) {
-        return (resourceBytes[offset] << 24) |
-                ((resourceBytes[offset+1] & 0xff) << 16) |
-                ((resourceBytes[offset+2] & 0xff) << 8) |
-                ((resourceBytes[offset+3] & 0xff));
+        return bytes.getInt(offset);
     }
     private int[] getInts(int offset, int count) {
         int[] ints = new int[count];
         for(int i = 0; i < count; offset += 4, ++i) {
-            ints[i] = (resourceBytes[offset] << 24) |
-                        ((resourceBytes[offset+1] & 0xff) << 16) |
-                        ((resourceBytes[offset+2] & 0xff) << 8) |
-                        ((resourceBytes[offset+3] & 0xff));
+            ints[i] = bytes.getInt(offset);
         }
         return ints;
     }
     private char[] getTable16KeyOffsets(int offset) {
-        int length = s16BitUnits.charAt(offset++);
+        int length = b16BitUnits.charAt(offset++);
         if(length > 0) {
-            return s16BitUnits.substring(offset, offset + length).toCharArray();
+            char[] result = new char[length];
+            if(length <= 16) {
+                for(int i = 0; i < length; ++i) {
+                    result[i] = b16BitUnits.charAt(offset++);
+                }
+            } else {
+                CharBuffer temp = b16BitUnits.duplicate();
+                temp.position(offset);
+                temp.get(result);
+            }
+            return result;
         } else {
             return emptyChars;
         }
@@ -555,118 +559,111 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
         }
     }
 
-    /** Refers to ASCII key string bytes, for key string matching. */
-    private static final class ByteSequence {
-        private byte[] bytes;
-        private int offset;
-        public ByteSequence(byte[] bytes, int offset) {
-            this.bytes = bytes;
-            this.offset = offset;
-        }
-        public byte charAt(int index) {
-            return bytes[offset + index];
-        }
-    }
-    private String makeKeyStringFromBytes(int keyOffset) {
+    private static String makeKeyStringFromBytes(ByteBuffer keyBytes, int keyOffset) {
         StringBuilder sb = new StringBuilder();
         byte b;
-        while((b = keyStrings[keyOffset++]) != 0) {
+        while((b = keyBytes.get(keyOffset)) != 0) {
+            ++keyOffset;
             sb.append((char)b);
         }
         return sb.toString();
     }
-    private String makeKeyStringFromString(int keyOffset) {
-        int endOffset = keyOffset;
-        while(poolBundleKeysAsString.charAt(endOffset) != 0) {
-            ++endOffset;
-        }
-        return poolBundleKeysAsString.substring(keyOffset, endOffset);
-    }
-    private ByteSequence RES_GET_KEY16(char keyOffset) {
-        if(keyOffset < localKeyLimit) {
-            return new ByteSequence(keyStrings, keyOffset);
-        } else {
-            return new ByteSequence(poolBundleKeys, keyOffset - localKeyLimit);
-        }
-    }
     private String getKey16String(int keyOffset) {
         if(keyOffset < localKeyLimit) {
-            return makeKeyStringFromBytes(keyOffset);
-        } else {
-            return makeKeyStringFromString(keyOffset - localKeyLimit);
-        }
-    }
-    private ByteSequence RES_GET_KEY32(int keyOffset) {
-        if(keyOffset >= 0) {
-            return new ByteSequence(keyStrings, keyOffset);
+            return makeKeyStringFromBytes(bytes, keyOffset);
         } else {
-            return new ByteSequence(poolBundleKeys, keyOffset & 0x7fffffff);
+            return makeKeyStringFromBytes(poolBundleKeys, keyOffset - localKeyLimit);
         }
     }
     private String getKey32String(int keyOffset) {
         if(keyOffset >= 0) {
-            return makeKeyStringFromBytes(keyOffset);
+            return makeKeyStringFromBytes(bytes, keyOffset);
         } else {
-            return makeKeyStringFromString(keyOffset & 0x7fffffff);
+            return makeKeyStringFromBytes(poolBundleKeys, keyOffset & 0x7fffffff);
         }
     }
     // Compare the length-specified input key with the
-    // NUL-terminated tableKey.
-    private static int compareKeys(CharSequence key, ByteSequence tableKey) {
-        int i;
-        for(i = 0; i < key.length(); ++i) {
-            int c2 = tableKey.charAt(i);
+    // NUL-terminated table key.
+    private static int compareKeys(CharSequence key, ByteBuffer keyBytes, int keyOffset) {
+        for(int i = 0;; ++i, ++keyOffset) {
+            int c2 = keyBytes.get(keyOffset);
             if(c2 == 0) {
-                return 1;  // key > tableKey because key is longer.
+                if(i == key.length()) {
+                    return 0;
+                } else {
+                    return 1;  // key > table key because key is longer.
+                }
+            } else if(i == key.length()) {
+                return -1;  // key < table key because key is shorter.
             }
             int diff = (int)key.charAt(i) - c2;
             if(diff != 0) {
                 return diff;
             }
         }
-        return -(int)tableKey.charAt(i);
     }
     private int compareKeys(CharSequence key, char keyOffset) {
-        return compareKeys(key, RES_GET_KEY16(keyOffset));
+        if(keyOffset < localKeyLimit) {
+            return compareKeys(key, bytes, keyOffset);
+        } else {
+            return compareKeys(key, poolBundleKeys, keyOffset - localKeyLimit);
+        }
     }
     private int compareKeys32(CharSequence key, int keyOffset) {
-        return compareKeys(key, RES_GET_KEY32(keyOffset));
+        if(keyOffset >= 0) {
+            return compareKeys(key, bytes, keyOffset);
+        } else {
+            return compareKeys(key, poolBundleKeys, keyOffset & 0x7fffffff);
+        }
     }
 
     String getString(int res) {
         int offset=RES_GET_OFFSET(res);
-        int length;
-        if(RES_GET_TYPE(res)==ICUResourceBundle.STRING_V2) {
-            int first = s16BitUnits.charAt(offset);
+        if(res != offset /* RES_GET_TYPE(res) != URES_STRING */ &&
+                RES_GET_TYPE(res) != ICUResourceBundle.STRING_V2) {
+            return null;
+        }
+        if(offset == 0) {
+            return emptyString;
+        }
+        Object value = resourceCache.get(res);
+        if(value != null) {
+            return (String)value;
+        }
+        String s;
+        if(res != offset) {  // STRING_V2
+            int first = b16BitUnits.charAt(offset);
             if((first&0xfffffc00)!=0xdc00) {  // C: if(!U16_IS_TRAIL(first)) {
                 if(first==0) {
-                    return emptyString;
+                    return emptyString;  // Should not occur, but is not forbidden.
                 }
-                int endOffset;
-                for(endOffset=offset+1; s16BitUnits.charAt(endOffset)!=0; ++endOffset) {}
-                return s16BitUnits.substring(offset, endOffset);
-            } else if(first<0xdfef) {
-                length=first&0x3ff;
-                ++offset;
-            } else if(first<0xdfff) {
-                length=((first-0xdfef)<<16)|s16BitUnits.charAt(offset+1);
-                offset+=2;
-            } else {
-                length=((int)s16BitUnits.charAt(offset+1)<<16)|s16BitUnits.charAt(offset+2);
-                offset+=3;
-            }
-            return s16BitUnits.substring(offset, offset+length);
-        } else if(res==offset) /* RES_GET_TYPE(res)==URES_STRING */ {
-            if(res==0) {
-                return emptyString;
+                StringBuilder sb = new StringBuilder();
+                sb.append((char)first);
+                char c;
+                while((c = b16BitUnits.charAt(++offset)) != 0) {
+                    sb.append(c);
+                }
+                s = sb.toString();
             } else {
-                offset=getResourceByteOffset(offset);
-                length=getInt(offset);
-                return new String(getChars(offset+4, length));
+                int length;
+                if(first<0xdfef) {
+                    length=first&0x3ff;
+                    ++offset;
+                } else if(first<0xdfff) {
+                    length=((first-0xdfef)<<16)|b16BitUnits.charAt(offset+1);
+                    offset+=2;
+                } else {
+                    length=((int)b16BitUnits.charAt(offset+1)<<16)|b16BitUnits.charAt(offset+2);
+                    offset+=3;
+                }
+                s = b16BitUnits.subSequence(offset, offset + length).toString();
             }
         } else {
-            return null;
+            offset=getResourceByteOffset(offset);
+            int length = getInt(offset);
+            s = new String(getChars(offset+4, length));
         }
+        return (String)resourceCache.putIfAbsent(res, s, s.length() * 2);
     }
 
     String getAlias(int res) {
@@ -676,9 +673,14 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
             if(offset==0) {
                 return emptyString;
             } else {
+                Object value = resourceCache.get(res);
+                if(value != null) {
+                    return (String)value;
+                }
                 offset=getResourceByteOffset(offset);
                 length=getInt(offset);
-                return new String(getChars(offset+4, length));
+                String s = new String(getChars(offset + 4, length));
+                return (String)resourceCache.putIfAbsent(res, s, length * 2);
             }
         } else {
             return null;
@@ -694,10 +696,24 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
             } else {
                 offset=getResourceByteOffset(offset);
                 length=getInt(offset);
+                if(length==0) {
+                    return emptyBytes;
+                }
+                // Not cached: The array would have to be cloned anyway because
+                // the cache must not be writable via the returned reference.
                 if(ba==null || ba.length!=length) {
                     ba=new byte[length];
                 }
-                System.arraycopy(resourceBytes, offset+4, ba, 0, length);
+                offset += 4;
+                if(length <= 16) {
+                    for(int i = 0; i < length; ++i) {
+                        ba[i] = bytes.get(offset++);
+                    }
+                } else {
+                    ByteBuffer temp = bytes.duplicate();
+                    temp.position(offset);
+                    temp.get(ba);
+                }
                 return ba;
             }
         } else {
@@ -715,9 +731,23 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
                 // in case it matters whether the buffer's mark is defined or undefined.
                 return emptyByteBuffer.duplicate();
             } else {
+                // Not cached: The returned buffer is small (shares its bytes with the bundle)
+                // and usually quickly discarded after use.
+                // Also, even a cached buffer would have to be cloned because it is mutable
+                // (position & mark).
                 offset=getResourceByteOffset(offset);
                 length=getInt(offset);
-                return ByteBuffer.wrap(resourceBytes, offset+4, length).slice().asReadOnlyBuffer();
+                if(length == 0) {
+                    return emptyByteBuffer.duplicate();
+                }
+                offset += 4;
+                ByteBuffer result = bytes.duplicate();
+                result.position(offset).limit(offset + length);
+                result = result.slice();
+                if(!result.isReadOnly()) {
+                    result = result.asReadOnlyBuffer();
+                }
+                return result;
             }
         } else {
             return null;
@@ -731,6 +761,8 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
             if(offset==0) {
                 return emptyInts;
             } else {
+                // Not cached: The array would have to be cloned anyway because
+                // the cache must not be writable via the returned reference.
                 offset=getResourceByteOffset(offset);
                 length=getInt(offset);
                 return getInts(offset+4, length);
@@ -742,101 +774,96 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
 
     Container getArray(int res) {
         int type=RES_GET_TYPE(res);
-        int offset=RES_GET_OFFSET(res);
-        switch(type) {
-        case UResourceBundle.ARRAY:
-        case ICUResourceBundle.ARRAY16:
-            if(offset==0) {
-                return new Container(this);
-            }
-            break;
-        default:
+        if(!URES_IS_ARRAY(type)) {
             return null;
         }
-        switch(type) {
-        case UResourceBundle.ARRAY:
-            return new Array(this, offset);
-        case ICUResourceBundle.ARRAY16:
-            return new Array16(this, offset);
-        default:
-            return null;
+        int offset=RES_GET_OFFSET(res);
+        if(offset == 0) {
+            return EMPTY_ARRAY;
+        }
+        Object value = resourceCache.get(res);
+        if(value != null) {
+            return (Container)value;
         }
+        Container array = (type == UResourceBundle.ARRAY) ?
+                new Array(this, offset) : new Array16(this, offset);
+        return (Container)resourceCache.putIfAbsent(res, array, 0);
     }
 
     Table getTable(int res) {
-        int type=RES_GET_TYPE(res);
-        int offset=RES_GET_OFFSET(res);
-        switch(type) {
-        case UResourceBundle.TABLE:
-        case ICUResourceBundle.TABLE16:
-        case ICUResourceBundle.TABLE32:
-            if(offset==0) {
-                return new Table(this);
-            }
-            break;
-        default:
+        int type = RES_GET_TYPE(res);
+        if(!URES_IS_TABLE(type)) {
             return null;
         }
-        switch(type) {
-        case UResourceBundle.TABLE:
-            return new Table1632(this, offset);
-        case ICUResourceBundle.TABLE16:
-            return new Table16(this, offset);
-        case ICUResourceBundle.TABLE32:
-            return new Table32(this, offset);
-        default:
-            return null;
+        int offset = RES_GET_OFFSET(res);
+        if(offset == 0) {
+            return EMPTY_TABLE;
         }
+        Object value = resourceCache.get(res);
+        if(value != null) {
+            return (Table)value;
+        }
+        Table table;
+        int size;  // Use size = 0 to never use SoftReferences for Tables?
+        if(type == UResourceBundle.TABLE) {
+            table = new Table1632(this, offset);
+            size = table.getSize() * 2;
+        } else if(type == ICUResourceBundle.TABLE16) {
+            table = new Table16(this, offset);
+            size = table.getSize() * 2;
+        } else /* type == ICUResourceBundle.TABLE32 */ {
+            table = new Table32(this, offset);
+            size = table.getSize() * 4;
+        }
+        return (Table)resourceCache.putIfAbsent(res, table, size);
     }
 
     // Container value classes --------------------------------------------- ***
 
     static class Container {
-        protected ICUResourceBundleReader reader;
         protected int size;
         protected int itemsOffset;
 
         int getSize() {
             return size;
         }
-        int getContainerResource(int index) {
+        int getContainerResource(ICUResourceBundleReader reader, int index) {
             return ICUResourceBundle.RES_BOGUS;
         }
-        protected int getContainer16Resource(int index) {
+        protected int getContainer16Resource(ICUResourceBundleReader reader, int index) {
             if (index < 0 || size <= index) {
                 return ICUResourceBundle.RES_BOGUS;
             }
             return (ICUResourceBundle.STRING_V2 << 28) |
-                   reader.s16BitUnits.charAt(itemsOffset + index);
+                   reader.b16BitUnits.charAt(itemsOffset + index);
         }
-        protected int getContainer32Resource(int index) {
+        protected int getContainer32Resource(ICUResourceBundleReader reader, int index) {
             if (index < 0 || size <= index) {
                 return ICUResourceBundle.RES_BOGUS;
             }
             return reader.getInt(itemsOffset + 4 * index);
         }
-        Container(ICUResourceBundleReader reader) {
-            this.reader = reader;
+        Container() {
         }
     }
     private static final class Array extends Container {
-        int getContainerResource(int index) {
-            return getContainer32Resource(index);
+        @Override
+        int getContainerResource(ICUResourceBundleReader reader, int index) {
+            return getContainer32Resource(reader, index);
         }
         Array(ICUResourceBundleReader reader, int offset) {
-            super(reader);
             offset = reader.getResourceByteOffset(offset);
             size = reader.getInt(offset);
             itemsOffset = offset + 4;
         }
     }
     private static final class Array16 extends Container {
-        int getContainerResource(int index) {
-            return getContainer16Resource(index);
+        @Override
+        int getContainerResource(ICUResourceBundleReader reader, int index) {
+            return getContainer16Resource(reader, index);
         }
         Array16(ICUResourceBundleReader reader, int offset) {
-            super(reader);
-            size = reader.s16BitUnits.charAt(offset);
+            size = reader.b16BitUnits.charAt(offset);
             itemsOffset = offset + 1;
         }
     }
@@ -844,7 +871,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
         protected char[] keyOffsets;
         protected int[] key32Offsets;
 
-        String getKey(int index) {
+        String getKey(ICUResourceBundleReader reader, int index) {
             if (index < 0 || size <= index) {
                 return null;
             }
@@ -853,7 +880,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
                         reader.getKey32String(key32Offsets[index]);
         }
         private static final int URESDATA_ITEM_NOT_FOUND = -1;
-        int findTableItem(CharSequence key) {
+        int findTableItem(ICUResourceBundleReader reader, CharSequence key) {
             int mid, start, limit;
             int result;
 
@@ -878,19 +905,18 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
             }
             return URESDATA_ITEM_NOT_FOUND;  /* not found or table is empty. */
         }
-        int getTableResource(String resKey) {
-            return getContainerResource(findTableItem(resKey));
+        int getTableResource(ICUResourceBundleReader reader, String resKey) {
+            return getContainerResource(reader, findTableItem(reader, resKey));
         }
-        Table(ICUResourceBundleReader reader) {
-            super(reader);
+        Table() {
         }
     }
     private static final class Table1632 extends Table {
-        int getContainerResource(int index) {
-            return getContainer32Resource(index);
+        @Override
+        int getContainerResource(ICUResourceBundleReader reader, int index) {
+            return getContainer32Resource(reader, index);
         }
         Table1632(ICUResourceBundleReader reader, int offset) {
-            super(reader);
             offset = reader.getResourceByteOffset(offset);
             keyOffsets = reader.getTableKeyOffsets(offset);
             size = keyOffsets.length;
@@ -898,22 +924,22 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
         }
     }
     private static final class Table16 extends Table {
-        int getContainerResource(int index) {
-            return getContainer16Resource(index);
+        @Override
+        int getContainerResource(ICUResourceBundleReader reader, int index) {
+            return getContainer16Resource(reader, index);
         }
         Table16(ICUResourceBundleReader reader, int offset) {
-            super(reader);
             keyOffsets = reader.getTable16KeyOffsets(offset);
             size = keyOffsets.length;
             itemsOffset = offset + 1 + size;
         }
     }
     private static final class Table32 extends Table {
-        int getContainerResource(int index) {
-            return getContainer32Resource(index);
+        @Override
+        int getContainerResource(ICUResourceBundleReader reader, int index) {
+            return getContainer32Resource(reader, index);
         }
         Table32(ICUResourceBundleReader reader, int offset) {
-            super(reader);
             offset = reader.getResourceByteOffset(offset);
             key32Offsets = reader.getTable32KeyOffsets(offset);
             size = key32Offsets.length;
@@ -921,6 +947,261 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate {
         }
     }
 
+    // Resource cache ------------------------------------------------------ ***
+
+    /**
+     * Cache of some of one resource bundle's resources.
+     * Avoids creating multiple Java objects for the same resource items,
+     * including multiple copies of their contents.
+     *
+     * <p>Mutable objects must not be cached and then returned to the caller
+     * because the cache must not be writable via the returned reference.
+     *
+     * <p>Resources are mapped by their resource integers.
+     * Empty resources with offset 0 cannot be mapped.
+     * Integers need not and should not be cached.
+     * Multiple .res items may share resource offsets (genrb eliminates some duplicates).
+     *
+     * <p>This cache uses int[] and Object[] arrays to minimize object creation
+     * and avoid auto-boxing.
+     *
+     * <p>Large resource objects are stored in SoftReferences.
+     *
+     * <p>For few resources, a small table is used with binary search.
+     * When more resources are cached, then the data structure changes to be faster
+     * but also use more memory.
+     */
+    private static final class ResourceCache {
+        // Number of items to be stored in a simple array with binary search and insertion sort.
+        private static final int SIMPLE_LENGTH = 32;
+
+        // When more than SIMPLE_LENGTH items are cached,
+        // then switch to a trie-like tree of levels with different array lengths.
+        private static final int ROOT_BITS = 7;
+        private static final int NEXT_BITS = 6;
+
+        // Simple table, used when length >= 0.
+        private int[] keys = new int[SIMPLE_LENGTH];
+        private Object[] values = new Object[SIMPLE_LENGTH];
+        private int length;
+
+        // Trie-like tree of levels, used when length < 0.
+        private int maxOffsetBits;
+        /**
+         * Number of bits in each level, each stored in a nibble.
+         */
+        private int levelBitsList;
+        private Level rootLevel;
+
+        @SuppressWarnings("unchecked")
+        private static final Object putIfCleared(Object[] values, int index, Object item, int size) {
+            Object value = values[index];
+            if(!(value instanceof SoftReference)) {
+                assert size < LARGE_SIZE;  // Caller should be consistent for each resource.
+                return value;
+            }
+            assert size >= LARGE_SIZE;
+            value = ((SoftReference<Object>)value).get();
+            if(value != null) {
+                return value;
+            }
+            values[index] = new SoftReference<Object>(item);
+            return item;
+        }
+
+        private static final class Level {
+            int levelBitsList;
+            int shift;
+            int mask;
+            int[] keys;
+            Object[] values;
+
+            Level(int levelBitsList, int shift) {
+                this.levelBitsList = levelBitsList;
+                this.shift = shift;
+                int bits = levelBitsList & 0xf;
+                assert bits != 0;
+                int length = 1 << bits;
+                mask = length - 1;
+                keys = new int[length];
+                values = new Object[length];
+            }
+
+            Object get(int key) {
+                int index = (key >> shift) & mask;
+                int k = keys[index];
+                if(k == key) {
+                    return values[index];
+                }
+                if(k == 0) {
+                    Level level = (Level)values[index];
+                    if(level != null) {
+                        return level.get(key);
+                    }
+                }
+                return null;
+            }
+
+            Object putIfAbsent(int key, Object item, int size) {
+                int index = (key >> shift) & mask;
+                int k = keys[index];
+                if(k == key) {
+                    return putIfCleared(values, index, item, size);
+                }
+                if(k == 0) {
+                    Level level = (Level)values[index];
+                    if(level != null) {
+                        return level.putIfAbsent(key, item, size);
+                    }
+                    keys[index] = key;
+                    values[index] = (size >= LARGE_SIZE) ? new SoftReference<Object>(item) : item;
+                    return item;
+                }
+                // Collision: Add a child level, move the old item there,
+                // and then insert the current item.
+                Level level = new Level(levelBitsList >> 4, shift + (levelBitsList & 0xf));
+                int i = (k >> level.shift) & level.mask;
+                level.keys[i] = k;
+                level.values[i] = values[index];
+                keys[index] = 0;
+                values[index] = level;
+                return level.putIfAbsent(key, item, size);
+            }
+        }
+
+        ResourceCache(int maxOffset) {
+            assert maxOffset != 0;
+            maxOffsetBits = 28;
+            while(maxOffset <= 0x7ffffff) {
+                maxOffset <<= 1;
+                --maxOffsetBits;
+            }
+            int keyBits = maxOffsetBits + 2;  // +2 for mini type: at most 30 bits used in a key
+            // Precompute for each level the number of bits it handles.
+            if(keyBits <= ROOT_BITS) {
+                levelBitsList = keyBits;
+            } else if(keyBits < (ROOT_BITS + 3)) {
+                levelBitsList = 0x30 | (keyBits - 3);
+            } else {
+                levelBitsList = ROOT_BITS;
+                keyBits -= ROOT_BITS;
+                int shift = 4;
+                for(;;) {
+                    if(keyBits <= NEXT_BITS) {
+                        levelBitsList |= keyBits << shift;
+                        break;
+                    } else if(keyBits < (NEXT_BITS + 3)) {
+                        levelBitsList |= (0x30 | (keyBits - 3)) << shift;
+                        break;
+                    } else {
+                        levelBitsList |= NEXT_BITS << shift;
+                        keyBits -= NEXT_BITS;
+                        shift += 4;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Turns a resource integer (with unused bits in the middle)
+         * into a key with fewer bits (at most keyBits).
+         */
+        private int makeKey(int res) {
+            // It is possible for resources of different types in the 16-bit array
+            // to share a start offset; distinguish between those with a 2-bit value,
+            // as a tie-breaker in the bits just above the highest possible offset.
+            // It is not possible for "regular" resources to share a start offset with each other,
+            // but offsets for 16-bit and "regular" resources overlap;
+            // use 2-bit value 0 for "regular" resources.
+            int type = RES_GET_TYPE(res);
+            int miniType =
+                    (type == ICUResourceBundle.STRING_V2) ? 1 :
+                        (type == ICUResourceBundle.TABLE16) ? 3 :
+                            (type == ICUResourceBundle.ARRAY16) ? 2 : 0;
+            return RES_GET_OFFSET(res) | (miniType << maxOffsetBits);
+        }
+
+        private int findSimple(int key) {
+            // With Java 6, return Arrays.binarySearch(keys, 0, length, key).
+            int start = 0;
+            int limit = length;
+            while((limit - start) > 8) {
+                int mid = (start + limit) / 2;
+                if(key < keys[mid]) {
+                    limit = mid;
+                } else {
+                    start = mid;
+                }
+            }
+            // For a small number of items, linear search should be a little faster.
+            while(start < limit) {
+                int k = keys[start];
+                if(key < k) {
+                    return ~start;
+                }
+                if(key == k) {
+                    return start;
+                }
+                ++start;
+            }
+            return ~start;
+        }
+
+        @SuppressWarnings("unchecked")
+        synchronized Object get(int res) {
+            // Integers and empty resources need not be cached.
+            // The cache itself uses res=0 for "no match".
+            assert RES_GET_OFFSET(res) != 0;
+            Object value;
+            if(length >= 0) {
+                int index = findSimple(res);
+                if(index >= 0) {
+                    value = values[index];
+                } else {
+                    return null;
+                }
+            } else {
+                value = rootLevel.get(makeKey(res));
+                if(value == null) {
+                    return null;
+                }
+            }
+            if(value instanceof SoftReference) {
+                value = ((SoftReference<Object>)value).get();
+            }
+            return value;  // null if the reference was cleared
+        }
+
+        synchronized Object putIfAbsent(int res, Object item, int size) {
+            if(length >= 0) {
+                int index = findSimple(res);
+                if(index >= 0) {
+                    return putIfCleared(values, index, item, size);
+                } else if(length < SIMPLE_LENGTH) {
+                    index = ~index;
+                    if(index < length) {
+                        System.arraycopy(keys, index, keys, index + 1, length - index);
+                        System.arraycopy(values, index, values, index + 1, length - index);
+                    }
+                    ++length;
+                    keys[index] = res;
+                    values[index] = (size >= LARGE_SIZE) ? new SoftReference<Object>(item) : item;
+                    return item;
+                } else /* not found && length == SIMPLE_LENGTH */ {
+                    // Grow to become trie-like.
+                    rootLevel = new Level(levelBitsList, 0);
+                    for(int i = 0; i < SIMPLE_LENGTH; ++i) {
+                        rootLevel.putIfAbsent(makeKey(keys[i]), values[i], 0);
+                    }
+                    keys = null;
+                    values = null;
+                    length = -1;
+                }
+            }
+            return rootLevel.putIfAbsent(makeKey(res), item, size);
+        }
+    }
+
     private static final String ICU_RESOURCE_SUFFIX = ".res";
 
     /**
index 939cf6bf4d45646910ff7aa08806d7732482b841..14f6a27f0e650860a4f6faae5fc4453672f66be5 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *******************************************************************************
- * Copyright (C) 2009-2011, International Business Machines Corporation and         *
- * others. All Rights Reserved.                                                *
+ * Copyright (C) 2009-2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
  *******************************************************************************
  */
 package com.ibm.icu.impl;
@@ -40,23 +40,22 @@ public class ICUResourceTableAccess {
                     table = table.getWithFallback(item);
                     return table.getString(1);
                 } else {
-                    ICUResourceBundle table = lookup(bundle, tableName);
+                    ICUResourceBundle table = bundle.findWithFallback(tableName);
                     if (table == null) {
                         return item;
                     }
                     ICUResourceBundle stable = table;
                     if (subtableName != null) {
-                        stable = lookup(table, subtableName);
+                        stable = table.findWithFallback(subtableName);
                     }
                     if (stable != null) {
-                        ICUResourceBundle sbundle = lookup(stable, item);
-                        if (sbundle != null) {
-                            result = sbundle.getString(); // possible real exception
-                            break;
+                        result = stable.findStringWithFallback(item);
+                        if (result != null) {
+                            break; // possible real exception
                         }
                     }
 
-                    // if we get here, stable was null, or sbundle was null
+                    // if we get here, stable was null, or there was no string for the item
                     if (subtableName == null) {
                         // may be a deprecated code
                         String currentName = null;
@@ -65,20 +64,18 @@ public class ICUResourceTableAccess {
                         } else if (tableName.equals("Languages")) {
                             currentName = LocaleIDs.getCurrentLanguageID(item);
                         }
-                        ICUResourceBundle sbundle = lookup(table, currentName);
-                        if (sbundle != null) {
-                            result = sbundle.getString(); // possible real exception
-                            break;
+                        result = table.findStringWithFallback(currentName);
+                        if (result != null) {
+                            break; // possible real exception
                         }
                     }
 
                     // still can't figure it out? try the fallback mechanism
-                    ICUResourceBundle fbundle = lookup(table, "Fallback");
-                    if (fbundle == null) {
+                    String fallbackLocale = table.findStringWithFallback("Fallback"); // again, possible exception
+                    if (fallbackLocale == null) {
                         return item;
                     }
 
-                    String fallbackLocale = fbundle.getString(); // again, possible exception
                     if (fallbackLocale.length() == 0) {
                         fallbackLocale = "root";
                     }
@@ -99,9 +96,4 @@ public class ICUResourceTableAccess {
         // If the result is empty return item instead
         return ((result != null && result.length() > 0) ? result : item);
     }
-
-    // utility to make the call sites in the above code cleaner
-    private static ICUResourceBundle lookup(ICUResourceBundle bundle, String resName) {
-        return ICUResourceBundle.findResourceWithFallback(resName, bundle, null);
-    }
 }
index bd18c0454d3244bde4963c4ae32404b4012e6700..177206b90d33e5a44cbe88baf73d0bbb9c04e11a 100644 (file)
@@ -1,6 +1,6 @@
 /*
 **********************************************************************
-* Copyright (c) 2003-2013 International Business Machines
+* Copyright (c) 2003-2014 International Business Machines
 * Corporation and others.  All Rights Reserved.
 **********************************************************************
 * Author: Alan Liu
@@ -286,8 +286,7 @@ public final class ZoneMeta {
             try {
                 UResourceBundle top = UResourceBundle.getBundleInstance(
                         ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
-                UResourceBundle names = top.get(kNAMES);
-                ZONEIDS = names.getStringArray();
+                ZONEIDS = top.getStringArray(kNAMES);
             } catch (MissingResourceException ex) {
                 // throw away..
             }
index f393b0e45812be64bbb848a515d05aff82fa1af2..1143bedd5dfc990668cfff9c84aae10dab56f83b 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *******************************************************************************
- * Copyright (C) 2013-2014, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
+ * Copyright (C) 2013-2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
  *******************************************************************************
  */
 package com.ibm.icu.text;
@@ -417,9 +417,9 @@ public final class RelativeDateTimeFormatter {
             Direction direction,
             ICUResourceBundle bundle,
             String key) {
-        bundle = bundle.findWithFallback(key);
-        if (bundle != null) {
-            unitStrings.put(direction, bundle.getString());
+        String s = bundle.findStringWithFallback(key);
+        if (s != null) {
+            unitStrings.put(direction, s);
         }
     }
 
index adcbaa49028f8c17522690ba21f242612cf14c92..fbda76c170411f4580b1c2c19dce59079b00fd25 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *******************************************************************************
- * Copyright (C) 2009-2012, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
+ * Copyright (C) 2009-2014, International Business Machines Corporation and
+ * others. All Rights Reserved.
  *******************************************************************************
  */
 package com.ibm.icu.impl;
@@ -95,17 +95,17 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
             if (plurals != null) {
                 ICUResourceBundle pluralsBundle = plurals.findWithFallback(isoCode);
                 if (pluralsBundle != null) {
-                    ICUResourceBundle pluralBundle = pluralsBundle.findWithFallback(pluralKey);
-                    if (pluralBundle == null) {
+                    String pluralName = pluralsBundle.findStringWithFallback(pluralKey);
+                    if (pluralName == null) {
                         if (!fallback) {
                             return null;
                         }
-                        pluralBundle = pluralsBundle.findWithFallback("other");
-                        if (pluralBundle == null) {
+                        pluralName = pluralsBundle.findStringWithFallback("other");
+                        if (pluralName == null) {
                             return getName(isoCode);
                         }
                     }
-                    return pluralBundle.getString();
+                    return pluralName;
                 }
             }
 
@@ -186,12 +186,12 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid
                 ICUResourceBundle brb = srb.findWithFallback("beforeCurrency");
                 ICUResourceBundle arb = srb.findWithFallback("afterCurrency");
                 if (arb != null && brb != null) {
-                    String beforeCurrencyMatch = brb.findWithFallback("currencyMatch").getString();
-                    String beforeContextMatch = brb.findWithFallback("surroundingMatch").getString();
-                    String beforeInsert = brb.findWithFallback("insertBetween").getString();
-                    String afterCurrencyMatch = arb.findWithFallback("currencyMatch").getString();
-                    String afterContextMatch = arb.findWithFallback("surroundingMatch").getString();
-                    String afterInsert = arb.findWithFallback("insertBetween").getString();
+                    String beforeCurrencyMatch = brb.findStringWithFallback("currencyMatch");
+                    String beforeContextMatch = brb.findStringWithFallback("surroundingMatch");
+                    String beforeInsert = brb.findStringWithFallback("insertBetween");
+                    String afterCurrencyMatch = arb.findStringWithFallback("currencyMatch");
+                    String afterContextMatch = arb.findStringWithFallback("surroundingMatch");
+                    String afterInsert = arb.findStringWithFallback("insertBetween");
 
                     return new CurrencySpacingInfo(
                             beforeCurrencyMatch, beforeContextMatch, beforeInsert,