欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

举例详解Java编程中HashMap的初始化以及遍历的方法

程序员文章站 2024-03-06 16:21:14
一、hashmap的初始化 1、hashmap 初始化的文艺写法    hashmap 是一种常用的数据结构,一般用来做数据字典或者 hash 查找...

一、hashmap的初始化
1、hashmap 初始化的文艺写法
   hashmap 是一种常用的数据结构,一般用来做数据字典或者 hash 查找的容器。普通青年一般会这么初始化:

 

hashmap<string, string> map =
  new hashmap<string, string>();
 map.put("name", "june"); 
 map.put("qq", "2572073701");

看完这段代码,很多人都会觉得这么写太啰嗦了,对此,文艺青年一般这么来了:
 

hashmap<string, string> map =
  new hashmap<string, string>() {
 {
 put("name", "june"); 
 put("qq", "2572073701"); 
 }
 };

   嗯,看起来优雅了不少,一步到位,一气呵成的赶脚。然后问题来了,有童鞋会问:纳尼?这里的双括号到底什么意思,什么用法呢?哈哈,其实很简单,看看下面的代码你就知道啥意思了。

public class test {
 /*private static hashmap< string, string> map = new hashmap< string, string>() {
  {
   put("name", "june");
   put("qq", "2572073701");
  }
 };*/
 public test() {
  system.out.println("constructor called:构造器被调用");
 }
 static {
  system.out.println("static block called:静态块被调用");
 }
 {
  system.out.println("instance initializer called:实例初始化块被调用");
 }
 public static void main(string[] args) {
  new test();
  system.out.println("=======================");
  new test();
 }
}

输出:

 static block called:静态块被调用
 instance initializer called:实例初始化被调用
 constructor called:构造器被调用
 =======================
 instance initializer called:实例初始化被调用
 constructor called:构造器被调用

也就是说第一层括弧实际是定义了一个匿名内部类 (anonymous inner class),第二层括弧实际上是一个实例初始化块 (instance initializer block),这个块在内部匿名类构造时被执行。这个块之所以被叫做“实例初始化块”是因为它们被定义在了一个类的实例范围内。
上面代码如果是写在 test 类中,编译后你会看到会生成 test$1.class 文件,反编译该文件内容:

d:\eclipse_indigo\workspace_home\cdhjobs\bin\pvuv\>jad -p test$1.class
// decompiled by jad v1.5.8g. copyright 2001 pavel kouznetsov.
// jad home page: http://www.kpdus.com/jad.html
// decompiler options: packimports(3)
// source file name: test.java

package pvuv.zhaopin;
import java.util.hashmap;
// referenced classes of package pvuv.zhaopin:
// test
 class test$1 extends hashmap // 创建了一个 hashmap 的子类
 {
 test$1()
 { // 第二个 {} 中的代码放到了构造方法中去了 
 put("name", "june");
 put("qq", "2572073701");
 }
 }

d:\eclipse_indigo\workspace_home\cdhjobs\bin\pvuv\>

2、推而广之
  这种写法,推而广之,在初始化 arraylist、set 的时候都可以这么玩,比如你还可以这么玩:

 list<string> names = new arraylist<string>() {
 {
 for (int i = 0; i < 10; i++) {
  add("a" + i);
 }
 }
 };
 system.out.println(names.tostring()); // [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]

3、java7:增加对 collections 的支持
在 java 7 中你可以像 ruby, perl、python 一样创建 collections 了。
note:这些集合是不可变的。

ps:由于原文[5]作者并没有标出 java 7 哪个小版本号引入的这些新特性,对于留言报错的同学,请尝试大于 1.7.0_09 或者 java8 试试?

list list = new arraylist();
 list.add("item");
 string item = list.get(0);
 set< string> set = new hashset< string>();
 set.add("item");
 map< string, integer> map = new hashmap< string, integer>();
 map.put("key", 1);
 int value = map.get("key");
 // 现在你还可以: 
 list< string> list = ["item"];
 string item = list[0];
 
 set< string> set = {"item"};
 
 map< string, integer> map = {"key" : 1};
 int value = map["key"];

4、文艺写法的潜在问题
    文章开头提到的文艺写法的好处很明显就是一目了然。这里来罗列下此种方法的坏处,如果这个对象要串行化,可能会导致串行化失败。
  1.此种方式是匿名内部类的声明方式,所以引用中持有着外部类的引用。所以当串行化这个集合时外部类也会被不知不觉的串行化,当外部类没有实现serialize接口时,就会报错。

2.上例中,其实是声明了一个继承自hashmap的子类。然而有些串行化方法,例如要通过gson串行化为json,或者要串行化为xml时,类库中提供的方式,是无法串行化hashset或者hashmap的子类的,从而导致串行化失败。解决办法:重新初始化为一个hashmap对象:

new hashmap(map);

这样就可以正常初始化了。

5、执行效率问题
   当一种新的工具或者写法出现时,猿们都会来一句:性能怎么样?(这和男生谈论妹纸第一句一般都是:“长得咋样?三围多少?”一个道理:))
关于这个两种写法我这边笔记本上测试文艺写法、普通写法分别创建 10,000,000 个 map 的结果是 1217、1064,相差 13%。

public class test {
 public static void main(string[] args) {
  long st = system.currenttimemillis();
  /*
  for (int i = 0; i < 10000000; i++) {
   hashmap< string, string> map = new hashmap< string, string>() {
    {
     put("name", "june");
     put("qq", "2572073701");
    }
   };
  }
  system.out.println(system.currenttimemillis() - st); // 1217
  */
  for (int i = 0; i < 10000000; i++) {
   hashmap< string, string> map = new hashmap< string, string>();
   map.put("name", "june");
   map.put("qq", "2572073701");
  }
  system.out.println(system.currenttimemillis() - st); // 1064
 }
}

6、由实例初始化块联想到的一些变量初始化问题
  从代码上看,a 为什么可以不先声明类型?你觉得 a、b、c 的值分别是多少?能说明理由么?
tips:如果你对这块机制不了解,建议试着反编译一下字节码文件。

6.1 测试源码

public class test {
 
 int e = 6;
 test() {
  int c = 1;
  this.f = 5;
  int e = 66;
 }
 int f = 55;
 int c = 11;
 int b = 1;
 {
  a = 3;
  b = 22;
 }
 int a = 33;
 static {
  d = 4;
 }
 static int d = 44;
 
 int g = 7;
 int h = 8;
 public int test(){
  g = 77;
  int h = 88;
  system.out.println("h - 成员变量:" + this.h);
  system.out.println("h - 局部变量: " + h);
  return g;
 }
 public static void main(string[] args) {
  system.out.println("a: " + new test().a);
  system.out.println("b: " + new test().b);
  system.out.println("c: " + new test().c);
  system.out.println("d: " + new test().d);
  system.out.println("f: " + new test().f);
  system.out.println("e: " + new test().e);
  system.out.println("g: " + new test().test());
 }
}

6.2 字节码反编译:

// decompiled by jad v1.5.8g. copyright 2001 pavel kouznetsov.
// jad home page: http://www.kpdus.com/jad.html
// decompiler options: packimports(3)
// source file name: test.java
import java.io.printstream;
public class test
{
 test()
 {
  this.e = 6;
  f = 55;
  this.c = 11;
  b = 1;
  a = 3;
  b = 22;
  a = 33;
  g = 7;
  h = 8;
  int c = 1;
  f = 5;
  int e = 66;
 }
 public int test()
 {
  g = 77;
  int h = 88;
  system.out.println((new stringbuilder("h - \u6210\u5458\u53d8\u91cf\uff1a")).append(this.h).tostring());
  system.out.println((new stringbuilder("h - \u5c40\u90e8\u53d8\u91cf: ")).append(h).tostring());
  return g;
 }
 public static void main(string args[])
 {
  system.out.println((new stringbuilder("a: ")).append((new test()).a).tostring());
  system.out.println((new stringbuilder("b: ")).append((new test()).b).tostring());
  system.out.println((new stringbuilder("c: ")).append((new test()).c).tostring());
  new test();
  system.out.println((new stringbuilder("d: ")).append(d).tostring());
  system.out.println((new stringbuilder("f: ")).append((new test()).f).tostring());
  system.out.println((new stringbuilder("e: ")).append((new test()).e).tostring());
  system.out.println((new stringbuilder("g: ")).append((new test()).test()).tostring());
 }
 int e;
 int f;
 int c;
 int b;
 int a;
 static int d = 4;
 int g;
 int h;
 static
 {
  d = 44;
 }
}
6.3 output:
 a: 33
 b: 22
 c: 11
 d: 44
 f: 5
 e: 6
 h - 成员变量:8
 h - 局部变量: 88
 g: 77


二、hashmap遍历方法示例
第一种:
  

map map = new hashmap();
  iterator iter = map.entryset().iterator();
  while (iter.hasnext()) {
  map.entry entry = (map.entry) iter.next(); object key = entry.getkey();
  object val = entry.getvalue();
  }

  效率高,以后一定要使用此种方式!
  第二种:
 

 map map = new hashmap();
  iterator iter = map.keyset().iterator();
  while (iter.hasnext()) {
  object key = iter.next();
  object val = map.get(key);
  }

  效率低,以后尽量少使用!
  hashmap的遍历有两种常用的方法,那就是使用keyset及entryset来进行遍历,但两者的遍历速度是有差别的,下面请看实例:
 

 public class hashmaptest {
  public static void main(string[] args) ...{
  hashmap hashmap = new hashmap();
  for (int i = 0; i < 1000; i ) ...{
  hashmap.put("" i, "thanks");
  }
  long bs = calendar.getinstance().gettimeinmillis();
  iterator iterator = hashmap.keyset().iterator();
  while (iterator.hasnext()) ...{
  system.out.print(hashmap.get(iterator.next()));
  }
  system.out.println();
  system.out.println(calendar.getinstance().gettimeinmillis() - bs);
  listhashmap();
  }
  public static void listhashmap() ...{
  java.util.hashmap hashmap = new java.util.hashmap();
  for (int i = 0; i < 1000; i ) ...{
  hashmap.put("" i, "thanks");
  }
  long bs = calendar.getinstance().gettimeinmillis();
  java.util.iterator it = hashmap.entryset().iterator();
  while (it.hasnext()) ...{
  java.util.map.entry entry = (java.util.map.entry) it.next();
  // entry.getkey() 返回与此项对应的键
  // entry.getvalue() 返回与此项对应的值
  system.out.print(entry.getvalue());
  }
  system.out.println();
  system.out.println(calendar.getinstance().gettimeinmillis() - bs);
  }
  }

  对于keyset其实是遍历了2次,一次是转为iterator,一次就从hashmap中取出key所对于的value。而entryset只是遍历了第一次,他把key和value都放到了entry中,所以就快了。
  注:hashtable的遍历方法和以上的差不多!