分析Java中Map的遍历性能问题

一、引言

我们知道java HashMap的扩容是有成本的,为了减少扩容的次数和成本,可以给HashMap设置初始容量大小,如下所示:

HashMap<string, integer=""> map0 = new HashMap<string, integer="">(100000);

但是在实际使用的过程中,发现性能不但没有提升,反而显著下降了!代码里对HashMap的操作也只有遍历了,看来是遍历出了问题,于是做了一番测试,得到如下结果:

HashMap的迭代器遍历性能与 initial capacity 有关,与size无关

二、迭代器测试

贴上测试代码:

public class MapForEachTest {

    public static void main(String[] args) {
        HashMap<string, integer=""> map0 = new HashMap<string, integer="">(100000);

        initDataAndPrint(map0);

        HashMap<string, integer=""> map1 = new HashMap<string, integer="">();

        initDataAndPrint(map1);

    }

    private static void initDataAndPrint(HashMap map) {

        initData(map);

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            forEach(map);
        }
        long end = System.currentTimeMillis();
        System.out.println("");
        System.out.println("HashMap Size: " + map.size() +  " 耗时: " + (end - start) + " ms");
    }

    private static void forEach(HashMap map) {
        for (Iterator<map.entry<string, integer="">> it = map.entrySet().iterator(); it.hasNext();){
            Map.Entry<string, integer=""> item = it.next();
            System.out.print(item.getKey());
            // do something
        }

    }

    private static void initData(HashMap map) {
        map.put("a", 0);
        map.put("b", 1);
        map.put("c", 2);
        map.put("d", 3);
        map.put("e", 4);
        map.put("f", 5);
    }

}

这是运行结果:

我们将第一个Map初始化10w大小,第二个map不指定大小(实际16),两个存储相同的数据,但是用迭代器遍历100次的时候发现性能迥异,一个36ms一个4ms,实际上性能差距更大,这里的4ms是600次System.out.print的耗时,这里将print注掉再试下

for (Iterator<map.entry<string, integer="">> it = map.entrySet().iterator(); it.hasNext();){
    Map.Entry<string, integer=""> item = it.next();
    // System.out.print(item.getKey());
    // do something
}

输出结果如下:

可以发现第二个map耗时几乎为0,第一个达到了28ms,遍历期间没有进行任何操作,既然石锤了和 initial capacity 有关,下一步我们去看看为什么会这样,找找Map迭代器的源码看看。

三、迭代器源码探究

我们来看看Map.entrySet().iterator()的源码;

public final Iterator<map.entry<k,v>> iterator() {
    return new EntryIterator();
}

其中EntryIterator是HashMap的内部抽象类,源码并不多,我全部贴上来并附上中文注释

abstract class HashIterator {
    // 下一个Node
    Node<k,v> next; // next entry to return
    // 当前Node
    Node<k,v> current;     // current entry
    // 预期的Map大小,也就是说每个HashMap可以有多个迭代器(每次调用 iterator() 会new 一个迭代器出来),但是只能有一个迭代器对他remove,否则会直接报错(快速失败)
    int expectedModCount;  // for fast-fail

    // 当前节点所在的数组下标,HashMap内部是使用数组来存储数据的,不了解的先去看看HashMap的源码吧
    int index;             // current slot

    HashIterator() {
        // 初始化 expectedModCount
        expectedModCount = modCount;
        // 浅拷贝一份Map的数据
        Node<k,v>[] t = table;
        current = next = null;
        index = 0;
        // 如果 Map 中数据不为空,遍历数组找到第一个实际存储的素,赋值给next
        if (t != null && size > 0) { // advance to first entry
            do {} while (index < t.length && (next = t[index++]) == null);
        }
    }

    public final boolean hasNext() {
        return next != null;
    }

    final Node<k,v> nextNode() {
        // 用来浅拷贝table,和别名的作用差不多,没啥用
        Node<k,v>[] t;
        // 定义一个e指存储next,并在找到下一值时返它自己
        Node<k,v> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();

        // 使current指向e,也就是next,这次要找的值,并且让next = current.next,一般为null
        if ((next = (current = e).next) == null && (t = table) != null) {
            do {} while (index < t.length && (next = t[index++]) == null);
        }
        return e;
    }

    /**
     * 删除元素,这里不讲了,调的是HashMap的removeNode,没啥特别的
     **/
    public final void remove() {
        Node<k,v> p = current;
        if (p == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        K key = p.key;
        removeNode(hash(key), key, null, false, false);
        // 用来保证快速失败的
        expectedModCount = modCount;
    }
}

上面的代码一看就明白了,迭代器每次寻找下一个元素都会去遍历数组,如果 initial capacity 特别大的话,也就是说 threshold 也大,table.length就大,所以遍历比较耗性能。

table数组的大小设置是在resize()方法里:

Node<k,v>[] newTab = (Node<k,v>[])new Node[newCap];
table = newTab;

四、其他遍历方法

注意代码里我们用的是Map.entrySet().iterator(),实际上和keys().iterator(), values().iterator() 一样,源码如下:

final class KeyIterator extends HashIterator
    implements Iterator<k> {
    public final K next() { return nextNode().key; }
}

final class ValueIterator extends HashIterator
    implements Iterator<v> {
    public final V next() { return nextNode().value; }
}

final class EntryIterator extends HashIterator
    implements Iterator<map.entry<k,v>> {
    public final Map.Entry<k,v> next() { return nextNode(); }
}

这两个就不分析了,性能一样。

实际使用中对集合的遍历还有几种方法:

  • 普通for循环+下标
  • 增强型for循环
  • Map.forEach
  • Stream.forEach

普通for循环+下标的方法不适用于Map,这里不讨论了。

4.1、增强型for循环

增强行for循环实际上是通过迭代器来实现的,我们来看两者的联系

源码:

private static void forEach(HashMap map) {
    for (Iterator<map.entry<string, integer="">> it = map.entrySet().iterator(); it.hasNext();){
        Map.Entry<string, integer=""> item = it.next();
        System.out.print(item.getKey());
        // do something
    }
}

private static void forEach0(HashMap<string, integer=""> map) {
    for (Map.Entry entry : map.entrySet()) {
        System.out.print(entry.getKey());
    }
}

编译后的字节码:

// access flags 0xA
  private static forEach(Ljava/util/HashMap;)V
   L0
    LINENUMBER 41 L0
    ALOAD 0
    INVOKEVIRTUAL java/util/HashMap.entrySet ()Ljava/util/Set;
    INVOKEINTERFACE java/util/Set.iterator ()Ljava/util/Iterator; (itf)
    ASTORE 1
   L1
   FRAME APPEND [java/util/Iterator]
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.hasNext ()Z (itf)
    IFEQ L2
   L3
    LINENUMBER 42 L3
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; (itf)
    CHECKCAST java/util/Map$Entry
    ASTORE 2
   L4
    LINENUMBER 43 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEINTERFACE java/util/Map$Entry.getKey ()Ljava/lang/Object; (itf)
    CHECKCAST java/lang/String
    INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V
   L5
    LINENUMBER 45 L5
    GOTO L1
   L2
    LINENUMBER 46 L2
   FRAME CHOP 1
    RETURN
   L6
    LOCALVARIABLE item Ljava/util/Map$Entry; L4 L5 2
    // signature Ljava/util/Map$Entry<ljava lang="" string;ljava="" integer;="">;
    // declaration: item extends java.util.Map$Entry<java.lang.string, java.lang.integer="">
    LOCALVARIABLE it Ljava/util/Iterator; L1 L2 1
    // signature Ljava/util/Iterator<ljava util="" map$entry<ljava="" lang="" string;ljava="" integer;="">;>;
    // declaration: it extends java.util.Iterator<java.util.map$entry<java.lang.string, java.lang.integer="">>
    LOCALVARIABLE map Ljava/util/HashMap; L0 L6 0
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0xA
  // signature (Ljava/util/HashMap<ljava lang="" string;ljava="" integer;="">;)V
  // declaration: void forEach0(java.util.HashMap<java.lang.string, java.lang.integer="">)
  private static forEach0(Ljava/util/HashMap;)V
   L0
    LINENUMBER 50 L0
    ALOAD 0
    INVOKEVIRTUAL java/util/HashMap.entrySet ()Ljava/util/Set;
    INVOKEINTERFACE java/util/Set.iterator ()Ljava/util/Iterator; (itf)
    ASTORE 1
   L1
   FRAME APPEND [java/util/Iterator]
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.hasNext ()Z (itf)
    IFEQ L2
    ALOAD 1
    INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; (itf)
    CHECKCAST java/util/Map$Entry
    ASTORE 2
   L3
    LINENUMBER 51 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEINTERFACE java/util/Map$Entry.getKey ()Ljava/lang/Object; (itf)
    INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/Object;)V
   L4
    LINENUMBER 52 L4
    GOTO L1
   L2
    LINENUMBER 53 L2
   FRAME CHOP 1
    RETURN
   L5
    LOCALVARIABLE entry Ljava/util/Map$Entry; L3 L4 2
    LOCALVARIABLE map Ljava/util/HashMap; L0 L5 0
    // signature Ljava/util/HashMap<ljava lang="" string;ljava="" integer;="">;
    // declaration: map extends java.util.HashMap<java.lang.string, java.lang.integer="">
    MAXSTACK = 2
    MAXLOCALS = 3

都不用耐心观察,两个方法的字节码除了局部变量不一样其他都几乎一样,由此可以得出增强型for循环性能与迭代器一样,实际运行结果也一样,我不展示了,感兴趣的自己去copy文章开头和结尾的代码试下。

4.2、Map.forEach

先说一下为什么不把各种方法一起运行同时打印性能,这是因为CPU缓存的原因和JVM的一些优化会干扰到性能的判断,附录全部测试结果有说明

直接来看源码吧

@Override
public void forEach(BiConsumer<!--? super K, ? super V--> action) {
    Node<k,v>[] tab;
    if (action == null)
        throw new NullPointerException();
    if (size > 0 && (tab = table) != null) {
        int mc = modCount;
        for (int i = 0; i < tab.length; ++i) {
            for (Node<k,v> e = tab[i]; e != null; e = e.next)
                action.accept(e.key, e.value);
        }
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }
}

很简短的源码,就不打注释了,从源码我们不难获取到以下信息:

  • 该方法也是快速失败的,遍历期间不能删除元素
  • 需要遍历整个数组
  • BiConsumer加了@FunctionalInterface注解,用了 lambda

第三点和性能无关,这里只是提下

通过以上信息我们能确定这个性能与table数组的大小有关。

但是在实际测试的时候却发现性能比迭代器差了不少:

4.3、Stream.forEach

Stream与Map.forEach的共同点是都使用了lambda表达式。但两者的源码没有任何复用的地方。

不知道你有没有看累,先上测试结果吧:

耗时比Map.foreach还要高点。

下面讲讲Straam.foreach顺序流的源码,这个也不复杂,不过累的话先去看看总结吧。

Stream.foreach的执行者是分流器,HashMap的分流器源码就在HashMap类中,是一个静态内部类,类名叫 EntrySpliterator

下面是顺序流执行的方法

public void forEachRemaining(Consumer<!--? super Map.Entry<K,V-->> action) {
    int i, hi, mc;
    if (action == null)
        throw new NullPointerException();
    HashMap<k,v> m = map;
    Node<k,v>[] tab = m.table;
    if ((hi = fence) < 0) {
        mc = expectedModCount = m.modCount;
        hi = fence = (tab == null) ? 0 : tab.length;
    }
    else
        mc = expectedModCount;
    if (tab != null && tab.length >= hi &&
        (i = index) >= 0 && (i < (index = hi) || current != null)) {
        Node<k,v> p = current;
        current = null;
        do {
            if (p == null)
                p = tab[i++];
            else {
                action.accept(p);
                p = p.next;
            }
        } while (p != null || i < hi);
        if (m.modCount != mc)
            throw new ConcurrentModificationException();
    }
}

从以上源码中我们也可以轻易得出遍历需要顺序扫描所有数组

五、总结

至此,Map的四种遍历方法都测试完了,我们可以简单得出两个结论

  • Map的遍历性能与内部table数组大小有关,也就是说与常用参数 initial capacity 有关,不管哪种遍历方式都是的
  • 性能(由高到低):迭代器 == 增强型For循环 > Map.forEach > Stream.foreach

这里就不说什么多少倍多少倍的性能差距了,抛开数据集大小都是扯淡,当我们不指定initial capacity的时候,四种遍历方法耗时都是3ms,这3ms还是输入输出流的耗时,实际遍历耗时都是0,所以数据集不大的时候用哪种都无所谓,就像不加输入输出流耗时不到1ms一样,很多时候性能消耗是在遍历中的业务操作,这篇文章不是为了让你去优化代码把foreach改成迭代器的,在大多数场景下并不需要关注迭代本身的性能,Stream与Lambda带来的可读性提升更加重要。

所以此文的目的就当是知识拓展吧,除了以上说到的遍历性能问题,你还应该从中能获取到的知识点有:

  • HashMap的数组是存储在table数组里的
  • table数组是resize方法初始化的,new Map不会初始化数组
  • Map遍历是table数组从下标0递增排序的,所以他是无序的
  • keySet().iterator,values.iterator, entrySet.iterator 来说没有本质区别,用的都是同一个迭代器
  • 各种遍历方法里,只有迭代器可以remove,虽然增强型for循环底层也是迭代器,但这个语法糖隐藏了 remove 方法
  • 每次调用迭代器方法都会new 一个迭代器,但是只有一个可以修改
  • Map.forEach与Stream.forEach看上去一样,实际实现是不一样的

附:四种遍历源码

private static void forEach(HashMap map) {
    for (Iterator<map.entry<string, integer="">> it = map.entrySet().iterator(); it.hasNext();){
        Map.Entry<string, integer=""> item = it.next();
        // System.out.print(item.getKey());
        // do something
    }
}

private static void forEach0(HashMap<string, integer=""> map) {
    for (Map.Entry entry : map.entrySet()) {
        System.out.print(entry.getKey());
    }
}

private static void forEach1(HashMap<string, integer=""> map) {
    map.forEach((key, value) -> {
        System.out.print(key);
    });

}

private static void forEach2(HashMap<string, integer=""> map) {
    map.entrySet().stream().forEach(e -> {
        System.out.print(e.getKey());
    });

}

附:完整测试类与测试结果+一个奇怪的问题

public class MapForEachTest {

    public static void main(String[] args) {
        HashMap<string, integer=""> map0 = new HashMap<string, integer="">(100000);
        HashMap<string, integer=""> map1 = new HashMap<string, integer="">();
        initData(map0);
        initData(map1);

        testIterator(map0);
        testIterator(map1);
        testFor(map0);
        testFor(map1);
        testMapForeach(map0);
        testMapForeach(map1);
        testMapStreamForeach(map0);
        testMapStreamForeach(map1);

    }

    private static void testIterator(HashMap map) {

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            forEach(map);
        }
        long end = System.currentTimeMillis();
        System.out.println("");
        System.out.println("HashMap Size: " + map.size() +  " 迭代器 耗时: " + (end - start) + " ms");
    }

    private static void testFor(HashMap map) {

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            forEach0(map);
        }
        long end = System.currentTimeMillis();
        System.out.println("");
        System.out.println("HashMap Size: " + map.size() +  " 增强型For 耗时: " + (end - start) + " ms");
    }

    private static void testMapForeach(HashMap map) {

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            forEach1(map);
        }
        long end = System.currentTimeMillis();
        System.out.println("");
        System.out.println("HashMap Size: " + map.size() +  " MapForeach 耗时: " + (end - start) + " ms");
    }

    private static void testMapStreamForeach(HashMap map) {

        long start = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            forEach2(map);
        }
        long end = System.currentTimeMillis();
        System.out.println("");
        System.out.println("HashMap Size: " + map.size() +  " MapStreamForeach 耗时: " + (end - start) + " ms");
    }

    private static void forEach(HashMap map) {
        for (Iterator<map.entry<string, integer="">> it = map.entrySet().iterator(); it.hasNext();){
            Map.Entry<string, integer=""> item = it.next();
            System.out.print(item.getKey());
            // do something
        }
    }

    private static void forEach0(HashMap<string, integer=""> map) {
        for (Map.Entry entry : map.entrySet()) {
            System.out.print(entry.getKey());
        }
    }

    private static void forEach1(HashMap<string, integer=""> map) {
        map.forEach((key, value) -> {
            System.out.print(key);
        });

    }

    private static void forEach2(HashMap<string, integer=""> map) {
        map.entrySet().stream().forEach(e -> {
            System.out.print(e.getKey());
        });

    }

    private static void initData(HashMap map) {
        map.put("a", 0);
        map.put("b", 1);
        map.put("c", 2);
        map.put("d", 3);
        map.put("e", 4);
        map.put("f", 5);
    }

}

测试结果:

如果你认真看了上面的文章的话,会发现测试结果有个不对劲的地方:

MapStreamForeach的耗时似乎变少了

我可以告诉你这不是数据的原因,从我的测试测试结果来看,直接原因是因为先执行了 Map.foreach,如果你把 MapForeach 和 MapStreamForeach 调换一下执行顺序,你会发现后执行的那个耗时更少。

以上就是分析Java中Map的遍历性能问题的详细内容,更多关于Java Map 遍历性能的资料请关注我们其它相关文章!

(0)

相关推荐

  • 剖析Java中HashMap数据结构的源码及其性能优化

    存储结构 首先,HashMap是基于哈希表存储的.它内部有一个数组,当元素要存储的时候,先计算其key的哈希值,根据哈希值找到元素在数组中对应的下标.如果这个位置没有元素,就直接把当前元素放进去,如果有元素了(这里记为A),就把当前元素链接到元素A的前面,然后把当前元素放入数组中.所以在Hashmap中,数组其实保存的是链表的首节点.下面是百度百科的一张图: 如上图,每个元素是一个Entry对象,在其中保存了元素的key和value,还有一个指针可用于指向下一个对象.所有哈希值相同的key(也就

  • java集合类HashMap源码解析

    Map集合 Map集合存储的是键值对 Map集合的实现类: HashTable.LinkedHashMap.HashMap.TreeMap HashMap 基础了解: 1.键不可以重复,值可以重复: 2.底层使用哈希表实现: 3.线程不安全: 4.允许key为null,但只允许有一条记录为null,value也可以为null,允许多条记录为null: 源码分析 (一)以JDK1.7为例 1.存储结构 数据结构:数组+链表 首先hashmap内部有一个Entry类型的数组table: 通过Entr

  • JAVA遍历Map集合的几种方法汇总

    今天学习了Map集合的几种方法,尤其是遍历Map集合感觉尤为重要,所以发出来供大家学习和自己复习以用. 众所周知Map集合里存储元素是以键值对的方式存储元素,一个是Key一个是value. 开发过程中推荐使用第四种方法,本文章注释很清晰,仔细看完相信会对你有所帮助的! 方法一: 首先去通过获取迭代器,通过迭代器去遍历集合,获取key然后通过key去获取value,然后就完成了Map集合元素的遍历 public static void main(String[] args) { //使用泛型去创建

  • Java中Map的遍历方法及性能测试

    1. 阐述 对于Java中Map的遍历方式,很多文章都推荐使用entrySet,认为其比keySet的效率高很多.理由是:entrySet方法一次拿到所有key和value的集合:而keySet拿到的只是key的集合,针对每个key,都要去Map中额外查找一次value,从而降低了总体效率.那么实际情况如何呢? 为了解遍历性能的真实差距,包括在遍历key+value.遍历key.遍历value等不同场景下的差异,我试着进行了一些对比测试. 2. 对比测试 一开始只进行了简单的测试,但结果却表明k

  • 详解 Java HashMap 实现原理

    HashMap 是 Java 中最常见数据结构之一,它能够在 O(1) 时间复杂度存储键值对和根据键值读取值操作.本文将分析其内部实现原理(基于 jdk1.8.0_231). 数据结构 HashMap 是基于哈希值的一种映射,所谓映射,即可以根据 key 获取到相应的 value.例如:数组是一种的映射,根据下标能够取到值.不过相对于数组,HashMap 占用的存储空间更小,复杂度却同样为 O(1). HashMap 内部定义了一排"桶",用一个叫 table 的 Node 数组表示:

  • Java HashMap原理及实例解析

    这篇文章主要介绍了Java HashMap原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 示例 1 : HashMap的键值对 HashMap储存数据的方式是-- 键值对 package collection; import java.util.HashMap; public class TestCollection { public static void main(String[] args) { HashMap<String

  • Java HashMap三种循环遍历方式及其性能对比实例分析

    本文实例讲述了Java HashMap三种循环遍历方式及其性能对比.分享给大家供大家参考,具体如下: HashMap的三种遍历方式 (1)for each map.entrySet() Map<String, String> map = new HashMap<String, String>(); for (Entry<String, String> entry : map.entrySet()) { entry.getKey(); entry.getValue();

  • Java中使用HashMap改进查找性能的步骤

    Java中,HashMap,其实就是键值对.一个Key,对应一个值:写数据时,指定Key写对应值:读取时凭Key找到相应值.感觉就跟Redis差不多. // 创建 HashMap 对象 Sites HashMap<Integer, String> Sites = new HashMap<Integer, String>(); // 添加键值对 Sites.put(1, "Google"); Sites.put(2, "Runoob"); Si

  • 深入理解Java中的HashMap

    一.HashMap的结构图示 ​本文主要说的是jdk1.8版本中的实现.而1.8中HashMap是数组+链表+红黑树实现的,大概如下图所示.后面还是主要介绍Hash Map中主要的一些成员以及方法原理. ​那么上述图示中的结点Node具体类型是什么,源码如下.Node是HashMap的内部类,实现了Map.Entery接口,主要就是存放我们put方法所添加的元素.其中的next就表示这可以构成一个单向链表,这主要是通过链地址法解决发生hash冲突问题.而当桶中的元素个数超过阈值的时候就换转为红黑

  • 分析Java中Map的遍历性能问题

    一.引言 我们知道java HashMap的扩容是有成本的,为了减少扩容的次数和成本,可以给HashMap设置初始容量大小,如下所示: HashMap<string, integer=""> map0 = new HashMap<string, integer="">(100000); 但是在实际使用的过程中,发现性能不但没有提升,反而显著下降了!代码里对HashMap的操作也只有遍历了,看来是遍历出了问题,于是做了一番测试,得到如下结果:

  • Java中Map循环遍历的五种方法实现

    目录 1.创建一个Enum 2.开始遍历 方法一 方法二 方法三 方法四 方法五 因为Map比较常用,所以今天来总结下Map取值比较常用的几种遍历方法. 1.创建一个Enum public enum FactoryStatus {     BAD(0,"ou"),     GOOD(1,"yeah");     private int status;     private String description;     FactoryStatus(int stat

  • Java中map遍历方式的选择问题详解

    1. 阐述 对于Java中Map的遍历方式,很多文章都推荐使用entrySet,认为其比keySet的效率高很多.理由是:entrySet方法一次拿到所有key和value的集合:而keySet拿到的只是key的集合,针对每个key,都要去Map中额外查找一次value,从而降低了总体效率.那么实际情况如何呢? 为了解遍历性能的真实差距,包括在遍历key+value.遍历key.遍历value等不同场景下的差异,我试着进行了一些对比测试. 2. 对比测试 一开始只进行了简单的测试,但结果却表明k

  • 关于java中Map的九大问题分析

    通常来说,Map是一个由键值对组成的数据结构,且在集合中每个键是唯一的.下面就以K和V来代表键和值,来说明一下java中关于Map的九大问题. 0.将Map转换为List类型 在java中Map接口提供了三种集合获取方式:Key set,,value set, and key-value set..它们都可以通过构造方法或者addAll()方法来转换为List类型.下面代码就说明了如何从Map中构造ArrayList: // key list List keyList = new ArrayLi

  • Java实现Map集合遍历的四种常见方式与用法分析

    本文实例讲述了Java实现Map集合遍历的四种常见方式与用法.分享给大家供大家参考,具体如下: ~Map集合是键值对形式存储值的,所以遍历Map集合无非就是获取键和值,根据实际需求,进行获取键和值 1. 无非就是通过map.keySet()获取到值,然后根据键获取到值 for(String s:map.keySet()){ System.out.println("key : "+s+" value : "+map.get(s)); } 2. 通过Map.Entry(

  • Java中Map的entrySet()使用说明

    由于Map中存放的元素均为键值对,故每一个键值对必然存在一个映射关系. Map中采用Entry内部类来表示一个映射项,映射项包含Key和Value Map.Entry里面包含getKey()和getValue()方法 Set<Entry<T,V>> entrySet() 该方法返回值就是这个map中各个键值对映射关系的集合. 可使用它对map进行遍历. Iterator<Map.Entry<Integer, Integer>> it=map.entrySet

  • Java中Map接口使用以及有关集合的面试知识点汇总

    目录 Map接口 存储特点 常用实现类 创建方法 常用方法 遍历方法 不同实现类的使用 集合面试知识点补充 结语 Map接口 存储特点 以键(key)值(value)对的形式存储 键无序.无下标.元素不可重复 值无序.无下标.元素可以重复 常用实现类 HashMapJDK1.2 底层哈希表实现 线程不安全,效率高 LinkedHashMapJDK1.2 是HashMap的子类,底层哈希表实现 线程不安全,效率高 TreeMapJDK1.2 是SortedMap的实现类,底层红黑树实现 线程不安全

  • Java中Map的排序问题详解

    Map的种类 在Java中,Map的主要作用是存储键值对.由于是根据键得到值,所以不允许键重复.它主要有如下几个类别: HashMap: 最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的.HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致.如果需要同步,可以用Collections的sy

  • JS自定义对象实现Java中Map对象功能的方法

    本文实例讲述了JS自定义对象实现Java中Map对象功能的方法.分享给大家供大家参考.具体分析如下: Java中有集合,Map等对象存储工具类,这些对象使用简易,但是在JavaScript中,你只能使用Array对象. 这里我创建一个自定义对象,这个对象内包含一个数组来存储数据,数据对象是一个Key,可以实际存储的内容!   这里Key,你要使用String类型,和Java一样,你可以进行一些增加,删除,修改,获得的操作. 使用很简单,我先把工具类给大家看下: 复制代码 代码如下: /**  *

随机推荐