集合
1. Java集合简介
- 特点:
- 实现了接口和实现类的分离
- 支持
泛型
,一个集合中只能存入一种类型的元素 - 通过
迭代器
(Iterator)访问集合,而无需知道内部的存储方式
- 包括:Collection下的List、Set接口,Map接口
- 遗留类:Hashtable:线程安全的Map;Vector:线程安全的List;Stack:基于Vector的栈;不应再使用
2. List
- List和数组几乎一样:有序、使用索引
- 不同的是:数组的添加、删除(中间的)元素时,需要挪动元素,很麻烦,并且数组容量受限;
而List的ArrayList实现类就很方便,它把添加删除给封装了起来;在元素满时还会自动扩容(扩容1.5倍,复制原数组的数据,再修改原数组的引用); - 接口方法:
- 在末尾添加一个元素:
boolean add(E e)
- 在指定索引添加一个元素:
boolean add(int index, E e)
- 删除指定索引的元素:
E remove(int index)
- 删除某个元素:
boolean remove(Object e)
- 获取指定索引的元素:
E get(int index)
- 获取链表大小(包含元素的个数):
int size()
-
LinkedList
通过链表也实现了List接口;LinkedList相比于ArrayList:获取指定元素更慢、中间的插入删除更快、内存占用更大 -
List接口特点:
- 允许添加重复元素
- 允许添加null元素
-
创建:
- new ArrayList()/LinkedList()
- JDK9新增创建
不可变集合
的静态方法:List.of(E…),创建后不能添加删除元素,不能有null
-
遍历List:
- for + get(int index):get()方法只对ArrayList高效,对于LinkedList速度很慢(链表需要从头开始遍历找)
- 迭代器
Iterator
:由List实例调用iterator()
方法创建Iterator对象;不同的List实例有不同的Iterator对象;而这个Iterator对象的子类对象有不同List的最高效的访问方式;
通过Iterator对象的boolean hasNext()方法
来判断有无下一个元素,E next()方法
来返回下一个元素; for each
:就是简化的Iterator方式
编译器会把foreach转为迭代器
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
for (String s : list) {
System.out.println(s);
}
- 集合=>数组:Integer[] array = list.toArray(new Integer[list.size()]);
数组=>集合:Arrays.asList(T…)
jdk9: List list = List.of(array);
3. 编写equals方法
- 为什么?
List中如何判断两个集合元素是否相等,如boolean contains(Object o)
、int indexOf(Object o)
方法?
使用equals()方法判断,若集合元素类型没有重写该方法,则使用==
来判断,对于引用类型来说,是判断内容相等,而不是引用相等,所以需要重写equals(); - equals(Object o)方法接收的是Object,所以使用
instanceof
判断Object是否指向当前实例,是就向下转型,否则返回false;
基本类型使用==
,引用类型使用Objects.equals(this.name , p.name );
直接使用引用类型属性的equals方法需要先判空,并且都为null时认为相等;
所以使用Objects.equals()方法比较方便
- 如果不使用类似contains()、indexOf()这些内部需要判断元素相等的方法,那么放入集合的元素就可以不实现equals()方法;
4. Map
-
使用List进行查找时平均扫描一半元素才可以,而使用Map可以根据key高效查找
-
Map<K,V>是键值映射表,使用
V put(K k,V v)
就存入一个键值对,k存在返回k原来的对应值,不存在返回null,再通过V get(K k)
就可以获取值,不存在返回null; -
遍历Map:
- foreach遍历map.keySet(),得到key的集合;
- foreach遍历map.entrySet(),得到的就是每个键值对了,entrySet()中每一个是Map.Entry<键,值>内部类实例
-
没有顺序
5. 编写equals和hashCode
- HashMap可以根据key直接拿到value:因为内部
用一个大数组存储所有的value
,并根据k直接计算出value所在的数组索引
- key不能重复,使用key查找时,
两个key应该内容相同
。Map中对key作比较通过equals实现,和List的查找类似,map的key对象必须重写equals方法
; - 如何根据key计算出value的索引?并且相同的key(equals()为true)必须计算出相同的索引
通过key的hashCode()
方法得到一个整数再经过hash函数得到value的索引,进而返回value;
所以还要重写key的hashCode()函数
,hashCode()的规范:
- 两对象相等(equals为true),则hashCode必须相等;(必须满足)
- 两对象不相等,hashCode尽量不相等;(尽量满足,保证查询效率)
- 和equals()一样,需要对字段判空,所以使用
Objects.hash(字段1。。。)
equals()中用到的字段,必须也用在hashCode()中;
equals()中没有用到的字段,不能用在hashCode()中;
- hashMap初始化数组大小只有
16
,而key的hashCode值很大,& (容量-1),这样就可以把索引确定到(0,容量-1),保证不会超数组范围 - 如果超出数组容量,HashMap内部字段扩容(2倍),再重新根据hashCode值计算新的索引:&(新容量-1);
扩容会导致key-value对被重新分布,所以创建HashMap时最好指定容量new HashMap<>(10000);
这个容量虽然可以随意设定,但是HashMap内部的数组长度总是:2n,是为了计算索引时更方便(容量为2n,可以使用&代替%,计算机&速度更快),比10000大的最小的是214=16384;
- 两个key的hashCode()尽量不相等,但如果相等了,怎么办?(哈希冲突)
这个数组存储的不是单纯的value,而是一个List链表,链表中存的是键值对
Person p = map.get(“a”);
查找时,根据a找到数组索引的那个List链表,之后再遍历这个List,找到key为a的那个entry,然后再返回这个value;
冲突概率越大,查找效率越低,所以尽量满足hashCode不相等
6. Set
- 只存储不重复的key,就用Set
- 方法
- 将元素添加进Set:boolean add(E e)
- 将元素从Set删除:boolean remove(Object e)
- 判断是否包含元素:boolean contains(Object e)
- 经常使用Set去除重复元素;
- Set的元素和Map的key类似,也要
重写equals()、hashCode()