重构Java Map of Map of Map

6

我正在审核一个旧项目的代码,发现使用了以下数据结构,使用了Map套嵌Map套嵌Map(三层嵌套的Map):

// data structure
Map<String, Map<String, Map<String, List<String>>>> tagTree 
            = new HashMap<String, Map<String,Map<String,List<String>>>>();

从 Map 中获取值(我认为这是一个好的部分)

// fetch at tag values
List<String> tagList1 = tagTree.get("Java").get("Active").get("Tags");
List<String> tagList2 = tagTree.get("Java").get("Latest").get("SubTags");

将值放入Map中(这有点复杂且容易出错)
// put values
Map<String, Map<String, List<String>>> javaLangMap = new HashMap<String, Map<String, List<String>>>();
Map<String, List<String>> javaStatusMap = new HashMap<String, List<String>>();
List<String> javaTagList = new ArrayList<String>();

javaTagList.add("Java-OOP");
javaTagList.add("Java-Variables");
// put tag list
javaStatusMap.put("Tags", javaTagList);
// put status-wise tag
javaLangMap.put("Active", javaStatusMap);
// put language-wise tag
tagTree.put("Java", javaLangMap);

目前,这个结构是用来维护以下结构的:

标签语言 -> 标签状态 -> 标签类型 -> 标签列表

我计划重构这个映射表,因为其他开发人员很难阅读。

请分享您的想法,如何考虑以下情况来完成重构:

  • 所有四层在运行时都可能会更改。
  • 所有级别都应该是可访问的。
  • 需要内存解决方案,即不使用数据库表层次结构。

1
三个地图级别之间的数据关系是什么? - Jos
1
创建对象而不是具有值的映射,怎么样? - BobTheBuilder
1
基本上你正在查看的是:{Java={Active={Tags=[Java-OOP,Java-Variables]}}} - Suparna
1
@SomBhattacharyya 可能是因为手动实例化嵌套的映射很麻烦且容易出错。 - Valentin
如果Map中的条目数量变得很高,你应该考虑根据@BobTheBuilder的建议创建对象,并且可以依靠DB来满足查询需求。您可以创建具有4个字段(语言、状态、类型和值)的对象。如果entry cont较少且您需要类似上面提到的超快速查找,则当前设计在我的意见中是适当的。或者您可以考虑使用guava Multimap。 - Jos
显示剩余9条评论
6个回答

11
如果你只想访问数据结构的最后一层,你可以使用一个Multimap<Triple<String,String,String>,String>Multimap<K,V>是来自Guava的数据结构,基本上是一个更好的Map<K,Collection<V>>Triple<L,M,R>是来自Apache Commons Lang3的三元组数据结构,它是Comparable并实现了equals
你可以像这样声明你的标记树:
Multimap<Triple<String, String, String>, String> tagTree = HashMultimap.create();

然后像这样填写:

tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-OOP");
tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-Variables");

或:

tagTree.putAll(Triple.of("Java", "Active", "Tags"), Arrays.asList("Java-OOP", "Java-Variables"));

然后像这样从中获取您的值:

Set<String> values = tagTree.get(Triple.of("Java", "Active", "Tags"));

这里有另一种可能适合您的简易解决方案,可以使用1、2或3个键获取:

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

public class ThreeLevelMap<K1, K2, K3, V> {
    private Map<K1, Map<K2, Multimap<K3, V>>> firstLevelMap = new HashMap<>();
    private Map<Pair<K1, K2>, Multimap<K3, V>> secondLevelMap = new HashMap<>();
    private Multimap<Triple<K1, K2, K3>, V> thirdLevelMap = HashMultimap.create();

    public void put(K1 key1, K2 key2, K3 key3, V value) {
        thirdLevelMap.put(Triple.of(key1, key2, key3), value);

        final Pair<K1, K2> secondLevelKey = Pair.of(key1, key2);
        Multimap<K3, V> secondLevelContainer = secondLevelMap.get(secondLevelKey);
        if (secondLevelContainer == null) {
            secondLevelContainer = HashMultimap.create();
            secondLevelMap.put(secondLevelKey, secondLevelContainer);
        }
        secondLevelContainer.put(key3, value);

        Map<K2, Multimap<K3, V>> firstLevelContainer = firstLevelMap.get(key1);
        if (firstLevelContainer == null) {
            firstLevelContainer = new HashMap<>();
            firstLevelMap.put(key1, firstLevelContainer);
        }
        firstLevelContainer.put(key2, secondLevelContainer);
    }

    public Collection<V> get(K1 key1, K2 key2, K3 key3) {
        return thirdLevelMap.get(Triple.of(key1, key2, key3));
    }

    public Multimap<K3, V> get(K1 key1, K2 key2) {
        return secondLevelMap.get(Pair.of(key1, key2));
    }

    public Map<K2, Multimap<K3, V>> get(K1 key1) {
        return firstLevelMap.get(key1);
    }
}

您可以这样使用它:

ThreeLevelMap<String, String, String, String> tlm = new ThreeLevelMap<>();
tlm.put("Java", "Active", "Tags", "Java-OOP");
tlm.put("Java", "Active", "Tags", "Java-Variables");

Map<String, Multimap<String, String>> firstLevelMap = tlm.get("Java");
Multimap<String, String> secondLevelMap = tlm.get("Java", "Active");
Collection<String> tags = tlm.get("Java", "Active", "Tags");

我认为这是粗略的原因是:

  • get方法返回的地图是可修改的
  • 我没有实现remove方法
  • 我没有进行大量测试

谢谢...当只有第四层可访问时,您的答案太棒了 :-) - mmuzahid
@mmuzahid 不用谢。如果您想要尝试另一种解决方案,我已经添加了大致的基础知识。 - Valentin
是“Multimap”还是“MultiMap”? - mmuzahid
Guava中的Multimap - Valentin
我已经导入了以下库: import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Table; 但是 PairTriple 仍然无法解析。 - mmuzahid
显示剩余3条评论

2

您可以创建类来保存数据结构

public class A {
    Map<String, List<String>> map;
}

public class B {
    Map<String, A> map;
}

Map<String, B> tagTree;

2
我认为这不是一个很糟糕的解决方案。它只是一棵树的表示,其中每个叶子节点都在第三层。如果不是这种情况(不同的叶子节点级别等),则需要构建一个树类结构。
但我会改变的是把所有内容放在一个类中,包括获取和设置方法以及空值检查。
在以下代码中,add方法处理了中间级别映射的容错处理,而get方法检查中间级别的空值:
public class TreeStructure {
  Map<String, Map<String, Map<String, List<String>>>> tagTree 
            = new HashMap<String, Map<String,Map<String,List<String>>>>();

  // ... Constructor ...

  // This method adds all intermediate levels if not existing
  public void add(String level1, String level2, String level3) {
    String l1 = tagTree.get(level1);
    if(l1 == null)
      tagTree.put(level1, new HashMap<String, Map<String, List<String>>>());
    l1 = tagTree.get(level1);
    String l2 = l1.get(level2),
    if(l2 == null)
      tagTree.put(level2, new Map<String, List<String>>(););
    l2 = l1.get(level2);
    String l3 = l2.get(level3);
    if(l3 == null) l2.add(level3, new ArrayList<>());
  }

  // This method checks, if every intermediate level existed
  // Otherwise, get() returns null, and the next get() would fail
  public String get(String level1, String level2, String level3) {
    String l1 = tagTree.get(level1);
    if(l1 == null)
      return null;
    String l2 = l1.get(level2),
    if(l2 == null)
      return null;
    l2 = l1.get(level2);
    String l3 = l2.get(level3);
    return l3;
  }
}

(代码未经测试)


2

使用三元组的映射表:

class Tuple3<A,B,C> {
   private A a;
   private B b;
   private C c;
   // getters, setters, constructor
   // make sure equals() and hashCode() are okay
}

需要注意的是,Map of Map of Maps 可以在 O(1) 的时间复杂度内告诉你某个元素是否存在,只需查找外部映射。而使用元组解决方案时,您只能使用完整的键。


1

我认为没有理由重构这个Map结构。但如果可能的话,将这个Map封装在另一个class中并给其他开发人员提供一个干净的接口可能是一个不错的主意。

...

public void addTag(String language, String status, String tag)

public void removeTag(String language, String status, String tag)

public List<String> getTags(String language, String status)

...

1
我喜欢以上提出的大部分解决方案。我认为设计越简单有效,它就会更易于支持。
因此,我使用了基础——纯对象组合来重构您的代码。
package design;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class JavaTag{
  private String tags;

  JavaTag(String tags){
    this.tags = tags;
  }
 }

class JavaTagStatusList{
  private ArrayList<JavaTag> tagList = new ArrayList<JavaTag>();

  JavaTagStatusList(){

  }

  public void addJavaTag (JavaTag tagObj){
    if (tagObj != null){
        tagList.add(tagObj);
    }
  }
}

class JavaTagStatusMap {
    private HashMap<String, JavaTagStatusList> tagStatusMap = new HashMap<String, JavaTagStatusList>();

    JavaTagStatusMap(){
    }


    public void addTagStatusEntry(String status, JavaTag obj){
      if (tagStatusMap.containsKey(status)){
        tagStatusMap.get(status).addJavaTag(obj);
      }
      else {
        JavaTagStatusList statusList = new JavaTagStatusList();
        statusList.addJavaTag(obj);
        tagStatusMap.put(status, statusList);
    }
  }
}

主要:

public class MapofMapRefactor {

    public static void main(String[] args) {
      JavaTag tag1 = new JavaTag("Java-OOP");
      JavaTag tag2 = new JavaTag("Java-Variables");

      JavaTagStatusMap statusMap = new JavaTagStatusMap();
      statusMap.addTagStatusEntry("Active", tag1);
      statusMap.addTagStatusEntry("Active", tag2);

      // HashMap of Java Lang Map
      HashMap<String, JavaTagStatusMap> javaLanguageMap = new HashMap<String, JavaTagStatusMap>();
      javaLanguageMap.put("Java", statusMap);   

  }

}

我们不能使用“enum JavaStatus”,因为它可能在运行时被更改(添加了几个状态)。 - mmuzahid
明白了。让我再根据这个新信息编辑一下例子。 - Suparna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接