一、为什么使用集合
数组:长度、类型比较固定,灵活性不好
集合:可以动态保存任意多个对象,使用较方便
二、集合框架体系(背下来!)
集合主要分为两组:
1、单列集合Collection
2、双列集合(键值对)Map
三、Collection
子类可以存放多个元素,可以时Object。不能直接被实现,通过其子类实现。
3.1 遍历方法
迭代器遍历
Iterator iterator = coll.iterator() //获取一个迭代器对象
iterator.next() //下移,并将当前指向元素返回(类型为Object)
iterator.hasNext() //判断是否还有下一个元素
显示快捷键ctrl+j
2、增强for循环 (简易版iterator遍历)
for(Object obj : iterator) {...}
3、普通for循环
for(int i=0; i<list.size(); i++){...}
3.2 常用方法
add(Object obj) //新增
remove(Object obj) //删除
contain() //查找
clear() //清空
addAll(Collection c) //添加多个元素
3.3 List
3.3.1 特性
添加顺序和取出顺序一致,且元素可以重复
元素可以使用索引访问
3.3.2 常用方法
add(int index, Object obj) //在指定位置添加对象
addAll(int index, Collection c ) //在指定位置添加集合
get(int index) //获取指定元素
indexOf(Object obj) //查询obj在集合中首次出现的位置
lastIndexOf(Object obj) //查询obj在集合中最后出现的位置
remove(int index) //删除指定元素
set(int index, Object obj) //相当于替换指定元素
subList(int fromIndex, int toIndex) //返回从fromIndex到toIndex之间的元素(不包括toIndex元素)
3.3.3 ArrayList
3.3.3.1 特性
可添加多个null对象
使用数组实现数据存储
基本等同Vector,但线程不安全
transient 关键字:表示瞬间,短暂的,表示该属性不会被序列化
3.3.3.2 源码分析add方法
结论:
1、底层维护了一个elementData的Object对象类型数组
2、如果使用无参构造器,elementData初始化容量为0,首次添加数据,则扩容为10,,如需再次扩容,则扩容为当前1.5倍(使用Arrays.copyOf实现扩容)
3、如果是指定大小的有参构造器,则elementData初始化为指定大小,如需扩容,则扩容当前1.5倍
3.3.4 Vector
3.3.4.1 特性
底层为elementData的对象数组
使用synchronized修饰,线程安全
3.3.4.2 源码分析add方法
结论:
1、使用无参构造器,默认初始化为10,满后,按当前的2倍扩容
2、使用有参构造器,初始化为指定大小,满后,按当前2倍扩容
3.3.5 LinkedList
3.3.5.1 特性
底层实现了双向链表和双端队列
可添加任意元素(可以重复),包括null
线程不安全,没实现同步和互斥
两个属性first(头节点)、last(尾节点)
每个节点(Node对象),属性:prev(指向前一个节点)、next(指向后一个节点)、item(真正存放数据)
使用LinkedList添加或删除数据,不用通过数组完成,效率高
3.3.5.2 源码分析add方法
3.3.6 对比选择
改查较多,使用ArrayList
增删较多,使用LinkedList
多线程,使用Vector
3.4 Set
3.4.1 遍历方法
可以使用迭代器,增强for循环,不能使用索引
添加顺序和取出顺序不一致,且元素不可以重复,可以添加一个null
取出的顺序虽然不是添加的顺序,但是固定的(源码分析!)
3.4.2 HashSet
package com.yans.java.collection.set;
import java.util.HashSet;
import java.util.Set;
public class TestHashSet {
public static void main(String[] args) {
Set set = new HashSet();
//添加元素,添加成功则返回true,否则返回false
//不能向HashSet添加重复元素/数据
System.out.println(set.add("john"));//T
System.out.println(set.add("jack"));//T
System.out.println(set.add(null));//T
System.out.println(set.add("marry"));//T
System.out.println(set.add("jack"));//F
System.out.println("Set = " + set);
set.remove("john"); //删除数据
System.out.println("Set = " + set);
//初始化对象
set = new HashSet();
set.add("lucy"); //T
set.add("lucy"); //F
//两个同名的不同的对象,属于两个不同元素
set.add(new Dog("tom")); //T
set.add(new Dog("tom")); //T
System.out.println("Set = " + set);
//重点来了
//思考:new String("davy") 两个对象是否时重复元素
//add方法源码解读分析!!
set.add(new String("davy")); //T
set.add(new String("davy")); //F
System.out.println("Set = " + set);
}
}
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
3.4.2.1 底层机制
HashMap 底层(数组+链表+红黑树)
模拟简单数组+链表
package com.yans.java.collection.set;
public class HashSetStructure {
public static void main(String[] args) {
//模拟HashSet(HashMap 底层结构 数组+链表)
//1、创建一个数组,类型时Node[]
//有些人Node数组称为table
Node[] table = new Node[16];
System.out.println("table = " + table);
//2、创建节点
Node john = new Node("john", null);
//3、将john节点加入到table的第2个元素中
table[1] = john;
System.out.println("table = " + table);
//4、创建节点
Node mary = new Node("mary", null);
//5、将mary挂载到john节点后
john.next = mary;
System.out.println("table = " + table);
//6、创建节点
Node lucy = new Node("lucy", null);
//7、将lucy挂载到mary节点后
mary.next = lucy;
System.out.println("table = " + table);
//8、将节点davy加入到数组的第3个元素中
Node davy = new Node("davy", null);
table[2] = davy;
System.out.println("table = " + table);
}
}
class Node {
Object item;
Node next; //指向下一节点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
Debug调试查看结构如下:
3.4.2.2 源码解读如何实现添加元素(hash() + equals())
结论:
1、底层是HashMap
2、扩容机制:添加一个元素时,初始化table长度为16(临界值threshold是16*加载因子0.75=12,到达临界值就扩容当前2倍,依次类推),先得到hash值(不等于hashCode),会转成->索引值
3、找到存储数据表table,看这个索引位置是否已存放有元素
4、如果没有,直接加入(相同的hashcode的key在同一条链表上)
5、如果有,调用equals方法比较(比较方法由程序员重写决定,不能简单的理解为比较字符串是否相等),如果相同,就放弃添加;如果不同,则添加到最后(如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样,并且满足下面两个条件之一:(1)准备加入的key和p指向的Node节点的key时同一个对象;(2)p指向的Node节点的key的equals和准备加入的key比较后相同)
6、扩容机制:在java8中,如果一条链表的元素个数 到达 TREEIFY_THRESHOLD(默认为8),且table的大小 大于等于 MIN_TREEIFY_CAPACITY(默认64),就会进化为红黑树(TreeNode)
7、size++:table或单条链表上加一个对象size就会+1,达到临界值12就会触发扩容
3.4.3 LinkedHashSet
3.4.3.1 底层机制
继承HashSet,实现Set
底层是LinkedHashMap,底层维护了一个数组+双向链表
根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使元素插入和遍历顺序一致
不能添加重复元素
3.4.3.2 源码解析
结论:
1、LinkedHashSet维护了一个LinkedHashMap,即底层结构为hash表和双向链表(有head和tail)
2、第一次添加时,直接将数组table扩容到16,存放的节点类型为LinkedHashMap$Entry(数组是HashMap$Node[],存放数据是LinkedHashMap$Entry类型,Entry继承HashMap.Node类)
2、每个记得有before和after属性,形成双向链表
3、计算hash值,获取索引,获取table中的位置,再equals比较(原理和HashSet一样)
4、元素插入和遍历顺序一致
四、 Map(JD8)
4.1 特性
1、存放映射关系的数据:Key-Value(双列元素)
2、Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
3、key不允许重复,当有相同key时,就会把前面的value替换成新的
4、key可以为null,value也可以为null,但是key为null的只能有一个
5、常用Stirng作为key
7、key和value存在单向一对一关联关系,即通过key能获取对于value
8、线程不安全
9、一对key-value就是一个Entry。为了方便程序员遍历,还会创建一个EntrySet,存放Entry类型数据,而一个Entry对象就有key、value,EntrySet<Entry<K,V>>,即transient Set<Map.Entry<K,V>> entrySet;
HashMap.Node实现了Map.Entry(提供了重要方法getKey和getValue)
4.2 遍历方法
containsKey():查找键是否存在
1、用keySet()取出所有key,通过增强for循环和迭代器遍历通过get(key)获取value
2、用values()方法取出所有key,通过增强for循环和迭代器遍历
3、通过entrySet()获取key-value,通过增强for循环和迭代器遍历,将entry转成Map.Entry,使用getKey()和getValue()获取
4.3 HashMap源码分析
结论:
1、扩容机制和HashSet相同
2、添加key-val时,通过key的hash值获取在数组table中的索引值,判断该索引处是否有元素,没有则直接添加;有的话就判断该元素和准备加入的key是否相等,相等则直接替换对于value,不相等时需判断时数结构还是链表结构,做出相应处理,如果发现容量不够则进行扩容。
4.4 Hashtable
继承Dictionary,实现Map
键和值都不能为null,,否则会抛异常NullPointerException
使用方法和HashMap基本相同,但Hashtable是线程安全,实现了同步synchronized
底层有数组 Hashtable$Entry
扩容机制:添加一个元素时,初始化table长度为11,临界值为8(当前长度*加载因子0.75).达到临界值则扩容到23(当前大小的2倍+1)
4.5 Properties
继承Hashtable并实现Map,使用特点和Hashtable相同
通常用于读取.properties配置文件
五、集合选型规则(JDK8)
1、先判断存储的类型(一组对象(单列),一组键值对(双列))
2、一组对象(单列)Collection
允许重复:List
增删多:LinkedList(底层维护一个双向链表)
查改多:ArrayList(底层维护Object类型可变数组)
不允许重复:Set
无序:HashSet(底层是HashMap,维护一个哈希表,底层是数组+链表+红黑树)
排序:TreeSet
插入和取出顺序一致:LinkedHashSet(底层是LinkedHashMap,数组+双向链表)
3、一组键值对(双列)Map
键无序:HashMap(维护一个哈希表,底层是数组+链表+红黑树)
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap(底层是HashMap,数组+双向链表)
读取文件:Properties
六、TreeSet
1、 使用无参构造器创建时,仍然是无序的
2、使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类),指定排序规则
3、构造器把比较对象传给底层TreeMap的属性this.comparator
七、TreeMap
1、使用无参构造器创建时,仍然是无序的
2、构造器把比较对象传给属性this.comparator
去重机制:传入一个Comparator匿名对象,使用compare方法比较,如果返回0,则认为是相同元素,就不加入;如果不传入Comparator匿名对象,默认使用你添加对象的Comparable接口的compareTo方法去重。
package com.yans.java.collection.set;
import java.util.Comparator;
import java.util.TreeMap;
public class TestTreeMap {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按字符串长度比较
//return ((String)o1).length() - ((String)o2).length();
//按照字符串大小比较
return ((String)o1).compareTo((String)o2);
}
});
treeMap.put("tom","tom");
treeMap.put("jack","jack");
treeMap.put("mary","mary");
System.out.println("treeMap = "+ treeMap);
}
}
八、Collections工具类
reverse(List):反转List中的元素顺序
shuffle(List):对List元素进行随机排序
sort(List):对List元素进行按升序排序
sort(List, Comparator):根据指定的比较方法(Comparator)进行排序
swap(List, int, int):将集合List中i处元素与j处元素互换
查找、替换
max(Collection):根据元素的自然排序返回最大的元素
max(Collection, Comparator):根据元素的指定排序(Comparator)返回最大的元素
min(Collection):根据元素的自然排序返回最小的元素
min(Collection, Comparator):根据元素的指定排序(Comparator)返回最小的元素
frequency(Collection, Object):统计集合中某对象出现的次数
copy(List dest, List src):将src中的内容复制到dest(为了完整拷贝,需要先将dest复制,大小和src.size一样)
replaceAll(List list, Object oldValue, Object newValue):使用新值替换旧值