集合框架 - 封装数据结构
要求:
1.方法的使用
2.原理的掌握
3.数据结构的实现
1.概念
集合是java提供的一种容器,主要负责存储任意数量的数据.
2.集合和数组的异同
1)相同点:
都是容器,可以存储多个数据
2)不同点:
a.数组的长度是不可变的,集合的长度是可变的.
b.数组可以存储基本数据类型和引用数据类型,集合只能存储引用数据类型,如果要存储基本数据类型,则需要存储对应的包装类.
3.集合体系
Collection(I) - 单列集合
|- List(I)
|- ArrayList(C) - 数组
|- LinkedList(C) - 双向链表
|- Vector(C) - 数组
|- Set(I)
|- HashSet(C) - 散列表(哈希表)
|- LinkedHashSet(C) - 双向链表 + 散列表
|- SortedSet(I)
|- TreeSet(C) - 二叉树
|- Queue(I)
|- Deque(I)
|- LinkedList(C) - 双向链表
Map(I) - 双列集合
|- **HashMap**(C) - 散列表
|- LinkedHashMap(C) - 双向链表 + 散列表
|- HashTable(C) - 散列表
|- **ConcurrentHashMap**(C) - 散列表
|- SortedMap(I)
|- TreeMap(C) - 二叉树
一、Collection(I) - 单列集合
1)创建方式
Collection con = new 实现类()
2)常用API
a. 普通常用方法:
add(E e):添加
remove(Object):根据元素进行删除
size():获取集合长度
isEmpty():判断集合是否为空
clear():清空集合
b. 集合相关方法:
boolean addAll(Collection<? extends E> c)
boolean containsAll(Collection<?> c)
boolean removeAll(Collection<?> c)
boolean retainAll(Collection<?> c)
c. 跟数组相关方法:
集合转数组:
List<String> strList = new ArrayList<>();
//new String[0]指定了返回数组的类型,0节约了空间
String[] strArray = strList.toArray(new String[0]);
数组转集合:
String[] s = new String[]{"hello", "world", "haha"};
List<String> strList = Arrays.asList(s); //将数组转为集合
strList.add("heihei"); //抛出UnsupportedOperationException 异常
这是因为Arrays.asList()方法返回的并不是java.util.ArrayList,而是java.util.Arrays的一个内部类,这个内部类并没有实现集合的修改方法。
正确的转换方式:
用ArrayList进行封转(最简单的方式):
String[] s = new String[]{"hello", "world", "haha"};
List<String> strList = new ArrayList<>(Arrays.asList(s));
1.Iterable(I)
1)有且只有一个iterator(),用于获取迭代器
2)Iterable是Collection集合的父接口,给该集合中的元素提供可遍历的方式.
3)Iterator(I) - 迭代器
a. Iterator是一个接口,他的作用就是用于遍历迭代所有的元素.
b. 使用前提: 必须实现Iterable.
c. 常用API
①.hasNext(): 询问有没有下一个元素
②.next(): 移动游标到下一个位置
③.remove(): 删除元素
4)foreach - 增强for循环- 简易版迭代器
a. JDK1.5之后出现的,其中原理就是实现了一个Iterator迭代器.
b. 只有实现Iterable接口的类才能使用迭代器和增强for循环,进行遍历集合.
c. 语法结构
for(集合/数组中元素的类型 变量名 : 要遍历的集合/数组 ){
}
d. 作用:专门用于遍历/迭代集合或数组
缺点:只能用于获取集合中的元素,不能删除元素.
2、泛型编程
1)概念
泛型本质上是提供类型的"类型参数",也可以称之为参数化类型。
我们可以给类、接口、变量、方法指定类型参数,可以通过参数限制操作的数据类型,从而保证了类型转换的绝对安全。
2)泛型的作用:
强制了集合存储固定的数据类型,提高了程序的安全性.
避免了ClassCastException的发生.
3)泛型的使用
a.自定义泛型类
①泛型类一般用于类中的属性类型不确定的情况下.
b.泛型方法
①是否拥有泛型方法,与其所在类是不是泛型类没有关系.
②如果是static方法需要使用泛型,则需要在方法上标记泛型.
c.泛型接口
①实现类实现接口,不实现泛型,在new的时候进行指定类型
②实现类实现接口的同时指定泛型,接口名<指定类型>
4)泛型的高级使用
a. 通配符
①概念
当使用泛型类或接口时,传递的数据泛型类型不确定可以通过通配符 <?> 表示
②注意
一旦使用了通配符,那么只能使用Object类中的共性方法;集合中元素的自身方法无法使用
b. 泛型的上限与下限
①上限
语法:类型名称**<? extends E>** 对象名称
范围:E只能取 E的子类 和 E本身
②下限
语法:类型名称**<? super E>** 对象名称
范围:E只能取 E的父类 和 E本身
3.List(I)
1.特点:
a.元素有序
b.元素可重复
2.常用方法
1)从Collection继承过来的方法
2)带有index下标的方法
get(int index)
set(int index,Object obj)
remove(int index)
List subList(int fromIndex, int toIndex)
boolean addAll(int index, Collection<? extends E> c)
4.ArrayList©
1)底层实现:数组
2)特点:
a.按照顺序排列,每个元素都带有标号.
b.除了有标号是连续,内存中物理空间也是连续的.
3)优缺点:
优点:查询元素快(可以通过下标(索引),快速访问到指定位置上的元素)
缺点:插入/删除慢,需要连续的物理空间,空间使用率低(插入,删除都是需要移动元素,所以元素移动需要执行时间,导致效率降低,练习的物理空间比较占用内存)
5.LinkedList(C)
1)底层数据结构:双向链表
底层实现:节点(数据 + 下一个/上一个节点的引用)
2)特点:
a.链表的存储结构固定顺序,物理结构空间不连续.
b.没有标号,有头节点和尾节点,所以所有节点的访问都可以从头/尾节点出发.
3)优缺点:
优点:插入 / 删除效率高,不需要连续的物理内存空间,所以孔家使用率高.
缺点:查询效率低.
4)特有方法(带有First / Last的方法)
void addFirst(E e)
void addLast(E e)
E getFirst()
E getLast()
E removeFirst()
E removeLast()
6.Vector© - 和ArrayList一样
1)底层数据结构:顺序表
底层实现:数组
2)特点:
a.按照顺序排列,每个元素都带有标号.
b.除了有标号是连续,内存中物理空间也是连续的.
3)优缺点:
a.优点:
查询元素快(可以通过下标(索引),快速访问到指定位置上的元素)
b.缺点:
插入/删除慢,需要连续的物理空间,空间使用率低(插入,删除都是需要移动元素,所以元素移动需要执行时间,导致效率降低,练习的物理空间比较占用内存)
4)ArrayList 和 Vector的区别
a.效率问题
①.ArrayList线程不安全,效率高
②.Vector线程安全,效率低
b.扩容问题
①.ArrayList容量为原容量的1.5倍
②.Vector为原容量的2倍
7.Queue(I) - 队列
1.特点:
a. 先进先出
b. 队列也是线性结构,是有顺序的,但是没有标号,不能从中间插入 / 删除数据
2.特有方法
offer():向队列尾部追加元素
peek():从队列头部获取元素(队列不变)
poll():从队列头部获取元素(队列改变)
3.Deque(I)
1)Queue的子接口,可以作为双端队列 / 栈实现
2)作为双端队列实现
a 特点:
① 先进先出
② 两头可进,两头可出
b 特有方法
带有 First / Last方法
例如:offerFirst( ) / offerLast( )
3)作为栈实现(LIFO)
a 压栈
① push( )
b 弹栈
② pop( )
8、Set(I) - 无序且唯一
1.特点
a.set集合的物理空间是不连续的,添加没有顺序(不是随机)
b.不允许有重复值
c.最多只允许包含一个null值
2.HashSet©
1)底层实现: 哈希表(散列表) - 不是数据结构!!!
2)保证元素的唯一性的方式
a.HashsSet是根据对象的哈希值来确定元素在集合中存储的位置.
b.想要保证元素的唯一性就必须依赖于两个方法: hashCode() 和 equals()
3)HashSet的存储过程
①.调用自身hashCode方法,计算储存位置
②.如果位置上没有元素,则直接存入
③.如果位置上有元素,则调用自身的equals()和该位置上的元素进行比较
④.如果不相同,遍历下一个节点继续使用equals进行判断,直到链表末尾,如果都不一致,则直接存入链表末尾.
⑤.如果相同,则进行覆盖.
3.LinkedHashSet(C)
1)底层实现:哈希表 + 双向链表
2)特点:有序且唯一
4.TreeSet(C)
1)底层结构:二叉树(红黑树)
2)父接口:SortedSet(I),可排序集合
3)特点:
a 元素唯一
b 没有下标
4)作用:对元素进行排序
9、Comparable - 自然顺序排序
1)使用:
a 在实体类中实现Comparable接口
b 重写Comparable的compareTo(Object o)
2)比较规则:
比较当前对象(this)和传入的对象(o)
this > o:返回负数,小的放左边
this = o:返回0, 不存入
this < o:返回正数,大的放右边
正序 和 倒叙
this在前,o在后 -> 前比后 -> 正序
o在前,this在后 -> 后比前 -> 倒序
10、Comparator - 比较器排序
1)使用:
a 在构造方法中以匿名内部类参数实现Comparator接口
b 重写Comparator的compare(Object o1,Object o2)
2)比较规则:
比较当前对象(o1)和传入的对象(o2)
o1 < o2:返回负数,小的放左边
o1 = o2:返回0, 不存入
o1 >o2:返回正数,大的放右边
正序 和 倒叙
o1在前,o2在后 -> 前比后 -> 正序
o2在前,o1在后 -> 后比前 -> 倒序
二、Map(I) - 双列集合(key - value)
| - HashMap(C)
| - LinkedHashMap(C)
| - TreeMap(C)
| - HashTable(C)
| - properties(C)
| - ConcurrentHashMap(C)
1.Map
1)概述
Map是一种键值对(key - value)集合,每一个元素都包含一个key对象和value对象;用于保存有映射关系的数据
2)特点
a.key和value都可以使用泛型,意味着可以使用所有的引用类型
b.key不可重复(Set),value可重复(List)
c.key和value一对一映射,可以通过指定key找到value
3)常用方法
V put(K key, V value)
V get(Object key)
V remove(Object key)
Map集合的遍历方式:
方式一:获取所有的key,并获取对应的Set类型
Set keySet()
方式二:获取所有的value,并获取对应的Collection类型
Collection values()
方式三:获取键值对entry,并获取对应的Set类型
Set<Map.Entry<K,V>> entrySet()
①.在Set中只能放一种泛型类型,所以键值对(Entry)也是一个整体泛型类型
②.Entry属于Map的内部接口,用法跟内部类一样
③.Entry的结构:(key : value)
a.entry.getKey() - 获取key
b.entry.getValue() - 获取value
2.HashMap
1)底层实现:
JDK1.7:数组 + 链表
采用头插法:新进来的entry节点会取代原有头节点位置,原有节点会顺延到链表中
优点:后进的元素查找效率高
缺点:容易死循环
JDK1.8:数组 + 链表 | 红黑树
采用尾插法:在进行扩容操作之后,链表会保持原本的顺序,也就是保存之前节点的引用关系,
优点:后进的元素查找效率高
缺点:容易死循环
2)HashMap的存储过程
a.计算key的hashCode值,确定内存中的位置
b.如果位置上有元素,使用equals判断是否一致
c.如果一致,则进行覆盖
d.如果不一致,则遍历下一个节点继续进行equals判断,直到链表末尾都不一致,
则添加到链表末尾,能解决链表成环的问题(死循环)
3)初始化空间大小
a.如果太小,则会造成链表多,效率低
b.如果太大,空间浪费
4)扩容机制
加载因子:扩容的条件(默认0.75f),调用resize()方法扩容
*— 和时间成本成反比,和空间成本成正比
3.LinkedHashMap
1)底层实现:散列表 + 双链表
2)特点:key按照存储顺序存放
4.TreeMap
1)底层实现: 二叉树
2)作用:依赖于自然排序/比较器排序,对key进行排序
3)创建方式
new TreeMap(): 默认排序方式
new TreeMap(new Comparator):指定比较器排序
5.ConcurrentHashMap
1)底层实现:
JDK1.7:数组 + 链表
具体实现:Segment数组 + hashEntry[ ]
:重新计算hashCode值
// 根据 key 的 keyCode 重新计算 hash 值。
int hash = hash(key.hashCode());
// 搜索指定 hash 值在对应 table 中的索引。
int i = indexFor(hash, table.length); //i = (n - 1) & hash
JDK1.8:数组 + 链表 | 红黑树
具体实现:CAS + synchronized
:直接使用hashCode值
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
2)CAS(Compare And Swap)比较交换 - 乐观策略
a.存储位置(V)
原值(A)
新值(B)