List
List接口的特点有序(插入顺序和遍历顺序一致),且允许重复
List接口中的元素有索引(下标)
ArrayList底层使用数组实现
list.add(1,“abc”);//add方法使用下标插入固定位置的方法,这个下标不能超过size()+1
list.remove(1);//如果集合中存储的是整数元素,那么remove添加的参数优先是索引,所以这个1是移除第二个位置上的元素
list.remove(new Integer(1));//如果想删除整数对象元素,那么需要创建封装类对象,封装类对象会按照值进行删除
List集合的遍历方法有三种
1.传统for循环
2.增强for循环
3.迭代器
ArrayList底层使用一个变长数组实现,这个数组从jdk1.8开始在集合对象初始化的时候是不给出长度的(节省内存空间)
jdk1.7及jdk1.7之前这个数组的长度是在创建ArrayList对象的时候就初始化长度10
ArrayList底层数组扩容时使用的是1.5倍扩容,ArrayList底层数组在数组元素已满的情况下扩容
在ArrayList对象第一次调用add方法向集合中添加元素的时候,这个数组会被初始化长度10
Vector对象创建的时候就已经初始化长度10
Vector绝大多数操作集合元素的方法都是同步方法,效率低
LinkedList底层使用双向链表实现
LinkedList存取效率高,遍历效率低
LinkedList有一套对首尾元素单独操作的方法
总结:常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index,Object ele)
长度:siize()
遍历:1.Iterator迭代器
2.增强for循环
3.普通for循环
ArrayList底层实现:
ArrayList创建时,看构造参数是否传入int类型的变量,如果传入了则初始化elementData数组长度为传入的值,如果没传入则调用无参构造初始化elementData数组长度为0,第一次调用add方法时将数组扩容长度为10,当数组元素个数达到数组长度时,再次add方法则会进行1.5倍扩容,扩容是按照size+size>>1的位运算得到扩容后的数组长度,然后创建一个该长度的新数组,再将原数组的元素copy到新数组中,然后将需要添加的元素插入到数组;
LinkedList底层实现:
LinkedList里面维护了两个属性,first和last,first指向链表的头元素,last指向链表的尾元素,而元素是使用的一个Node节点类型封装的,Node也是一个类,它是LinkedList的一个静态内部类,Node节点里面维护了三个属性,item、prev、next,item表示当前的元素,prev指向上一个Node节点的item元素,next指向下一个Node节点的item元素,如果是首节点那么它的prev就会指向null,如果是尾节点那么它的next就会指向null;
LinkedList创建时,first和last都会指向空,size为0,当第一次调用add方法时,实际调用的是linkLast方法,将元素封装到Node节点中,并且前后节点都指向空,LinkedList的frist和last都指向这个节点;如果不是第一次添加元素,那么add会调用linkLast方法,将last末尾节点指向一个Node类型的l引用,这个l就是链表末尾的节点,然后新创建一个Node节点,item就是新添加的元素,frist指向l(之前的末尾节点),last指向null,然后将last指向新的Node节点,这样新的链表头节点不变,末尾节点变为新添加元素封装的节点,并且prev指向之前的末尾节点,并且size+1;
Set
Set集合的特点,过滤重复记录,无序(插入顺序和存储顺序不一致,向HashSet集合中存元素,元素的存储顺序是固定的,和插入顺序无关)
向Set集合添加元素时,如果Set集合判定元素重复,则添加动作不执行
HashSet集合使用元素的hashCode()和equals()方法来判定元素是否重复
在一个类没有重写equals方法的时候,equals方法只能判定两个对象是否来自同一个地址
只有来自同一地址的两个对象才会被HashSet过滤掉
当一个元素被添加进HashSet集合的时候,会首先调用这个对象的hashCode()方法
比较当前对象是不是和原来数组中的元素hashCode方法返回的值相同,如果不相同,则直接将新元素添加集合,此时不会调用equals方法
如果新元素返回的hashCode值和原元素返回的hashCode值相同,那么会调用新元素的equals方法来判断是否新元素和老元素重复
如果equals方法返回false,则新元素被添加进集合,如果equals返回true,HashSet会使用元素的hashCode()方法和equals()方法共同判断新元素是否和老元素重复,重复,那么新元素将不会被添加。
LinkedHashSet插入顺序就是遍历顺序
LinkedHashSet去重的方式和HashSet一样使用hashCode()和equals()
LinkedHashSet使用了和HashSet一样的保存方式,但是遍历方式不一样
向TreeSet中添加元素不仅可以去掉重复记录,还可以进行排序,升序排列
如果放入到TreeSet中的元素不能进行排序,那么存储的动作将会抛出异常
放到TreeSet中的元素必须可以进行排序,必须实现Comparable接口
TreeSet在添加元素的时候调用元素的compareTo()方法进行排序,当两个对象调用compareTo()方法,
返回0的时候,判定两个对象相同,此时不会添加第二个元素
实体类实现Comparable接口,重写compareTo方法,实现的元素排序方式被称作自然排序
compareTo()返回int型,该返回为0的时候代表调用方法的对象和参数对象相等
返回负数代表调用方法的对象小,参数对象大,返回正数代表调用方法的对象大,参数对象小
TreeSet定制排序的方式需要我们为TreeSet对象添加一个比较器对象
如果compare返回0表示两个参数相等,返回负数代表第一个参数小,第二个参数大
返回正数代表第一个参数大,第二个参数小
HashSet底层实现就是HashMap,只是将value值固定为一个常量PRESENT且这个常量是new Object();
Map
存储kv键值对的集合,底层基于Node数组+单向链表+红黑树的数据结构,key无序唯一,value无序可重复,线程不安全的
创建HashMap时不传入参数则会初始化一个空的Node数组,长度为0,加载因子0.75,第一次调用put方法时初始长度16;如果传入参数则会初始大于该参数的最小2的幂次方的数组长度;
执行添加方法put时实际上会调用putVal方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义一个局部引用tab接收哈希表,定义一个Node节点p,定义int遍历n,i
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果哈希表为空或哈希表长度为0就进行第一次扩容,并将哈希表长度赋值给n
if ((tab = table) == null || (n = tab.length) == 0
n = (tab = resize()).length;
//获取到添加元素的索引位置,将哈希标此哈希槽的Node节点赋值给p,判断p是否为空,为空则直接将元素封装成Node节点添加
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//如果不为空表示哈希冲突,则需要进入此代码块进行后续操作
else {
//定义一个Node类型引用e,定义一个K类型k(Node中的key是K类型)
Node<K,V> e; K k;
//如果p的哈希值和添加元素的哈希值相同,且添加元素的key和p的key是同一个对象,则直接将p赋值e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果p属性TreeNode类型,那么调用红黑树添加元素的方法
else if (p instanceof HashMap.TreeNode)
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//在这说明哈希表结构还是数组+链表
else {
//遍历链表
for (int binCount = 0; ; ++binCount) {
//将p的下一个元素赋值给e,如果进入代码块说明链表末尾还没跳出说明没有相同元素
if ((e = p.next) == null) {
//将元素封装Node节点添加至链表尾部
p.next = newNode(hash, key, value, null);
//判断链表长度是否达到8,如果达到则调用树化方法,会继续判断哈希表长度是否达到64,如果是则进行真正的树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//依次判断链表当前元素的哈希值和添加元素哈希是否相同,相同就马上break跳出
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//将e赋值给p,移动下一个链表继续循环,这步很关键
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//操作哈希表次数+1
++modCount;
//如果哈希表元素大于加载因子*数组长度则进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
LinkedHashMap
底层数据结构是数组+双向链表,不允许添加重复key,可以保证存储顺序和遍历顺序一致
afterNodeInsertion()方法是核心
添加元素其实调用的就是HashMap的putVal()方法
每次添加元素会对key做一个hash算法,获取一个索引值,如果该索引下为空则直接添加Entry节点,如果不为空比较添加元素的哈希值和当前哈希表这个索引上的哈希值是否相同,如果不同则添加至末尾,操作其实和HashMap底层差不多,只是链表是双向链表且会调用afterNodeInsertion()方法将后插入的节点的before指向上一个节点,上一个节点的after指向刚添加的节点;