点击上方 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
-
List允许插入重复元素;
-
List允许插入多个null元素;
-
List作为有序集合,保证了元素按照插入的顺序进行排列;
-
List提供ListIterator迭代器,可以提供双向访问的功能;
-
List常用的实现类有:可随意访问元素的ArrayList、应用于增删频繁的LinkedList、利用synchronized关键字实现线程安全的Vector等。
Set
-
Set不包含重复元素;
-
Set只允许一个null元素的存在;
-
Set接口较为流行的实现类有:基于HashMap实现的HashSet、实现SortedSet接口且能更具compare()和compareTo()的定义进行排序的TreeSet等。
Map
-
存储结构是键值对,一个键对应一个值;
-
不允许包含重复的键,Map可能会持有相同的值对象,但键对象必须是唯一的;
-
在Map中可以有多个null值,但最多只能有一个null键;
-
Map不是Collection的子接口或实现类**,Map是跟Collection“同级”的接口**;
-
Map中比较流行的实现类是采用散列函数的HashMap、以及利用红黑树实现排序的TreeMap等。
2. List、Set和Map的使用场景
List
-
需要使用索引来访问容器的元素,ArrayList可以提供更快速的访问(底层是数组实现);
-
需要经常增删元素,LinkedList则会是最佳的选择(底层是链表实现);
-
数据量不大,并且有线程安全(synchronized关键字)的要求,可以选择Vector;
-
有线程安全(ReentrantLock实现)和性能的要求,读多写少的情况,CopyOnWriteArrayList会是更好的选择。
Set
-
需要快速查询元素,可以使用HashSet(采用散列函数);
-
如果有排序元素的需要,可以使用TreeSet(采用红黑树的树结构排序元素);
-
急需要加快查询速度,还需要按插入顺序来存储数据,LinkedHashSet是最好的选择(采用散列函数的同时,还使用链表维护元素的次序)。
Map
-
需要快速查询键值元素,可以使用HashMap(采用散列函数);
-
如果需要将键进行排序,可以使用TreeMap(按照红黑树对键进行排序);
-
在存储数据少,不允许有null值,又有线程安全(synchronized关键字)的要求,可以选择Hashtable(父类是Dictionary);
-
如果需要线程安全(Node+CAS+Synchronized),且有数据量和性能要求,ConcurrentHashMap是最佳的选择。
3. 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;
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
.......
}
Set<Integer> set = new HashSet<>(list);
List<Integer> list = new ArrayList<>(set);
4. Set是怎么保证元素不重复?
private transient HashMap<E,Object> map;
public boolean add(E e) {
//如果return true,则表示不包含此元素return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
//倒数第二个参数为false,表示允许旧值替换
//最后一个参数为true,表示HashMap不处于创建模式
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> 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<K,V> 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<K,V>)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;
}
//链表节点中的元素<key, value>与put操作控制的元素<key, value>相同时,不做重复操作,直接跳出程序
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;
}
5. 如何在遍历的同时删除ArrayList中的元素?
从后向前遍历元素
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(3);
list.add(4);
for(int i=0; i<list.size()-1; i++){
if(list.get(i) == 3){
list.remove(new Integer(3));
}
}
System.out.println(list);
}
[1, 2, 3, 4]
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(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)
...
private class Itr implements Iterator<E> {
......
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
Iterator<Integer> i = list.iterator();
while(i.hasNext()){
Integer integer = i.next();
if(integer == 3){
i.remove();
}
}
如果看到这里,说明你喜欢这篇文章,请 转发、点赞。同时 标星(置顶)本公众号可以第一时间接受到博文推送。
推荐阅读
1. 彻底理解 SpringIOC、DI
2. 图解 Spring 循环依赖
3. GitHub 重大更新:在线开发上线
4. Spring Boot 整合 Netty(附源码)
《Java技术栈学习手册》
,覆盖了Java技术、面试题精选、Spring全家桶、Nginx、SSM、微服务、数据库、数据结构、架构等等。
获取方式:点“ 在看,关注公众号 Java后端 并回复 777 领取,更多内容陆续奉上。
喜欢文章,点个在看
本文分享自微信公众号 - Java后端(web_resource)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
相关文章
暂无评论...