1.集合的理解和好处
1.1集合定义
对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能
1.2数组的不足
- 长度开始时必须指定,而且一旦指定,不能更改
- 保存的必须为同一类型的元素
- 使用数组进行增加/删除元素比较麻烦
1.3集合的优点
- 可以动态保存任意多个对象,使用比较方便
- 提供了一系列方便的操作对象的方法:add、remove、set、get等
- 使用集合添加,删除新元素比较简捷
1.4和数组的区别
- 数组长度固定,集合长度不固定
- 数组可以存储基本类型和引用类型,集合只能存储引用类型(基本类型只能装箱后存储)
2.集合(Collection)的框架体系
2.1常用方法
- add(Object o):添加单个元素
- remove(Object o):删除指定元素
- contains(Object o):查找元素是否存在
- size():获取元素个数
- isEmpty():判断是否为空
- clear():清空
- addAll(Collection <? extends E> c):添加多个元素
- containsAll(Collection <?> c):查找多个元素是否都存在
- removeAll(Collection <?> c):删除多个元素
2.2迭代器(Itrator)
IDEA快捷键:itit
//haNext(); 有没有下一个元素
//next(); 获取下一个元素
//remove(); 删除当前元素
Iterator it = collection.iterator();
while(it.hasNext()){
String object = (String)it.next(); //强转
// 可以使用it.remove(); 进行移除元素
// collection.remove(); 不能用collection其他方法 会报并发修改异常
}
//当退出while循环时,iterator迭代器指向最后的元素
//如果希望再次遍历,需要重置迭代器
it =collection.iterator();
2.3增强for循环(因为无下标)
for(Object object : collection){ }
- 底层是迭代器
- 可以理解为简化版本的迭代器
IDEA快捷键:I
3.List接口和常用方法
3.1List接口基本介绍
List接口是Collection接口的子接口
- List集合类中的每个元素都有序(即添加顺序和取出顺序一致)、且可重复
- List集合当中的每个元素都有其对应的顺序索引(从零开始)
- JDK API中的List接口的实现类有:
常用的有:ArrayList、LinkedList和Vector
3.2List接口的常用方法
boolean add(E e) //Appends the specified element to the end of this list (optional operation).
void add(int index, E element) //Inserts the specified element at the specified position in this list (optional operation).
boolean addAll(Collection<? extends E> c) //Appends all of the elements in the specified collection to the end of this list, in the order that they are returned by the specified collection's iterator (optional operation).
boolean addAll(int index, Collection<? extends E> c) //Inserts all of the elements in the specified collection into this list at the specified position (optional operation).
int indexOf(Object o)//Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.
int lastIndexOf(Object o)//Returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element.
E remove(int index)//Removes the element at the specified position in this list (optional operation).
boolean remove(Object o)//Removes the first occurrence of the specified element from this list, if it is present (optional operation).
E set(int index, E element)//Replaces the element at the specified position in this list with the specified element (optional operation).
List<E> subList(int fromIndex, int toIndex)//Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive.
3.3遍历
- 使用for遍历
for(int i = 0; i < lise.size(); i++){
sout(list.get(i));
}
- 使用增强for
for(Object list: collection){ }
- 使用迭代器
Iterator it = collection.iterator();
while(it.hasNext()){
String object = (String)it.next(); //强转
// 可以使用it.remove(); 进行移除元素
// collection.remove(); 不能用collection其他方法 会报并发修改异常
}
- 使用列表迭代器 💡(注意和迭代器区别)
ListIterator li = list.listIterator();
while(li.hasNext()){
System.out.println(li.nextIndex() + ":" + li.next()); //从前往后遍历
}
while(li.hasPrevious()){
System.out.println(li.previousIndex() + ":" + li.previous()); //从后往前遍历
}
4.ArrayList底层结构和源码分析
注意事项
- 允许添加(多个)null值
- ArrayList是用数组(维护了一个Object类型的数组elementData)来实现数据存储的,
transient Object[] elementData
,transient表示瞬间,短暂的,表示该属性不会被序列化。
- ArrayList基本等同于Vector,除了ArrayList是线程不安全的,在多线程情况下,不建议使用ArrayList。
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容为10,如需要再次扩容,则扩容为elementData的1.5倍。
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
扩容机制:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
5.Vector底层结构和源码剖析
- Vector类定义的说明
- Vector底层也是一个对象数组,protected Object[] elementData
- Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
例如:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
- 在开发中,需要线程安全时,考虑使用Vector。
- 扩容:如果是无参,则默认容量是10,满后,就按两倍扩容;如果是有参,初始容量为指定大小,满后按2倍扩容。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
==注意:newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
==capacityIncrement 为自增容量。
6.LinkedList底层结构
全面说明:
- 底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重写),包括Null
- 线程不安全,未实现同步
底层操作机制:
- 底层维护了一个双向链表(它是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。)
- 维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中prev指向前一个,通过next指向后一个节点。最终实现了双向链表
- 所以linkedlist的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
7.ArrayList和LinkedList比较
底层结构 | 增删的效率 | 改查的效率 | |
ArrayList | 可变数组 | 较低、数组扩容 | 较高 |
LinkedList | 双向链表 | 较高、通过链表追加 | 较低 |
如何选择使用:
- 如果改查操作比较多,选择ArrayList;如果增删比较多,选择Linkedlist.
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList。
- 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另一个是LinkedList。
注意:两者都是线程不安全的,应在单线程环境下使用
8.Set接口和常用方法
Set接口的常用方法:
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
Set接口的遍历方式:
同Collection的遍历方式一样,因为Set接口是Collection的子接口
- 可以使用迭代器
- 增强for循环
- 不能使用索引的方式来获取
Set接口基本介绍
- 无序(添加和取出的顺序不一致),没有索引
- 取出的顺序不会再变
- 不允许重复元素,所有最大包含一个null
- 实现类有:
9.HashSet
HashSet的全面说明:
- 实现了Set接口
- HashSet实际上是HashMap,看源码:
public HashSet() {
map = new HashMap<>();
}
- 可以存放一个null
- 不保证元素是有序的,取决于hash后,再确定索引的结果
- 不能有重复元素/对象
HashSet的底层机制说明
- HashSet的底层是HashMap,HashMap的底层是数组+链表+红黑树
- 当链表和数组的规模超过一定界限时,链表就被重组为红黑树。
HashSet添加一个元素的步骤(hash()+equals())
- 底层是HashMap
- 添加一个元素时,先得到Hash值,再转换成索引值
- 找到存储数据的table,看这个索引位置是否已经存储有元素
- 如果没有,直接加入
- 如果有,调用==equals(该方法可自定义)==方法比较,如果相同,就放弃添加,如果不相同,则添加在最后
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小==>=======MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)
HashSet的扩容和转换成红黑树机制
- 第一次添加时,table数组扩容到16,临界值(treshold)是16*加载因子(loadFactor)是0.75=12
- 如果table数组使用到了临界值12(不是有12条链表,只要总共有12个元素,即使分布在少于12条链表上,就算是达到了table数组的临界值12),就会扩容到162=32(按两倍扩容),新的临界值就是32 0.75=24,依次类推
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小==>=======MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树),否则仍采用数组扩容机制
10.LinkedHashSet
- 是HashSet的子类
- 底层是一个LinkedHashMap(HashMap的子类),底层维护了一个数组+双向链表(有head和tail)
- 每一个结点有before和after属性,这样可以形成双向链表
- 根据元素的hashCode值来决定元素的存储位置,同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
- 第一次添加时,直接将table(类型:HashMap
Entry(继承了HashMap$Node)(数组多态)
- 不允许添加重复元素
11.Map接口和常用方法
Map接口实现类的特点(以HashMap为例)
- Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
- Map中的Key和Value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的Key不允许重复,原因和HashSet一样,当有相同Key的元素加入,之前的相同Key的元素会被现在加入的元素替换
- Map中的Value可以重复。
- Map的Key可以为null,value也可以为null,注意Key为null,只能有一个,value为null,可以有多个
- 常用String类作为Map的Key
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
- Map存放数据时,一对k-v是放在一个HashMap$Node中的,又因为Node实现了Entry接口,有些说法也说一对k-v就是一个Entry
- 为了方便遍历,还会创建EntrySet集合,该集合存放的元素的类型是Entry(里面有getKey和getValue方法分别获取key和value),(transient Set<Map.Entry<K,V>>) entrySet中定义的类型是Map.Entry,但是实际上存放的是HashMap$Node(因为Node实现了Entry接口,多态性,此时将Node向上转型为Map.Entry即可使用Map.Entry的getKey和getValu方法)
- 通过entrySet()方法初始化(transient Set<Map.Entry<K,V>>)entrySet
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
entrySet = new EntrySet():EntrySet是HashMap的内部类,其中定义了迭代器方法 iterator() ,故entrySet实际上没有重新创造新元素,而是通过迭代器指向原来的Node元素
- HashMap中有两个重要的方法:(Set)keySet()/(Collection )values()方法,分别为key和value返回了一个set和collection,方便管理,但是==这个set和Collection里的元素还是指向node里的元素,并没有创建新元素(因为这两个方法的底层都是使用迭代器)==)
- Map体系继承图
- Map接口常用方法
-
- put
- remove
- get
- size
- isEmpty
- clear
- containsKey
- Map的遍历方法:
-
- 先取出所有的key(通过keySet方法,该方法返回Set),再通过key取出对应的value(通过get方法),因为返回结果是set类型,所以又可以使用增强for循环和迭代器方法来遍历
- 把所有的value取出(通过values方法返回一个collection集合),因为返回结果是collection类型,所以又可以使用增强for循环和迭代器方法遍历
- 通过entrySet来获取k-v()
-
-
- Set<Map.Entry<K,V>> entrySet()
-
-
-
- 认真理解obj为什么要转型为Map.Entry!!!
-
Map map = new HashMap();
Set entrySet = map.entrySet();//该方法返回类型为Set<Map.Entry<K,V>>
for(Object obj:entrySet)
System.out.println(obj.getClass());//obj的运行时类型为HashMap$Node
Map.Entry entry = (Map.Entry)obj;//obj向上转型(因为是从运行时类型HashMap$Node转为Map.Entry,所以是向上转型)为Map.Entry(不能直接转为HashMap$Node类型,因为其访问权限为default,只能在同一个包中被使用,HashMap$Node相当于被隐藏在HashMap内部,在其他地方无法访问;Map.Entry可以被访问是因为Map是一个接口,而接口内部的成员的默认访问权限为public),此时才能调用其父接口的成员,又因为entry是由HashMap$Node向上转型得到的,所以当entry使用方法时使用的是其实现类HashMap$Node实现的方法。
System.out.println(entry.getKey()+"-"+entry.getValue());
12.HashMap
- HashMap小结
-
- 使用频率最高
- 以Key-Value对的方式来存储数据
- Key不能重复,但是值可以重复,允许null键和null值
- 如果添加相同的key,则会覆盖原来的key-val,等同于修改(看源码知道只是将新value替换原来旧value,没有替换一样的key)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式存储的
- 没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
- 底层机制(和HashSet一样)
-
- HashSet的底层是HashMap,HashMap的底层是数组+链表+红黑树
-
- 当链表和数组的规模超过一定界限时,链表就被重组为红黑树。
添加一个元素的步骤(hash()+equals())
-
- 底层是HashMap
- 添加一个key-val对时,先得到key的Hash值,再转换成索引值
- 找到存储数据的table,看这个索引位置是否已经存储有元素
- 如果没有,直接加入
- 如果有,调用equals(该方法可自定义)key方法比较,如果相同,就直接替换val,如果不相同,则需要判断是树结构还是链表结构,做出相应的处理。如果添加时发现容量不够,则需要扩容resize()。
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小==>=======MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)
- 第一次添加时,table数组扩容到16,临界值(treshold)是16*加载因子(loadFactor)是0.75=12
- 如果table数组使用到了临界值12(不是有12条链表,只要总共有12个元素,即使分布在少于12条链表上,就算是达到了table数组的临界值12),就会扩容到162=32(按两倍扩容),新的临界值就是32 0.75=24,依次类推
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小==>=======MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树),否则仍采用数组扩容机制
13.Hashtable
基本介绍
- 键和值都不能为null,否则会抛出NullPointerException
- 使用方法基本和HashMap一样
- Hashtable是线程安全的(synchronized),HashMap是线程不安全的
底层简介
- 底层有数组
Hashtable$Entry[]
初始化大小为11
- 临界值
threshold 8 =11*0.75(加载因子)
扩容机制
- 执行方法
addEntry(hash, key, value, index);
、、将k-v对封装到Entry
if (count >= threshold)
时,就进行扩容
- 按照
int newCapacity = (oldCapacity << 1) + 1;
的大小(oldCapacity * 2 + 1
)扩容
14.Properties
基本介绍
- 继承自Hashtable类并且实现了Map接口,也是使用了一种键值对的形式来保存数据
- 他的使用特点和Hashtable类似
- Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
- 说明:工作后 xxx.properties文件通常作为配置文件,这个知识点在IO流中举例
常用方法
- put
- remove
- get/getProperty
15.开发中如何选择集合实现类
主要取决于业务操作
- 先判断存储的类型(一组对象还是一组键值对)
- 一组对象:Collection接口
允许重复:List
增删多:增删多:LinkedList(底层维护了一个双向链表,所以linkedlist的元素的添加和删除,不是通过数组完成的,相对来说效率较高。)
改查多:ArrayList(底层维护了Object类型的数组)
不允许重复:Set
无序:HashSet
排序:TreeSet
插入和取出顺序一致:LinkedHashSet
- 一组键值对:Map
键无序:HashMap(底层是哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树)
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件 Properties
16.TreeSet
基本介绍
- TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet, Cloneable, java.io.Serializable接口。
- TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。
- TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
- TreeSet 实现了Cloneable接口,意味着它能被克隆。
- TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。
- TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
- TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。
- 另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。
- 底层是TreeMap,TreeSet中含有一个"NavigableMap类型的成员变量"m,而m实际上是"TreeMap的实例"。
- 不允许添加null值
总结:
(01) TreeSet实际上是TreeMap实现的。当我们构造TreeSet时;若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
(02) TreeSet是非线程安全的。
(03) TreeSet实现java.io.Serializable的方式。当写入到输出流时,依次写入“比较器、容量、全部元素”;当读出输入流时,再依次读取。
- 当comparator判断两个对象相等时,key不变,value被替换
17.TreeMap
- TreeMap的实现是红黑树算法的实现
- 若使用不带参数的构造函数,则TreeMap的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
- 在自然顺序比较中,需要让被比较的元素实现Comparable接口,否则在向集合里添加元素时,时报:"java.lang.ClassCastException: com.huangqiuping.collection.map.SortedTest cannot be cast to java.lang.Comparable"异常;
- 不允许添加null值
18.Collections工具类
Collections提供以下方法对List进行排序操作
void reverse(List list):反转
void shuffle(List list),随机排序
void sort(List list),按自然排序的升序排序
void sort(List list, Comparator c);定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j),交换两个索引位置的元素
查找,替换操作
Object binarySearch(List list, Object key), 对List进行二分查找,返回索引,注意List必须是有序的
Object max(Collection coll),根据元素的自然顺序,返回最大的元素。
Object max(Collection coll, Comparator c),根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
Object min(Collection coll),根据元素的自然顺序,返回最小的元素。
Object min(Collection coll, Comparator c),根据定制排序,返回最小元素,排序规则由Comparatator类控制。
void fill(List list, Object obj),用元素obj填充list中所有元素
int frequency(Collection c, Object o),统计元素出现次数
int indexOfSubList(List list, List target), 统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素。
public static void copy(List<? super T> dest,List<? extends T> src)将src中的内容复制到dest中