8

我遇到了一些正在做这样的事情的代码:

Map<String,String> fullNameById = buildMap1(dataSource1);
Map<String,String> nameById = buildMap2(dataSource2);
Map<String,String> nameByFullName = new HashMap<String,String>();
Map<String,String> idByName = new HashMap<String,String>();

Set<String> ids = fullNameById.keySet();
for (String nextId : ids) {
  String name = nameById.get(nextId);
  String fullName = fullNameById.get(nextId);
  nameByFullName.put(fullName, name);
  idByName.put(name, nextId);
}

我不得不盯着它看了几分钟才能弄清楚发生了什么。所有这些都相当于对 id 的连接操作和原始地图之一的反转。由于 Id、FullName 和 Name 始终是 1:1:1,在我看来应该有一些方法来简化这一点。我还发现前两张地图再也没有使用过,而且我发现上面的代码有点难以阅读。所以我正在考虑用这样的东西代替它(对我来说)读起来更干净

Table<String, String, String> relations = HashBasedTable.create();

addRelationships1(dataSource1, relations);
addRelationships2(dataSource2, relations);

Map<String,String> idByName = relations.column("hasId");
Map<String,String> nameByFullName = relations.column("hasName");
relations = null; // not used hereafter

在 addRelationships1 我做

relations.put(id, "hasFullName", fullname);

在我的查询产生值的 addRelationships2 中idname我做到了

relations.put(relations.remove(id,"hasFullName"), "hasName", name);
relations.put(name, "hasId", id);

所以我的问题是:

  1. 我通过处理器或内存或 GC 负载所做的工作是否存在潜在的低效率?我不这么认为,但我对 Table 的效率不是很熟悉。我知道 Table 对象在之后不会被 GC relations = null,我只是想说明它不会在随后的相当长的代码部分中再次使用。
  2. 我有没有提高效率?我不断地说服自己和不说服自己,我有也没有。
  3. 你觉得这更具可读性吗?或者这只是因为我写的而对我来说很容易阅读?我在这方面有点担心,因为事实Table并不为人所知。另一方面,顶层现在很清楚地说,“从两个来源收集数据并从中制作这两张地图。” 我也喜欢这样一个事实,即它不会让您想知道其他两张地图是否/在哪里使用(或不使用)。
  4. 你有比上述任何一种方法更好、更清洁、更快、更简单的方法吗?

请不要在这里进行优化早期/晚期讨论。我很清楚这个陷阱。如果它在不损害性能的情况下提高了可读性,我很满意。性能提升将是一个不错的奖励。

注意:我的变量和方法名称已在这里进行了清理,以防止业务领域分散讨论,我绝对不会将它们命名为 addRelationships1 或 datasource1!同样,最终的代码当然会使用常量而不是原始字符串。

4

2 回答 2

17

所以我自己做了一些小型基准测试,得出的结论是这两种方法在执行时间方面几乎没有区别。我通过交易运行数据集大小来保持正在处理的数据的总大小不变。我进行了 4 次运行,并从所有 4 次运行中为每个实现选择了最短的时间。再次令人欣慰的是,这两种实现在同一次运行中总是最快的。我的代码可以在这里找到。这是我的结果:

Case                      Maps (ms)   Table (ms)    Table vs Maps
100000 runs of size 10    2931        3035          104%
10000 runs of size 100    2989        3033          101%
1000 runs of size 1000    3129        3160          101%
100 runs of size 10000    4126        4429          107%
10 runs of size 100000    5081        5866          115%
1 run  of size 1000000    5489        5160          94%

因此,对于小型数据集,使用 Table 似乎会稍微慢一些。有趣的事情发生在 100,000 左右,然后到 100 万,表实际上更快。我的数据将在 100 到 1000 范围内徘徊,因此至少在执行时间上性能应该几乎相同。

至于可读性,我的观点是,如果有人试图弄清楚附近发生了什么并阅读代码,那么看到意图会明显容易得多。如果他们必须实际调试这段代码,可能会有点困难,因为Table它不太常见,并且需要一些复杂性才能理解。

我不确定的另一件事是创建哈希映射是否更有效,或者在随后迭代映射的所有键的情况下直接查询表。然而,这是一个不同的问题:)

喜剧的结局是,事实上,当我进一步分析代码(数百行)时,我发现 nameByFullname.get() 在日志记录之外的唯一重要用途(价值可疑)是将结果传递给 idByName 。得到()。所以最后我实际上将构建一个 idByFullName 映射和一个 idByName 映射,而不需要任何连接,并且无论如何都会删除整个表。但我猜它提出了一个有趣的 SO 问题。

于 2013-03-07T19:15:57.487 回答
5

tl; 博士,但恐怕你需要从原始设计迈出更大的一步。模拟 DB 表可能是一个不错的练习,但对我来说,您的代码并不是真正可读的。

  1. 我所做的事情是否存在潜在的低效率......不知道。
  2. 我有没有提高效率?恐怕你需要先测量一下。删除一些间接性肯定会有所帮助,但使用更复杂的数据结构可能会抵消它。总的来说,性能太复杂了。
  3. 你觉得这更具可读性吗?恐怕不是。
  4. 你有比上述任何一种方法更好、更清洁、更快、更简单的方法吗?但愿如此....

我在这样的代码中迷失的地方是对所有内容都使用字符串 - 将错误的字符串作为参数传递太容易了。所以我建议将它们聚合成一个对象,并提供通过它们的任何部分访问对象的映射。像这样微不足道的事情应该做:

class IdNameAndFullName {
    String id, name, fullName;
}

class IdNameAndFullNameMaps {
    Map<String, IdNameAndFullName> byId;
    Map<String, IdNameAndFullName> byName;
    Map<String, IdNameAndFullName> byFullName;
}

您显然可以IdNameAndFullNameMapsTable. 然而,除了使用一个很好的预先存在的数据结构之外,我认为其中没有任何优势。缺点是:

  • 效率损失
  • Table可读性丧失(出于同样的原因,我不会在这里使用,Tuple 应该避免
  • 使用字符串键(您的“hasId”和“hasName”)。
于 2013-03-02T00:29:33.087 回答