Java中的动态类,具有最小的内存开销

时间:2016-07-09 18:34:26

标签: java

在我的应用程序中,我需要创建动态类/类型/对象。我通常会使用HashMap<K,V>来解决它(感谢与反射相反的简单性),例如:

class DynObject {
   private HashMap<String,Object> fields;
}

但是,我需要创建很多这些对象 - 我的意思是很多(我的内存已经不足了)。
我有一个想法 - 有相同字段的实例的组(&#34;类型和#34;)因此保持每个实例上的映射是多余的。为了减少每个实例的大小,我尝试将HashMap<K,V>移到单独的类:

class DynamicType {

    private HashMap<String, Integer> fields = new HashMap<>();
    private String name;

    public HashMap<String, Integer> getFields() { return fields; }
}

class Dynamic {
    private Object[] values;
    private DynamicType type;

    public Dynamic(DynamicType ofType)
    {
        type = ofType;
        values = new Object[type.getFields().size()];
    }

    public Object getField(String name) { 
       return values[type.getFields().get(name)]; 
    }
    public void setField(String name, Object value) { 
       values[type.getFields().get(name)] = value; 
    }
}

...哪种方法可以完美运行,但还有另一个问题 - 我需要不时地在类型上添加需求字段,在这种情况下,DynamicType字段的更改(仅添加)不会回顾性地反映其Dynamic个实例 我事先并不知道有多少课程,他们的名字或他们的领域,而且现有课程的额外字段需要被发现&#34;在任何时候。整体实际存储的数据量很大 tl; dr:如何使用字段String-&gt; Object创建动态类型以保持实例上的内存开销最小化?

我最终使用了接受的答案和使用延迟值分配实现自己的LazyList<T>的想法的结合,测试显示它节省了高达50%的内存(使用各种类型的5个字段进行测试)。

1 个答案:

答案 0 :(得分:1)

执行此操作最有效的方法可能是:

  1. 将数组切换为列表。
  2. 维护从DynamicType到其每个实例的反向引用。
  3. 这样,DynamicType可以在需要时扩展每个实例。它看起来像这样:

    class DynamicType {
        private final String name;
    
        private Map<String, Integer> fields = new HashMap<>();
        private List<Dynamic> instances = new ArrayList<>();
    
        public DynamicType(String name) {
            this.name = name;
        }
    
        public Dynamic createInstance() {
            Dynamic d = new Dynamic(this);
    
            for (int i=0;i<fields.size();i++) {
                d.addField(null); //Some default value for uninitialized fields
            }
    
            instances.add(d);
            return d;
        }
    
        public void addField(String name, Object defaultValue) {
            fields.put(name, fields.size());
    
            for (Dynamic d : instances) {
                d.addField(defaultValue);
            }
        }
    
        protected Integer getFieldIdx(String name) {
            return fields.get(name);
        }
    }
    
    class Dynamic {
        private List<Object> values;
        private DynamicType type;
    
        protected Dynamic(DynamicType ofType)
        {
            type = ofType;
            values = new ArrayList<>();
        }
    
        protected void addField(Object value) {
            values.add(value);
        }
    
        public Object getField(String name) { 
           return values.get(type.getFieldIdx(name)); 
        }
    
        public void setField(String name, Object value) { 
           return values.set(type.getFieldIdx(name), value); 
        }
    }
    

    用法示例:

    DynamicType t = new DynamicType("t");
    t.addField("name", null);
    t.addField("age", null);
    
    Dynamic t1 = t.createInstance();
    Dynamic t2 = t.createInstance();
    
    t.addField("sex", "male"); //Now both t1 and t2 have sex -> male
    

    P.S。代码未经过测试,但理论上应该有效。

    旧回答:

    如果我正确理解了您的问题,则会复制String个密钥,因为大多数字段在“动态对象”上都有相同的名称。

    因此,最好的解决方案是只为这些字符串创建一个单独的索引,以便所有HashMaps共享同一个实例。您可能会在写入操作上获得额外的开销,但是在密钥出现问题时可以节省大量内存。

    这样的事情(没有OOP,但这个想法应该是显而易见的):

    Map<String, String> fieldNameIndex = new HashMap<>();
    
    void addEntry(Map<String, Object> map, String name, Object value) {
        String existingName = fieldNameIndex.get(name);
        if (existingName == null) {
            fieldNameIndex.put(name, name);
            existingName = name;
        }
    
        map.put(existingName, value);
    }
    

    基本上,此代码维护所有已使用密钥的内部索引,如果索引中未找到现有密钥,则分配新密钥。因此,如果使用相同的键创建N个对象,它们将引用相同的String实例。

    此外,根据新对象出现的频率和此操作的执行速度 - 您可能希望将索引切换到TreeMap。如果不断添加新的字段名称,HashMaps将表现得更好。但是,如果在初始加载后很少添加新键 - TreeMap可能会表现得更好。