整理笔记:
在说集合之前要先了解数据结构
数据结构
数据结构:存放数据的方式
数组 链表 栈 队列 二叉树
1、顺序结构
1.底层实现:数组
2.特点:
1)按照顺序排序,并且每个元素都带有标号
2)除了标号是连续的,内存中的物理空间也是连续的
3.功能:
增加 - 存储数据 add()
删除 - 删除数据 remove()
修改 - 修改数据 set()
查看 - 获得数据 get()
4.优缺点:
优点:因为有下标,所以查询速度快
缺点:插入/删除效率低。对空间使用率相对较低,因为需要连续的物理内存空间
2、链式结构
1.底层数据结构:
链表
2.底层实现:
Node(节点) -> (data(数据) + next(下一个节点的引用))
3.特点:
a.链表的物理内存空间不是连续的,应用率高
b.操作的是Node(data + next)
c.所有操作都必须从头节点开始,并且头节点不存数据
4.几种不同的链表:
a.单向链表
特点:Node-next,只能从头开始往下找
b.双向链表
特点:Node-next,可以从头开始往下找,也可以从尾往前找
head:不存数据,没有next
foot:不存数据,没有pre
c.循环链表
特点: Node-next,尾节点(有data数据的节点)指向头节点,可以在任意位置开始,都可以获取到想要的元素
总结: 顺序结构和链式结构统称为:线性结构(代表有固定顺序的结构)
集合:
1、Iterable接口:
1.API只有一个
itertor - 迭代器
2.作用:遍历/迭代数组(集合)
前提:必须继承Iterable接口。必须可迭代的。
3.iterator - 迭代器
a.运行原理:
获得迭代器,是有游标的,并且游标在第一个元素之前
b.三个方法:
hasNext() - 询问有没有下一个元素
next() - 获取下一个元素
remove() - 删除元素
注意: remove()不能使用集合中的remove(),因为会发生异常,只能使用迭代器自身的remove()
ConcurrentModificationException
c.简易版迭代器 - foreach
结构:
for(对象:集合){
要输出的内容;
}
:前 -> 每一次迭代出来的临时变量
:后 -> 要迭代的集合/数组
2、泛型:
泛型可以直接替换所有存放类型的地方(方法参数/返回值/类/接口)
运行时状态 -> 在JVM底层中支持的类型是Object。只是编译时状态添加,用来规范程序员写法的
Collection col = new Collection<>();
3、分类:
Collection - 接口(包含有很多子接口/实现类)
创建: Collection col = new ArrayList() ——> 向上造型
Collection:
|- List - 线性结构
| - ArrayList(C) - 顺序结构 - 数组
| - LinkedList(C) - 链式结构 - 双向链表
| - Vector(C) - 顺序结构 - 数组
| - Stack - 栈
|- Set - 散列结构(没有顺序)
| - HashSet(C) - 散列结构
| - SortSet(I) -
| - TreeSet(C) - 二叉树
|- Queue - 队列
| - Deque(I) - 双端队列,栈(根据方法区分)
| - LinkedList(C) - 链式结构 - 双向链表
Map - 接口(包含很多子接口/实现类)
| - HashMap(C) - 散列结构
| - LinkedListHashMap(C) - 线性结构
| - HashTable(C) - 散列结构
| - ConcurrentHashMap(C) - 散列结构
| - SortedMap(I)
| - TreeMap(C) - 二叉树
4、Collection子接口:
1、List集合 - 线性结构
1)特点:
a.List集合是有顺序的,所以是有序列表,可以使用游标
b.List集合允许有重复的值
2)独有方法:
get(int index)
set(int index, E element)
remove(int index)
add(int index, E element)
addAll(int index, Collection<? extends E> c)
indexOf(Object o)
lastIndexOf(Object o)
subList(int fromIndex, int toIndex) - 截取子集合
截取特点: 前包含,后不包含
3)常用实现类:
ArrayList
顺序结构 - 数组
LinkedList
链式结构
(1)特点:
a. LinkedList是双向链表
b. 双向链表有头节点和尾结点,可以从头节点开始,头节点不存数据(data) 只有next。可以从尾节点开始,尾节点不存数据(data) 只有pre
c. 所有的操作都必须从头节点或者尾节点出发
d. LinkedList 也是 Deque的实现类
(2)独有方法:
特点: First/Last
addFirst() - 从头加入元素
addLat() - 从尾加入元素
getFirst() - 获得集合中的第一个元素
getLast() - 获得集合中的最后一个元素
removeFirst() - 删除集合中的第一个元素
removeLast() - 删除集合中的最后一个元素
Vector
顺序结构 - 数组
1、特点:
a.Vector上带有线程同步锁(synchronized)
所以都是线程安全的,效率相对较低
2、Set集合:
1. HashSet©:散列表(没有顺序,不是随机!!!)
特点:
a.存储位置不是真正的随机,内存存储位置根据hashCode值计算 - 无序的
b.不允许有重复值 - 使用equals方法判断是否重复
存储过程:
①根据hashCode计算内存位置
②如果位置上没有元素,就直接添加
③如果位置上有元素,则使用equals方法进行比较
④比较时,如果相同则不存入
⑤如果不相同,挨个遍历链表中的元素,使用equals比较是否相同,如果不相同则在链表末尾添加
结论:
hashCode 和 equals 必须一起重写
2. SortedSet(I) - 父接口 - 可排序的集合
TreeSet©:
(1)底层数据结构:二叉树
(2)特点:
TreeSet是可排序的集合,并且专门用于Set集合的排序
(3)存储过程:
①跟根节点进行比较 - compareTo方法进行比较
② 如果比根节点大,存储在右边
③ 如果比根节点小,存储在左边
④ 如果和根节点一样,就不存入
3、Queue集合 - 队列
底层实现:基于数组或者链表实现
1)特点:
a.先进先出
b.队列也是线性结构,是有顺序的,但是没有标号
2)常用API:
offer() - 添加
peek() - 单独获取,队列不变
poll() - 获取并移除,队列改变
3)子接口 - Deque(I)
1)Deque作为队列实现 - 双端队列
有First/Last的方法
特有方法:
offerFirst()
offerLast()
peekFirst()
peekLast()
pollFirst()
pollLast()
2)Deque作为栈实现
a.特点:先进后出(后进先出)
b.特有方法:
push() - 进栈
pop() - 出栈
区分栈或者是双端队列:
因为最终的实现类都是LinkedList,所以只能通过方法区分
4、排序:
1.自然排序(默认排序) 和 自定义比较器排序
1)自然排序:
a.自定义的类要实现自然排序,就要再类中实现Comparable接口
b.并且重写Comparable接口中的 compareTo()
c.如果是JDK提供的类,就必须实现Comparable接口
2)自定义比较器:
a.使用匿名内部类形式直接实现Comparetor接口
b.并且重写Comparator接口中的 compare()
2.方法实现:
a.数组:
自然排序: Arrays.sort(数组) 自定义比较器: Arryas.sort(数组,new Comparator(){})
b.List:
自然排序: Collections.sort(集合) 自定义比较器: Collections.sort(集合,new Comparetor(){})
c.Set:
自然排序: new TreeSet(); 自定义比较器: new TreeSet(new Comparator(){})
d.Map:
自然排序: new TreeMap(); 自定义比较器: new TreeMap(new Comparator(){})
3.不管什么集合都可以用自然排序 和 自定义比较器排序
JDK默认都是自然排序
5、Map(I) - 映射表
Map是最大的接口,没有父接口,也就是没有继承Iterable接口,就不能是用迭代器的方式进行遍历/迭代
1.Map的格式
<K,V> -> <key(键) , Value(值)> -> 键值对 key = value / key:value
2.特点:
a.Map是以键值对的形式进行存储 b.key 和 value 都可以使用泛型<k,v>,就意味着所有引用类型都可以放
c.key不允许有重复值,value允许有重复值
d.key 和 value 允许有null值,但是key的null值只能有一个
e.存储时,根据key的hashCode值进行存储,所以是无序的
f.因为key是唯一的,所以可以根据key获取到相对应的value值
3.HashMap©
1)存储过程:
a.通过hashCode方法确定内存中的地址值
b.判断该位置上是否有元素
c.如果没有元素,直接存入
d.如果有元素,使用equals进行判断两个对象是否相等
e.如果相等,新元素将之前的元素进行覆盖
f.如果不相等
在jdk1.7之前,是先挨个遍历链表中的元素,并用equals进行比较两个元素是否相等,如果都不相等则添加到链表头部
在jdk1.8之后,是先挨个遍历链表中的元素,并用equals进行比较两个元素是否相等,如果都不相等则添加到链表尾部
2)内存空间大小
a.默认初始化容量:16
b.要进行扩容时:
如果内存满了再扩容,就会造成效率低下
如果过半就扩容,占用内存
c.加载因子 - 0.75
内存达到75%就进行扩容,扩容为原容量的2倍
4.HashTable©
5.ConcurrentHashMap©
6.SortedMap(I)
6.遍历方式
方式一:
通过keySet(),获取key值,返回值类型是Set集合,因为key值的特点是唯一
Set set = map.keySet();
方式二:
通过vaules(),获取value值,返回值类型是Collection,value值的特点是可以出现多个
Collection col = map.values()
方式三:
通过entrySet(),获取key 和 value值, 返回值类型是Set类型
但是因为Set只能存一个泛型,所以使用Map的内部接口Entry获取键和值的泛型<k,v>
Set<Map.Entry<K,V>>set = map.entrySet();
*7.HashTable 和HashMap的区别
a.底层实现区别
HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null
b.继承的类的区别
HashTable:Dictionary
HashMap:AbstractMap
c.效率问题区分
线程安不安全
HashTable:synchronized,线程安全。
HashMap:没有synchronized,线程不安全。
d.扩容机制
ashtable中hash数组默认大小是11,增加的方式是 old*2+1。
HashMap中hash数组的默认大小是16,而且一定是2的指数。
*8.HashTable 和HashMap 和 ConcurrentHashMap的区别
a.再加方法中的锁 -> 锁分离技术 b.jdk1.7 -> 数组(segment数组) + 链表(entry数组 ->
锁entry数组数据 -> 可重入锁(Lock))
jdk1.8 -> 数组 + CAS