java集合类-看这一篇就够了
继承关系
Collection 和 Collections 区别
- Collection是一个集合接口。 它提供了对集合对象进行基本操作的通用接口方法。
- Collections 是一个包装类。 它包含有各种有关集合操作的静态多态方法。
List
- List 特点:元素有放入顺序,元素可重复 。有顺序,即先放入的元素排在前面。
常用子类
1.ArrayList
参数 | 解释 |
---|---|
private static final int DEFAULT_CAPACITY | 初始容量 |
private static final Object[] EMPTY_ELEMENTDATA | 用于空实例的共享空数组实例 空数组 |
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA | 默认容量空数组 |
MAX_ARRAY_SIZE | 最大数组大小 |
transient Object[] elementData | ArrayList中维护的动态数组 |
private int size | 大小 |
1.1构造方法
public ArrayList(int initialCapacity) { //初始化指定长度的list
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() { // 空参 可以看出实际数组还未初始化
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) { // 传入Collection
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// toArray() 可能返回的不是 Object 数组 所以用 Arrays.copyOf 保证elementData 是object[]
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果是空 设置了一个 空object数组.
this.elementData = EMPTY_ELEMENTDATA;
}
}
1.2 扩容机制
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量不超出方法
elementData[size++] = e;
return true;
}
确保容量足够大
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //也是为了设置初始化容量为10个
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //结构改变的次数
// overflow-conscious code
if (minCapacity - elementData.length > 0) //判断是否需要扩容
grow(minCapacity); //扩容方法
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 大约 1.5 倍 扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); //最大边界其实是int 的 最大值
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity); // 这里才是真正 初始化 或者 扩容的地方
}
1.3 ArrayList 和Vector 的区别
- 他们都是AbstractList 的子类
- Vertor 方法 被synchronized修饰 是线程安全 但是效率比较低
2.LinkedList
参数 | 解释 |
---|---|
transient int size = 0 | 大小 |
transient Node first | 头节点 |
transient Node last | 尾节点 |
2.1 Node类
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.2 解释
- LinkedList 底层是链表结构
- 继承了Deque ,拥有Deque的方法
在 Java 的 Set 体系中,根据实现方式不同主要分为两大类。HashSet 和 TreeSet。(LinkedHashSet hash+链表 是有序的)
- TreeSet 是二叉树实现的,Treeset 中的数据是自动排好序的,不允许放入 null 值
- HashSet 是哈希表实现的,HashSet 中的数据是无序的,可以放入 null,但只能放入一个 null,两者中的值都不能重复,就如数据库中唯一约束。
- 在 HashSet 中,基本的操作都是有 HashMap 底层实现的,因为 HashSet 底层是用
HashMap 存储数据的。当向 HashSet 中添加元素的时候,首先计算元素的 hashcode
值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就
将元素添加进去;如果不为空,则用 equals 方法比较元素是否相等,相等就不添加,否则
找一个空位添加。 - TreeSet 的底层是 TreeMap 的 keySet(),而 TreeMap 是基于红黑树实现的,红黑
树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵
的一倍。 - TreeMap 是按 key 排序的,元素在插入 TreeSet 时 compareTo()方法要被调用,
所以 TreeSet 中的元素要实现 Comparable 接口。TreeSet 作为一种 Set,它不允许出
现重复元素。TreeSet 是用 compareTo()来判断重复元素的。
Map
成员变量
- transient int size 记录了 Map 中 KV 对的个数
- loadFactor 装载因子,用来衡量 HashMap 满的程度。loadFactor 的默认值为0.75f(static final float DEFAULT_LOAD_FACTOR = 0.75f;)。
- === threshold== 临界值,当实际 KV 个数超过 threshold 时,HashMap 会将容量扩容,threshold=容量*装载因子
- 除了以上这些重要成员变量外,HashMap 中还有一个和他们紧密相关的概念:capacity 容量,如果不指定,默认容量是 16(static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;)
size 和 capacity
- HashMap 中的 size 和 capacity 之间的区别其实解释起来也挺简单的。我们知道,HashMap 就像一个“桶”,那么 capacity 就是这个桶“当前”最多可以装多少元素,而size 表示这个桶已经装了多少元素。
- 默认情况下,一个 HashMap 的容量(capacity)是 16。
- 在一个 HashMap 第一次初始化的时候,默认情况下他的容量是 16,当达到扩容条件的时候,就需要进行扩容了,会从 16 扩容成 32。
- 默认情况下 HashMap 的容量是 16,但是,如果用户通过构造函数指定了一个数字作为容量,那么 Hash 会选择大于该数字的第一个 2 的幂作为容量。(1->1、7->8、9->16)
全网把Map中的hash()分析的最透彻的文章
loadFactor (装载因子)和 threshold(临界值)
- threshold = loadFactor * capacity。
- size 大于 threshold 时就会触发扩容
HashMap 中 hash 方法的原理
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
- 根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。
常见的 Hash 函数
- 直接定址法:直接以关键字 k 或者 k 加上某个常数(k+c)作为哈希地址。
- 数字分析法:提取关键字中取值比较均匀的数字作为哈希地址。
- 除留余数法:用关键字 k 除以某个不大于哈希表长度 m 的数 p,将所得余数作为哈希表地址。
- 分段叠加法:按照哈希表地址位数将关键字分成位数相等的几部分,其中最后一部分可以比较短。然后将这几部分相加,舍弃最高进位后的结果就是该关键字的哈希地址。
- 平方取中法:如果关键字各个部分分布都不均匀的话,可以先求出它的平方值,然后按照需求取中间的几位作为哈希地址。
- 伪随机数法:采用一个伪随机数当作哈希函数。
解决碰撞的方法
- 开放定址法: 开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
- 链地址法: 将哈希表的每个单元作为链表的头结点,所有哈希地址为 i 的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。
- 再哈希法: 当哈希地址发生冲突用其他的函数计算另一个哈希函数地址,直到冲突不再产生为止。
- 建立公共溢出区: 将哈希表分为基本表和溢出表两部分,发生冲突的元素都放入溢出表中。
HashMap 的数据结构
为什么 HashMap 的默认容量设置成 16
hash 运算的过程其实就是对目标元素的 Key 进行 hashcode,再对 Map 的容量进行取模,而 JDK 的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map 的容量一定得是 2 的幂。而作为默认容量,太大和太小都不合适,所以 16 就作为一个比较合适的经验值被采用了。
要设置 HashMap 的初始化容量
如果我们没有设置初始容量大小,随着元素的不断增加,HashMap 会发生多次扩容,而 HashMap 中的扩容机制决定了每次扩容都需要重建 hash 表,是非常影响性能的
Java 8 中 stream 相关用法
Java 8 API 添加了一个新的抽象称为流Stream。
Stream 介绍
- Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对Java 集合运算和表达的高阶抽象。
- Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- 这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
创建stream
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream<String> stream = strings.stream();
Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream 中间操作
- filter 以下代码片段使用 filter 方法过滤掉空字符串:
List<String> strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "ho
llis");
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::p
rintln);
//Hollis, , HollisChuang, H, hollis
- map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().map( i -> i*i).forEach(System.out::println);
//9,4,4,9,49,9,25
- limit/skip limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保理 4 个元素:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);
//3,2,2,3
- sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().sorted().forEach(System.out::println);
//2,2,3,3,3,5,7
- distinct 主要用来去重,以下代码片段使用 distinct 对元素进行去重:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);
//3,2,7,5
forEach
Stream 提供了方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach输出了 10 个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
count
count 用来统计流中的元素个数。
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());
//7
collect
collect 就是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果:
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
strings = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList());
System.out.println(strings);
//Hollis, HollisChuang, Hollis666, Hollis