List接口特点
- List集合所有的元素是以一种线性方式进行存储的,例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
- 它是一个元素存取有序的集合。即元素的存入顺序和取出顺序有保证。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List集合关心元素是否有序,而不关心是否重复。
- List接口的主要实现类
- ArrayList:动态数组
- Vector:动态数组
- LinkedList:双向链表
- Stack:栈
public class ListTest {
@Test
public void test1(){
//jdk7;
ArrayList<String> list = new ArrayList<>();//底层初始化数组。
// Object[] elements = new Object[10];
list.add("aa");
list.add("bb");
//添加时容量不够,则进行自动扩容。默认扩容为原数组的1.5倍,并将所有的数组元素复制到新的数组之中。
}
@Test
public void test2(){
//jdk8:
ArrayList<String> list = new ArrayList<>();//底层初始化数组。
// Object[] elements = new Object[]{};
list.add("aa");//首次添加元素时,才初始化数组长度为10,并且将element[0]进行赋值。
list.add("bb");
}
}
jdk7中,初始化时底层数组,长度为10;jdk8中,初始化底层数组,但此时不确定初始长度,直到为第一个元素赋值,才确认底层数组初始长度为10,并对数组中的element [0]进行赋值。
链表与动态数组的区别(ArrayList与LinkedList的区别)
动态数组底层的物理结构是数组,因此根据索引访问的效率非常高。但是非末尾位置的插入和删除效率不高,因为涉及到移动元素。另外添加操作时涉及到扩容问题,就会增加时空消耗。
链表底层的物理结构是链表,因此根据索引访问的效率不高,即查找元素慢。但是插入和删除不需要移动元素,只需要修改前后元素的指向关系即可,所以插入、删除元素快。而且链表的添加不会涉及到扩容问题。
ArrayList与Vector的区别
它们的底层物理结构都是数组,我们称为动态数组。
- ArrayList是新版的动态数组,线程不安全,效率高,Vector是旧版的动态数组,线程安全,效率低。
- 动态数组的扩容机制不同,ArrayList默认扩容为原来的1.5倍,Vector默认扩容增加为原来的2倍。
- 数组的初始化容量,如果在构建ArrayList与Vector的集合对象时,没有显式指定初始化容量,那么Vector的内部数组的初始容量默认为10,而ArrayList在JDK 6.0 及之前的版本也是10,JDK8.0 之后的版本ArrayList初始化为长度为0的空数组,之后在添加第一个元素时,再创建长度为10的数组。原因:
- 用的时候,再创建数组,避免浪费。因为很多方法的返回值是ArrayList类型,需要返回一个ArrayList的对象,例如:后期从数据库查询对象的方法,返回值很多就是ArrayList。有可能你要查询的数据不存在,要么返回null,要么返回一个没有元素的ArrayList对象。
HashMap
(1)哈希表的物理结构
HashMap和Hashtable底层都是哈希表(也称散列表),其中维护了一个长度为2的幂次方的Entry类型的数组table,数组的每一个索引位置被称为一个桶(bucket),你添加的映射关系(key,value)最终都被封装为一个Map.Entry类型的对象,放到某个table[index]桶中。
使用数组的目的是查询和添加的效率高,可以根据索引直接定位到某个table[index]。
HashMap中的put方法添加会修改的过程:
将(key1,value1)添加到当前的map中
调用key1所在的hashCode方法,计算key1所在的哈希值,此哈希值经过某种计算(hash())之后,得到哈希值2
哈希值2:hash(key1.hashCode)
哈希值2再通过某种算法(indexFor())之后,就确定了(key1,value1)在数组table中的位置。
①如果此索引位置1的数组上没有元素,则(key1,value1)添加成功--->直接存放
②如果此索引位置1的数组上有元素(key2,value2),则需要继续比较key1和key2的哈希值2
若:key1与key2的哈希值2不同,则(key1,value1)添加成功--->将(key1,value1)与原有的(key2,value2)构成单向链表
若:key1与key2的哈希值2相同,则调用key1所在类的equals()方法,将key2作为参数传入
返回false:(key1,value1)添加成功
返回true:将value2替换为value1。
满足如下条件时,集合map考虑扩容:
1、(size>=threshold)&&(null != table+[i])
当元素的个数到达临界值(数组的长度*加载因子),考虑扩容,默认扩容为原本的两倍。
加载因子偏大-->出现链表的概率增大,节省内存空间,但后续删改操作相对麻烦。
jdk7与jdk8的区别:
①
使用HashMap()的构造器创建对象时,并没有在底层初始化长度为16的table数组。
②
jdk8中添加的key,value封装到了HashMap.Node类的对象中。而非jdk7中的HashMap.Entry。
③
jdk8中新增的元素所在的索引位置如果有其他元素。在经过一系列判断后,如果能添加,则是旧的元素指向新的元素。而非jdk7中的新的元素指向旧的元素。“七上八下”
④
jdk7时底层的数据结构是:数组+单向链表。 而jdk8时,底层的数据结构是:数组+单向链表+红黑树。
树化:
红黑树出现的时机:当某个索引位置i上的链表的长度达到8,且数组的长度超过64时,此索引位置上的元素要从单向链表改为红黑树。
树退化:
如果索引i位置是红黑树的结构,当不断删除元素的情况下,当前索引i位置上的元素的个数低于6时,要从红黑树改为单向链表。
红黑树的索引1上的元素个数低于6
et集合与Map集合的关系
Set的内部实现其实是一个Map,Set中的元素,存储在HashMap的key中。即HashSet的内部实现是一个HashMap,TreeSet的内部实现是一个TreeMap,LinkedHashSet的内部实现是一个LinkedHashMap。