You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2017/10/05 18:22:00 UTC

karaf git commit: [KARAF-5395] Further improve memory consumption for capabilities / requirements

Repository: karaf
Updated Branches:
  refs/heads/master 03bc8dfed -> e5bd746b3


[KARAF-5395] Further improve memory consumption for capabilities / requirements


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/e5bd746b
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/e5bd746b
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/e5bd746b

Branch: refs/heads/master
Commit: e5bd746b34300ff10ba4a0ead6e6c08efd357982
Parents: 03bc8df
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Thu Oct 5 15:29:44 2017 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Thu Oct 5 20:21:35 2017 +0200

----------------------------------------------------------------------
 .../internal/resolver/CapabilityImpl.java       |   7 +-
 .../internal/resolver/FeatureResource.java      |   6 +-
 .../internal/resolver/RequirementImpl.java      |   7 +-
 .../internal/resolver/ResourceBuilder.java      |  29 +-
 .../internal/resolver/ResourceImpl.java         |   6 +-
 .../internal/resolver/ResourceUtils.java        |  31 +-
 .../features/internal/util/StringArrayMap.java  | 282 +++++++++++++++++++
 7 files changed, 332 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/e5bd746b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java
index ba6bfd7..991e3c9 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/CapabilityImpl.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.karaf.features.internal.util.StringArrayMap;
 import org.osgi.framework.Constants;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
@@ -36,10 +37,10 @@ public class CapabilityImpl extends BaseClause implements Capability {
 
     public CapabilityImpl(Resource resource, String namespace,
                           Map<String, String> dirs, Map<String, Object> attrs) {
-        this.namespace = namespace;
+        this.namespace = namespace.intern();
         this.resource = resource;
-        this.dirs = dirs;
-        this.attrs = attrs;
+        this.dirs = StringArrayMap.reduceMemory(dirs);
+        this.attrs = StringArrayMap.reduceMemory(attrs);
 
         // Handle mandatory directive
         Set<String> mandatory = Collections.emptySet();

http://git-wip-us.apache.org/repos/asf/karaf/blob/e5bd746b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java
index 6e1a164..e0017a8 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/FeatureResource.java
@@ -19,6 +19,7 @@ package org.apache.karaf.features.internal.resolver;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.Conditional;
@@ -107,10 +108,7 @@ public final class FeatureResource extends ResourceImpl {
         } else if (!version.startsWith("[") && !version.startsWith("(")) {
             version = Macro.transform(featureRange, version);
         }
-        RequirementImpl requirement = addIdentityRequirement(resource, name, TYPE_FEATURE, version);
-        if (condition) {
-            requirement.getDirectives().put(REQUIREMENT_CONDITIONAL_DIRECTIVE, CONDITIONAL_TRUE);
-        }
+        addIdentityRequirement(resource, name, TYPE_FEATURE, version != null ? new VersionRange(version) : null, true, condition);
     }
 
     public Feature getFeature() {

http://git-wip-us.apache.org/repos/asf/karaf/blob/e5bd746b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java
index f03fbb3..9dbf6c2 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/RequirementImpl.java
@@ -18,6 +18,7 @@ package org.apache.karaf.features.internal.resolver;
 
 import java.util.Map;
 
+import org.apache.karaf.features.internal.util.StringArrayMap;
 import org.osgi.framework.Constants;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
@@ -36,9 +37,9 @@ public class RequirementImpl extends BaseClause implements Requirement {
             Resource resource, String namespace,
             Map<String, String> dirs, Map<String, Object> attrs, SimpleFilter filter) {
         this.resource = resource;
-        this.namespace = namespace;
-        this.dirs = dirs;
-        this.attrs = attrs;
+        this.namespace = namespace.intern();
+        this.dirs = StringArrayMap.reduceMemory(dirs);
+        this.attrs = StringArrayMap.reduceMemory(attrs);
         this.filter = filter;
         // Find resolution import directives.
         optional = Constants.RESOLUTION_OPTIONAL.equals(this.dirs.get(Constants.RESOLUTION_DIRECTIVE));

http://git-wip-us.apache.org/repos/asf/karaf/blob/e5bd746b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
index afb05a1..68cb48f 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceBuilder.java
@@ -18,7 +18,6 @@ package org.apache.karaf.features.internal.resolver;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -28,6 +27,7 @@ import java.util.Set;
 
 import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
+import org.apache.karaf.features.internal.util.StringArrayMap;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
@@ -113,7 +113,7 @@ public final class ResourceBuilder {
         // Now that we have symbolic name and version, create the resource
         String type = headerMap.get(Constants.FRAGMENT_HOST) == null ? IdentityNamespace.TYPE_BUNDLE : IdentityNamespace.TYPE_FRAGMENT;
         {
-            Map<String, Object> attrs = new HashMap<>(4);
+            Map<String, Object> attrs = new StringArrayMap<>(3);
             attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, bundleSymbolicName);
             attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, type);
             attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, bundleVersion);
@@ -138,9 +138,14 @@ public final class ResourceBuilder {
             String attachment = bundleCap.dirs.get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE);
             attachment = (attachment == null) ? Constants.FRAGMENT_ATTACHMENT_RESOLVETIME : attachment;
             if (!attachment.equalsIgnoreCase(Constants.FRAGMENT_ATTACHMENT_NEVER)) {
-                Map<String, Object> hostAttrs = new HashMap<>(bundleCap.attrs);
-                Object value = hostAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
-                hostAttrs.put(BundleRevision.HOST_NAMESPACE, value);
+                Map<String, Object> hostAttrs = new StringArrayMap<>(bundleCap.attrs.size());
+                for (Map.Entry<String, Object> e : bundleCap.attrs.entrySet()) {
+                    String k = e.getKey();
+                    if (BundleRevision.BUNDLE_NAMESPACE.equals(k)) {
+                        k = BundleRevision.HOST_NAMESPACE;
+                    }
+                    hostAttrs.put(k, e.getValue());
+                }
                 resource.addCapability(new CapabilityImpl(
                         resource, BundleRevision.HOST_NAMESPACE,
                         bundleCap.dirs,
@@ -410,7 +415,7 @@ public final class ResourceBuilder {
                 // Inject filter directive.
                 // TODO: OSGi R4.3 - Can we insert this on demand somehow?
                 Map<String, String> dirs = clause.dirs;
-                Map<String, String> newDirs = new HashMap<>(dirs.size() + 1);
+                Map<String, String> newDirs = new StringArrayMap<>(dirs.size() + 1);
                 newDirs.putAll(dirs);
                 newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
 
@@ -759,7 +764,7 @@ public final class ResourceBuilder {
             for (String pkgName : clause.paths) {
                 // Prepend the package name to the array of attributes.
                 Map<String, Object> attrs = clause.attrs;
-                Map<String, Object> newAttrs = new HashMap<>(attrs.size() + 1);
+                Map<String, Object> newAttrs = new StringArrayMap<>(attrs.size() + 1);
                 newAttrs.putAll(attrs);
                 newAttrs.put(BundleRevision.PACKAGE_NAMESPACE, pkgName);
 
@@ -834,7 +839,7 @@ public final class ResourceBuilder {
             // Inject filter directive.
             // TODO: OSGi R4.3 - Can we insert this on demand somehow?
             Map<String, String> dirs = clauses.get(0).dirs;
-            Map<String, String> newDirs = new HashMap<>(dirs.size() + 1);
+            Map<String, String> newDirs = new StringArrayMap<>(dirs.size() + 1);
             newDirs.putAll(dirs);
             newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
 
@@ -983,7 +988,7 @@ public final class ResourceBuilder {
                 // Inject filter directive.
                 // TODO: OSGi R4.3 - Can we insert this on demand somehow?
                 Map<String, String> dirs = clause.dirs;
-                Map<String, String> newDirs = new HashMap<>(dirs.size() + 1);
+                Map<String, String> newDirs = new StringArrayMap<>(dirs.size() + 1);
                 newDirs.putAll(dirs);
                 newDirs.put(Constants.FILTER_DIRECTIVE, sf.toString());
 
@@ -1197,8 +1202,8 @@ public final class ResourceBuilder {
 
     static class ParsedHeaderClause {
         public final List<String> paths = new ArrayList<>();
-        public final Map<String, String> dirs = new LinkedHashMap<>(1);
-        public final Map<String, Object> attrs = new LinkedHashMap<>(1);
-        public final Map<String, String> types = new LinkedHashMap<>(1);
+        public final Map<String, String> dirs = new StringArrayMap<>(0);
+        public final Map<String, Object> attrs = new StringArrayMap<>(0);
+        public final Map<String, String> types = new StringArrayMap<>(0);
     }
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/e5bd746b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
index 756c440..0d85843 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceImpl.java
@@ -17,10 +17,10 @@
 package org.apache.karaf.features.internal.resolver;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.karaf.features.internal.util.StringArrayMap;
 import org.osgi.framework.Version;
 import org.osgi.framework.namespace.IdentityNamespace;
 import org.osgi.resource.Capability;
@@ -45,8 +45,8 @@ public class ResourceImpl implements Resource {
 
     public ResourceImpl(String name, String type, Version version) {
         caps = new ArrayList<>();
-        Map<String, String> dirs = new HashMap<>();
-        Map<String, Object> attrs = new HashMap<>();
+        Map<String, String> dirs = new StringArrayMap<>(0);
+        Map<String, Object> attrs = new StringArrayMap<>(3);
         attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, name);
         attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, type);
         attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, version);

http://git-wip-us.apache.org/repos/asf/karaf/blob/e5bd746b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
index 80af07b..c1798e9 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/resolver/ResourceUtils.java
@@ -16,17 +16,20 @@
  */
 package org.apache.karaf.features.internal.resolver;
 
-import java.util.HashMap;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
+import org.apache.karaf.features.internal.util.StringArrayMap;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Resource;
 
+import static org.apache.karaf.features.internal.resolver.FeatureResource.CONDITIONAL_TRUE;
+import static org.apache.karaf.features.internal.resolver.FeatureResource.REQUIREMENT_CONDITIONAL_DIRECTIVE;
 import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
 import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
 import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
@@ -94,11 +97,17 @@ public final class ResourceUtils {
     }
 
     public static RequirementImpl addIdentityRequirement(ResourceImpl resource, String name, String type, VersionRange range, boolean mandatory) {
-        Map<String, String> dirs = new HashMap<>(1);
-        Map<String, Object> attrs = new HashMap<>(4);
+        return addIdentityRequirement(resource, name, type, range, mandatory, false);
+    }
+    public static RequirementImpl addIdentityRequirement(ResourceImpl resource, String name, String type, VersionRange range, boolean mandatory, boolean conditional) {
+        Map<String, String> dirs = new StringArrayMap<>((mandatory ? 0 : 1) + (conditional ? 1 : 0));
+        Map<String, Object> attrs = new StringArrayMap<>((name != null ? 1 : 0) + (type != null ? 1 : 0) + (range != null ? 1 : 0));
         if (!mandatory) {
             dirs.put(REQUIREMENT_RESOLUTION_DIRECTIVE, RESOLUTION_OPTIONAL);
         }
+        if (conditional) {
+            dirs.put(REQUIREMENT_CONDITIONAL_DIRECTIVE, CONDITIONAL_TRUE);
+        }
         if (name != null) {
             attrs.put(IDENTITY_NAMESPACE, name);
         }
@@ -121,12 +130,12 @@ public final class ResourceUtils {
         for (Capability cap : required.getCapabilities(null)) {
             if (cap.getNamespace().equals(IDENTITY_NAMESPACE)) {
                 Map<String, Object> attributes = cap.getAttributes();
-                Map<String, String> dirs = new HashMap<>(1);
+                Map<String, String> dirs = new StringArrayMap<>(1);
                 dirs.put(REQUIREMENT_RESOLUTION_DIRECTIVE, mandatory ? RESOLUTION_MANDATORY : RESOLUTION_OPTIONAL);
-                Map<String, Object> attrs = new HashMap<>(4);
+                Version version = (Version) attributes.get(CAPABILITY_VERSION_ATTRIBUTE);
+                Map<String, Object> attrs = new StringArrayMap<>(version != null ? 3 : 2);
                 attrs.put(IDENTITY_NAMESPACE, attributes.get(IDENTITY_NAMESPACE));
                 attrs.put(CAPABILITY_TYPE_ATTRIBUTE, attributes.get(CAPABILITY_TYPE_ATTRIBUTE));
-                Version version = (Version) attributes.get(CAPABILITY_VERSION_ATTRIBUTE);
                 if (version != null) {
                     attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(version, true));
                 }
@@ -137,21 +146,21 @@ public final class ResourceUtils {
 
     public static String toFeatureRequirement(String feature) {
         String[] parts = feature.split("/");
-        Map<String, Object> attrs = new HashMap<>(4);
+        Map<String, Object> attrs = new StringArrayMap<>(parts.length > 1 ? 3 : 2);
         attrs.put(IDENTITY_NAMESPACE, parts[0]);
         attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE);
         if (parts.length > 1) {
             attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(parts[1]));
         }
-        Map<String, String> dirs = new HashMap<>();
-        dirs.put(Constants.FILTER_DIRECTIVE, SimpleFilter.convert(attrs).toString());
+        Map<String, String> dirs = Collections.singletonMap(
+            Constants.FILTER_DIRECTIVE, SimpleFilter.convert(attrs).toString());
         return new RequirementImpl(null, IDENTITY_NAMESPACE, dirs, attrs).toString();
     }
 
     public static String toFeatureCapability(String feature) {
         String[] parts = feature.split("/");
-        Map<String, String> dirs = new HashMap<>(1);
-        Map<String, Object> attrs = new HashMap<>(4);
+        Map<String, String> dirs = Collections.emptyMap();
+        Map<String, Object> attrs = new StringArrayMap<>(parts.length > 1 ? 3 : 2);
         attrs.put(IDENTITY_NAMESPACE, parts[0]);
         attrs.put(CAPABILITY_TYPE_ATTRIBUTE, TYPE_FEATURE);
         if (parts.length > 1) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/e5bd746b/features/core/src/main/java/org/apache/karaf/features/internal/util/StringArrayMap.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/util/StringArrayMap.java b/features/core/src/main/java/org/apache/karaf/features/internal/util/StringArrayMap.java
new file mode 100644
index 0000000..d05b7a2
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/util/StringArrayMap.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.karaf.features.internal.util;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+
+public class StringArrayMap<V> implements Map<String, V> {
+
+    private Object[] table;
+    private int size;
+
+    public static <T> Map<String, T> reduceMemory(Map<String, T> map) {
+        switch (map.size()) {
+            case 0:
+                return Collections.emptyMap();
+            case 1:
+                Entry<String, T> e = map.entrySet().iterator().next();
+                return Collections.singletonMap(e.getKey().intern(), e.getValue());
+            default:
+                if (map instanceof StringArrayMap) {
+                    StringArrayMap<T> m = (StringArrayMap) map;
+                    if (m.size == m.table.length / 2) {
+                        return map;
+                    }
+                }
+                return new StringArrayMap<>(map);
+        }
+    }
+
+    public StringArrayMap(Map<String, ? extends V> map) {
+        if (map instanceof StringArrayMap) {
+            size = ((StringArrayMap) map).size;
+            table = Arrays.copyOf(((StringArrayMap) map).table, size * 2);
+        } else {
+            size = 0;
+            table = new Object[map.size() * 2];
+            for (Entry<String, ? extends V> e : map.entrySet()) {
+                int i = size++ << 1;
+                table[i++] = e.getKey().intern();
+                table[i] = e.getValue();
+            }
+        }
+    }
+
+    public StringArrayMap() {
+        this(32);
+    }
+
+    public StringArrayMap(int capacity) {
+        table = new Object[capacity * 2];
+        size = 0;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public V get(Object key) {
+        String k = ((String) key).intern();
+        for (int i = 0, l = size << 1; i < l; i += 2) {
+            if (k == table[i]) {
+                return (V) table[i + 1];
+            }
+        }
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public V put(String key, V value) {
+        key = key.intern();
+        for (int i = 0, l = size << 1; i < l; i += 2) {
+            if (key == table[i]) {
+                V old = (V) table[i + 1];
+                table[i + 1] = value;
+                return old;
+            }
+        }
+        if (table.length == 0) {
+            table = new Object[2];
+        } else if (size * 2 == table.length) {
+            Object[] n = new Object[table.length * 2];
+            System.arraycopy(table, 0, n, 0, table.length);
+            table = n;
+        }
+        int i = size++ << 1;
+        table[i++] = key;
+        table[i] = value;
+        return null;
+    }
+
+    @Override
+    public Set<String> keySet() {
+        return new AbstractSet<String>() {
+            @Override
+            public Iterator<String> iterator() {
+                return new Iterator<String>() {
+                    int index = 0;
+
+                    @Override
+                    public boolean hasNext() {
+                        return index < size;
+                    }
+
+                    @Override
+                    public String next() {
+                        if (index >= size) {
+                            throw new NoSuchElementException();
+                        }
+                        return (String) table[(index++ << 1)];
+                    }
+                };
+            }
+
+            @Override
+            public int size() {
+                return size;
+            }
+        };
+    }
+
+    @Override
+    public Collection<V> values() {
+        return new AbstractCollection<V>() {
+            @Override
+            public Iterator<V> iterator() {
+                return new Iterator<V>() {
+                    int index = 0;
+
+                    public boolean hasNext() {
+                        return index < size;
+                    }
+
+                    @SuppressWarnings("unchecked")
+                    public V next() {
+                        if (index >= size) {
+                            throw new NoSuchElementException();
+                        }
+                        return (V) table[(index++ << 1) + 1];
+                    }
+                };
+            }
+
+            @Override
+            public int size() {
+                return size;
+            }
+        };
+    }
+
+    @Override
+    public Set<Entry<String, V>> entrySet() {
+        return new AbstractSet<Entry<String, V>>() {
+            @Override
+            public Iterator<Entry<String, V>> iterator() {
+                return new Iterator<Entry<String, V>>() {
+                    int index = 0;
+
+                    public boolean hasNext() {
+                        return index < size;
+                    }
+
+                    @SuppressWarnings("unchecked")
+                    public Entry<String, V> next() {
+                        if (index >= size) {
+                            throw new NoSuchElementException();
+                        }
+                        int i = index << 1;
+                        index++;
+                        return new Entry<String, V>() {
+                            @Override
+                            public String getKey() {
+                                return (String) table[i];
+                            }
+
+                            @Override
+                            public V getValue() {
+                                return (V) table[i + 1];
+                            }
+
+                            @Override
+                            public V setValue(V value) {
+                                throw new UnsupportedOperationException();
+                            }
+                        };
+                    }
+                };
+            }
+
+            @Override
+            public int size() {
+                return size;
+            }
+        };
+    }
+
+    @Override
+    public int size() {
+        return size;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        String k = ((String) key).intern();
+        for (int i = 0, l = size * 2; i < l; i += 2) {
+            if (table[i] == k) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        for (int i = 0, l = size * 2; i < l; i += 2) {
+            if (Objects.equals(table[i + 1], value)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public V remove(Object key) {
+        String k = ((String) key).intern();
+        for (int i = 0, l = size * 2; i < l; i += 2) {
+            if (table[i] == k) {
+                Object v = table[i + 1];
+                if (i < l - 2) {
+                    System.arraycopy(table, i + 2, table, i, l - 2 - i);
+                }
+                table[l - 1] = null;
+                table[l - 2] = null;
+                size--;
+                return (V) v;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void putAll(Map<? extends String, ? extends V> m) {
+        for (Map.Entry<? extends String, ? extends V> e : m.entrySet()) {
+            put(e.getKey(), e.getValue());
+        }
+    }
+
+    @Override
+    public void clear() {
+        size = 0;
+        Arrays.fill(table, null);
+    }
+
+}