JAVA实习找工作——集合相关问题
引言
先简述Java中的集合结构,分为两类:单列集合Collection和双列集合Set
单列集合Collection:有序可重复,一般用来代替数组,称作可变数组
双列集合Map:
ArrayList、Vector、和LinkedList的区别
ArrayList:作为List接口的主要实现类之一,线程不安全,效率高,底层使用Object[] elementData存储。
JDK7:
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
JDK8:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
后续的添加和扩容操作与jdk 7 无异。
总结小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储。
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
在扩容方面,默认扩容为原来的数组长度的2倍。
HashSet
HashSet底层依然使用数组和链表存储,主要体现在无序性和不可重复性
向HashSet中add元素a时,先调用a元素的类hashCode()方法计算元素a的hash值,将这个hash值通过某种算法散列算法计算一个数组中位置i,当数组中i位置不存在元素时,则a元素添加成功;如果i位置存在元素b时,比较b的hash值和a的hash值,不一样就使用链表形式将a,b存在i位置;如果hash值相同就比较equals方法,相同就不存储,不同就链表方式存储。
HashMap
JDK7
HashMap map = new HashMap():在实例化以后,底层创建了长度是16的一维数组Entry[] table。
...可能已经执行过多次put操作...
map.put(key1,value1):首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况。如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2。如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同(哈希冲突),继续比较:调用key1所在类的equals(key2)方法,比较:如果equals()返回false:此时key1-value1添加成功。----情况3。如果equals()返回true:使用value1替换value2。补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。
HashMap在jdk8中相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没创建一个长度为16的数组
2. jdk 8底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层创建长度为16的数组
4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储。
红黑树
为了解决hash冲突的问题,即一个hash值下面挂着很长的链表,所以在jdk1.8中HashMap底层由数组、链表、红黑树构成,红黑树是一种平衡二叉查找树,所谓“平衡”,就是说不存在某个子树很长。
红黑树的几大特点:
(1)节点要么是红色,要么是黑色
(2)根节点必须是黑色
(3)叶子节点必须是黑色的Null节点
(4)每个红色节点的2个子节点必须是黑色
(5)树中任意一个节点到每个叶子节点的路径中都包含相同数量的黑色节点
红黑树可以保证自平衡靠的三大法宝:左旋、右旋、变色
左旋:旋转节点的右节点为父节点,右节点的左子树给旋转节点当右子树
右旋:旋转节点的左节点成为父节点,左节点的右子树成为旋转节点的左子树
变色: