一、集合的概述
- 集合、数组都是用来存储、获取、操作数据的容器,此时的存储主要指的是内存层面的存储,不涉及持久化存储
- 集合和数组的对比:
- 数组:长度不可变;没有提供查看有效元素个数的方法
- 集合:长度可以改变;可以存储任意类型的对象;
只能存储对象(不能存储基本数据类型)
集合的分类:
- collection接口:
单列数据
,定义了存储一组对象的方法的集合- List:有序,可重复的集合,又称“动态”数组
- Set:无序,不可重复的集合,类似高中讲的集合(无序、确定、互异性)
- Map接口:
双列数据
,保存具有映射关系“k-v健值对”的集合,类似于高中的函数(y=f(x),支持多对一,不支持一对多)
二、Collection接口
Collection是List、Set、Queue接口的父接口,该接口中定义的方法既可以操作set集合,也可以操作List、Queue集合,常用的方法:
作用 | 方法 |
---|---|
增 | add(Object o) addAll(Collection coll) |
删 | boolean remove(Object obj):通过equals判断是否是要删除的那个元素。只会删除找到的第一个元素 boolean removeAll(Collection coll):取当前集合的差集 |
判断是否包含某个元素 | boolean contains(Object obj):通过equals方法判断 boolean containsAll(Collection c):通过equals方法,拿两个集合的元素挨个比较 |
判断是否为空集合 | isEmpty() |
获取有效元素的个数 | int size() |
清空集合 | clear() |
取两个集合的交集 | boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c |
集合—>数组 | Object[] toArray() 数组—>集合:Arrays.asList(123,456) |
集合是否相等 | boolean equals(Object obj) |
获取集合对象的哈希值 | hashCode() |
遍历 | iterator():返回迭代器(设计模式的一种),用于集合遍历 |
Iterator迭代器:
主要方法:hastNext(),next()
ArrayList<Object> list = new ArrayList<>();
Iterator<Object> iterator = list.iterator();
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next():指针下移,将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
1 、Collection之List
List:单列数据,有序、可以重复的,用来替换数组,又称“动态”数组
List中的常用方法:
list除了从Collection继承的方法外,还添加了一些根据索引来操作集合元素的方法
作用 | 方法 |
---|---|
增 | add(int index,Object o):在index位置插入元素 addAll(int index,Collection coll):从index位置开始将coll里的元素插入 |
删 | E remove(int index):移除index位置的元素,并返回此元素 |
改 | Object set(int index, Object ele):将index位置的元素改为ele |
查 | Object get(int index):获取指定index位置的元素 |
查2 | int indexOf(Object obj):返回obj在集合中首次出现的位置 int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置 |
截取 | List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合 |
- 实现类一:ArrayList
List的主要实现类,线程不安全的,效率高。底层使用Object[] elementData(初始容量为10的数组),扩容时,扩大1.5倍 - 实现类二:LinkedList
底层使用双向链表
,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素,对于频繁插入、删除
的操作,效率比ArrayList高
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;
}
}
新增的方法:
作用 | 方法 |
---|---|
增 | void addFirst(Object obj) void addLast(Object obj) |
删 | Object removeFirst() Object removeLast() |
查 | Object getFirst() Object getLast() |
- 实现类三:Vector
List的古老实现类,线程安全(方法中加synchronized),效率低,底层使用Object[] elementData。扩容时扩大2倍
新增的方法:
作用 | 方法 |
---|---|
增 | void addElement(Object obj) void insertElementAt(Object obj,int index) |
删 | void removeElement(Object obj) void removeAllElements() |
改 | void setElementAt(Object obj,int index) |
常见面试题:
1、ArrayList和LinkedList的异同?
同:ArrayList和LinkedList都继承了List用来存储单列数据,二者都是线程不安全的,效率高
异:ArrayList底层是动态数组,当容量不够时,需要扩容。对随机访问List(get和set操作)效率高,因为LinkedList要移动指针从前往后依次查找。
LinkedList底层是双向链表,不存在扩容,对于新增和删除(add和remove操作)效率高,因为ArrayList需要移动数据
2、ArrayList和Vector的异同
同:底层数据结构都是数组
异:ArrayList是线程不安全的,效率高;扩容时,扩大为原来的1.5倍
Vector因为使用了synchronized,是线程安全的;效率低,扩容时,扩大为原来的2倍。
3、谈谈你的理解?ArrayList底层 是什么?扩容机制?
2、Collection之Set
set接口是collection的子接口,set接口没有提供额外的方法,使用的都是Collection定义的方法
特点是:无序、不可重复,类似于高中讲的集合(确定性、互异性、无序性)
无序性:并不等于随机性,指的是添加数据时数组在底层存储不是按照索引的位置依次存储,而是根据hash值经过一系列的算法决定存储的位置
不可重复性:先通过hashCode()方法看存储位置是否有元素,如果有再通过equals()比较,如果没有元素则直接添加,所以重写equals()方法的同时需要重写hashCode()保证相同的对象具有相同的散列码
- Set的实现类一:HashSet
线程不安全的,可以存null健null值。底层是数组+链表的结构
HashSet的添加过程:
当我们向HashSet中添加元素a,先通过a所在的类的hashCode方法计算元素a的哈希值,然后哈希值通过一些列算法得到元素a在HashSet中存储位置,判断数组此位置上是否已经存在元素,如果此位置上没有其他元素,则元素a直接添加成功,如果此位置上有其他元素b(或者以链表形式存在多个元素)则比较元素a与元素b的哈希值,如果哈希值不相同,则元素a以链表形式添加成功,如果哈希值相同,则调用元素a所在的类的equals方法,返回true则添加失败,返回false则以链表形式添加成功(链表:七上八下)
- HashSet的实现类二:LinkedHashSet
LinkedHashSet是HashSet的子类,它在HashSet的数据结构上使用链表来维护添加的顺序,所以遍历时可以按照添加的顺序遍历。这样设计的目的是对于频繁的遍历操作LinkedHashSet的效率要高于HashSet。
- Set的实现类三:TreeSet
底层使用红黑树存储(二叉树的一种),要求放入TreeSet类中的数据是同一个类的对象
,可以按照对象的某些属性进行排序
排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)
判断对象是否是否相同
:自然排序中是使用compareTo()是否返回0,定制排序中使用compare()方法,都不再使用equals方法
小葵花妈妈开课了:
问题一:为什么重写equals方法时需要重写hashCode方法?
自定义的类如果没有重写hashCode方法,则使用的是Object类的hashCode方法,Object中的hashCodeI方法使用的是本地类库,相当于每次new一个对象,就随机的分配一个哈希值,所以如果一个类有自己特有的逻辑相等的概念(即重写equals方法),需要重写hashCode方法。
问题二:使用idea等编译工具重写的hashCode方法中为什么要乘31
例如:自定义User类,里面有name和age属性,重写的hashCode方法如下:
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31*result + age;
return result;
}
首先,我们认为当User的name和age相同时,就是同一个对象,即哈希值相同。用简单的思路可以将hashCode方法写为hash=name.hashCode()+age,但如果name.hashCode()=20,age=12和name.hashCode()=12,age=20的两个对象其实并不是相同的对象,但他们的哈希值相同,就会存在哈希冲突的情况,冲突之后,就会接着通过equals方法比较,返回false时就会以链表的形式添加,虽然也能存进去,但我们希望他存在数组中
第二步,就考虑到将某个数乘以一个系数放大,那么这个系数就要尽量大,因为计算出来的哈希值越大,所谓的 “冲突”就越少,查找起来效率也会提高。(减少冲突)。但也不能太大,太大可能造成内存溢出。为了效率考虑,我们首先考虑到2的幂次方且为一个素数,31=(2<<5)-1
三、Map接口
双列数据
,保存具有映射关系“k-v健值对”的集合,类似于高中的函数(y=f(x),支持多对一,不支持一对多)
- HashMap
作为Map的主要实现类,线程不安全的,效率高,可以存储键值对为null的key和value
LinkedHashMap在HashMap的基础上,底层添加了一对指针指向前一个元素和后一个元素,保证了在遍历时,可以按照添加的顺序遍历。 - HashTable
作为Map的古老实现类,线程安全,效率低,不能存储null的key和value。
Properties用来处理配置文件,key和value都是String类型 - TreeMap
底层使用红黑树,按照添加的key-value对其排序,实现排序遍历。key的自然排序和定制排序
面试题:
1、HashMap的底层原理?
2、HashMap 和 Hashtable的异同
3、CurrentHashMap 和 Hashtable的异同