类集java.util.*
顶级接口 :Collection
Map
Ilerator
类集是java对数据结构的成熟实现,不用自己再去写了。
数组和链表的区别 (面试常考)
- 数组存取(按下标查找)速度快,链表很慢
- 数组需要事先知道长度,链表不需要
- 数组空间通常有限制,需要大块连续内存块,链表是离散分配节点的,通过指针相连,空间无限制
- 链表插入删除元素很快,无需移动
- 链表的特点,只有一个前驱,只有一个后继
二叉树
- 先序遍历:根左右
- 中序遍历:左根右
- 后序遍历:左右根
待补充:遍历顺序在算法题解题中有妙用。
红黑树
速度快,接近平衡树,查找叶子元素最少和最多次数不多于2倍。
Collection接口
单值存储。
为了区分集合中是否允许有重复元素,现在使用其子接口List
和set
List
-
都提供了Iterator方法,返回
ListIterator
实例。 -
缓存后还需要取出来,直接让remove返回就可以。
-
set修改,add添加,都可以指定index
-
允许重复
ArrayList Vector LinkedList
- 构造方法有3种。
(Collection<? extends E> o)
可以根据迭代器返回的顺序构建包含指定元素的列表。
ArrayList
-
ArrayList发现存不了就会扩容
-
其add方法只会返回true
-
扩容:
elementData=grow();
grow返回的是Object[] -
扩容算法:方法重载,无参方法里返回有参grow,传一个最小容量(原容量+1)过去,根据旧数组和新长度创建新的数组并且给他复制进去,如下
-
带参grow的内容:返回
Arrays.copyOf(elementData,newCapacity(minCapacity));
-
新长度怎么算:
private int newCapacity(int minCapacity){
//旧容量
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//相当于除2,也就是旧+旧/2的长度
if(newCapacity - minCapacity <= 0){
//如果没成功,没增长 两种可能 1是0 2是溢出
if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)//如果为默认的空???第一次创建 没长度 默认为0
return Math.max(DEFAULT_CAPACITY, minCapacity);//返回默认和传参中的最大值 第一次:0&1
if(minCapacity < 0)
//int最大值+1 溢出 符号位改变成为负数 突然联想到了银行家算法
throw new OutOfMemoryError();
return minCapacity;
}
//最大长度 检查是否比最大值小 Integer.MAX_VALUE-8
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);//如果比最大值还要大,会执行这个方法
}
/**
* 过大的长度
*/
private static int hugeCapacity(int minCapacity){
//一直在传这个minCapacity
if(minCapacity < 0) //说明溢出
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)//是否要很大的长度
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
Vector
-
线程安全
-
这个类的iterator和listIterator方法返回的迭代器是快速失败的。
-
构造方法有4个,两参的要传递指定初始容量和容量增量的
-
扩容方法计算新容量时,如果增量大于0就加增量,否则直接加个旧长度,然后是同样的判断是否溢出等等
LinkedList
双向链表实现。特殊方法:
- addFirst addLast
- peek poll push等
- 可以实现队列和栈
迭代器Iterator
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
//循环条件有下一个
Integer i = iterator.next();//移动迭代器指针
iterator.remove();//删除迭代器返回的最后一个元素,必须next获取之后才能删除
hasPrevious();//往前走 但是需要先往下走才能往回
iterator.add(100);//紧接在next()返回的元素之前 previous返回的元素之后插入,隐式游标之前插入 对next()调用不受影响,对previous调用将返回新插入的元素
iterator.set(200);//
}
总结:
- 判断hasNext()
- 获取next()
forEach 增强for循环
for(int a:arr){
//
}
迭代数组或者集合(Collection)
Set
set是跟collection一样没有get,它不会包含重复元素,不保证顺序。
可以使用Iterator,forEach遍历
HashSet
散列存放。还是双值存储,放入的是元素和一个默认值。
add()方法可以返回true或false
TreeSet
采用有序二叉树存储。自然顺序。
- 该类Iterator快速失败,遍历的是集合本身,记录遍历到哪个变量,如果有另外的线程要修改数据,那么迭代器出现异常。ConcurrentModificationException。
安全失败:失败不会出错,把集合复制了一份,遍历的是复制的那一份。
char A是65编码
System.out.println((char)65);
如果想要自己设定顺序,重写comparator比较器,实现Comparable接口
static class Person implements Comparable<Person> {
//this和o进行比较,返回负数(this小于) 正数(this大于) 0(相等)
@Override
public int compareTo(Person o){
if(this.age>o.age){
return 1;
}else if(this.age<o.age){
return -1;
}else{
return 0;
}
}
}
set不允许相等。
Map
-
Map跟Collection是一个级别的。双值存储。
-
key不可重复
-
遍历操作很麻烦
重要方法:
-
KeySet() 通过钥匙来找数据,把每个键拿出来
-
是否存在 containsKey containsValue
-
values()返回包含的值的Collection形式
哈希表HashMap
动态数组+链表+红黑树
- hashCode()返回int值
- 调用对象的hashCode值跟数组的长度取余(默认长度16取余是0~15,得到下标)
- 如果重复下标,链表
- 如果哈希桶中链表长度已经到8还要加,转换为红黑树
- 当哈希桶中的数据量减少到6时,从红黑树转换为链表
- 初始桶数量16 散列因子0.75(如果已经75%存入数据,则扩容为原长度2倍)
源码分析
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默认初始化容量16
//put->putVal
public V put(K key, V value) {
return putVal(hash(key), key, value, onlyIfAbsent:false, evict:true);//通过hash()方法计算key的hash值,键,值
}
//下标计算方式:tab[i=(n-1)&hash]结果就是拿hash值对n-1取余
-
调用put(key,value);
-
通过hash算法得到key的hash值
-
判断Node数组table是否为null或长度为0
-
调用resize()方法对table进行扩容|判断table[i]是否已有元素
-
如果已有,判断key是否重复
-
判断table[i]的类型是链表节点(如果=8(TREEIFY_THRESHOLD,转红黑树)还是红黑树节点 调用不同方法插入
-
在新节点插入后,modCount++,判断size是否大于临界值,如果是,调用resize()方法
遍历方法
for(String key:map.keySet()){
String value = map.get(key);
}
for(String value:map.values()){
System.out.println(value);
}
初始容量和负载因子
ConcurrentHashMap
在java.util.concurrent.*包里。
HashMap HashTable ConcurrentHashMap这几个容器的区别
- HashMap 线程不安全 效率高
- HashTable 线程安全 效率低 锁所有桶
- ConcurrentHashMap 分段锁机制,线程安全,效率较高
- 只有当操作同一下标的时候才锁
LinkedHashMap既在哈希表里又在双向链表里
hashCode()
result = 31 * result + (element == null? 0:element.hashCode());
如果修改对象但没put进去,就会导致hashCode不一样,就没法找到。
如果新增对象name和info是一样的,还要判断equals,所以也找不到。
jdk9中的集合新特性
创建固定长度的集合的便捷方法。
list set map
static <E> List<E> of(E e1, E e2, E e3)
List<String> list = List.of("xxx","xxx");