集合的框架体系
Java 的集合类很多,主要分为两大类,如图
- Collection的实现类

- Map的实现类

说明:
- 集合主要是两组(单列集合 , 双列集合)
- Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
- Map 接口的实现子类 是双列集合,存放的== K-V==
Collection 接口
Collection 接口的特点
- Collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类, (List集合类)可以存放重复的元素,有些(Set集合类)不可以
- 有些Collection的实现类, (List集合类)是有序的,有些(Set集合类)是无序的
- Collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的
Collection接口常用方法
- add:添加单个元素
- remove:删除指定元素
- contains:查找元素是否存在
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清空
- addAll:添加多个元素
- containsAll:查找多个元素是否都存在
- removeAll:删除多个元素
Collection接口遍历方式
迭代器遍历
基本介绍

- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
- Iterator的结构[看下图]
- Iterator仅用于遍历集合,Iterator本身并不存放对象。


注意:
- 在while循环语句中使用remove()方法只能使用iterator.remove();即迭代器本身的remove方法,使用接口的remove方法会抛异常
- 快捷键,快速生成 while => itit
- 显示所有的快捷键的的快捷键 ctrl + j
- 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
- 如果希望再次遍历,需要重置我们的迭代器
增强for
增强for循环,可以代替iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。
- 基本语法
for(元素类型 元素名 : 集合名或数组名){
访问元素
}
提示:
快捷键:大写的i
List接口

基本介绍
List接口是Collection接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
- JDK API中List接口的实现类如上图,常用的有:ArrayList , LinkedList和Vector
常用方法
- void add(int index, Object ele):在 index 位置插入 ele 元素
- boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
- Object get(int index):获取指定 index 位置的元素
- int indexOf(Object obj):返回 obj 在集合中首次出现的位置
- int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
- Object remove(int index):移除指定 index 位置的元素,并返回此元素
- Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换.
- List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合[前闭后开)
三种遍历方式 [ArrayList, LinkedList,Vector]
- 方式一:使用iterator
Iterator iter = col.iterator();
while(iter.hasNext()){
Object o = iter.next();
} - 方式二:使用增强for
for(Object o:col){
} - 方式三:使用普通for
for(int i=0;i<list.size();i++){
Object object = list.get(i);
System.out.println(object);
}
说明:使用LinkedList完成 使用方式和ArrayList 一样
练习
冒泡排序
public class ListExercise02 {
public static void main(String[] args) {
//List list = new ArrayList();
List list = new LinkedList();
//List list = new Vector();
list.add(new Book("红楼梦", "曹雪芹", 100));
list.add(new Book("西游记", "吴承恩", 10));
list.add(new Book("水浒传", "施耐庵", 19));
list.add(new Book("三国", "罗贯中", 80));
//list.add(new Book("西游记", "吴承恩", 10));
//如何对集合进行排序
//遍历
for (Object o : list) {
System.out.println(o);
}
//冒泡排序
sort(list);
System.out.println("==排序后==");
for (Object o : list) {
System.out.println(o);
}
}
//静态方法
//价格要求是从小到大
public static void sort(List list) {
int listSize = list.size();
for (int i = 0; i < listSize - 1; i++) {
for (int j = 0; j < listSize - 1 - i; j++) {
//取出对象 Book
Book book1 = (Book) list.get(j);
Book book2 = (Book) list.get(j + 1);
if (book1.getPrice() > book2.getPrice()) {//交换
list.set(j, book2);
list.set(j + 1, book1);
}
}
}
}
}
class Book{}
ArrayList
基本介绍
- permits all elements,including null,ArrayList 可以加入null ,并且多个
- ArrayList是由数组来实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是==线程不安全(执行效率高)==看源码.
在多线程情况下,不建议使用ArrayList
ArrayList扩容机制源码分析
先说结论,再分析源码:
- ArrayList中维护了一个Object类型的数组elementData. [debug看源码]
transient object[ ] elementData; //transient表示瞬间,短暂的,表示该属性不会被序列化 - 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
- 如果使用的是指定大小的(有参)构造器,则初始elementData容量为指定大小,如果需要扩容
则直接扩容elementData为1.5倍。
示意图:


案例演示:
字节码:
ArrayList list = new ArrayList(8);
//使用for给list集合添加 1-10数据
for (int i = 1; i <= 10; i++) {
list.add(i);
}
debug源码:
- 初始化一个指定长度的对象数组
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- 扩容:list.add()[debug]

扩容关键语句:
- calculateCapacity方法中的:(判断数组为空,初始化大小为10)
return Math.max(DEFAULT_CAPACITY, minCapacity);//默认值=10
- grow方法中的:(若加入后的(所需)容量小于当前数组大小,1.5倍扩容)
int newCapacity = oldCapacity + (oldCapacity >> 1);
Vector
- Vector类的定义说明

- Vector底层亦是一个对象数组,protected Object[] elementData;
- Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
public synchronized E get (int index){
if (index>=elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
} - 在开发中,需要线程同步安全时,考虑使用Vector
Vector扩容机制源码分析
案例演示:
字节码:
Vector vector = new Vector();
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
Vector vector=new Vector();
无参构造器=>this(10)=>单参构造器=>this(初始容量,单次扩充容量)=>双参构造器

扩容解读:

关键:
扩容机制:
单参或无参构造器默认隐藏参数capacityIncrement(解释为容量扩充)为0,若指定该参数不为0,则扩容机制发生改变,由原来的默认2倍扩容改为增加capacityIncrement容量
grow方法中:int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
Vector 和 ArrayList 的比较

LinkedList
介绍:
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
说明:
- LinkedList底层维护了一个双向链表.
- LinkedList中维护了两个属性first和last ,分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prey, next, item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

底层操作机制
CRUD
添加元素

即处理链表尾结点:
声明初始化新的结点=>将其定义为尾结点=>若老尾结点为空(即链表为空)则将其定义为头结点=>若老尾结点不为空则挂在老的next上
删除元素
linkedList.remove();//默认删除第一个结点

处理链表头结点:
提取待删结点item=>清空待删结点的item和next(垃圾处理)=>下一个结点变为头结点=>若下一个结点为空,则头尾皆空=>否则清空它的pre=>返回删除结点的item
ArrayList 和 LinkedList 的比较

如何选择ArrayList和LinkedList:
- 如果我们改查的操作多,选择ArrayList
- 如果我们增删的操作多,选择LinkedList
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList,也就是说,要根据业务来进行选择
Set 接口
介绍:
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个null
- JDK API中Set接口的实现类有:

注意:取出的顺序虽然不是添加的顺序,但是他是固定的
常用方法
常用方法和 Collection 接口一样.
两种遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
- 可以使用迭代器
- 增强for
- 不能使用索引的方式来获取.
HashSet(重点)
扩容机制源码分析
HashSet无参构造器(也可指定初始容量参数)=>调用HashMap构造器(数组table默认为null)=>加载因子=默认值0.75初始化

add方法源码:

关键语句:
hashSet.add=>map.put=>putval(该方法包含元素添加语句:newNode那两句)
说明:
其中putval方法形参调用hash方法,hash方法体调用hashCode方法
而putval方法体中关键方法为==resize()==方法即扩容方法:具体表现为:
当table为空或size大小大于阈值threshold时,调用resize方法扩容table
两种情况区别:
-
数组为空时,newCap = DEFAULT_INITIAL_CAPACITY;
//默认初始容量DEFAULT_INITIAL_CAPACITY = 1 << 4;
-
数组长度大于0时,newCap = oldCap << 1;

扩容总结(前提:索引对应的链表没有进化成红黑树):
正常情况(key的哈希值不同):元素添加到不同索引,table从null开始,第一次扩容到16,当size(元素个数)超过阈值即(0.75*length),直接2倍扩容;
同哈希值情况:元素添加到同一链表上,当该链表结点超过8个开始调用treeifyBin方法每次添加元素到该链表都2倍扩容,当数组容量扩到64以上直接树化;
图文说明:

注意:为达到添加对象同哈希值的目的,案例中的标准类的可方法重写equals和hashCode
LinkedHashSet
介绍:
- LinkeciHashSet是HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashSet不允许添重复元素

底层机制
构造器:
Set set=new LinkedHashSet();
构造器底层走的是形参(initialCapacity)为16的HashSet构造器,再走LinkdedHashMap的构造器
添加元素机制
原则和hashSet一样
但是在走到HashMap层的putval方法中,添加结点后将结点连接成双向链表的语句在末尾的afterNodeInsertion(evict);
总结:
-
LinkedHashSet 加入顺序和取出元素/数据的顺序一致
-
LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
-
LinkedHashSet 底层结构 (数组table+双向链表)
-
添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
-
数组是 HashMap$Node[] 存放的元素/数据
是 LinkedHashMap$Entry类型
-
继承关系是在内部类完成.[源码如下]
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
Map
Map 接口实现类的特点
注意:这里以JDK8为例
介绍:
- Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
- Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet一样,前面分析过源码.
- Map中的value可以重复
- Map的key可以为null, value也可以为null,注意key为null,只能有一个,value为null,可以多个.
- 常用String类作为Map的key
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
- Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的,又因为Node实现了Entry接口,有些书上也说一对k-v就是一个Entry[如图]

Map 接口常用方法
- put:添加或替换(key相同)
- remove:根据键删除映射关系
- get:根据键获取值
- size:获取元素个数
- isEmpty:判断个数是否为 0
- clear:清除 k-v
- containsKey:查找键是否存在
Map 接口遍历方法

4种Map遍历方法如下:
- containsKey:查找键是否存在
- keySet:获取所有的键
- entrySet:获取所有关系
- values:获取所有的值
总结:本质上三种方法得到的集合都是实现了Set接口的集合(由于value可以重复所以其实现的是Collection接口不是Set接口),其中entrySet中的元素,更是使用了多态的方式创建的,只是为了让其使用父接口的两个方法getkey,getvalue
位置说明:
- Set set = map.entrySet();//HashMap$EntrySet
- Set set1 = map.keySet();//HashMap内部类
- Collection values = map.values();//HashMap内部类
具体表现为:
- k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
- k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry对象就有k,v EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
- entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node,这是因为 static class Node<K,V> implements Map.Entry<K,V>
- 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法K getKey(); V getValue();
具体遍历方法:三种内部类(接口)集合的遍历分别迭代器和增强for:3*2=6种
HashMap
总结:
- Map接口的常用实现类:HashMap, Hashtable和Properties
- HashMap是Map接口使用频率最高的实现类.
- HashMap是以key-val对的方式来存储数据(HashMap$Node类型)
- key不能重复,但是值可以重复,允许使用null键和null值。
- 如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key不会替换,val会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的(jdk8的hashMap底层数组+链表+红黑树)
- HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
底层机制及源码剖析

扩容机制:[和HashSet相同]
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75
- 当添加key-val时,通过key的哈希值得到在table的索引.然后判断该索引处是否有元素,如果没有元素直接添加.如果该索引处有元素,继续判断钙元素的key和准备加入的key是否相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理.如果添加时发现容量不够,则需要扩容.
- 第1次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)
- 以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,以此类推.
- 在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
添加机制(源码)
添加元素(key不同)的原理同hashSet原理一样,key相同则替换value,
具体操作由HashMap层的putVal方法中的末尾语句实现:
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
}
扩容机制
初始容量为0(无参构造器),第一次由resize方法扩容到16,再由临界值2倍扩容或树化
Hashtable

分析源码:
- Hashtable的put方法中健壮性判断,value为空直接抛空指针异常,而在调用key的方法时,hashCode方法没有重写,用的是Object类的hashCode方法,会抛空指针异常,前面HashMap的hashCode方法重写过:三元运算,key为null返回0
- 调用put方法=>Hashtable的put方法(计算hash=hashCode)(key相同则替换value)=>put中调用addEntry方法(添加元素)=>rehash(是否扩容):int newCapacity = (oldCapacity << 1) + 1;
替换语句:entry.value = value;
添加语句:tab[index] = new Entry<>(hash, key, value, e);
扩容机制
构造器:
- 无参(this10)=>构造Entry<>[initialCapacity]的数组
无参,默认赋值10;有参赋值0,默认改为1;
Hashtable 和 HashMap 对比

Properties
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据。
- 他的使用特点和Hashtable类似
- Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象并进行读取和修改
- 说明:工作后~.properties文件通常作为配置文件,这个知识点在io流举例,
*读写Properties配置文件
https://blog.youkuaiyun.com/jason_bone_/article/details/122871488
TreeSet&TreeMap
TreeSet

底层维护的是类型为TreeMap的静态内部类(Entry<K,V>)对象的链表
无参构造器:自然排序
有参构造器(比较器:匿名内部类):定制排序

说明:TreeSet的有参构造器将比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator
添加元素:
treeSet.add()=>TreeMap.put()=>内部调用比较器的重写的compare方法给key找位置然后加入到链表:比较结果为负则新结点放左边,结果为0即相同则不添加

重写案例:
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//下面 调用String的 compareTo方法进行字符串大小比较
//如果老韩要求加入的元素,按照长度大小排序
//return ((String) o2).compareTo((String) o1);
return ((String) o1).length() - ((String) o2).length();
}
});
TreeMap
和treeSet原理相同,TreeSet中默认值value=0,比较key,key相同则替换value
关键句:return t.setValue(value);
总结
开发中如何选择集合实现类
- 先判断存储的类型(一组对象[单列]或一组健值对[双列])
- 一组对象[单列]:Collection接口
- 允许重复:List
- 增删多:LinkedList [底层维护了一个双向链表]
- 改查多:ArrayList [底层维护Object类型的可变数组]
- 不允许重复:Set
- 无序:HashSet [底层是HashMap,维护了一个哈希表即(数组+链表+红黑树)]
- 排序:TreeSet
- 插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
- 允许重复:List
- 一组键值对[双列]:Map
- 键无序: HashMap [底层是:哈希表jdk7:数组十链表,jdk8:数组+链表十红黑数]
- 键排序:TreeMap
- 键插入和取出顺序一致: LinkedHashMap
- 读取文件 Properties
补充
Collections工具类
说明:
- Collections是一个操作Set,List和Map等集合的工具类
- Collections 中提供了一系列静态方法对集合元素进行排序,查询和修改等操作
API方法:
- reverse(List):反转List中元素的顺序
- shuffle(List):对List集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List集合元素按升序排序
- sort(List, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
- swap(List, int, int):将指定list集合中的i处元素和j处元素进行交换
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object)
- void copy(List dest,List src):将src中的内容复制到dest中
ArrayList dest = new ArrayList();
//为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
for (int i = 0; i < list.size(); i++) {
dest.add(null);//""
}
//拷贝
Collections.copy(dest,list);
System.out.println("拷贝出dest="+dest);
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
Arrays工具类
-
通过字符串的形式打印数组public static String toString(int[] a)
-
通过原来的数组复制指定长度新数组public static int[] copyOf(int[] original,int newLength)
-
针对数组进行排序public static void sort(int[] a)
自然顺序比较器Comparable< T >
定制顺序比较器Comparator< T >
1089

被折叠的 条评论
为什么被折叠?



