Java学习记录-集合
![总体概括](https://i-blog.csdnimg.cn/blog_migrate/196c5ddd1fae667e5fba1c402cfddaaf.jpeg#pic_center)
Collection
Iterator说明
迭代器,是一种设计模式,专门为容器遍历而设计,每次调用iterator()都会得到一个全新的迭代器对象,游标在第一个元素之前。
- next()将指针下移,将此处元素返回;
- hasNext()判断下一位是否有元素;
- remove()将元素删除,若未调用next()方法则不能remove,或者在上一次remove后不能再次remove(一个next()不能remove两次)
List
元素有序,可重复;以下三者都实现了List接口,存储特点相同:
ArrayList
List接口的主要实现类,线程不安全,效率高;底层使用Object[](elementData)存储,只是对数组封装;
源码分析:
- jdk7:
ArrayList list = new ArrayList();
底层初始化了一个容量为10的Object[] elementData;
add(123);elementData[0]=new Integer(123);若此时容量不够,则扩容,默认为原来容量的1.5倍,同时将旧数组copy到新数组中。
建议开发中默认初始化容量,防止扩容次数过多 - jdk8:
ArrayList list = new ArrayList();
底层初始化了Object[] elementData = {}
第一次调用时才初始化一个长度为10的数组,再添加数据;
总结:7中类似于单例模式的饿汉式,8类似于懒汉式
LinkedList
底层使用双向链表存储,对于频繁插入、删除操作,此类型效率高;
源码分析:
LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null
//Node节点
private static class Node<E>{
E item;
Node<E> next;
Node<E> prev;
Node(Node<E>,prev,E element,Node<E next){
this.prev=prev;
this.item=item;
this.next=next;
}
}
Vector
List接口的古老实现类,线程安全,公共方法都用了synchronized关键字
源码分析:
构造器初始化了一个容量为10的数组,默认扩容为原来的2倍
Set
存储无序的、不可重复的。
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组的索引顺序添加,而是根据数据的哈希值判断;
- 不可重复性:保证添加的元素按照equals判断时不能返回true;即相同的元素不能填加进来。
- 注意:
- Set接口中没有额外的方法,都是Collection中声明的方法;
- 向Set中添加的数据,其所在类一定要重写hashCode和equals方法;
- 重写的两个方法尽可能保持一致性:相等的对象必须要相等的散列码。
- 添加元素的过程(HashSet):
- 向HashSet中添加元素a,首先调用a所在类的hashCode方法,计算a的哈希值,此哈希值接着通过某种算法计算出在底层HashSet数组中的存放位置(索引位置),判断此位置上是否有元素:
- 如果此位置上没有其他元素,则a添加成功。—情况1
- 如果此位置上有其他元素b(或者以链表形式存在的多个元素),则比较a和b的哈希值:
如果哈希值不相同,则a添加成功。—情况2
如果哈希值相同,进而需要调用元素a所在类的equals方法:
equals返回true,a添加失败;
equals返回false,a添加成功;—情况3
对于2,3添加成功时:元素a与其他元素在指定索引位置上以链表形式存储;
7:头插法,8尾插法;
HashSet
作为Set接口的主要实现类,线程不安全的,可以存储null值,底层是数组加链表形式;
LinkedHashSet
作为HashSet的子类,遍历内部数据时,可以按照添加的顺序遍历;
再添加数据时,每个数据还维护了两个引用,记录此数据前一个和后一个数据;
对于频繁的遍历操作时,效率高于HashSet。
TreeSet
- 可以按照添加对象的指定属性,进行排序。
- 向TreeSet集合中添加的数据,必须是相同类的对象;
- 两种方法排序:
- 自然排序(实现comparable接口):比较两个对象是否相同的标准为:compareTo返回0,不再是equals()
- 定制排序:比较两个对象是否相同的标准为:Compartor接口的compare()实现方法返回0,不再是equals。
Map
双列数据,存储key-value对的数据。
HashMap
- 是Map的主要实现类;线程不安全的,效率高;可以存储key、value为null的数据。
- 底层结构:数组+链表(jdk7),数组+链表+红黑树(jdk8)
- Map的结构理解:
- Map中的key:无序的,不可重复的,使用Set存储所有的key;key所在的类要重写equals和hashCode方法(HashMap为例);
- Map中的value:无序的,可重复的,使用Collection存放(单列数据);value所在的类要重写equals方法;
- 一个键值对:kay-value构成了一个Entry对象;
- Map中的entry,无序的,不可重复的,使用Set存放所有的entry。
- HashMap的底层实现原理:
JDK7
- HashMap map = new HashMap();
实例化以后,底层创建了长度为16的一维数组Entry[] table; - …可能执行过多次put操作…
- map.put(key1,value1);
首先调用key1所在类的hashCode方法(再加上hash()里面的运算)计算哈希值,此哈希值接着通过某种算法计算出在底层Entry数组中的存放位置(索引位置):
如果 此位置上的数据为空,则entry添加成功。—情况1
如果 此位置上的数据不为空(意味着此位置上存在一个或多个(链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果 key1的哈希值与已经存在的数据的哈希值都不相同,则key1-value1添加成功(entry添加成功);—情况2
如果 key1的哈希值与已经存在的某一个数据(key2-value2)的哈希值相同,继续比较,调用key1所在类的equals(key2):
如果 equals返回false:此时key1-value1添加成功;—情况3
如果 equals返回true:使用value1替换value2. - 关于情况2,3:此时key1-value2以链表的形式存储。
- 扩容问题:当超出临界值(当前数组长度*加载因子(0.75))且当前元素要存放的位置为空,则扩容。默认扩容方式容量为原来的两倍,并将原有的数据复制过来。
JDK8
- new HashMap();底层没有创建一个容量为16的数组;
- 底层数组是Node[],不是Entry[];
- 首次调用put方法时,才初始化长度为16的数组;
- 底层结构:数组+链表+红黑树;
当数组某一个索引位置上的元素以链表形式存在的数据个数>8,且当前数组长度>=64,此索引位置上的数据采用红黑树存储;(若>8,且不为空,<64,则扩容) - 一些常量:
- DEFAULT_INITIAL_CAPACITY:默认容量16;
- DEFAULT_LOAD_FACTOR:默认加载因子:0.75;
- threshold:扩容的临界值:容量*加载因子(16*0.75);
- TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值8,转化为红黑树;
- MIN_TREEIFY_CAPACITY:桶中的Node被树化时,最小的hash表容量64
LinkedHashMap
- 保证在遍历map元素时,可以按照添加的顺序实现遍历。在原有的底层结构基础上,添加了一对引用,指向前一个和后一个元素。对于频繁的遍历操作,此类的执行效率高于HashMap。
- 底层数据结构:
static class Entry<K,V> extends HashMap.Node<K,V>{
ENtry<K,V> before,after;//能够记录前一个\后一个元素
Entry<int hash,K key, V value,Node<K,V> next>{
super(hash,key,value,next);
}
}
TreeMap
保证按照添加的key-value对进行排序,实现排序遍历;考虑key的自然排序或者订制排序,底层使用红黑树实现。
Hashtable
作为古老的实现类;线程安全,效率低;不能存储为null的数据。
Properties
常用来处理配置文件,k-v都是String类型。
PS:
- 感谢尚硅谷宋红康老师的讲解。
- 个人学习理解所用,如有误或者补充请下方留言,谢谢~