Java 集合 有两大模块!
Collection 下 :
List系(有序、元素可重)
Set系(无序、元素不可重)set 根据 equals 和 hashcode判断(是否重复)
一个对象要存储在set中,必须重写 equals 和 hashCode 方法!Map下:
HashMap 不同步
TreeMap 同步
首先,List 和 Set 类似,都是单例元素的集合!有相同父接口:Collection
1、Set 里面 元素 不可重复
- 即不能有两个 相等 (不仅仅是相同)的对象,即假设Set集合中有了一个A对象,现在我要向Set集合再存入一个B对象,但B对象与A对象equals相等,则B对象存储不进去。
- Set 集合的 add():当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true,当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。
- 遍历:不能根据下标取!只能以 Iterator 接口取得所有的元素,再逐一遍历各个元素!
2、List 表示有先后顺序的集合
- add() 方法:
add(Obj e):按先来后到的顺序,排到最后!
add(int index,Obj e):插入指定位置,其他后移!
本质:用索引变量指向插入的对象!- 查询:
用Iterator接口取得所有的元素,再逐一遍历各个元素
get(index):根据下标获取
3、 Map 与 List 、Set 不同
map 是两列的集合!
- put(obj key,obj value):不能存储重复的key,这个重复的规则也是按equals比较相等。
- get(Object key),也可以获得所有的key的结合,还可以获得所有的value的结合,还可以获得key和value组合成的Map.Entry对象的集合。
List以特定次序来持有元素,可有重复元素。Set无法拥有重复元素,内部排序。Map保存key-value值,value可多值。
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
都实现接口Collection -> List 接口,都是有序集合,可以按位置取! 这是和Set系 最大的两个不同:重复、取值!
同步性:
Vector 是线程安全的!
ArrayList 是线程不安全的!
扩容机制:
Vector默认扩容2倍;而ArrayLsit默认扩容1.5倍!
ArrayList
和Vector
都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢
Vector
由于使用了 synchronized 方法(线程安全),通常性能上较 ArrayList 差。而 LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,索引就变慢了,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
LinkedList
也是线程不安全的, LinkedList 实现了Deque
(双端队列)接口,使得 LinkedList 可以被当作堆栈和队列来使用。
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
- 当队列使用:
Deque queue = new LinkedList();
用offer()插入 和
poll()方法删除:从队首获取元素,同时获取的这个元素将从原队列删除;- 当做栈列使用:
Deque stack = new LinkedList();
push() :添加!
pop()弹出:表示返回栈顶的元素,同时该元素从栈中删除,当栈中没有元素时,调用该方法会发生异常;注意:使用peek()查询!
HashMap
是Hashtable
的轻量级实现(非线程安全的实现)!都完成了Map接口!主要区别在于
HashMap
允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于Hashtable
。
HashMap
允许将null作为一个entry的key或者value,而 Hashtable 不允许。
HashMap
把Hashtable
的 contains 方法去掉了,改成 containsvalue 和 containsKey 。因为contains方法容易让人引起误解。
Hashtable
继承自 Dictionary 类,而HashMap
是Java1.2引进的Map interface的一个实现。最大的不同是(同步与否), Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供同步。
历史原因:
Hashtable
是基于陈旧的 Dictionary 类的,HashMap
是Java 1.2引进的Map接口的一个实现
同步性:Hashtable
是线程安全的,也就是说是同步的,而HashMap
是线程序不安全的,不是同步的
值:只有HashMap
可以让你将空值作为key或value
快速失败 (见20题 ):HashMap提供对key的Set进行遍历,因此它是fail-fast的,但Hashable
提供对key的Enumeration进行遍历,它不支持fail-fast。
同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。
在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。
Java5介绍了并发集合ConcurrentHashMap
,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。
不管是同步集合还是并发集合他们都支持线程安全,他们之间主要的区别体现在性能和可扩展性,还有他们如何实现的线程安全上。
同步
HashMap , Hashtable , HashSet , Vector , ArrayList
相比他们并发的实现(ConcurrentHashMap , CopyOnWriteArrayList , CopyOnWriteHashSet
)会慢得多。造成如此慢的主要原因是锁, 同步集合会把整个Map或List锁起来,而并发集合不会。并发集合实现线程安全是通过使用先进的和成熟的技术像锁剥离。
- 比如
ConcurrentHashMap
会把整个Map 划分成几个片段,只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段。(分段上锁!)- 同样的,
CopyOnWriteArrayList
允许多个线程以非同步的方式读,当有线程写的时候它会将整个List复制一个副本给它。(非同步读+复制写)如果在读多写少这种对并发集合有利的条件下使用并发集合,这会比使用同步集合更具有可伸缩性。
poll() 和 remove() 都是从队列中取出一个元素
poll() 在获取元素失败的时候会返回空
remove() 失败的时候会抛出异常
LinkedHashMap
维持的顺序是元素插入的顺序;
PriorityQueue
是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部(小/大顶堆)。
当遍历一个 PriorityQueue
时,没有任何顺序保证;
但是 LinkedHashMap
能保证遍历顺序是元素插入的顺序。
WeakHashMap
的工作与正常的HashMap
类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。
ArrayList
的默认大小是 10 个元素,
HashMap
的默认大小是 16 个元素(必须是2的幂)
private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
Comparable接口有compareTo(T OBJ)方法,它被排序方法所使用。我们应该重写这个方法,如果“this”对象比传递的对象参数更小、相等或更大时,它返回一个负整数、0或正整数。
都是用于比较两个对象“顺序”的接口
都可以使用Collections.sort()方法来对对象集合进行排序
所属包不同:
Comparable
位于java.lang包下,而Comparator
则位于java.util包下
Comparable
是在集合内部定义的方法实现的排序,Comparator
是在集合外部实现的排序
使用
Comparable
接口来实现对象之间的比较时,可以使这个类型(设为A)实现Comparable
接口,并可以使用**Collections.sort()**方法来对A类型的List进行排序,之后可以通过a1.comparaTo(a2)来比较两个对象;当使用
Comparator
接口来实现对象之间的比较时,只需要创建一个实现Comparator
接口的比较器(设为AComparator),并将其传给Collections.sort()方法即可对A类型的List进行排序,之后也可以通过调用比较器AComparator.compare(a1, a2)
来比较两个对象。
可以说一个是自己完成比较,一个是外部程序实现比较的差别而已。
用Comparator
是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
使用
Comparable
方式比较时,我们将比较的规则写入了比较的类型中,其特点是高内聚。但如果哪天这个规则需要修改,那么我们必须修改这个类型的源代码。如果使用
Comparator
方式比较,那么我们不需要修改比较的类,其特点是易维护,但需要自定义一个比较器,后续比较规则的修改,仅仅是改这个比较器中的代码即可。
你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过Collections.sort() 来排序。
TreeMap 是一个通过红黑树实现有序的 key-value 集合。
TreeMap 继承 AbstractMap,也即实现了Map,它是一个Map集合。
TreeMap实现了 NavigableMap 接口,它支持一系列的导航方法,
TreeMap实现了 Cloneable 接口,它可以被克隆
TreeMap
本质是 Red-Black Tree,它包含几个重要的成员变量:root、size、comparator。其中root是红黑树的根节点。它是Entry类型,Entry是红黑树的节点,它包含了红黑树的6个基本组成:key、value、left、right、parent和color。Entry节点根据Key排序,包含的内容是value。Entry中key比较大小是根据比较器comparator来进行判断的。size是红黑树的节点个数。
错误写法示例一:
public static void remove(ArrayList<String> list) {
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.equals("bb")) {
list.remove(s);
}
}
}
错误写法示例二:
public static void remove(ArrayList<String> list) {
for (String s : list) {
if (s.equals("bb")) {
list.remove(s);
}
}
}
源码分析
ArrayList 中的 remove方法进行了重载!
- remove(Obj):
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
一般 最终调用 faseRemove 方法
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // Let gc do its work
}
可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。
错误写法一:在遍历第二个元素字符串bb时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也是字符串bb)至当前位置,导致下一次循环遍历时后一个字符串bb并没有遍历到,所以无法删除。针对这种情况可以倒序删除的方式来避免;因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。
错误写法二:foreach写法是对实际的 Iterable、hasNext、next方法的简写,问题同样处在上文的 fastRemove 方法中,可以看到第一行把modCount变量的值加一,但在 ArrayList 返回的迭代器(该代码在其父类AbstractList中):
public Iterator<E> iterator() {
return new Itr();
}
这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的 next 方法:
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
第一行
checkForComodification
方法(modCount 改变 抛出异常!):
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法把修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。
public static void remove(ArrayList<String> list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("bb")) {
it.remove();
}
}
}
原理:1.8之前:数组+链表;1.8之后:数组+链表+红黑树。(都是为了解决hash冲突)
内部存储结构:1.8之前:Entry
;1.8之后:Node 。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
可以看见Node类中除了键值对(key-value)以外,还有额外的两个数据:
- hash : 这个是通过计算得到的散列值
- next:指向另一个Node,这样HashMap可以像链表一样存储数据
// 默认容量大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 装载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 转换为二叉树的阀值(这也是为什么链表长为8时会转换为红黑树)
static final int TREEIFY_THRESHOLD = 8;
// 转换为二叉树的最低阀值
static final int UNTREEIFY_THRESHOLD = 6;
// 二叉树最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
// 哈希表
transient Node[] table;
// 键值对的数量
transient int size;
// 记录HashMap结构改变次数,与HashMap的快速失败相关
transient int modCount;
// 扩容的阈值
int threshold;
// 装载因子
final float loadFactor;
put操作: hash获得下标->封装为对象(Node/Entry)->插入(是否扩容)
(注意以下源码)
else if (p instanceof TreeNode) // 如果当前p是二叉树,则放入二叉树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
与
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
// 如果链表中的值大于(转换为二叉树的阈值)TREEIFY_THRESHOLD - 1,则将链表转换成二叉树
1.8之前:头插法!O(n)
1.8之后:尾插链表+插入红黑树 O(logn)
get操作: 思路如下:
- bucket里的第一个节点,直接命中;
- 如果有冲突,则通过key.equals(k)去查找对应的entry
- 若为树,则在树中通过key.equals(k)查找,O(logn);
- 若为链表,则在链表中通过key.equals(k)查找,O(n)。
默认大小为16 ,当达到条件时调用resize()方法,扩大HashMap容量!
扩容条件: loadFactor(扩容因子) 的默认值为 0.75 ;即默认情况下:hashMap的元素个数超过 默认大小(16)* 0.75 = 12 时,进行扩容!
HashMap 的 capacity 必须满足是 2的N次方 ,如果在构造函数内指定的容量n不满足,HashMap会通过下面的算法将其转换为大于n的最小的2的N次方数。(tableSizeFor方法:移位+或)有兴趣了解的同学可以参考:https://www.cnblogs.com/xiyixiaodao/p/14483876.html
// 减1→移位→按位或运算→加1返回
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
是非线程安全的!多线程环境可使用Hashtable!
在 Java5 以后,出现了
ConcurrentHashMap
ConcurrentHashMap 相对于 Hashtable 来说:
将 hash 表分为16个桶(默认值),诸如 get、 put、 remove 等常用操作只锁住当时使用的桶!
是Java集合的一种错误检测机制!当多个线程对集合上的结构进行改变是,可能会产生 fast-fail!(是可能,不是一定)。
例如:
假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException 异常,从而产生fail-fast机制。
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
从上我们可以知道!modCount 是记录每次HashMap结构修改!forEach方法会在在进入for循环之前,将modCount赋值给mc,如果在for循环之后,HashMap的结构变化了,那么导致的结果就是modCount != mc,则抛出ConcurrentModificationException()异常。
1、什么时候会使用HashMap?他有什么特点? 是基于Map接口的实现,存储键值对时,它可以接收 null 的键值,是非同步的,HashMap存储着Entry(hash, key, value, next)对象。
工作原理+底层实现
get、put的原理!
扩容机制!
每种编程语言中都有集合。集合框架的部分优点如下:
1、使用核心集合类降低开发成本,而非实现我们自己的集合类。
2、随着使用经过严格测试的集合框架类,代码质量会得到提高。
3、通过使用JDK附带的集合类,可以降低代码维护成本。
4、复用性和可操作性。
Java1.5 引入了泛型,所有的集合接口和实现都大量地使用它。
泛型允许我们为集合提供一个可以容纳的对象类型
如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。
因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
尽管Map接口和它的实现也是集合框架的一部分,但 Map 不是集合,集合也不是 Map 。因此,Map 继承 Collection 毫无意义,反之亦然。
如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。
Iterator
接口提供遍历任何 Collection 的接口。
可以从一个 Collection中 使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration
。迭代器允许调用者在迭代过程中移除元素(17的最后有提到!)。
package java.util;
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
函数接口不同:
Enumeration
只有2个函数接口。 通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
Iterator
只有3个函数接口。 Iterator除了能读取集合的数据之外,也能数据进行删除操作。
Iterator 支持 fail-fast 机制,而 Enumeration 不支持:
Enumeration
是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现Enumeration时,添加了同步。而
Iterator
是JDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator
是支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
所以说iterator 是安全的
语义不明,已知的是,
Iterator
的协议不能确保迭代的次序。然而要注意,ListIterator没有提供一个add操作,它要确保迭代的顺序。
它可以在当前Iterator的顶层实现,但是它用得很少,如果将它加到接口中,每个继承都要去实现它,这没有意义。
List<String> strList = new ArrayList<>();
//使用for-each循环
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
使用迭代器更加线程安全,因为它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException。
每次我们尝试获取下一个元素的时候,Iterator fail-fast属性检查当前集合结构里的任何改动。如果发现任何改动,它抛出ConcurrentModificationException。
Collection
中所有Iterator
的实现都是按fail-fast来设计的(ConcurrentHashMap
和CopyOnWriteArrayList
这类并发集合类除外)。
Iterator
的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util 包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。并发集合和同步集合的区别!
使用并发集合 来避免ConcurrentModificationException,因为是 **fail-safe(安全失败)**的!如:ConcurrentHashMap、CopeOnWriteArrayList!
Iterator接口定义了遍历集合的方法,但它的实现则是集合实现类的责任。(具体的类的遍历方式不同)。每个能够返回用于遍历的Iterator的集合类都有它自己的Iterator实现内部类。
这就允许集合类去选择迭代器是fail-fast还是fail-safe的。比如,ArrayList迭代器是fail-fast的,而CopyOnWriteArrayList迭代器是fail-safe的。
UnsupportedOperationException是用于表明操作不支持的异常。
在JDK类中已被大量运用,在集合框架java.util.Collections.UnmodifiableCollection将会在所有add和remove操作中抛出这个异常。
我们可以使用任何类作为Map的key,然而在使用它们之前,需要考虑以下几点:
(1)如果类重写了equals()方法,它也应该重写hashCode()方法。
(2)类的所有实例需要遵循与equals()和hashCode()相关的规则。请参考之前提到的这些规则。
(3)如果一个类没有使用equals(),你不应该在hashCode()中使用它。
(4)用户自定义key类的最佳实践是使之为不可变的,这样,hashCode()值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashCode()和equals()在未来不会改变,这样就会解决与可变相关的问题了。
例如:我有一个类MyKey,在HashMap中使用它。
//传递给MyKey的name参数被用于equals()和hashCode()中
MyKey key = new MyKey('Pankaj');
//assume hashCode=1234
myHashMap.put(key, 'Value');
// 以下的代码会改变key的hashCode()和equals()值
key.setName('Amit');
//assume new hashCode=7890
//下面会返回null,因为HashMap会尝试查找存储同样索引的key,而key已被改变了,匹配失败,返回null
myHashMap.get(new MyKey('Pankaj'));
//那就是为何String和Integer被作为HashMap的key大量使用。
Map接口提供三个集合视图(三个返回值):
1、Set keyset():返回map中包含的所有key的一个Set视图。
2、Collection values():返回一个map中包含的所有value的一个Collection视图。
3、Set
> entrySet() :返回一个map钟包含的所有映射的一个集合视图。
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。
然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。
基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
java.util.EnumSet是使用枚举类型的集合实现。
当集合创建时,枚举集合中的所有元素必须来自单个指定的枚举类型,可以是显示的或隐示的。EnumSet是不同步的,不允许值为null的元素。它也提供了一些有用的方法,比如
copyOf(Collection c)
、of(E first,E…rest)
和complementOf(EnumSet s)
。
是一个阻塞队列,位于concurrent包下!
在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。
BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。(内部进行等待(阻塞))
BlockingQueue
的实现,比如ArrayBlockingQueue
、LinkedBlockingQueue
、PriorityBlockingQueue
,、SynchronousQueue
等。Java.util.Collections是一个工具类仅包含静态方法,它们操作或返回集合。
数组排序:Arrays.sort()
对象列表排序:Collections.sort()
都有用于自然排序(使用Comparable)或基于标准的排序(使用Comparator)的重载方法sort()
Collections.sort():内部使用数组排序方法!!
Collections需要花时间将列表转换为数组。
Sort方法!分别使用:Comparable、Comparator
在作为参数传递之前,我们可以使用Collections.unmodifiableCollection(Collection c) 方法创建一个 只读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。
我们可以使用 Collections.synchronizedCollection(Collection c) 根据指定集合来获取一个synchronized(线程安全的)集合。
1、根据需要选择正确的集合类型。 比如,如果指定了大小,我们会选用Array而非ArrayList。如果我们想根据插入顺序遍历一个Map,我们需要使用TreeMap。如果我们不想重复,我们应该使用Set。
2、一些集合类允许指定初始容量,所以如果我们能够估计到存储元素的数量,我们可以使用它,就避免了重新哈希或大小调整。
3、基于接口编程,而非基于实现编程,它允许我们后来轻易地改变实现。
4、总是使用类型安全的泛型,避免在运行时出现ClassCastException。
5、使用JDK提供的不可变类作为Map的key,可以避免自己实现hashCode()和equals()。
6、尽可能使用Collections工具类,或者获取只读、同步或空的集合,而非编写自己的实现。它将会提供代码重用性,它有着更好的稳定性和可维护性。
TreeSet:类实现Comparable接口!比较元素大小!
TreeMap:key实现Comparable接口!根据键进行比较!
一个是要求传入的容器的元素实现Comparable接口!sort(list);
一个是第二个参数传入比较器!sort(list,compara()->{……})