欢迎访问 生活随笔!

凯发k8官方网

当前位置: 凯发k8官方网 > 编程语言 > java >内容正文

java

java map按照value排序-凯发k8官方网

发布时间:2024/10/14 java 23 豆豆
凯发k8官方网 收集整理的这篇文章主要介绍了 java map按照value排序_基础:java集合需要注意的 5 个问题 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

点击上方 java后端,选择 设为星标

优质文章,及时送达


java集合中的list、set和map作为java集合食物链的顶级,可谓是各有千秋。本文将对于list、set和map之间的联系与区别进行介绍,以及这三者衍生出来的问题进行介绍(若无特地说明,jdk版本皆为1.8):

  • list、set和map的联系和区别是什么?
  • list、set和map的使用场景有哪些?
  • list与set之间的怎么转换?
  • set是怎么保证元素不重复?
  • 如何在遍历的同时删除arraylist中的元素?

1. list、set和map的联系和区别

list和set是collection的实现类,而map与collection是属于“同级“。

list

list的特性:
  • list允许插入重复元素
  • list允许插入多个null元素
  • list作为有序集合,保证了元素按照插入的顺序进行排列;
  • list提供listiterator迭代器,可以提供双向访问的功能;
  • list常用的实现类有:可随意访问元素的arraylist、应用于增删频繁的linkedlist、利用synchronized关键字实现线程安全的vector等。

set

set的特性:
  • set不包含重复元素
  • set只允许一个null元素的存在;
  • set接口较为流行的实现类有:基于hashmap实现的hashset、实现sortedset接口且能更具compare()和compareto()的定义进行排序的treeset等。

map

map的特性:
  • 存储结构是键值对,一个键对应一个值;
  • 不允许包含重复的键,map可能会持有相同的值对象,但键对象必须是唯一的;
  • 在map中可以有多个null值,但最多只能有一个null键
  • map不是collection的子接口或实现类**,map是跟collection“同级”的接口**;
  • map中比较流行的实现类是采用散列函数的hashmap、以及利用红黑树实现排序的treemap等。

2. list、set和map的使用场景

上文我们介绍完了list、set和map之间的联系和区别,接下来我们来看下这三者在使用场景上的差异。

list

如果经常使用索引来访问元素,或者是需要能够按照插入顺序进行存储,list会是不错的选择。
  • 需要使用索引来访问容器的元素,arraylist可以提供更快速的访问(底层是数组实现);
  • 需要经常增删元素,linkedlist则会是最佳的选择(底层是链表实现);
  • 数据量不大,并且有线程安全(synchronized关键字)的要求,可以选择vector
  • 有线程安全(reentrantlock实现)和性能的要求,读多写少的情况,copyonwritearraylist会是更好的选择。

set

想要保证插入元素的唯一性,可以选择set的实现类。
  • 需要快速查询元素,可以使用hashset(采用散列函数);
  • 如果有排序元素的需要,可以使用treeset(采用红黑树的树结构排序元素);
  • 急需要加快查询速度,还需要按插入顺序来存储数据,linkedhashset是最好的选择(采用散列函数的同时,还使用链表维护元素的次序)。

map

如果需要按键值对的形式进行数据存储,那么map是正确的选择。
  • 需要快速查询键值元素,可以使用hashmap(采用散列函数);
  • 如果需要将键进行排序,可以使用treemap(按照红黑树对键进行排序);
  • 在存储数据少,不允许有null值,又有线程安全(synchronized关键字)的要求,可以选择hashtable(父类是dictionary);
  • 如果需要线程安全(node cas synchronized),且有数据量和性能要求,concurrenthashmap是最佳的选择。

3. list与set之间的转换

因为list和set都实现了collection接口中的addall(collection extends e> c)方法,而且list和set也提供了collection extends e> c为参数的构造函数,所以可以采用构造函数的形式,完成list和set的互相转换。addall(collection extends e> c)方法public boolean addall(collection extends e> c) {boolean modified = false;for (e e : c)if (add(e))
                modified = true;return modified;
    }以set接口的实现类hashset为例,其提供了collection extends e> c为参数的构造函数。public hashset(collection extends e> c) {map = new hashmap<>(math.max((int) (c.size()/.75f) 1, 16));
        addall(c);
    }以list接口的实现类arraylist为例,也可以看到它提供了collection extends e> c为参数的构造函数。public arraylist(collection extends e> c) {
        elementdata = c.toarray();
        .......
    }所以我们可以得到set与list之间的转换方式:setset = new hashset<>(list);
listlist = new arraylist<>(set);

4. set是怎么保证元素不重复?

我们以set接口最流行的实现类hashset为例,对set保证元素不重复的原因进行介绍。private transient hashmap map;public boolean add(e e) {
    //如果return true,则表示不包含此元素  return map.put(e, present)==null;
}从上可知,hashset是依赖hashmap得以实现,其中添加的元素作为hashmap的键来存储。所以接下来就是在介绍“hashmap是怎么保证不允许有相同的键存在”了。public v put(k key, v value) {//倒数第二个参数为false,表示允许旧值替换//最后一个参数为true,表示hashmap不处于创建模式return putval(hash(key), key, value, false, true);
}在这里,我们可以看到在进行putval()方法之前,会将key代入hash()方法中进行散列。final v putval(int hash, k key, v value, boolean onlyifabsent,
                   boolean evict) {
        node[] tab; node p; int n, i;//如果哈希表为空,调用resize()方法创建一个哈希表,并用n记录哈希表的长度if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;//如果指定参数hash(key的hashcode()值)在表中没有对应的桶,即没有碰撞//(n-1)&hash计算key将被放置的槽位//(n-1)&hash本质上是hash%n,只是位运算更快if ((p = tab[i = (n - 1) & hash]) == null)//如果没有碰撞,直接将键值对插入到map中即可
            tab[i] = newnode(hash, key, value, null);else { //如果桶中已经存在了元素
            node e; k k;//比较桶中的第一个元素(数组中的结点)的hash值、key是否相等if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))//如果相等,则将第一个元素p用e来记录
                e = p;else if (p instanceof treenode) //当前桶中无该键值对,且桶的结构为红黑树,则按照红黑树结构的规则插入元素
                e = ((treenode)p).puttreeval(this, tab, hash, key, value);else { //如果桶中无该键值对,且桶的结构为链表,则按照链表结构将元素插入到尾部for (int bincount = 0; ; bincount) {if ((e = p.next) == null) { //遍历到链表尾部
                        p.next = newnode(hash, key, value, null);//检查链表长度是否达到阈值,达到则将该槽位的节点组织形式,转化为红黑树if (bincount >= treeify_threshold - 1) // -1 for 1st
                            treeifybin(tab, hash);break;
                    }//链表节点中的元素与put操作控制的元素相同时,不做重复操作,直接跳出程序if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))break;
                    p = e;
                }
            }// 如果put操作控制的元素的key和hashcode,与已经插入的元素相等时,执行以下操作if (e != null) { // existing mapping for key// oldvalue记录e的value
                v oldvalue = e.value;// onlyifabsent为false,或旧值为null时,允许替换旧值,否则无需替换if (!onlyifabsent || oldvalue == null)
                    e.value = value;//访问后回调
                afternodeaccess(e);//返回旧值return oldvalue;
            }
        }// 更新结构化修改信息
         modcount;// 键值对数目如果超过阈值时,执行resize()方法if ( size > threshold)
            resize();// 插入后回调
        afternodeinsertion(evict);return null;
    }从以上源码中我们可以看出,将一个键值对放入hashmap时,首先会根据key的hashcode()返回值和hashmap的长度决定该元素的存储位置,如果两个key的hash值相同,那么它们的存储位置相同。如果这两个key的equals比较返回true,那么新添加的元素newvalue就会覆盖原来的元素oldvalue,key不会被覆盖。当hashset中的add()方法里,map.put(e, present) == null为false时,hashset添加元素失败。所以如果向hashset中添加一个已经存在的元素,新添加的元素不会覆盖原来已有的元素。

5. 如何在遍历的同时删除arraylist中的元素?

平时我们可能会觉得遍历arraylist并删除其中元素是一件很简单的事情,但其实这个操作很容易出bug,接下来我们一起看下怎么样绕过这些坑。

从后向前遍历元素

我们先从前向后遍历的同时,进行删除元素:public static void main(string[] args){
        listlist = new arraylist<>();list.add(1);list.add(2);list.add(3);list.add(3);list.add(4);for(int i=0; i            }
        }
        system.out.println(list);
    }运行结果为:[1, 2, 3, 4]造成这个现象的原因,在【java集合】arraylist的使用及原理中笔者稍有提及。在于arraylist执行remove()操作时,将既定元素删除时还把该元素后的所有元素向前移动一位。这就导致了在遍历[1,2,3,3,4]时,删除前一个元素“3”后,将其后元素向前移动一位,因下标[2]已经被遍历过了,所以就遗漏了第二个“3”。对于这个问题,我们只需要换个遍历的角度即可——从后往前遍历:for(int i=list.size()-1; i>=0; i--){if(list.get(i) == 3){list.remove(new integer(3));
    }
}运行结果为:[1, 2, 4]从后往前遍历,在删除某一元素之后,也不用担心在遍历过程中会遗漏元素。

iterator.remove()

除了上述遍历方法,还有一种遍历方式是我们经常使用的——for-each遍历:for(integer i : list){if(i == 3){list.remove(new integer(3));
    }
}运行结果:exception in thread "main" java.util.concurrentmodificationexception
  at java.util.arraylist$itr.checkforcomodification(arraylist.java:901)
  at java.util.arraylist$itr.next(arraylist.java:851)
  ...我们知道,for-each的遍历方式其实是iterator、hashnext()、next()的复合简化版。当点开arraylist.checkforcomodification()方法可以看到:private class itr implements iterator {
    ......final void checkforcomodification() {if (modcount != expectedmodcount)throw new concurrentmodificationexception();
  }
}这里的modcount是arraylist的,而expectedmodcount是itr的,所以其实出错的地方在于,运行arraylist.remove()方法时改变了modcount,这就打破了原本modcount == expectedmodcount之间和平友好的关系,导致报出并发修改异常。所以在使用迭代器迭代时(显示或for-each的隐式)不要使用arraylist.remove(),改为使用iterator.remove()即可:iterator i = list.iterator();while(i.hasnext()){
integer integer = i.next();if(integer == 3){
    i.remove();
    }
}参考资料:list、set、map的区别https://www.cnblogs.com/iveshe/p/6108933.htmlarraylist循环遍历并删除元素的常见陷阱https://www.cnblogs.com/huangjinyong/p/9455163.htmljava中set集合是如何实现添加元素保证不重复的?https://www.cnblogs.com/wupeixuan/p/8858816.html最后
-end-

如果看到这里,说明你喜欢这篇文章,请 转发、点赞。同时 标星(置顶)本公众号可以第一时间接受到博文推送。

1. 彻底理解 springioc、di

2. 图解 spring 循环依赖

3. github 重大更新:在线开发上线

4. spring boot 整合 netty(附源码)

最近整理一份资料《java技术栈学习手册》,覆盖了java技术、面试题精选、spring全家桶、nginx、ssm、微服务、数据库、数据结构、架构等等。

获取方式:点“ 在看,关注公众号 java后端 并回复 777 领取,更多内容陆续奉上。

喜欢文章,点个在看 

总结

以上是凯发k8官方网为你收集整理的java map按照value排序_基础:java集合需要注意的 5 个问题的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得凯发k8官方网网站内容还不错,欢迎将凯发k8官方网推荐给好友。

  • 上一篇:
  • 下一篇:
网站地图