
目录
一、集合框架概述
二、集合框架的继承体系
单列集合继承体系图 :
双列集合继承体系图 :
三、Collection接口
常用方法
四、Iterator接口
Iterator接口的抽象方法
获取迭代器接口实现类
迭代器遍历Collection集合
迭代器的实现原理
并发修改异常
如何解决并发修改异常?
集合存储自定义对象并迭代
自定义Person类
主体
五、List接口
List接口的特点
List集合特有方法(索引)
List集合的特有迭代器
List接口的实现类的数据结构
ArrayList(动态数组)
ArrayList集合特点
ArrayList源码解析
ArrayList类成员变量
ArrayList集合类的构造方法
ArrayList集合类的方法——add()
LinkedList(链表)
LinkedList集合的特点
LinkedList集合特有方法
LinkedList源码分析
LinkedList集合的成员变量
LinkedList集合的成员内部类Node(节点)
LinkedList集合的方法
六、Set集合
Set集合的遍历
Set集合实现类——HashSet
对象的哈希值
String类的哈希值
哈希值的相关问题
哈希表的数据结构
哈希表特征
树化条件
哈希表存储对象的过程
哈希表存储自定义对象
哈希表源码
构造方法(无参数)
HashMap类的成员变量
HashMap内部类Node
Set集合存储方法add(),底层调用HashMap的put()方法
哈希相关问题
红黑树(Red-Black-Tree)
TreeSet集合使用
TreeSet集合,底层是红黑树结构,依赖于TreeMap的实现
TreeSet存储自定义对象
LinkedHashSet集合
Collections工具类
Map集合
Map接口方法
Map集合的遍历
一
二
三
HashMap
LinkedHashMap
Hashtable集合类
Vector集合类
TreeMap集合
Properties
一、集合框架概述
JDK1.2版本后,出现集合框架,到JDK1.5后,大幅度优化
- 集合本质 : 存储对象的容器
- 集合和数组的区别与共同点
- 集合、数组都是容器,都可以存储数据
- 集合只能存储引用数据类型,不存储基本数据类型
- 数组既可以存储引用数据类型,也可以存储基本数据类型
- 数组定长,集合变长
| 方法的定义 | 方法作用 |
|---|---|
| boolean add(E) | 元素添加到集合 |
| void clear() | 清空集合容器中的元素 |
| boolean contains(E) | 判断元素是否在集合中 |
| boolean isEmpty() | 判断集合的长度是不是0,是0返回true |
| int size() | 返回集合的长度,集合中元素的个数 |
| boolean remove(E) | 移除集合中指定的元素,移除成功返回true |
| T[] toArray(T[] a) | 集合转成数组 |
Iterator接口的抽象方法迭代器接口Iterator,为集合进行遍历,迭代器技术是Collection集合的通用遍历方式
- boolean hasNext() : 判断集合中是否有下一个可以遍历的元素,如果有返回true
- E next() : 获取集合中的下一个元素
- void remove() : 移除遍历到的元素
迭代器的目的 : 遍历集合
集合的顶层接口之一的Collection接口中定义了方法 : 方法的名字就是iterator(),返回值是Iterator接口类型,返回的是Iterator接口实现类的对象
Iterator iterator();
迭代器遍历Collection集合
Iterator
迭代器的实现原理
并发修改异常每个集合容器,内部结构不同(单列集合、双列集合),但是迭代器都可以进行统一的遍历实现
结论 : 迭代器是隐藏在集合的内部的,提供公共的访问方式,Iterator接口
产生原因 : 在迭代器遍历集合的过程中,使用了集合的功能,改变了集合的长度造成
Collection chest = new ArrayList<>();
chest.add("灰二");
chest.add("翔阳");
chest.add("灰二");
chest.add("翔阳");
chest.add("灰二");
Iterator it = chest.iterator();
while (it.hasNext()){
//下面这一行代码会抛出ConcurrentModificationException异常
//ConcurrentModificationException : 并发修改异常
String obj = it.next();
if(obj.equals("翔阳")){
chest.remove(obj);
}
}
System.out.println(chest);
如何解决并发修改异常?
使用Iterator接口自带的remove()方法
Collection chest = new ArrayList<>();
chest.add("灰二");
chest.add("翔阳");
chest.add("灰二");
chest.add("翔阳");
chest.add("灰二");
Iterator it = chest.iterator();
while (it.hasNext()){
//下面这一行代码会抛出ConcurrentModificationException异常
//ConcurrentModificationException : 并发修改异常
String obj = it.next();
/*if(obj.equals("翔阳")){
chest.remove(obj);
}*/
if(obj.equals("翔阳")){
it.remove();
}
}
System.out.println(chest);
集合存储自定义对象并迭代
自定义Person类
private String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person,"+"name : "+name+",age : "+age;
}
主体
Collection chest = new ArrayList<>();
Person p1 = new Person("李明",20);
Person p2 = new Person("鲍勃",21);
Person p3 = new Person("玛丽",19);
chest.add(p1);
chest.add(p2);
chest.add(p3);
Iterator iterator = chest.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
五、List接口
List接口的特点
- 接口的实现类具有索引
- 接口的元素允许重复
- 接口的元素有序
- 有序 : 存入元素和取出元素的顺序一致
- List接口的所有实现子类都具有三个特点
List接口中的方法 listIterator() 返回迭代器,迭代器的接口是ListIterator,集合的专用迭代器.
- ListIterator迭代器接口的方法
- boolean hasNext()
- E next()
- boolean hasPrevious() 判断集合中是否有上一个元素,反向遍历
- E previous() 取出集合的上一个元素
List接口的实现类的数据结构List集合特有的迭代器遍历集合方式和普通迭代器遍历集合的方式相同
-
数组
-
链表
ArrayList类实现接口List,ArrayList具备了List接口的特性 (有序,重复,索引)
-
ArrayList集合底层的实现原理是数组,大小可变 (存储对象的时候长度无需考虑).
-
数组的特点 : 查询速度快,增删慢.
-
数组的默认长度是10个,每次的扩容是原来长度的1.5倍.
-
ArrayList是线程不安全的集合,运行速度快.
private static final int DEFAULT_CAPACITY = 10; //默认容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空数组
transient Object[] elementData; //ArrayList集合中的核心数组
private int size; //记录数组中存储个数
ArrayList集合类的构造方法private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //数组扩容的最大值
无参构造器 :
本地数组变量指向空数组
//无参数构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //数组没有长度
有参构造器 :
根据自己传入的参数构造相应的数组
//有参数的构造方法
public ArrayList(int 10) {
if (initialCapacity > 0) {
//创建了10个长度的数组
this.elementData = new Object[10];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
ArrayList集合类的方法——add()
new ArrayList<>().add("abc"); //集合中添加元素
public boolean add("abc") {
//检查容量 (1)
ensureCapacityInternal(size + 1);
//abc存储到数组中,存储数组0索引,size计数器++
elementData[size++] = "abc";//数组扩容为10
return true;
}
如果在创建ArrayList集合对象时,没有给定数集合初始容量,集合变量会指向空数组;当首次添加元素时,会自动使用默认容量创建集合对象(默认容量 10 )
//检查集合中数组的容量, 参数是1
private void ensureCapacityInternal(int minCapacity = 1) {
//calculateCapacity 计算容量,方法的参是数组 , 1
// ensureExplicitCapacity (10) 扩容的
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算容量方法, 返回10
private static int calculateCapacity(Object[] elementData, int minCapacity = 1) {
//存储元素的数组 == 默认的空的数组 构造方法中有赋值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//返回最大值 max(10,1)
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//扩容
private void ensureExplicitCapacity(int minCapacity = 10) {
modCount++;
// 10 - 数组的长度0 > 0
if (minCapacity - elementData.length > 0)
//grow方法(10) 数组增长的
grow(minCapacity);
}
当集合不能再添加元素时,会自动进行扩容 *** 作,扩容大小为原来的1.5倍
//增长的方法,参数是(10)
private void grow(int minCapacity = 10) {
//变量oldCapacity保存,原有数组的长度 = 0
int oldCapacity = elementData.length; // 0
//新的容量 = 老 + (老的 / 2)
int newCapacity = oldCapacity + (oldCapacity >> 1);// 0
// 0 - 10 < 0 新容量-计算出的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; //新容量 = 10
//判断是否超过最大容量
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);
}
LinkedList(链表)
LinkedList集合的特点
LinkedList类实现接口List,LinkedList具备了List接口的特性 (有序,重复,索引)
- LinkedList底层实现原理是链表,双向链表(双端队列)
- LinkedList增删速度快
- LinkedList查询慢
- LinkedList是线程不安全的集合,运行速度快
集合是链表实现,可以单独 *** 作链表的开头元素和结尾元素
-
void addFirst(E e) 元素插入到链表开头
-
void addLast(E e) 元素插入到链表结尾
-
E getFirst() 获取链表开头的元素
-
E getLast() 获取链表结尾的元素
-
E removeFirst() 移除链表开头的元素
-
E removeLast() 移除链表结尾的元素
-
void push(E e)元素推入堆栈中
-
E pop()元素从堆栈中d出
transient int size = 0; //集合中存储元素个数计数器
transient Node
first; //第一个元素是谁
LinkedList集合的成员内部类Node(节点)transient Node
last; //最后一个元素是谁
//链表中,每个节点对象
private static class Node {
E item; //我们存储的元素
Node next; // 下一个节点对象
Node prev; // 上一个节点对象
//构造方法,创建对象,传递上一个,下一个,存储的元素
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList集合的方法
add() : 添加元素
//添加元素 e 存储元素 abc
//再次添加元素 e
void linkLast(E "abc") {
//声明新的节点对象 = last
final Node l = last; // l = null l "abc"节点
//创建新的节点对象,三个参数, 最后一个对象,"abc", 上一个对象null
final Node newNode = new Node<>(l, e, null);
//新节点赋值给最后一个节点
last = newNode;
if (l == null)
//新存储的几点赋值给第一个节点
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
get() : 获取元素
//集合的获取的方法
//index是索引, size 长度计数器
Node node(int index) {
//索引是否小于长度的一半,折半思想
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
六、Set集合
Set集合的遍历Set集合是接口,继承Collection接口,Set接口不能存储重复元素
Set集合的所有实现类都具有上述特征
Set集合中的方法和父接口Collection集合中的方法一致
Set集合实现类——HashSetSet集合的遍历使用 : 迭代器遍历(Collection接口都可以使用Iterator迭代器对象迭代)
-
HashSet集合类的特点 :
-
实现Set接口,底层调用的是HashMap集合(只使用,key存储数据)
-
HashSet的底层实现是哈希表(数组 + 红黑树)
-
HashSet不保证迭代顺序,存储和取出元素的数据可能不同
-
线程不安全,运行速度快
-
String类的哈希值Object类是所有类的顶级父类
Object类中的hashCode()方法
public native int hashCode(); // C++语言编写,不开源
: 此方法返回的int值是虚拟地址,与计算机实际存储地址没有关联
返回值的格式 : [I@1b6d3586 @后是十六进制的哈希值
字符串String类重写了hashCode(),自定义了哈希值,哈希值的计算方法 :
h = 31 * 上一次的计算结果 + 字符数组中元素的ASCII码值(h初始值为0)
*31 的目的,减少相同哈希值的计
哈希值的相关问题字符串内容不一样,但是哈希值是有可能相同
例 : 通话 和 重地
哈希表的数据结构两个对象A,B
两个对象哈希值相同,equals方法一定返回true吗?
不一定
equals方法返回true,两个对象哈希值相同?
哈希值一定相同
结论 : 两个对象哈希值相同,不要求equals方法返回true;equals方法返回true,则哈希值一定相同
哈希表特征数组 + 链表 + 红黑树
-
哈希表底层数组长度默认是16,每次扩容是原来的2倍
-
加载因子0.75f(数组中元素存储的个数到达长度的75%时,就会对数组进行扩容)
- 数组长度大于等于64
- 链表长度大于等于8,该条链表进行红黑树化
- 确保存入的元素是引用类型,(自动装箱)
- 哈希表中将Object类的hashCode方法进行了封装+该进,通过对象的hash值计算数组上的索引
- 检查该位置的链表(总结 : hash值 + equals)
- 先检查hash值,如果hash值不同肯定不是重复元素,可以继续向后检查
- 如果hash值相同,使用equals方法检查对象内容是否相等,如果相等则将原来的对象进行覆盖,如果不相同,继续向后检查,直至为空是,创建新的节点加入链表
总结 : 哈希表存储对象去重的真正含义是覆盖,而且去重指的是内容去重,而不是地址去重
哈希表存储自定义对象自定义对象要使用哈希表存储时 :
需要重写hash()和 equals()方法
public class Student {
private int age;
private String name;
public Student(){}
public Student( String name,int age) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = age;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
哈希表源码
HashSet集合本身不具有任何功能,内部调用了双列集合HashMap的对象
- 构造方法(无参数)
public HashSet() { map = new HashMap<>(); } - HashMap类的成员变量
//哈希表数组的初始化容量,16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子
static final int TREEIFY_THRESHOLD = 8;//阈值,转红黑树
static final int UNTREEIFY_THRESHOLD = 6;//阈值,解除红黑树
static final int MIN_TREEIFY_CAPACITY = 64;//阈值,转红黑树
哈希表转换成为红黑树的两个条件 :
(1)数组长度大于等于64
(2)链表长度大于等于8
解除红黑树化的条件 :
(1)链表长度小于等于6
- HashMap内部类Node
//节点
static class Node implements Map.Entry {
final int hash; //对象哈希值
final K key; //存储的对象
V value; //使用Set的集合,value没有值
Node next; //链表的下一个节点
}
- Set集合存储方法add(),底层调用HashMap的put()方法
HashMap存储的是键值对(key—value),而在Set集合中仅仅使用key来存储元素
//HashMap存储对象的方法put,Key存储的元素,V是空的对象
public V put(K key, V value) {
//存储值,传递新计算哈希值,要存储的元素
return putVal(hash(key), key, value, false, true);
}
//传递存储的对象,再次计算哈希值
//尽量降低哈希值的碰撞
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//存储值,重写计算的哈希值,要存储值
final V putVal(int hash, K key, V value, boolean false,
boolean true) {
//Node类型数组, Node类型数组 n, i
Node[] tab; Node p; int n, i;
//tab =Node[]=null
if ((tab = table) == null || (n = tab.length) == 0){
//n=赋值为 tab数组=resize()方法返回数组,默认长度的数组16
n = (tab = resize()).length;// 16
//数组的长度-1 & 存储对象的哈希值,确定存储的位置
//判断数组的索引上是不是空的
if ((p = tab[i = (n - 1) & hash]) == null)
//数组索引 赋值新的节点对象,传递计算的哈希值,存储的对象
tab[i] = newNode(hash, key, value, null);
else{
//数组的索引不是空,要存储的对象,已经有了
//判断已经存在的对象,和要存储对象的哈希值和equals方法
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//遍历该索引下的链表,和每个元素比较hashCode和equals
}
}
}
总概述 :
(1)确保哈希表已经存在(2)添加元素 : 使用hash值判断 + equals()方法
- 哈希相关问题
-
JDK7没有转红黑树
-
JDK8转成红黑树
-
转成树的两个参数
-
当一个数组中存储的链表长度>=8 转树
-
数组的整体长度超过64
-
-
树转回链表
-
链表的长度 <=6
-
-
-
JDK7元素采用头插法,JDK8元素采用尾插法
-
- 红黑树(Red-Black-Tree)
TreeSet集合使用
二叉树,本质就是链表
查询速度快
每个一个节点,只有两个子节点,左和右
树长偏了
自然平衡二叉树
二叉树的基础上,改进,保证树是平衡的
红黑树
每个节点有颜色,要么红,要么是黑
根节点必须是黑色
叶子节点必须是黑色
变量表示颜色,true黑色,false红色
TreeSet集合,底层是红黑树结构,依赖于TreeMap的实现TreeSet存储自定义对象红黑树的特点 : 查找速度快,线程不安全
额外特点 : 可以对存储到红黑树的元素进行排序,元素的自然顺序 abcd.. 字典顺序
LinkedHashSet集合TreeSet集合如果存储自定义对象,对自定义的类的要求是 :
实现java.lang.Comparable接口,重写方法compareTo()方法
Collections工具类底层数据结构是哈希表,继承HashSet集合
LinkedHashSet数据是双向链表,有序的集合,存储和取出的顺序一致
-
java.util.Collection集合的顶级接口
-
java.util.Collections *** 作集合的工具类
-
工具类的方法全部静态方法,类名直接调用
-
主要 *** 作Collection系列的单列集合,少部分功能可以 *** 作Map集合
-
| 名称 | 作用 |
| Collections.sort(集合),无返回值 | 排序 |
| Collections.reverseOrder(集合) ,无返回值 | 逆序 |
| Collections.shuffle(集合),无返回值 | 随机交换 |
| Collections.binarySearch(集合, 目标),有返回值索引 | 二分查找 |
Map集合
Map
K : 存储键的数据类型
V : 存储值的数据类型
键 - 值 : 对应关系,通过键可以得到值
Java中,将这种关系封装成了一个entry对象
| key(键) | value(值) |
| 0 | "灰二" |
| 1 | "翔阳" |
Map集合的遍历 一
- V put(K,V)存储键值对,存储重复元素,覆盖之前的元素,并放回之前的元素
- V get(K)通过键得到对应的值,没有这个键返回null
boolean containsKey(K)判断集合是否包含这个键,包含返回true
boolean containsValue(V)判断集合是否包含这个值,包含返回true
int size() 返回集合长度,Map集合中键值对的个数
V remove(K)移除指定的键值对,返回被移除之前的值
Collection
values() Map集合中的所有的值拿出,存储到Collection集合
- 实现思想 : 键找值
- Map接口定义了方法 keySet() 所有的键,存储到Set集合
- 遍历Set集合
- 取出Set集合元素 Set集合的元素是Map集合的键
- Map集合方法get()传递键获取值
- 总结 : keySet() + 迭代器遍历 + get(K) 对Map集合遍历
- 实现思想 : 键值对应关系
- Map接口的方法 Set< Map.Entry
> entrySet() -
方法返回Set集合,集合中存储的元素
-
存储的是Map集合中,键值对映射关系的对象 , 内部接口 Map.Entry
-
-
-
遍历Set集合
-
取出Set集合中的元素
-
元素类型 : Map.Entry接口对象
-
接口的方法 : getKey() ,getValue()
-
- Map接口的方法 Set< Map.Entry
- 实现思想 : 遍历值
- Map接口定义了方法 values() 所有的值,存储到Collection集合
- 遍历Collection集合
LinkedHashMap
- HashMap集合特点
是哈希表结构
保证键唯一性,用于键的对象,必须重写hashCode,equals方法
线程不安全集合,运行速度快
集合运行使用null,作为键或者值
Hashtable集合类LinkedHashMap继承HashMap实现Map接口,LinkedHashMap底层实现原理是哈希表,双向链,存取有序. 其它的特性和父类HashMap一样
Vector集合类Map接口的实现类Hashtable, Hashtable类诞生于JDK1.0版本, Map接口诞生于JDK1.2版本. Hashtable类从JDK1.2开始,改进为实现Map接口
Hashtable类的特点
底层数据结构是哈希表
线程安全的,运行速度慢,被更加先进的HashMap取代
不允许null值,null键, 存储null直接抛出空指针异常
TreeMap集合List接口的实现Vector,命运和Hashtable一样
Vector类的特点
底层实现结构是数组
数组的默认容量是10,每次扩容是原来的长度*2
线程安全,运行速度慢,被ArrayList取代
Properties
- TreeMap集合的特点
底层实现是红黑树结构 (添加查询速度比较快)
存储到TreeMap中元素,对键进行排序
排序依据 :
对象的自然顺序,作为键的对象,实现了接口Comparable
自己提供比较器,实现接口Comparator,优先级高
线程不安全的,运行速度快
Properties集合特点
继承Hashtable,实现Map接口
底层是哈希表结构
线程是安全的,运行速度慢
集合没有泛型的写法,键和值的数据类型锁定为String类型
集合有自己的特有方法
此集合可以和IO流对象结合使用,实现数据的持久存储
方法和IO相关 : load(输入流)
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)