几个概念
1. Collection接口:单列数据,定义了存取一组对象的方法的集合
- List:元素有序,可以重复的集合
- Set:元素无序,不可重复的集合
2. Map接口:双列数据,保存具有key-value键值对的集合
3. Conllection接口方法
- retainAll(collection对象):求两个集合交集
- removeAll(collection对象):求两个集合差集
- toarry():集合转数据
- Arrays.aslist(数组):数组转集合
- iterator():返回一个迭代器对象,仅用于遍历集合,默认游标在第一个元素之前
4. Collections集合方法:均为static方法
- reverse():反转
- shuffle():随机排序
- frequency():返回指定元素出现的次数
- copy():复制list,复制出来的list大小不可小于被复制的list
5. List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组
- ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
- LinkedList:对于频繁的插入、删除操作,此类效率比ArrayList高;底层使用双向链表存储
- Vector:作为List接口的古老实现类;线程安全、效率低;底层使用Object[] elementData存储
6. Map接口:双列数据,存储key-value对的数据
- HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
- Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
List
1. ArrayList源码
- JDK7的情况
- 底层创建了长度是10的Object[]数组elementData
- 数组容量不够,默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中
- 建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
- JDK8的情况
- 底层Object[] elementData初始化为{},并没有创建长度为10的数组
- 第一次调用add()时,底层才创建了长度10的数组,并将数据添加到elementData[0]
- 后续的添加和扩容操作与jdk7无异
- 7与8的对比
- jdk7中的ArrayList的对象的创建类似于单例的饿汉式
- jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
2. LinkdeList源码
- 内部声明了Node类型的first和last属性,默认值为null
- 调用add()时,将数据封装到Node中,创建了Node对象
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;
}
}
3. Vector源码
- 通过Vector()构造器创建对象时,底层都创建了长度为10的数组
- 在扩容方面,默认扩容为原来的数组长度的2倍
4. 调用remover()注意
- remove(int index):移除指定index位置的元素
- remove(Object obj):移除指定对象元素
5. ArrayList和LinkedList的异同
- 二者都线程不安全,相对线程安全的Vector,执行效率高。
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
6. ArrayList和Vector的区别
- Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。
- 正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。
- Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。
- Vector还有一个子类Stack。
Set
1. 对于存放在Set容器中的对象
- 对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则
- 重写的两个方法尽可能保持一致性,相同的对象必须具有相同的散列码
- TreeSet存储的对象需要实现Comparable接口或者new TreeSet时传入Comparator对象
2. Set的两个特性
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。即相同的元素只能添加一个。
3. HashSet
- 不能保证元素的排列顺序
- 线程不安全的
- 可以存储null值
4. 添加元素的过程:以HashSet为例
- 向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值
- 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素
- 如果此位置上没有其他元素,则元素a添加成功
- 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值
- 如果hash值不相同,则元素a添加成功
- 如果hash值相同,进而需要调用元素a所在类的equals()方法
- equals()返回true,元素a添加失败
- equals()返回false,则元素a添加成功
- 若元素a添加成功且指定索引位置上已经存在数据,则以链表的方式存储
- jdk 7:元素a放到数组中,指向原来的元素。
- jdk 8:原来的元素在数组中,指向元素a
5. 为什么用Eclipse/IDEA复写hashCode方法,有31这个数字
- 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
- 并且31只占用5bits,相乘造成数据溢出的概率较小。
- 31可以由i*31==(i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)
- 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)
6. LinkedHashSet
- HashSet的子类
- 由于是双向链表,遍历内部数据时,可以按照添加的顺序访问
- 其内部顺序还是无序的
- 对于频繁的遍历操作,LinkedHashSet效率高于HashSet
7. TreeSet
- 添加的数据,要求是相同类的对象
- 对象必须实现Comparable接口,或者创建TreeSet传入Comparator对象
- 底层使用的是红黑树,保证插入数据是按照大小排序
- 查询速度比list快