一:总体介绍:
Java集合框架介绍
集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下,但支持多线程的集合类位于java.util.concurrent包下。
Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue(Java5新增的队列),因此Java集合大致也可分成List、Set、Queue、Map四种接口体系,(注意:Map不是Collection的子接口)。
其中List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合;Map代表的是存储key-value对的集合,可根据元素的key来访问value。
Java集合和数组的区别:
已定义集合的大小可变,而已定义的数组大小不可变(注意:集合内部实现机制也用到了对象数组,只是通过数组的复制或链式存储的方式使其从外部看起来是可变的,而其本身并未颠覆数组大小不可变这一观点)
数组可以存储基本数据类型和引用类型,而集合只能存储引用类型,例如传入add方法中的int会被自动封装成Integer类型
数组只能存储相同类型的数据,而集合如未确定泛型的具体类型,则可存储任意引用类型数据
Guava集合框架介绍
Guava 是一款 Google 开源工具类,包含许多 Google 内部 Java
项目依赖的核心类。Guava 扩展 Java 基础类工程,比如集合,并发等,也增加一些其他强大功能,比如缓存,限流等功能。另外 Guava 推出一些类,如 Optional
,甚至被 Java 开发者学习,后续增加到 JDK 中。Guava 创造很多 JDK 没有,但是我们日常却明显有用的新集合类型。这些新类型使用 JDK 集合接口规范,所以使用方法与 JDK 集合框架差不多,并没有增加很多使用难度。
集合接口
属于 JDK 还是 Guava
对应的 Guava 工具类
Collection
JDK
Collections2:不要和java.util.Collections混淆
List
JDK
Lists
Set
JDK
Sets
SortedSet
JDK
Sets
Map
JDK
Maps
SortedMap
JDK
Maps
Queue
JDK
Queues
Multiset
Guava
Multisets
Multimap
Guava
Multimaps
BiMap
Guava
Maps
Table
Guava
Tables
二:接口及类详解【JAVA】
Iterable 接口
常用方法:
方法名或属性名称
描述
注意事项
备注
Iterator iterator();
返回每个元素的迭代器
default void forEach(Consumer super T> action)
循环操作每个元素
default Spliterator spliterator()
创建一个spliterator
重点方法介绍及代码示例
/**
* iteable测试类
*/
public class TestItearble {
// 返回迭代器,hasNext方法判断是否有下一个元素 next方法取下一个元素
public static void testIterator(List list) {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
// foreach 内部用的还是iterator
public static void testForeach(Set set) {
set.forEach(System.out::println);
}
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(4);
testIterator(list);
Set set = new HashSet<>();
set.add("one");
set.add("three");
set.add("four");
set.add("one");
testForeach(set);
}
spliterator是java1.8新提出的能够进行并行遍历的迭代器.首先, iterator是专门用于迭代集合元素的方法,在List类中就有iterator()方法.集合通过调用Iterator方法,可以对该集合进行循环,效果就相当于是使用了for循环,但是iterator的好处就是不论是List,还是Map,都可以通过iterator进行遍历.但是,通过iterator和for循环一样,都是单线程的操作,Spliterator也用于遍历数据源中的元素,但它是为了并行执行而设计的。Java 8已经为集合框架中包含的所有数据结构提供了一个默认的Spliterator实现。集合实现了Spliterator接口,接口提供了一个spliterator方法。详解可见:https://blog.csdn.net/sl1992/article/details/100149187?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.channel_param
public class TestSplitror {
private static class Thread extends java.lang.Thread {
private final Spliterator list;
private Thread(Spliterator list) {
this.list = list;
}
@Override
public void run() {
list.forEachRemaining(System.out::println);
}
}
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 0; i < 2000; i++) {
list.add("split" + i);
}
// 拆分迭代器
Spliterator spliterator = list.spliterator();
Spliterator stringSpliterator = spliterator.trySplit();
Thread stringthread = new Thread<>(spliterator);
Thread stringthread1 = new Thread<>(stringSpliterator);
stringthread.start();
stringthread1.start();
while (true) {
if (stringthread.isAlive() || stringthread1.isAlive()) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
System.out.println("main end");
}
}
Collection 接口
常用方法
方法名或属性名称
描述
注意事项
备注
int size();
返回集合的元素数量
如果元素数量超过Integer.MAX_VALUE返回Integer.MAX_VALUE.
boolean isEmpty();
返回集合是否没有元素
boolean contains(Object o);
如果集合有该元素返回true
ClassCastException 元素类型不相容
Object[] toArray();
返回一个装有改集合元素的数组
排序
T[] toArray(T[] a)该方法可以对返回的数组类型进行精确控制。而非像toArray方法一样返回Object[]
。
boolean add(E e);
添加一个元素,添加成功返回true
boolean remove(Object o);
移除一个元素,移除成功返回true
boolean containsAll(Collection> c);
是否存在当前集合中的所有元素
boolean addAll(Collection extends E> c);
添加当前集合中的所有元素
boolean removeAll(Collection> c);
移除当前集合中的所有元素
default boolean removeIf(Predicate super E> filter)
根据给定的公式移除元素
default Stream stream()
返回streeam流
default Stream parallelStream()
返回并行stream流
重点方法介绍及代码示例
add() 向集合中添加一个元素。集合更改则添加成功返回true,如果该集合不允许重复并且已经包含指定的元素。返回false。部分子类的add方法可能会限制添加到集合中的元素类型,或者不会将NULL添加到集合中。
public class TestCollectionApi {
public static void main(String[] args) {
Collection set = new HashSet<>();
boolean one = set.add("one");
boolean one1 = set.add("one");
System.out.println("set第一次添加add" + one);
System.out.println("set第二次添加add" + one1);
Collection list = new ArrayList<>();
boolean one2 = list.add("list");
boolean one3 = list.add("list");
System.out.println("list第一次添加add" + one2);
System.out.println("list第二次添加add" + one3);
}
}
返回结果为:true false true true
List接口
一个排序的集合(也被称作序列),接口使用者可以精确控制每个元素在集合中的插入位置,用户可以根据integer类型的索引访问和搜索集合中的元素。不像set,集合通常允许重复的元素,如果他们允许null元素存在的话,也可以存在重复的null。
方法名或属性名称
描述
注意事项
备注
default void replaceAll(UnaryOperator operator)
将此列表中的每个元素替换为对该元素应用运算符的结果。
default void sort(Comparator super E> c)
根据明确的比较器进行排序
E get(int index);
返回该位置的元素
E set(int index, E element);
用明确的元素替换明确位置的元素。
void add(int index, E element);
插入元素到固定的位置,原该位置元素及后面的元素索引加一。
E remove(int index);
移除具体位置的元素,后面的元素索引减一
int indexOf(Object o);
返回该元素的索引位置或者返回-1(如果不存在)
ListIterator listIterator();
返回一个list迭代器
int lastIndexOf(Object o);返回最后一个该元素的索引位置或者-1(不存在)
List subList(int fromIndex, int toIndex);
返回一个前闭后开的视图。
重点方法介绍及代码示例
public class TestListApi {
public static void main(String[] args) {
List list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
list.set(1, "d");
System.out.println(list);
list.add(1, "k");
System.out.println(list);
list.replaceAll(x -> {
if (x.equals("a")) {
return "tt";
}
return x;
});
System.out.println(list);
list.sort(String::compareTo);
System.out.println(list);
List list1 = list.subList(0, 2);
list1.set(0, "kkkk");
System.out.println(list);
}
}
返回结果为:
[a, d, c]
[a, k, d, c]
[tt, k, d, c]
[c, d, k, tt]
[kkkk, d, k, tt]
Set接口
元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)
Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
SortedSet接口有一个实现类:TreeSet(底层由平衡二叉树实现)
Set : 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
HashSet : 为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
TreeSet : 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。
LinkedHashSet : 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
方法名或属性名称
描述
注意事项
备注
boolean contains(Object o);
如果集合有该元素返回true
ClassCastException 元素类型不相容
Object[] toArray();
返回一个装有改集合元素的数组
排序
T[] toArray(T[] a)该方法可以对返回的数组类型进行精确控制。而非像toArray方法一样返回Object[]
。
boolean add(E e);
添加一个元素,添加成功返回true
boolean remove(Object o);
移除一个元素,移除成功返回true
boolean containsAll(Collection> c);
是否存在当前集合中的所有元素
boolean retainAll(Collection> c);
取交集
boolean addAll(Collection extends E> c);
添加当前集合中的所有元素
boolean removeAll(Collection> c);
移除当前集合中的所有元素
default boolean removeIf(Predicate super E> filter)
根据给定的公式移除元素
TreeSet
TreeSet实现了SortedSet接口,它是一个有序的集合类,TreeSet的底层是通过TreeMap实现的。TreeSet并不是根据插入的顺序来排序,而是根据实际的值的大小来排序。TreeSet也支持两种排序方式:
1:自然排序。2:自定义排序
如何保证排序和唯一性?
public V put(K key, V value)
{
// 先以 t 保存链表的 root 节点
Entry t = root;
// 如果 t==null,表明是一个空链表,即该 TreeMap 里没有任何 Entry
if (t == null)
{
// 将新的 key-value 创建一个 Entry,并将该 Entry 作为 root
root = new Entry(key, value, null);
// 设置该 Map 集合的 size 为 1,代表包含一个 Entry
size = 1;
// 记录修改次数为 1
modCount++;
return null;
}
int cmp;
Entry parent;
Comparator super K> cpr = comparator;
// 如果比较器 cpr 不为 null,即表明采用定制排序
if (cpr != null)
{
do {
// 使用 parent 上次循环后的 t 所引用的 Entry
parent = t;
// 拿新插入 key 和 t 的 key 进行比较
cmp = cpr.compare(key, t.key);
// 如果新插入的 key 小于 t 的 key,t 等于 t 的左边节点
if (cmp < 0)
t = t.left;
// 如果新插入的 key 大于 t 的 key,t 等于 t 的右边节点
else if (cmp > 0)
t = t.right;
// 如果两个 key 相等,新的 value 覆盖原有的 value,
// 并返回原有的 value
else
return t.setValue(value);
} while (t != null);
}
else
{
if (key == null)
throw new NullPointerException();
Comparable super K> k = (Comparable super K>) key;
do {
// 使用 parent 上次循环后的 t 所引用的 Entry
parent = t;
// 拿新插入 key 和 t 的 key 进行比较
cmp = k.compareTo(t.key);
// 如果新插入的 key 小于 t 的 key,t 等于 t 的左边节点
if (cmp < 0)
t = t.left;
// 如果新插入的 key 大于 t 的 key,t 等于 t 的右边节点
else if (cmp > 0)
t = t.right;
// 如果两个 key 相等,新的 value 覆盖原有的 value,
// 并返回原有的 value
else
return t.setValue(value);
} while (t != null);
}
// 将新插入的节点作为 parent 节点的子节点
Entry e = new Entry(key, value, parent);
// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的左子节点
if (cmp < 0)
parent.left = e;
// 如果新插入 key 小于 parent 的 key,则 e 作为 parent 的右子节点
else
parent.right = e;
// 修复红黑树
fixAfterInsertion(e); // ①
size++;
modCount++;
return null;
}
// 每当程序希望添加新节点时:系统总是从树的根节点开始比较 —— 即将根节点当成当前节点,如果新增节点大于当前节点、并且当前节点的右子节点存在,则以右子节点作为当前节点;如果新增节点小于当前节点、并且当前节点的左子节点存在,则以左子节点作为当前节点;如果新增节点等于当前节点,则用新增节点覆盖当前节点,并结束循环 —— 直到找到某个节点的左、右子节点不存在,将新节点添加该节点的子节点 —— 如果新节点比该节点大,则添加为右子节点;如果新节点比该节点小,则添加为左子节点。
TreeSet的底层实现
简单来说 treeset就是没有重复元素的treemap
public class TreeSet extends AbstractSet
implements NavigableSet, Cloneable, java.io.Serializable
{
// 使用 NavigableMap 的 key 来保存 Set 集合的元素
private transient NavigableMap m;
// 使用一个 PRESENT 作为 Map 集合的所有 value。
private static final Object PRESENT = new Object();
// 包访问权限的构造器,以指定的 NavigableMap 对象创建 Set 集合
TreeSet(NavigableMap m)
{
this.m = m;
}
public TreeSet() // ①
{
// 以自然排序方式创建一个新的 TreeMap,
// 根据该 TreeSet 创建一个 TreeSet,
// 使用该 TreeMap 的 key 来保存 Set 集合的元素
this(new TreeMap());
}
public TreeSet(Comparator super E> comparator) // ②
{
// 以定制排序方式创建一个新的 TreeMap,
// 根据该 TreeSet 创建一个 TreeSet,
// 使用该 TreeMap 的 key 来保存 Set 集合的元素
this(new TreeMap(comparator));
}
public TreeSet(Collection extends E> c)
{
// 调用①号构造器创建一个 TreeSet,底层以 TreeMap 保存集合元素
this();
// 向 TreeSet 中添加 Collection 集合 c 里的所有元素
addAll(c);
}
public TreeSet(SortedSet s)
{
// 调用②号构造器创建一个 TreeSet,底层以 TreeMap 保存集合元素
this(s.comparator());
// 向 TreeSet 中添加 SortedSet 集合 s 里的所有元素
addAll(s);
}
}
//从上面代码可以看出,TreeSet 的 ① 号、② 号构造器的都是新建一个 TreeMap 作为实际存储 Set 元素的容器,而另外 2 个构造器则分别依赖于 ① 号和 ② 号构造器,由此可见,TreeSet 底层实际使用的存储容器就是 TreeMap。
注意:如果想实现自然排序,那么需保证排序的类实现了comparable接口,否则会抛出java.lang.ClassCastException
ArrayList
官方解释:List接口的动态数组实现,允许所有类型,包括null,除了实现List接口之外,此类提供方法来操作内部用于存储列表。这个类大致相当于Vector,但它不是同步的。
ArrayList初始化
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认的容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组,当调用无参数构造函数的时候默认给个空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
*
* 空数组,当调用无参数构造函数的时候默认给个空数组,我们将其与空的元素数据区分开来,以了解添加第一个元素时要膨胀多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储ArrayList中元素的数组容器,ArrayList的容量就是数组的长度,任何一个空的数组都会被拓展到DEFAULT_CAPACITY个容量,当第一个元素添加时。
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 包含的元素的数量
*/
private int size;
/**
* 构造方法传入默认的capacity 设置默认数组大小
*/
public ArrayList(int initialCapacity) {
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;
}
// get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
}
ArrayList动态扩容
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
/**
* 添加一个元素到列表的尾部
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 增加容量确保他可以保存minCapacity的元素
*/
private void grow(int minCapacity) {
// 首先容量翻1.5倍
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 翻倍之后还不满足,容量赋值为所需最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 最大值为Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将原来数组的值copy新数组中去, ArrayList的引用指向新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
/**
* 移除制定位置的元素,返回值为被移除的元素
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
// 移除元素右边的元素左移
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 最后一位置空
elementData[--size] = null; // clear to let GC do its work ???
return oldValue;
}
}
Array.copyOf与System.ArrayCopyOf
public static T[] copyOf(U[] original, int newLength, Class extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
由此可见Array.copyOf与System.ArrayCopyOf均为浅拷贝。
HashMap
HashMap
是一个关联数组、哈希表,它是线程不安全的,允许key为null,value为null。遍历时无序。其底层数据结构是数组称之为哈希桶,每个桶里面放的是链表,链表中的每个节点,就是哈希表中的每个元素。 在JDK8中,当链表长度达到8,会转化成红黑树,以提升它的查询、插入效率.
因其底层哈希桶的数据结构是数组,所以也会涉及到扩容的问题。
当HashMap
的容量达到threshold
域值时,就会触发扩容。扩容前后,哈希桶的长度一定会是2的次方。这样在根据key的hash值寻找对应的哈希桶时,可以用位运算替代取余操作,更加高效。而key的hash值,并不仅仅只是key对象的hashCode()
方法的返回值,还会经过扰动函数的扰动,以使hash值更加均衡。
但就算原本的hashCode()
取得很好,每个key的hashCode()
不同,但是由于HashMap
的哈希桶的长度远比hash取值范围小,默认是16,所以当对hash值以桶的长度取余,以找到存放该key的桶的下标时,由于取余是通过与操作完成的,会忽略hash值的高位。因此只有hashCode()
的低位参加运算,发生不同的hash值,但是得到的index相同的情况的几率会大大增加,这种情况称之为hash碰撞。 即,碰撞率会增大。扰动函数就是为了解决hash碰撞的。它会综合hash值高位和低位的特征,并存放在低位,因此在与运算时,相当于高低位一起参与了运算,以减少hash碰撞的概率。(在JDK8之前,扰动函数会扰动四次,JDK8简化了这个操作)
扩容操作时,会new一个新的Node
数组作为哈希桶,然后将原哈希表中的所有数据(Node
节点)移动到新的哈希桶中,相当于对原哈希表中所有的数据重新做了一个put操作。所以性能消耗很大,可想而知,在哈希表的容量越大时,性能消耗越明显。
数据域
/**
* 默认初始容量16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量必须小于 1 << 3
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* load factor
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 当add一个元素到某个位桶,其链表长度达到8时将链表转换为红黑树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
*
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 存储元素的数组
*/
transient Node[] table;
/**
*
*/
transient Set> entrySet;
/**
* 包含的键值对的个数
*/
transient int size;
/**
* 结构性修改的次数,fast—fail机制
*/
transient int modCount;
/**
*
*/
int threshold;
/**
* The load factor for the hash table.
*/
final float loadFactor;
static class Node implements Map.Entry {
//哈希值
final int hash;
final K key;
V value;
//链表后置节点
Node next;
Node(int hash, K key, V value, Node next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
//每一个节点的hash值,是将key的hashCode 和 value的hashCode 亦或得到的。
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
//设置新的value 同时返回旧value
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry,?> e = (Map.Entry,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
构造函数
public HashMap() {
//默认构造函数,赋值加载因子为默认的0.75f
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(int initialCapacity) {
//指定初始化容量的构造函数
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//同时指定初始化容量 以及 加载因子, 用的很少,一般不会修改loadFactor
public HashMap(int initialCapacity, float loadFactor) {
//边界处理
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//初始容量最大不能超过2的30次方
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//加载因子不能为负数
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//设置阈值为 》=初始化容量的 2的n次方的值
this.threshold = tableSizeFor(initialCapacity);
}
//新建一个哈希表,同时将另一个map m 里的所有元素加入表中
public HashMap(Map extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
//根据期望容量cap,返回2的n次方形式的 哈希桶的实际容量 length。 返回值一般会>=cap
static final int tableSizeFor(int cap) {
//经过下面的 或 和位移 运算, n最终各位都是1。
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//判断n是否越界,返回 2的n次方作为 table(哈希桶)的阈值
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
扩容机制
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab存放 当前的哈希桶, p用作临时链表节点
Node[] tab; Node p; int n, i;
//如果当前哈希表是空的,代表是初始化
if ((tab = table) == null || (n = tab.length) == 0)
//那么直接去扩容哈希表,并且将扩容后的哈希桶长度赋值给n
n = (tab = resize()).length;
//如果当前index的节点是空的,表示没有发生哈希碰撞。 直接构建一个新节点Node,挂载在index处即可。
// index 是利用 哈希值 & 哈希桶的长度-1,替代模运算
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {//否则 发生了哈希冲突。
//e
Node e; K k;
//如果哈希值相等,key也相等,则是覆盖value操作
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//将当前节点引用赋值给e
else if (p instanceof TreeNode)//红黑树暂且不谈
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {//不是覆盖操作,则插入一个普通链表节点
//遍历链表
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//遍历到尾部,追加新节点到尾部
p.next = newNode(hash, key, value, null);
//如果追加节点后,链表数量》=8,则转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果找到了要覆盖的节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e不是null,说明有需要覆盖的节点,
if (e != null) { // existing mapping for key
//则覆盖节点值,并返回原oldValue
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//这是一个空实现的函数,用作LinkedHashMap重写使用。
afterNodeAccess(e);
return oldValue;
}
}
//如果执行到了这里,说明插入了一个新的节点,所以会修改modCount,以及返回null。
//修改modCount
++modCount;
//更新size,并判断是否需要扩容。
if (++size > threshold)
resize();
//这是一个空实现的函数,用作LinkedHashMap重写使用。
afterNodeInsertion(evict);
return null;
}
final Node[] resize() {
//oldTab 为当前表的哈希桶
Node[] oldTab = table;
//当前哈希桶的容量 length
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//当前的阈值
int oldThr = threshold;
//初始化新的容量和阈值为0
int newCap, newThr = 0;
//如果当前容量大于0
if (oldCap > 0) {
//如果当前容量已经到达上限
if (oldCap >= MAXIMUM_CAPACITY) {
//则设置阈值是2的31次方-1
threshold = Integer.MAX_VALUE;
//同时返回当前的哈希桶,不再扩容
return oldTab;
}//否则新的容量为旧的容量的两倍。
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)//如果旧的容量大于等于默认初始容量16
//那么新的阈值也等于旧的阈值的两倍
newThr = oldThr << 1; // double threshold
}//如果当前表是空的,但是有阈值。代表是初始化时指定了容量、阈值的情况
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;//那么新表的容量就等于旧的阈值
else {}//如果当前表是空的,而且也没有阈值。代表是初始化时没有任何容量/阈值参数的情况
newCap = DEFAULT_INITIAL_CAPACITY;//此时新表的容量为默认的容量 16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新的阈值为默认容量16 * 默认加载因子0.75f = 12
}
if (newThr == 0) {//如果新的阈值是0,对应的是 当前表是空的,但是有阈值的情况
float ft = (float)newCap * loadFactor;//根据新表容量 和 加载因子 求出新的阈值
//进行越界修复
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//更新阈值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//根据新的容量 构建新的哈希桶
Node[] newTab = (Node[])new Node[newCap];
//更新哈希桶引用
table = newTab;
//如果以前的哈希桶中有元素
//下面开始将当前哈希桶中的所有节点转移到新的哈希桶中
if (oldTab != null) {
//遍历老的哈希桶
for (int j = 0; j < oldCap; ++j) {
//取出当前的节点 e
Node e;
//如果当前桶中有元素,则将链表赋值给e
if ((e = oldTab[j]) != null) {
//将原哈希桶置空以便GC
oldTab[j] = null;
//如果当前链表中就一个元素,(没有发生哈希碰撞)
if (e.next == null)
//直接将这个元素放置在新的哈希桶里。
//注意这里取下标 是用 哈希值 与 桶的长度-1 。 由于桶的长度是2的n次方,这么做其实是等于 一个模运算。但是效率更高
newTab[e.hash & (newCap - 1)] = e;
//如果发生过哈希碰撞 ,而且是节点数超过8个,转化成了红黑树
else if (e instanceof TreeNode)
((TreeNode)e).split(this, newTab, j, oldCap);
//如果发生过哈希碰撞,节点数小于8个。则要根据链表上每个节点的哈希值,依次放入新哈希桶对应下标位置。
else { // preserve order
//因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 high位= low位+原哈希桶容量
//低位链表的头结点、尾节点
Node loHead = null, loTail = null;
//高位链表的头节点、尾节点
Node hiHead = null, hiTail = null;
Node next;//临时节点 存放e的下一个节点
do {
next = e.next;
//这里又是一个利用位运算 代替常规运算的高效点: 利用哈希值 与 旧的容量,可以得到哈希值去模后,是大于等于oldCap还是小于oldCap,等于0代表小于oldCap,应该存放在低位,否则存放在高位
if ((e.hash & oldCap) == 0) {
//给头尾节点指针赋值
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}//高位也是相同的逻辑
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}//循环直到链表结束
} while ((e = next) != null);
//将低位链表存放在原index处,
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//将高位链表存放在新index处
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
https://blog.csdn.net/tuke_tuke/article/details/51588156?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
HashSet
对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单.
private transient HashMap map;
// 默认的value值
private static final Object PRESENT = new Object();
/**
* 无参构造方法,默认容量16,loadfactory0.75
*/
public HashSet() {
map = new HashMap<>();
}
/**
* 构造方法,默认容量为大于参数容量的2.幂次方,loadfactory0.75
*/
public HashSet(Collection extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
/**
* 自定义容量和loadfactory
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
/**
* 自定义容量 loadfactory0.75
*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* 自定义容量 loadfactory 和默认value
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
/**
*
*/
public boolean contains(Object o) {
return map.containsKey(o);
}
/**
* 讲元素置为key
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
BitSet
Bitset是Java中的一种数据结构。Bitset中主要存储的是二进制位,做的也都是位运算,每一位只用来存储0,1值,主要用于对数据的标记,用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。(非线程安全)
使用场景:整数,无重复。
参考文档:https://blog.csdn.net/kongmin_123/article/details/82225172?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
BitSet的底层实现是使用long数组作为内部存储结构的,这就决定了BitSet至少为一个long的大小,而且BitSet的大小为long类型大小(64位)的整数倍。
/*
* 位集被放到一个long型的数组中,由64位组成,需要6个地址位。
*/
private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1;
/* Used to shift left or right for a partial word mask */
private static final long WORD_MASK = 0xffffffffffffffffL;
/**
* @serialField bits long[]
*
* The bits in this BitSet. The ith bit is stored in bits[i/64] at
* bit position i % 64 (where bit position 0 refers to the least
* significant bit and 63 refers to the most significant bit).
*/
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("bits", long[].class),
};
/**
* 底层存储结构
*/
private long[] words;
BitSet的构造函数有两个,如果指定了初始化大小,那么会把他规整到一个大于或者等于这个数字的64的整倍数。比如64位,BitSet的大小是1个long,而65位时,指定了大小是2个long,即128位。做这么一个规定,主要是为了内存对齐,同时避免考虑到特殊情况的处理,简化程序。
public BitSet() {
initWords(BITS_PER_WORD);
sizeIsSticky = false;
}
public BitSet(int nbits) {
// nbits不能为负;大小为0可以
if (nbits < 0)
throw new NegativeArraySizeException("nbits < 0: " + nbits);
initWords(nbits);
sizeIsSticky = true;
}
// 给定索引,返回新的数组
private void initWords(int nbits) {
words = new long[wordIndex(nbits-1) + 1];
}
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
BitSet如何存储数据?怎么快速定位他的存储位置?
BitSet的默认初始大小是一个long数组,一个long数组就是64个bit,每一个bit的值就是二进制的0或者1,bit的值和相应位置就代表一个数在不在BitSet当中,0代表该数值不存在,1代表该数组值存在。这样就可以描述数据对数据进行标记了。
在这里,0、3、63等存放入了long数组中。
从上面的BitSet的结构图我们可以看到,要想定位一个数据,需要确定两个值:
(1)这个数位于哪个数组,也就是确定words[wordIndex] 的wordIndex是多少。
(2)这个数位于数组的哪一部分,也就是确定这个数的bitIndex是哪一位。
public void set(int bitIndex) {
// 传入的下标是否越界
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
// 判断要存入的bitIndex应该在那个数组中
int wordIndex = wordIndex(bitIndex);
// 如果现有数组不能满足,拓展大小
expandTo(wordIndex);
// 进行的逻辑运算,对1进行左移,然后与words[wordIndex]做或(or)运算。
words[wordIndex] |= (1L << bitIndex); // Restores invariants
checkInvariants();
}
BitSet的用法
BitSet bitSet = new BitSet();
bitSet.set(50);
// 位数 64
System.out.println(bitSet.size());
bitSet.set(130);
// 位数 192
System.out.println(bitSet.size());
// 是否存在
boolean b = bitSet.get(130);
System.out.println(b);
// stream
boolean match = bitSet.stream().anyMatch(bit -> Objects.equals(bit, 130));
System.out.println(match);
// 清空所有bit位 清空某一位 clear(int bitIndex)
bitSet.clear();
// 反转
bitSet.flip(30);
BitSet bitSet1 = new BitSet();
bitSet1.set(50);
bitSet1.set(250);
// 并集
bitSet1.or(bitSet);
// 交集
bitSet.and(bitSet1);
// 补集
bitSet.andNot(bitSet1);
BitSet的应用:
(1)大数据量的查找。
(2)大数据量的去重。
(3)大数据量的统计。
(4)大数据量的排序。
(5)求数据的并集、交集、补集等。
(6)大数据量的判别。
BitSet常见的应用是那些对海量数据进行一些统计工作,比如日志分析、用户数统计等等。
具体可参考:https://blog.csdn.net/kongmin_123/article/details/82257209
三:接口及类详解【GUAVA】
https://blog.csdn.net/kuyuyingzi/article/details/30529053?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5.channel_param
BiMap接口
继承自: java.util.Map;bimap(或“双向映射”)是一种保持其值和键的唯一性的映射。此约束使bimap支持“反向视图”,即另一个bimap,包含与此bimap相同的条目,但具有相反的键和值。 目前的实现类:EnumBiMap, EnumHashBiMap, HashBiMap, ImmutableBiMap
public class GuavaTester {
public static void main(String args[]){
BiMap empIDNameMap = HashBiMap.create();
empIDNameMap.put(new Integer(101), "Mahesh");
empIDNameMap.put(new Integer(102), "Sohan");
empIDNameMap.put(new Integer(103), "Ramesh");
//Emp Id of Employee "Mahesh"
System.out.println(empIDNameMap.inverse().get("Mahesh"));
}
输出结果:101
Multiset
Collection, Iterable;支持顺序独立相等的集合,如Set,但可能有重复的元素。多套有时也被称为包。多集合中彼此相等的元素被称为同一个元素的出现。一个元素在multiset中出现的总数称为该元素的计数(“frequency”和“multiplicity”这两个术语是等价的,但在这个API中没有使用)。由于元素的计数用int表示,因此multiset的值永远不能超过Integer.MAX_值任何一个元素的出现。
目前的实现类:ConcurrentHashMultiset, EnumMultiset, ForwardingMultiset, ForwardingSortedMultiset, ForwardingSortedMultiset.StandardDescendingMultiset, HashMultiset, ImmutableMultiset, ImmutableSortedMultiset, LinkedHashMultiset, TreeMultiset
public class GuavaTester {
public static void main(String args[]){
//create a multiset collection
Multiset multiset = HashMultiset.create();
multiset.add("a");
multiset.add("b");
multiset.add("c");
multiset.add("d");
multiset.add("a");
multiset.add("b");
multiset.add("c");
multiset.add("b");
multiset.add("b");
multiset.add("b");
//print the occurrence of an element
System.out.println("Occurrence of 'b' : "+multiset.count("b"));
//print the total size of the multiset
System.out.println("Total Size : "+multiset.size());
//get the distinct elements of the multiset as set
Set set = multiset.elementSet();
//display the elements of the set
System.out.println("Set [");
for (String s : set) {
System.out.println(s);
}
System.out.println("]");
//display all the elements of the multiset using iterator
Iterator iterator = multiset.iterator();
System.out.println("MultiSet [");
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("]");
//display the distinct elements of the multiset with their occurrence count
System.out.println("MultiSet [");
for (Multiset.Entry entry : multiset.entrySet())
{
System.out.println("Element: "+entry.getElement() +", Occurrence(s): " + entry.getCount());
}
System.out.println("]");
//remove extra occurrences
multiset.remove("b",2);
//print the occurrence of an element
System.out.println("Occurence of 'b' : "+multiset.count("b"));
}
输出结果:
Occurence of 'b' : 5
Total Size : 10
Set [
d
b
c
a
]
MultiSet [
d
b
b
b
b
b
c
c
a
a
]
MultiSet [
Element: d, Occurence(s): 1
Element: b, Occurence(s): 5
Element: c, Occurence(s): 2
Element: a, Occurence(s): 2
]
Occurence of 'b' : 3
Multimaps
提供作用于或生成多重映射的静态方法。
index
作为Maps.uniqueIndex的兄弟方法,Multimaps.index(Iterable, Function)通常针对的场景是:有一组对象,它们有共同的特定属性,我们希望按照这个属性的值查询对象,但属性值不一定是独一无二的。比方说,我们想把字符串按长度分组。
ImmutableSet digits = ImmutableSet.of("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine");
Function lengthFunction = new Function() {
public Integer apply(String string) {
return string.length();
}
};
ImmutableListMultimap digitsByLength= Multimaps.index(digits, lengthFunction);
/*
* digitsByLength maps:
* 3 => {"one", "two", "six"}
* 4 => {"zero", "four", "five", "nine"}
* 5 => {"three", "seven", "eight"}
*/
invertFrom
鉴于Multimap可以把多个键映射到同一个值(译者注:实际上这是任何 map都有的特性 ),也可以把一个键映射到多个值,反转Multimap也会很有用。Guava 提供了invertFrom(Multimap toInvert, Multimap dest)做这个操作,并且你可以自由选择反转后的Multimap实现。
注:如果你使用的是ImmutableMultimap,考虑改用ImmutableMultimap.inverse()做反转。
ArrayListMultimap multimap = ArrayListMultimap.create();
multimap.putAll("b", Ints.asList(2, 4, 6));
multimap.putAll("a", Ints.asList(4, 2, 1));
multimap.putAll("c", Ints.asList(2, 5, 3));
TreeMultimap inverse = Multimaps.invertFrom(multimap, TreeMultimap.create());
//注意我们选择的实现,因为选了TreeMultimap,得到的反转结果是有序的
/*
* inverse maps:
* 1 => {"a"}
* 2 => {"a", "b", "c"}
* 3 => {"c"}
* 4 => {"a", "b"}
* 5 => {"c"}
* 6 => {"b"}
*/
forMap
想在Map对象上使用Multimap的方法吗?forMap(Map)把Map包装成SetMultimap。这个方法特别有用,例如,与Multimaps.invertFrom结合使用,可以把多对一的Map反转为一对多的Multimap。
Map map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
SetMultimap multimap = Multimaps.forMap(map);
// multimap:["a" => {1}, "b" => {1}, "c" => {2}]
Multimap inverse = Multimaps.invertFrom(multimap, HashMultimap.create());
// inverse:[1 => {"a","b"}, 2 => {"c"}]
Table
Table代表一个特殊的映射,其中两个键可以在组合的方式被指定为单个值。它类似于创建映射的映射。
public class GuavaTester {
public static void main(String args[]){
//Table == Map>
/*
* Company: IBM, Microsoft, TCS
* IBM -> {101:Mahesh, 102:Ramesh, 103:Suresh}
* Microsoft -> {101:Sohan, 102:Mohan, 103:Rohan }
* TCS -> {101:Ram, 102: Shyam, 103: Sunil }
*
* */
//create a table
Table employeeTable = HashBasedTable.create();
//initialize the table with employee details
employeeTable.put("IBM", "101","Mahesh");
employeeTable.put("IBM", "102","Ramesh");
employeeTable.put("IBM", "103","Suresh");
employeeTable.put("Microsoft", "111","Sohan");
employeeTable.put("Microsoft", "112","Mohan");
employeeTable.put("Microsoft", "113","Rohan");
employeeTable.put("TCS", "121","Ram");
employeeTable.put("TCS", "122","Shyam");
employeeTable.put("TCS", "123","Sunil");
//get Map corresponding to IBM
Map ibmEmployees = employeeTable.row("IBM");
System.out.println("List of IBM Employees");
for(Map.Entry entry : ibmEmployees.entrySet()){
System.out.println("Emp Id: " + entry.getKey() + ", Name: " + entry.getValue());
}
//get all the unique keys of the table
Set employers = employeeTable.rowKeySet();
System.out.print("Employers: ");
for(String employer: employers){
System.out.print(employer + " ");
}
System.out.println();
//get a Map corresponding to 102
Map EmployerMap = employeeTable.column("102");
for(Map.Entry entry : EmployerMap.entrySet()){
System.out.println("Employer: " + entry.getKey() + ", Name: " + entry.getValue());
}
}
}
输出:
List of IBM Employees
Emp Id: 102, Name: Ramesh
Emp Id: 101, Name: Mahesh
Emp Id: 103, Name: Suresh
Employers: IBM TCS Microsoft
Employer: IBM, Name: Ramesh
四:常用API及原理解析
常用API
Map常用API
HashMap map6 = Maps.newHashMap();
// 如果不存在则存入
map6.putIfAbsent("one", "1");
map6.putIfAbsent("one", "2");
// 存在覆盖,不存在存入
map6.put("one","3");
map6.put("two", "2");
// 根据给出的function计算value
map6.compute("three", (x, y) -> x + y + "compute");
map6.computeIfPresent("two",(x,y) -> x + y + "compute1");
map6.computeIfAbsent()
// 根据key和funtion对value进行merge
map6.merge("one", "8", (x, y) -> x + y);
集合常用API
List yearMonthBoList = Lists.newArrayList();
// 根据给定的方式移除元素,有元素移除返回true,可以为空,不可以为null,注意源码移除方式,用其他循环代替是否可行?
boolean isRemove = yearMonthBoList.removeIf(x -> Objects.nonNull(x));
yearMonthBoList.add(new YearMonthBo(2020, 11));
yearMonthBoList.add(new YearMonthBo(2020, 12));
yearMonthBoList.add(new YearMonthBo(2020, 11));
yearMonthBoList.add(new YearMonthBo(2019, 11));
// 根据给定排序方式排序,可以为空,不可以为null
yearMonthBoList.sort(Comparator.comparing(YearMonthBo::getMonth));
// 添加集合元素,可以为空,不可以为null
yearMonthBoList.addAll(Lists.newArrayList(new YearMonthBo(2020,6)));
yearMonthBoList.add(new YearMonthBo(2020,4));
// get set remove foreach size indexOf等方法不再赘述
Stack常用API
// stack继承vector,实现了List接口,有List接口所有功能
Stack stack = new Stack<>();
// 放入一个元素
stack.push("a");
// 弹出元素
String pop = stack.pop();
// 取栈顶元素
String peek = stack.peek();
Queue常用API
// 队列先进先出
Queue queue = new ArrayBlockingQueue(10);
// 添加元素
queue.offer("a");
queue.offer("b");
// 队列头部元素
String peek = queue.peek();
// 取出队列头部元素
String poll = queue.poll();
常见集合处理方式
guava集合交集,差集,并集
Set sets = Sets.newHashSet(1, 2, 3, 4, 5, 6);
Set sets2 = Sets.newHashSet(3, 4, 5, 6, 7, 8, 9);
// 交集
System.out.println("交集为:");
SetView intersection = Sets.intersection(sets, sets2);
for (Integer temp : intersection) {
System.out.println(temp);
}
// 差集
System.out.println("差集为:");
SetView diff = Sets.difference(sets, sets2);
for (Integer temp : diff) {
System.out.println(temp);
}
// 并集
System.out.println("并集为:");
SetView union = Sets.union(sets, sets2);
for (Integer temp : union) {
System.out.println(temp);
}
java集合交集,差集,并集
public static void main(String[] args) {
List list1 = new ArrayList();
list1.add("A");
list1.add("B");
list1.add("C");
List list2 = new ArrayList();
list2.add("C");
list2.add("B");
list2.add("D");
// 并集
list1.addAll(list2);
// 去重复并集
list2.removeAll(list1);
list1.addAll(list2);
// 交集
list1.retainAll(list2);
// 差集
list1.removeAll(list2);
}
Stream流取并集的几种方式
List list1 = new ArrayList();
list1.add(new YearMonthBo(2020, 1));
list1.add(new YearMonthBo(2020, 2));
list1.add(new YearMonthBo(2019, 12));
List list2 = new ArrayList();
list2.add(new YearMonthBo(2020, 2));
list2.add(new YearMonthBo(2020, 3));
list2.add(new YearMonthBo(2019, 11));
// 1:集合并集
List collect = Stream.of(list1, list2).flatMap(List::stream).collect(Collectors.toList());
// 2:去重取并集
Set collect1 = Stream.of(list1, list2).flatMap(List::stream).collect(Collectors.toSet());
// 3:合并去重
List collect2 = Stream.of(list1, list2).flatMap(List::stream).distinct().collect(Collectors.toList());
// 4:合并并根据对象中的某些字段去重(相乘相加等操作均可)
ArrayList yearMonthBoArrayList = Lists.newArrayList(Stream.of(list1, list2).flatMap(List::stream)
.collect(Collectors.toMap(YearMonthBo::getYear, Function.identity(), (x, y) -> x == null ? y : x)).values());
// 5:集合合并
List collect3 = Stream.concat(list1.stream(), list2.stream()).collect(Collectors.toList());
集合排序
// 1:根据compare接口排序,需实现Comparable接口
Collections.sort(testList);
// 2:根据自定义比较器排序
Collections.sort(testList, YearMonthBo::compareTo);
// 3:根据自定义排序器排序
testList.sort(Comparator.comparingInt(YearMonthBo::getYear));
集合 转换为map
// collectors方法 tomap
Map map2 = testList.stream().collect(Collectors
.toMap(YearMonthBo::getYear, Function.identity(), (a1, a2) -> new YearMonthBo(a1.getYear() + a2.getYear(), a2.getMonth() + a1.getMonth())));
// collectors方法 grouping by
Map> map3 = testList.stream().collect(Collectors.groupingBy(YearMonthBo::getYear));
// guava tomap
Map map1 = Maps.uniqueIndex(testList, YearMonthBo::getYear);
集合去重
List list1 = new ArrayList();
list1.add(new YearMonthBo(2020, 1));
list1.add(new YearMonthBo(2020, 2));
list1.add(new YearMonthBo(2019, 12));
List list2 = new ArrayList();
list2.add(new YearMonthBo(2020, 2));
list2.add(new YearMonthBo(2020, 3));
list2.add(new YearMonthBo(2019, 11));
// 1:根据equals和hashcode去重
List collect4 = Stream.of(list1, list2).flatMap(List::stream).distinct().collect(Collectors.toList());
// 2:”根据对象属性去重
Collection values = Stream.of(list1).flatMap(List::stream)
.collect(Collectors.toMap(YearMonthBo::getYear, Function.identity(), (x, y) -> x == null ? y : x)).values();
// 3:根据对象属性去重
List collect5 = Stream.of(list1, list2).flatMap(List::stream).filter(distinctByKey(YearMonthBo::getYear)).collect(Collectors.toList());
集合反转
public static void main(String[] args) {
List list1 = Lists.newArrayList("a","b","c");
List list2 = new ArrayList();
list2.add("C");
list2.add("B");
list2.add("D");
// guava反转
Lists.reverse(list1 );
// java反转
for循环反转
}
笛卡尔积
HashSet set = Sets.newHashSet(1, 4, 3);
HashSet set1 = Sets.newHashSet(1, 9, 8);
Set> lists = Sets.cartesianProduct(set, set1);
System.out.println(lists);
字符串集合拼接(空分隔符,带分隔符,带分隔符和前缀后缀)
List strList = new ArrayList<>();
strList.addAll(Lists.newArrayList("aa", "vv", "ww"));
String collect = strList.stream().collect(Collectors.joining());
String collect = strList.stream().collect(Collectors.joining(","));
String collect = strList.stream().collect(Collectors.joining(",","prefix","suffix"));
初始化
java集合初始化
序号
初始化方式
备注
1
ArrayList objects1 = new ArrayList<>();
2
ArrayList objects1 = new ArrayList<>(cap);
3
ArrayList objects1 = new ArrayList<>(Collection);
guava集合初始化
序号
初始化方式
备注
1
HashSet integers = Sets.newHashSet(1, 2, 4);
自动识别泛型类型
2
HashSet objects = Sets.newHashSet();
3
Sets.newHashSetWithExpectedSize(5);
4
Sets.newHashSet(Itearable);
红黑树
简介:
红黑树是一种自平衡排序二叉树,树中每个节点的值,都大于或等于在它的左子树中的所有节点的值,并且小于或等于在它的右子树中的所有节点的值,这确保红黑树运行时可以快速地在树中查找和定位的所需节点,是一种特殊的排序二叉树。
排序二叉树要么是一棵空二叉树,要么是具有下列性质的二叉树:
若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
它的左、右子树也分别为排序二叉树。
排序二叉树的构成:每当程序希望添加新节点时:系统总是从树的根节点开始比较 ,即将根节点当成当前节点,如果新增节点大于当前节点、并且当前节点的右子节点存在,则以右子节点作为当前节点;如果新增节点小于当前节点、并且当前节点的左子节点存在,则以左子节点作为当前节点;如果新增节点等于当前节点,则用新增节点覆盖当前节点,并结束循环 —— 直到找到某个节点的左、右子节点不存在,将新节点添加该节点的子节点 —— 如果新节点比该节点大,则添加为右子节点;如果新节点比该节点小,则添加为左子节点。
红黑树的定义:
性质 1:每个节点要么是红色,要么是黑色。
性质 2:根节点永远是黑色的。
性质 3:所有的叶节点都是空节点(即 null),并且是黑色的。
性质 4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)
性质 5:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。
根据性质 5:红黑树从根节点到每个叶子节点的路径都包含相同数量的黑色节点,因此从根节点到叶子节点的路径中包含的黑色节点数被称为树的“黑色高度(black-height)”。
性质 4 则保证了从根节点到叶子节点的最长路径的长度不会超过任何其他路径的两倍。假如有一棵黑色高度为 3 的红黑树:从根节点到叶节点的最短路径长度是 2,该路径上全是黑色节点(黑节点 - 黑节点 - 黑节点)。最长路径也只可能为 4,在每个黑色节点之间插入一个红色节点(黑节点 - 红节点 - 黑节点 - 红节点 - 黑节点),性质 4 保证绝不可能插入更多的红色节点。由此可见,红黑树中最长路径就是一条红黑交替的路径。由此我们可以得出结论:对于给定的黑色高度为 N 的红黑树,从根到叶子节点的最短路径长度为 N-1,最长路径长度为 2 * (N-1)。
提示:排序二叉树的深度直接影响了检索的性能,正如前面指出,当插入节点本身就是由小到大排列时,排序二叉树将变成一个链表,这种排序二叉树的检索性能最低:N 个节点的二叉树深度就是 N-1。
红黑树通过上面这种限制来保证它大致是平衡的——因为红黑树的高度不会无限增高,这样保证红黑树在最坏情况下都是高效的,不会出现普通排序二叉树的情况。
由于红黑树只是一个特殊的排序二叉树,因此对红黑树上的只读操作与普通排序二叉树上的只读操作完全相同,只是红黑树保持了大致平衡,因此检索性能比排序二叉树要好很多。
但在红黑树上进行插入操作和删除操作会导致树不再符合红黑树的特征,因此插入操作和删除操作都需要进行一定的维护,以保证插入节点、删除节点后的树依然是红黑树。
插入节点后的修复
删除节点后的修复
五:实践
集合使用规范
集合对比
名称
是否有序
是否允许重复
线程安全
是否可以为空
特性
备注
ArrayList
有序
是
不安全
可以
LinkList
有序
是
不安全
可以
HashSet
无序
否
不安全
可以
TreeSet
有序
否
不安全
不可以
HashMap
无序
Key唯一 value可重复
不安全
key value均可以
TreeMap
有序
Key唯一 value可重复
不安全
value可以 key不可以
HashTable
无序
Key唯一 value可重复
安全
不可以
ConcurrentHashMap
无序
Key唯一 value可重复
安全
不可以
Vector
有序
是
安全
可以
区别与适用场景:
Set和List对比: Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
适用场景:频繁对集合元素进行增删操作的场景;需要保证数据不重复的场景。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
适用场景:查询较为频繁的场景;元素可重复;需在确定索引上进行增删操作的场景。
ArrayList和LinkList的对比:
ArrayList: 随机访问get和set优于linklist,因为linklist要移动指针。
LinkList:对于add和remove操作优于arraylist,因为arraylist要移动数据。(一般情况下)
利用set集合去重
/**
* 获取对应三级部门下的采购员
*/
public Set getPurControllerByDept(Integer deptId3) {
// 根据三级部门获取采购/销售/部门/品牌/品类关系
List relVoList = sccAuthJsfService.getCateBrandPurSalerRelByDept3(deptId3);
// 部门下不存在权限关系则记录异常日志,返回空关系集合
if (CollectionUtils.isEmpty(relVoList)) {
log.error("三级部门:{}下权限关系数据为空!", deptId3);
return Collections.emptySet();
}
// 将权限关系放到set集合中,自动去重
Set purchaserVoSet = Sets.newHashSet();
relVoList.forEach(relVo -> {
PurchaserVo purchaserVo = new PurchaserVo();
purchaserVo.setPurchaserErp(relVo.getPurchaserControlErp());
purchaserVo.setPurchaserName(relVo.getPurchaserControlName());
purchaserVoSet.add(purchaserVo);
});
return purchaserVoSet;
}
排序,去重,根据某字段排序
/**
* 合并去重排序
*/
private List unionAndDistinct2List(List purDataList,List purDataInDb) {
return Stream.concat(purDataList.stream(), purDataInDb.stream()).distinct().collect(
Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getValue()))),
ArrayList::new));
}
集合转换MAP
集合转换为map时可以使用Collectors类中的toMap方法,但是需要注意,如果有重复的键或者值时,会抛出异常。需自己传入处理重复的方法。 (也可转换为线程安全的map :toConcurrentMap)
也可以使用GroupingBy进行分组。
List yearMonthBoList = Lists.newArrayList();
yearMonthBoList.add(new YearMonthBo(2020, 11));
yearMonthBoList.add(new YearMonthBo(2020, 12));
yearMonthBoList.add(new YearMonthBo(2020, 11));
// Exception in thread "main" java.lang.IllegalStateException: Duplicate key 11
Map map1 = yearMonthBoList.stream().collect(Collectors.toMap(Function.identity(), YearMonthBo::getMonth));
// success
Map map2 = yearMonthBoList.stream().collect(Collectors.toMap(Function.identity(), YearMonthBo::getMonth, Integer::sum));
// success
Map> map4 = yearMonthBoList.stream().collect(Collectors.groupingBy(yearMonthBo -> yearMonthBo.getYear()));