文章目录
1.Iterable,Iterator接口
- forEach():用于支持forEach语句,即所有实现了该接口的类,都可以使用forEach遍历
- iterator(): 返回一个迭代器,用于遍历
- hasNext():确认在 迭代器中是否还有下一个接口
- next():返回下一个元素
- remove():移出当前元素
- Note:
-
当你使用迭代器遍历集合时,若试图进行删除操作(如下所示),必须使用迭代器的remove() 方法。当你使用集合的 remove() 方法时,会产生ConcurrentModificationException ,这是因为集合不允许被不同的线程修改,那么系统又是如何辨别修改集合的操作不是来自于迭代器的 remove() 方法呢? 我们可以提前来看一下AbstractList 中 iterator() 方法的实现。我们观察AbstraList源码时,可以发现一个成员变量modCount,在AbstractList中CURD操作时,这个成员都会自加一次,但是在生成迭代器以后,迭代器中的expectedModCount会记录当时的modCount值,并且在每次next() 和 remove() 操作前都会通过 checkForComodification() 来检查expectedModCount 是否等于 modCount ,如果不等于这说明使用了在另外线程中对集合做了修改,则会报错,但是这种方法有一个弊端,因为如果你在集合外只对该集合做一次修改,并且修改完以后就不再使用迭代器,则不会报错,如下所示。
@Test
public void test01() {
ArrayList<Integer> list = new ArrayList<>();
int count = 0;
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
Iterator<Integer> itr = list.iterator();
while (itr.hasNext()) {
list.remove(itr.next()); // 错误
itr.next();
itr.remove(); // 正确
}
while (itr.hasNext()) { //该方法成功,但是感觉并没有什么ruan用
if (count == 2) {
list.remove(itr.next());
break;
}
count++;
}
}
//iteraotr():在AbstractList中的实现方法
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
2.Collection
- Collection集合继承了Iterable接口,因此Collection的子实现类都是可以迭代的
3.List
- 可以看出在List中,增加了关于index的方法
- listIterator() : 这个方法和 iterator() 的区别是,前者可以双向遍历,其他都一致
4.AbstractList
- indexOf(Object) : 查找对应元素的下标,在遍历元素时,采用了迭代器的方法遍历
- Note:若在不实现RandomAccess接口的情况下,for循环遍历的效率比迭代器慢很多
public int indexOf(Object o) {
ListIterator<E> it = listIterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return it.previousIndex();
} else {
while (it.hasNext())
if (o.equals(it.next()))
return it.previousIndex();
}
return -1;
}
- subList(int,int):取原List中的一部分作为子集合,与原集合共享存储位置,也就是不论是修改子集合还是原集合,另一个都会被修改。
- class RandomAccessSubList:实现了RandomAccess的集合,即随机存取,通过查看源代码,发现实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。关于RandomAccess的作用请见DriveMan
5.ArrayList
该类实现了RandomAccess接口,所以支持下标访问。
- 构造函数:
- ArrayList(int initialCapacity) :根据给定的容量构造
- ArrayList():构造一个默认容量为10的ArrayList集合
- ArrayList(Collection<? extends E> c):通过一个Collection 集合构造一个ArrayList 集合
- ensureCapacity(int minCapacity):用于初始化ArrayList的底层数组,当minCapacity > elementDate.length * 1.5 时,将底层数组扩容为minCapacity,当小于时,则扩容为1.5被,[elementData为底层数组]
- trimToSize():将ArrayList的大小改成实际拥有元素的长度,可以和ensureCapacity()配合使用,当初始化完成后,由于1.5倍的扩容策略,可能会产生许多null值,因此这个时候调用此方法可以一定程度上优化程序。顺带一提,底层数组的扩容或者缩容多是调用Arrays.copyOf 或者System.arraycopy方法
- toArray():返回一个Object数组
- toArray(T[] a):返回一个T类型的数组,建议输入一个空数组,这里只是需要数组的a类型,若输入有元素的数组,元素会被覆盖
- clone():返回一个深拷贝的ArrayList
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
//测试
@Test
public void test() {
ArrayList<Integer> list = new ArrayList<>();
Integer[] s = {1, 2, 4, 5, 7, 5, 5, 1, 2, 3 ,5 , 7};
for (int i = 0; i < 8; i++) {
list.add(1);
}
List<Integer> sublist = list.subList(0, 6);
list.set(1, 2);
s = list.toArray(s);
System.out.println(Arrays.toString(s));
}
//Result:[1, 2, 1, 1, 1, 1, 1, 1, null, 3, 5, 7]
- removeIf(Predicate<?>):通过实现了接口**Predicate**的类来选择要移除的元素,建议在这里配合使用匿名内部类来进行选择性剔除,这个用法和**Comparator<?>**非常相似。
- replaceAll():待填坑
6. LinkedList
LinkedList并没有实现RandomAccess接口,故不能实现随机访问。LinkedList实现了Deque接口,所以它是一个双向链表。
- 构造函数
- LinkedList():创建一个空链表
- LinkedList(Collection):创建一个链表内部元素由给定集合内的元素组成
- descendingIterator():返回一个逆序的迭代器
- 应用:
- 作为队列:
- 单向队列:
- offer():向队列中添加元素
- peek():查看队列头元素
- poll():弹出队列头元素
- 双端队列
- offerFirst()/offerLast(): 向队列头部/尾部加入元素
- peekFirst()/peekLast(): 查看队列头部/尾部的第一个元素
- pollFirst()/pollLast(): 抛出队列头部/尾部的第一个元素
- 单向队列:
- 栈:
- push():入栈
- pop():出栈
- 作为队列:
//单向队列
Queue<String> q = new LinkedList<>();
//向队列尾部加入元素,只有顺序没有标号
//所以Queue没有和index有关的方法
q.offer("Tom");
q.offer("Lucy");
//从头部获取元素
String s1 = q.peek(); //查看队列头的元素
String s2 = q.poll(); //取出队列头的元素
//双向队列
Deque<String> dq = new LinkedList<>();
//从头加和从尾部加
dq.offerFirst("Tom1");
dq.offerFirst("Tom2");
dq.offerLast("Lucy1");
dq.offerLast("Lucy2");
System.out.println(dq);
//从头取元素和从尾部取元素
dq.peekFirst();
dq.peekLast();
//栈
Deque<String> stack = new LinkedList<>();
//入栈
stack.push("Tom");
stack.push("Tom1");
stack.push("Ranly");
//出栈
stack.pop();
stack.pop();
stack.pop();
System.out.println(stack);
7.ArrayList,LinkedList的效率问题
访问效率:ArrayList > LinkedList
插入删除效率: LinkedList > ArrayList
综合考虑,我们在开发中基本使用ArrayList为主。
8.Set,AbstractSet
- hashCode():通过将容器中所有元素的哈希值相加得出哈希值
9.HashSet
HashSet的初始大小为16,默认的负载因子为0.75(用于扩容)
- 构造函数:HashSet的构造函数都是通过构造一个HashMap来完成,Map的key部分放入Set的元素,value部分放入Object对象[源码中的对象为PRESENT],默认为null。
- public HashSet():创建一个空的HashSet
- public HashSet(Collection<? extends E> c):创建一个由指定集合内的元素构成的HashSet ,Set的初始大小为Math.max((int) (c.size()/.75f) + 1, 16)。
- public HashSet(int initialCapacity, float loadFactor):指定初始容量和负载因子
- public HashSet(int initialCapacity):指定初始容量
- default HashSet(int initialCapacity, float loadFactor, boolean dummy):dummy可以忽略
Set从实现上来说可以说是Map的的key部分,阅读源码后,HashSet大多方法的实现都是通过Map的方法完成
10.Map
Map并不继承Collection接口
- compute(k, BiFunction<? super K, ? super V, ? extends V> ): V:JDK1.8新增的方法,将key值设置成由原来的key和value映射出来的值,如果新value为null,则删除原来的键值对,返回新value值
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue); //这里使用的BiFunction
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
- computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction):V:如果给定的key值不存在,则添加由给定key值映射出来的value值,如果新的value为null,则不操作。如果存在对应的value值,返回value值,如果不存在则返回新的value值。
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) { //函数值只有在key值不存在的情况下才会计算出来,一定程度上优化了性能
put(key, newValue);
return newValue;
}
}
return v;
}
- computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction):V:如果key对应的键值对存在,则有函数计算出新的value值,如果value值为null,则删除原来的键值对,如果不为null,则取代原来的value,并返回;如果key对应的键值对不存在,则返回null。
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
- putIfAbsent(K key, V value):V:如果key对应的键值对不存在,则添加进去,并返回新的value值;如果存在则返回旧的value值。
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
- replaceAll(BiFunction<? super K, ? super V, ? extends V> function):void:通过BiFunction函数计算出新的value值,对map中存在的键值对进行修改,用新的value值,代替旧的value值。
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
- merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction):V:如果key值对应的键值对存在,则用函数计算出新的value代替,如果新的value值为null,则删除这个键值对,如果原来的减值对不存在,则使用给定的value值代替。
- interface Entry<K,V>:键值对
- comparingByKey(Comparator<? super K> cmp):
- comparingByKey(Comparator<? super K> cmp):