Java中类似于Perl哈希表的等效方法

8

由于其超级灵活性和方便性,我一直在大量使用Perl哈希表。例如,在Perl中,我可以执行以下操作:

$hash{AREA_CODE}->{PHONE}->{STREET_ADDR}

我想知道如何用Java实现相同的功能,我猜这可能与HashMap有关?

谢谢。


10
如果您描述了那段Perl代码的实际作用,那些懂Java但不懂Perl的人可以直接帮助您,而无需猜测。 - Michael Borgwardt
2
附注:$hash{AREA_CODE}->{PHONE}->{STREET_ADDR} 可以简写为 $hash{AREA_CODE}{PHONE}{STREET_ADDR}。 - Ashley
@Ashley - 我会认为前者(使用完整的->符号)比缩写版本更易于阅读和维护,但我知道这种观点并不被广泛共享。 - DVK
1
@Michael - 我同意这是一个普遍有效的观点,当询问这种类型的问题时;所以对于这个评论+1。然而,有足够数量的Perl开发人员非常熟练掌握Java,因此不太可能导致答案不足(正如周末内提交的答案所证明的那样:))。 - DVK
8个回答

18

由于其超级灵活性和便捷性,我一直在使用很多Perl哈希表。例如,在Perl中,我可以这样做: $hash {AREA_CODE} -> {PHONE} -> {STREET_ADDR} 我想知道如何在Java中实现同样的功能,我猜这与HashMap有关?

下面是 Java 代码,它近似地等同于上述 Perl 代码:

my %hash;
$hash{AREA_CODE}{PHONE}{STREET_ADDR} = "221B Baker Street";
printf "Street address is %s\n", $hash{AREA_CODE}{PHONE}{STREET_ADDR};

is

HashMap<String, HashMap<String, HashMap<String, String>>> hash =
    new HashMap<String, HashMap<String, HashMap<String, String>>>();

hash.put("AREA_CODE", new HashMap<String, HashMap<String, String>>());
hash.get("AREA_CODE").put("PHONE", new HashMap<String, String>());
hash.get("AREA_CODE").get("PHONE").put("STREET_ADDR", "221B Baker Street");

System.out.printf("Street address is %s\n",
    hash.get("AREA_CODE").get("PHONE").get("STREET_ADDR"));

这不是很“特别”吗?:)

我说“近似”的原因有很多。其中一个原因是在Java中,你只是想在下一行代码中执行与这个完全简单的Perl代码等价的操作,你就会感到非常沮丧,甚至到极点发怒。

$hash{AREA_CODE}{PREFIX} = 800;

如果你想在这方面获得像Perl那样的灵活性和便利性,Java是无法满足你的。更糟糕的是,Java的支持者经常会指责你甚至表达这样的愿望。


2
在我看来,你的Java代码在一般情况下有些不正确(尽管它在字面上翻译示例Perl代码时是100%正确的)。它没有检查AREA_CODE键是否已经映射到有效的哈希映射(如果您假设“刚刚声明”的空容器的特殊情况,则可以接受;但如果您不假设,则会中断)。对于值检索也是如此-它不检查get(“AREA_CODE”)的值是否为null。这意味着,在一般情况下,设置和获取三级嵌套哈希的Java等效代码甚至比您的代码更长,更丑陋。 - DVK
接受了。我之前误操作接受了那个。谢谢你的解释,真的帮了我很多。 - snoofkin
@DVK:我相信所有这些都属于我的“近似”范畴。 :) - tchrist
借口,借口 ;)。请查看我的答案,其中的代码(诚然,我甚至没有尝试编译或确保其正确性)精确地移植了Perl的哈希-哈希-哈希。 - DVK
1
好主意,但我怀疑它的性能表现(哈希图太多了)。最好按照这里描述的做法去做:https://dev59.com/z2435IYBdhLWcg3w7k6b - Marcus

6
首先,你的具体示例($hash{AREA_CODE}->{PHONE}->{STREET_ADDR}),使用硬编码字符串作为哈希键,在Java中并不是一个真正有用的数据结构,正如Michael Carman所指出的那样 - 应该将其存储为具有属性的类(而且说实话,这个数据结构在概念上是一个糟糕的数据结构 - 这种数据更可能是电话数组,而不是电话哈希)。
其次,假设你实际上是想要$hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR},到目前为止,似乎所有人的Java代码都没有实现通用等价代码 - 代码都假定Java哈希是新初始化的用于存储示例或完全填充的用于检索示例(换句话说,就像leonbloy的答案所指出的,缺少自动vivification功能)。
模拟自动vivification的正确代码是:
// This method will ensure that hash-of-hash-of-hashes structure exists of a given set of 3 keys.
public HashMap<String, HashMap<String, HashMap<String, Object>>>
 autovivification_3rd_level (
           HashMap<String, HashMap<String, HashMap<String, Object>>> hash
         , String AREA_CODE, String PHONE, String STREET_ADDR) {
    if (hash == null) {
        hash = new HashMap<String, HashMap<String, HashMap<String, Object>>>();
    }
    if (!hash.contains(AREA_CODE) || hash.get(AREA_CODE) == null) {
        hash.put(new HashMap<String, HashMap<String, Object>>());
    }
    HashMap<String, HashMap<String, Object>> AREA_CODE_hash
         = (HashMap<String, HashMap<String, Object>>) hash.get(AREA_CODE);
    if (!AREA_CODE_hash.contains(PHONE) || AREA_CODE_hash.get(PHONE) == null) {
        AREA_CODE_hash.put(new HashMap<String, Object>());
    }
    return hash;
}

////////////////////////////////////////////////////////////////////////////////////    

// Equivalent to Perl's "$hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR} = value;"
public Object put_3d_level_hash(
          HashMap<String, HashMap<String, HashMap<String, Object>>> hash
        , String AREA_CODE, String PHONE, String STREET_ADDR,
        , Object value) {
    hash = autovivification_3rd_level(hash, AREA_CODE, PHONE, STREET_ADDR);
    return hash.get(AREA_CODE).get(PHONE).put(STREET_ADDR, value);
}
put_3d_level_hash(hash, AREA_CODE, PHONE, STREET_ADDR, obj);

////////////////////////////////////////////////////////////////////////////////////    

// Equivalent to Perl's "$var = $hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR}"
public Object get_3d_level_hash(HashMap<String, HashMap<String, HashMap<String, Object>>> hash
                       , String AREA_CODE, String PHONE, String STREET_ADDR) {
    hash = autovivification_3rd_level(hash, AREA_CODE, PHONE, STREET_ADDR);
    return hash.get(AREA_CODE).get(PHONE).get(STREET_ADDR);
}
Object obj = get_3d_level_hash(hash, AREA_CODE, PHONE, STREET_ADDR);

我以前肯定写过那样的代码。这不是我想要的好时光! - tchrist
顺便说一句,我不是Java专家 - 如果代码有误或可以进一步改进,请随意编辑和改进它。 - DVK

5
请查看 Map 接口及其实现,特别是 HashMap
请注意,Java 没有 Perl 的自动初始化功能(一个方便但危险的特性)。
hash.get("areaCode").get("phone").get("streetAdr")

如果例如get(phone)返回null,将会抛出异常。同时需要注意的是,对于具有固定名称(“属性”)的事物,不应该使用哈希表,而应该定义自己的类,并使用其getter和setter方法。


1
认为应该是“Java没有Perl的自动复活”。 - Sdaz MacSkibbons
请注意,通用的Map接口与Perl的哈希表并不完全等价,因为Perl哈希表具有O(1)的访问/存储时间(作为真正的哈希映射),而实现Map接口的通用Java类可能没有这个特性。 - DVK
我添加了一个含有自动初始化的Java等效实现的答案。 - DVK
你可以通过调用 ArrayList<HashMap<String, String>> data = new ArrayList<HashMap<String, String>>(capacity, 1); 来获得 O(1),然后将任何键值对添加到哈希映射中。 - Marcus

2

Java有哈希表,但由于强类型的限制,其灵活性不如Perl中的哈希表。多维哈希表更难处理。在Perl中,你可以声明一个哈希表,并让自动化创建嵌套哈希表。

my %hash;
$hash{a}{b} = 1;

在Java中,你必须事先声明它是一个哈希的哈希。
Map<String,Map<String,Integer>> hash = new HashMap<String,HashMap<String,Integer>>();
hash.put("a", new HashMap<String, Integer>());
hash.get("a").put("b", new Integer(1));

对于每个额外的维度,您需要在声明中添加另一个嵌套的Map<K,V>。除了繁琐之外,这并不是非常面向对象的。


4
“这不是面向对象编程”的说法很荒谬,那又怎样呢?就多维关联数组(如 $hash{$a}{$b}{$c})而言,如果该说法适用,则同样必须适用于多维普通数组(如 $array[$i][$j][$k]),甚至也适用于单下标数组(如 $ARGV[$n])。为了笨拙地使用过度抽象的面向对象构造而避免使用下标聚合是浪费大家时间的行为。Java 无法像对待数组一样将哈希作为一等公民来处理,这只是 Java 无数不合理烦人之一。即使 C++(最终)也设法解决了这个问题。 - tchrist
只有我一个人这样认为,还是你的意思是:hash.get("a").put(new String("fff"),new Integer(1)); ?? - snoofkin
Java代码的最后一行是错误的:它忘记了put()上的下标。正确的写法应该是hash.get("a").put("b", new Integer(1)); - tchrist
原来那段 Java 代码的第一行也是错误的。它需要改为 HashMap<String, HashMap<String, Integer>> hash = new HashMap<String, HashMap<String, Integer>>();。Java 真有趣! - tchrist
是的,我喜欢Java,它很好,尽管出于许多其他原因,我更喜欢Perl。无论如何,我仍然无法让它工作。 - snoofkin
@soulSurfer2010:你接受的代码甚至无法编译!! - tchrist

1
你可以轻松地对哈希进行子类化,以添加一个自动创建的方法。
从:$hash{AREA_CODE}->{PHONE}->{STREET_ADDR} 到:hash.vivifyingGet(areaCode).put(phone, streetAddr)
假设我已经创建了哈希:
/**
  * A two-level autovivifying hashmap of X and Y to Z. Provides
  * a new method #vivifyingGet(X) which creates the next level of hash.
  */
Map<AreaCode, Map<Phone, StreetAddr>> hash =
    new HashMap<AreaCode, Map<Phone, StreetAddr>>() {
    /**
      * Convenience method to get or create the next level of hash.
      * @param key the first level key
      * @return the next level map
      */
    public Map<Phone, StreetAddr> vivifyingGet(Phone key) {
        if (containsKey(key)) {
            return get(key);
        } else {
            Map<Phone, StreetAddr> = hash = new HashMap<Phone, StreetAddr>();
            put(key, hash);
            return hash;
        }
    }
};

1
如果哈希键是常量,为什么不使用hash.getAreaCode().getPhone().getStreetAddr()?请记住,您的getter或构造函数需要处理默认值生成。

0

我在工作中非常想念Perl哈希表,并使用哈希类做了一些丑陋的解决方案。

上周我有一个想法,将整个东西实现在一个PerlMap类中,该类使用分隔符访问对象,最重要的是使用Lists来访问子集。

使用map.get(code:street:phone)map.put(code:street:phone,"123456789")可以很好地工作。要获取电话号码列表,只需使用map.getList(code:street)

我刚刚开始使用它在我的项目中。它没有复杂性的限制 :-),您可以自由选择分隔符。我把整个东西放在http://www.jdeer.org下。玩得开心。


-4

如果您想要这种灵活性,但仍然在JVM中运行,那么您可能会选择Groovy。tchrist喜欢忽略Java是强类型语言,而不是像Perl或PHP这样的动态类型语言-并且也喜欢忽略Java运行速度比它们快一个数量级,但显然这只是我“党派”的看法。


3
@Nathan - 两点。 (1)你有没有实际引用一个由各自语言专家编写的各种应用程序的良好执行性能研究,证明Java应用程序在数量级上优于Perl应用程序?还是你只是凭空捏造,因为你听说“perl很慢”? - DVK
2
即使在某些特定情况下Perl较慢(这可能是可能的,但请用事实支持此类断言),汇编语言可能比Java快得多(数量级?)。你是否计划因此转向汇编语言并放弃Java?哦,没错。编程语言在程序员效率/吞吐量/维护成本方面也有差异。 - DVK
3
如果你对我的 "效率/吞吐量/维护成本" 一项提出质疑,请查看我正确的实现 - 用我知道的最好的Java方式编写,需要35行代码以模拟2行Perl代码,这些Perl代码非常常用和有用的数据结构。而且我只模拟了一个非常简单的例子 - 正如tchrist所指出的那样,一旦你决定一级或二级键包含的内容与2级哈希表不同,Java等效代码就几乎无法写得好,而Perl仍然只有1行代码。高效的代码,就是这样 :) - DVK
我查看的每个基准测试都显示Java的性能比Perl高一个数量级。也许你可以引用一个相反的排序,因为我从未见过这样的基准测试。 - Nathan Crause
所以...你知道...写1或2行代码,务必尽管去写,我会写出更快的代码。这就是生活。 - Nathan Crause
显示剩余3条评论

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