首先我们要对集合类结构有一个大体的认识,所有集合都继承于迭代器,分为单列集合和映射集合,单列集合分为有序可重复和有序不可重复,大概结构如下图所示
集合类 | 介绍 |
---|---|
Vector | Vector 类实现了一个动态数组 |
ArrayList | 一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素 |
LinkedList | 一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。链表可分为单向链表和双向链表。 |
HashSet | HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。HashSet 允许有 null 值。HashSet 是无序的,即不会记录插入的顺序。HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。 |
HashMap | HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。HashMap 是无序的,即不会记录插入的顺序。 |
该部分内容摘自java成神之路
List特点:元素有放入顺序,元素可重复 。
有顺序,即先放入的元素排在前面。
Set特点:元素无放入顺序,元素不可重复。
无顺序,即先放入的元素不一定排在前面。 不可重复,即相同元素在set中只会保留一份。所以,有些场景下,set可以用来去重。 不过需要注意的是,set在元素插入时是要有一定的方法来判断元素是否重复的。这个方法很重要,决定了set中可以保存哪些元素。
ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。
Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.
HashMap和HashTable有何不同?
线程安全: HashTable 中的方法是同步的,而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用HashTable,但是要使用HashMap的话就要自己增加同步处理了。
继承关系: HashTable是基于陈旧的Dictionary类继承来的。 HashMap继承的抽象类AbstractMap实现了Map接口。
允不允许null值: HashTable中,key和value都不允许出现null值,否则会抛出NullPointerException异常。 HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
默认初始容量和扩容机制: HashTable中的hash数组初始大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。原因参考全网把Map中的hash()分析的最透彻的文章,别无二家。-HollisChuang’s Blog
哈希值的使用不同 : HashTable直接使用对象的hashCode。 HashMap重新计算hash值。
遍历方式的内部实现上不同 : Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。 HashMap 实现 Iterator,支持fast-fail,Hashtable的 Iterator 遍历支持fast-fail,用 Enumeration 不支持 fast-fail
HashMap 和 ConcurrentHashMap 的区别?
ConcurrentHashMap和HashMap的实现方式不一样,虽然都是使用桶数组实现的,但是还是有区别,ConcurrentHashMap对桶数组进行了分段,而HashMap并没有。
ConcurrentHashMap在每一个分段上都用锁进行了保护。HashMap没有锁机制。所以,前者线程安全的,后者不是线程安全的。
PS:以上区别基于jdk1.8以前的版本。
CopyOnWriteArrayList:CopyOnWriteArrayList采用的是一种写时复制的思想,也就是读写分离。
CopyOnWriteArrayList容器即写时复制的容器,往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行copy,复制出一个新的容器Object[] newElements,然后新的容器newElements中添加元素,添加完元素之后,在将原容器的引用指向新的容器setArray(newElements);。这样做的好处是可以对CopyOnWriteArrayList容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWriteArrayList容器也是有一种读写分离的思想,读和写不同的容器。
CopyOnWriteArraySet:由 CopyOnWriteArrayList实现
ConcurrentHashMap:
java1.8以前,它由Segment数组结构和HashEntry数组结构组成。Segment数组在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键-值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构;一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素;每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
java1.8以后,乐观锁+Sysnchronized,多线程并发向同一个散列桶添加元素时若散列桶为空,则触发乐观锁机制,线程获取散列桶中的版本号,在添加元素之前判断线程中的版本号与桶中的版本号是否一致,添加成功不一致,自旋若散列桶不为空,,则使用synchroinized.先访问到的线程给头结点解锁,形成链表或红黑树,JDK1.8中ConcrruentHashMap在保证线程安全的同时允许最大程序的多线程并发执行
1、ArrayList
import java.util.ArrayList;
import java.util.List;public class ArrayListInThread {public static void main(String[] args) {List list = new ArrayList<>();for (int i = 0; i < 10; i++) {new Thread(() -> {list.add("aaa");System.out.println(list);}).start();}}
}
查看三次结果
当线程过多时
2、HashMap
import java.util.HashMap;
import java.util.Map;
import java.util.Random;public class HashMapInThread {public static void main(String[] args) {Map map = new HashMap<>();for (int i = 0; i < 5; i++) {new Thread(() -> {Random random = new Random();int anInt = random.nextInt();map.put(anInt,anInt);System.out.println(map);}).start();}}
}
查看三次结果
3、HashSet
import java.util.HashSet;
import java.util.Random;
import java.util.Set;public class HashSetInThread {public static void main(String[] args) {Set set = new HashSet<>();for (int i = 0; i < 5; i++) {new Thread(() -> {Random random = new Random();int anInt = random.nextInt();set.add(anInt);System.out.println(set);}).start();}}
}
查看三次结果
主要有三种方式
/*** 使用迭代器删除元素* @param list*/public static void removeElementByIterator(List list, int number){Iterator iterator = list.iterator();while (iterator.hasNext()){if (iterator.next() == number){iterator.remove();}}}
/*** 倒序删除* @param list*/public static void removeElementByReverse(List list, int number){for (int i = list.size() - 1; i > 0; i--) {Integer element = list.get(i);if (element == number) {list.remove(element);}}}
/*** lambda表达式删除元素* @param list*/public static void removeElementByLambda(List list, int number){list.removeIf( num -> num == number );}