java集合学习
介绍:
java中的集合层次结构分为单列集合(Collection)和双列集合(Map),(其实就是一个节点能存储几个数据的区别,单列集合,一个节点存一个;双列集合,一个节点存两个数据,键和值)
Collection(单列集合)
collection是一个顶级的父类接口,JDK中没有提供实现类,提供了基本的管理元素的抽象方法,Collection有两个子接口,分别是List和set接口
List接口
-
List集合继承了Collection接口,所以继承了Collection的所有方法
-
List是一个有序的,有索引,元素可以重复的集合接口。
List实现类有ArrayList,LinkedList,Vector
ArrayList
特点:底层是数组实现,查询快,增删慢,线程不同步且不安全,效率高(查询)。
理解:底层是由数组实现的,所以有下标(索引),所以查询速度比较快,因为是数组,所以在插入和删除元素的时候,要移动数组中的其他元素,而且必要时还要进行扩容操作,这就造成了ArrayList在执行添加和删除元素的效率不高。
- 继承自AbstractList,同时实现了三个接口,分别是RandomAccess、Cloneable、Serializable三个接口,所以ArrayList支持快速访问,复制,序列化。
- 内部数组默认的初始容量是10,如果不够用就会以1.5倍的容量增长。
- 允许null值的存在,在使用的时候,很大几率引起NullPointerException(空指针异常),谨慎使用
LinkedList
特点:底层是双向链表实现,查询慢,增删快,线程不安全,效率高(增删),有序(输出顺序和输入顺序一致)。
理解:LinkedList底层是链表实现,所以在执行查询操作的时候效率较低,因为链表得从头元素一个一个的遍历,直到找到目标元素为止。但链表在执行元素的添加和删除操作的时候,效率比较高,只需要将指针所指向的地址变化一下就行。
- LinkedList底层是双向链表实现,所以在首尾查询的速度还可以,但中间的就比较慢了。
- 因为是链表,所以没有固定容量,不需要扩容
- 每个链表的节点需要存储前后节点的地址值,所以相对于ArrayList的单个节点会多占用内存资源。
- 允许null值的存在。
Vector
特点:底层是数组实现,查询快,增删慢,线程安全,效率低
Vector是一个动态数组,默认初始容量为10,默认情况下,Vector在每次扩容时,容量都会翻倍
Vector支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多个线程同时执行操作而引起数据的不一致性,但实现同步会消耗一部分资源,因此,访问速度自然而然的比 ArrayList 慢.
Set接口
- Set集合继承了Collection接口,并继承了所有父类的方法
- Set是一个无序,元素不能重复的集合(元素唯一)
- Set接口的实现类有HashSet,TreeSet,LinkedHashSet
HashSet
特点:线程不安全,元素无序,元素不允许重复,允许null值
底层是有哈希表实现,其实底层是一个HashMap实现,元素不允许重复,比较元素一不一样是先用**hashCode()判断值是否相同,值不同就直接存入集合,值相同就再用equals()**方法来判断是否相同,如果值相同了,就不存入集合,值不同就存入集合,这样做保证了元素的唯一性。
TreeSet
特点:treeSet是有序的Set集合,元素唯一不允许重复,不允许有null值,线程不同步不安全。
底层是红黑树结构(一种自平衡的二叉树结构)实现的,而且底层就是个TreeMap实现的。
可以对set集合中的元素进行排序,排序的方式有两种:自然排序(升序对元素)和构造方法指定的Comparator(比较器)进行排序,使用哪种方式取决于构造方法。
//这种无参构造方法使用自然排序的方式
public TreeSet() {
this(new TreeMap<E,Object>());
}
//指定比较器了
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//指定了比较器
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
//创建的treeSet包含set
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
//总结:
//默认JVM提供的TreeSet构造方法使用的是自然比较器来实现的排序
//可以使用自己指定的比较器,进行排序
iterator迭代器返回的是fail-fast迭代器
默认情况下,不允许为null值(null值比较会造成空指针异常)
Map双列集合:
Map双列集合,常用于键值对结构的数据,通过key进行索引,key不能重复,value可以重复,但存放的顺序是无序的。Map初始容量为16,默认的加载因子是0.75。
包含三个实现类:HashMap、HashTable、TreeMap
HashMap
特点:查询速度快,存取无序,key不允许重复但可以为null,value可以重复,线程不同步不安全
JDK1.8之前,底层哈希表(数组+双向链表);JDK8之后:底层也是哈希表(数组+双向链表+红黑树)
Hash碰撞:无论什么版本,底层都是一个Entry[]数组,存储数据的时候,会把key交给hash算法再与数组长度取余来计算数据的存放位置,具体算法:hash(key)%n,n就是数组的长度,也就是集合的容量。那如果多个数据经过hash算法值一样了该怎么办呢?位置一样了,这就叫做hash冲突(hash碰撞),解决方法是将在该位置上的对象都做成链表结构。
存储:HashMap根据键的hashCode值存储数据,在向map集合中**put()**元素时,先根据key的hashCode值得到这个元素在数组中的下标,然后判断该位置上是否已经存放有其他元素,如果有就用equals()方法对该位置上的元素或者是链表元素逐个进行比较,如果有相同的就覆盖,不同就存放到链表头部。当链表长度大于8且数组长度大于64时,就会将这个节点上的所有数据改为红黑树结构存储;若链表长度大于8但数组长度小于64时,则依旧使用链表,且数组扩容。
取出:HashMap元素的取出:使用**get()**方法,首先计算key的hashCode值再和数组取余,然后根据值找到数组中的对应位置,这个位置上存放的可能是单个元素或者是链表,需要使用equals()方法进行对key进行逐个比较,找到链表中对应的元素。
安全性:HashMap非线程安全,也就是同一时刻可以有多个线程同时操作HashMap,这可能导致数据的不一致性,如果要实现线程安全,可以用Collections的线程包装器synchronizedMap()方法使HashMap变成线程安全的集合,或者使用ConcurrentHashMap。
总结:如果这个Map的每个位置上只存放一个元素,那速度自然是最快的,但如果链表的话,就会慢一些了,所以我们应该让链表尽量分开,这样会提高查询效率。HashMap适合插入、删除、定位元素。
ConcurrentHashMap与HashTable的区别:
HashTable中只有一把锁,所有的并发线程都必须同时竞争一把锁,而ConcurrentHashMap将数据分段,每段数据持有一把自己的锁,这样就允许多个并发线程同时访问,同时也保证了线程安全,并且ConcurrentHashMap通过分段锁机制实现线程安全,效率高于HashTable.
HashTable(基本不用)
底层是哈希表(数组+链表(永不更改结构))
特点:查询速度快,存取无序,key不允许重复并且key和value都不可以为null,value可以重复
理解:HashTable是比较老的集合了,有缺陷,现在基本被淘汰了,Oracle官方早就不推荐使用了。现在的话,单线程转为使用Hashmap,多线程使用ConcurrentHashMap,HashTable的操作几乎和HashMap一致,主要区别在于HashTable实现了多线程同步,所以HashTable线程安全,在底层源码中几乎大部分方法都使用了synchronized锁,因为方法都加了锁,造成了HashTable效率极为低下。
TreeMap
底层是红黑树,适用于一个有序key进行遍历。
特点:查询速度快,元素有序,key不允许重复并且不可以为null,value可以重复。
排序方式:自然排序或指定比较器排序
红黑二叉树介绍:
红黑树结构:又称红黑二叉树,具有完整的二叉树性质,并且是一颗自平衡的排序二叉树。
二叉树基本性质:树中的任何几点的值大于它左子节点的值,且小于它的右节点的值,为了实现这个性质,许多大牛提出了多种算法:例如:AVL、SBT、伸展树、TREAP、红黑树等。
平衡二叉树特性:它是一棵空树或者它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。其实就是说这棵二叉树的任何一个节点的左右子树的高度都相近都差不多。
红黑二叉树:节点要么是黑色要么是红色,通过这两种颜色来维持二叉树的平衡。红黑二叉树的必要规则:
1、每个节点都只能是红色或黑色
2、根节点是黑色
3、每个叶子节点都是黑色
4、如果一个节点是红色,则它的两个节点都是黑的,也就是说在一条路径上不能出现相邻的两个红色节点
5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
…
总结:
-
线程安全分类:
-
LinkedList、ArrayList线程不安全
-
HashSet、TreeSet线程不安全
-
HashMap、TreeMap线程不安全
-
StringBuilder线程不安全,StringBuffer线程安全
安全集合:Vector、HashTable是线程同步的,线程安全
-
-
底层实现分类:
- ArrayList、Vector:底层数据结构是由数组实现,特点:查询快,增删慢
- LinkedList、LinkedHashSet:底层由链表实现,特点:查询慢,增删快
- TreeSet、TreeMap:底层由二叉树实现,排序方式可以是自然排序或者是比较器排序
- HashSet、HashMap:底层是由哈希实现,依赖两种方法:hashCode()和equals()方法
-
null值:
- TreeSet不允许放入null值,HashSet可以有null值
- ArrayList、LinkedList允许null的存在
- HashMap的key可以有null值,但不能重复,value可以有mull值
-
线程同步/异步:(可以理解为是否被synchronized修饰,多个线程同时操作同步集合时,线程是串行执行的)
- Vector是线程同步的,线程安全;ArrayList、LinkedList是非线程同步的,线程不安全。
- HashTable是线程同步的,线程安全;HashMap是非线程同步的,线程不安全。
- HashSet、TreeSet、LinkedHashSet都是非线程同步,线程不安全。
-
有序/无序:
- HashSet中的数据是无序的,TreeSet中的数据是有序的
- TreeMap和TreeSet都是有序的,默认保持自然排序
- List集合、LinkedHashMap、LinkedHashSet都是有序的,并且顺序会保持添加时的顺序
常见问题:
问题1:HashMap和HashTable的区别:
- HashMap是线程不安全的类,多线程下会造成冲突,单线程使用时效率较高;HashTable是线程安全的类,很多方法都是用synchronized修饰,因为加了很多锁,造成HashTable效率低下,也造成了现在没人用。
- 插入null值:HashMap允许有同一个键为Null,允许多个value为null;但HashTable不允许键和值为null
- 容量:HashMap底层数组长度必须底数是2的正整数次方,数组长度默认为16。而HashTable底层数组长度可以是任意值,这样会造成hash算法散射不均匀,容易造成hash冲突,HashTable默认长度为11.
- 结构区别:HashMap由数组+链表组成,在JDK1.8之后,链表长度大于8且数组大小大于64,则链表会变成红黑二叉树;而HashTable则一直都是数组+链表。
- 继承关系:HashTable继承自Dictionary类;而HashMap继承自AbStractMap类。
问题2:ArrayList和Vector的区别:
- Vector是多线程安全的,而ArrayList不是线程安全的,从源码中可以看出,Vector类中的方法很多使用了synchronized修饰。
- 因为Vector使用了synchronized修饰,所以效率比ArrayList低
- Vector可以设置增长因子,而ArrayList不可以
- Vector以前就存在了,是线程同步的集合,效率低,项目中一般多用ArrayList.
- 两者都是数组实现,具有数组的特性。
问题3:集合和数组的区别:
- 数组的长度是固定的,集合可以改变长度
- 数组可以存储基本的类型,也可以存储引用类型;集合只能存储引用类型
- 数组存储的元素必须是同一类型;集合存储的可以是不同类型的数据
问题4:常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
- Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
- Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
不断的努力,不断的去接近梦想,越挫越勇,吃尽酸甜苦辣,能够抵御寒冬,也能够拥抱春天,这样的才叫生活。