常用集合体系图:
Iterable
|
|--Collection
|------List
|------ArrayList
|------LinkedList
|------Vector
|
|--Set
|------HashSet
|------LinkedHashSet
|------TreeSet
|
|--Map
|------HashMap
|----------LinkedHashMap
|------ Hashtable
|----------Properties
|------SortedMap
|-----------TreeMap
优点:
- 集合不用指定长度,它可以自动扩容。
- 可以保存任意类型的元素。
一,Collection
常见方法:
* add/addAll 可以添加元素或者对象
* remove/removeAll
* contains/containsAll
* clear
* size 实际集合的大小
* isEmpty 是否为空
注意:
在使用Iterator的过程中,默认不能做增删,会影响迭代器的指针指空,
报异常ConCurrentModificationException。如果非要做增删可以使用迭代器本身的remove方法。
Collection的遍历方式
- 迭代器遍历
Iterator iterator = new ArrayList().iterator();
//判断,如果返回true,则进行下一步
while(iterator.hasNext()){
//下移一位,并获取对应元素
System.out.println(iterator.next());
}
- foreach遍历
for(Object o: col){
System.out.println(o);
}
显然后者要简洁一些。
二,List 的常用方法
* add(object)增
* remove(index)按指定索引删
* set(index,object)改
* indexOf(object)查
* add(index,object)插入
* get(index)获取
特点:
有序,可重,可变数组
遍历方式:
由于List是有序的(有一个整数索引记录了插入的位置),所以List可以使用for循环来遍历。
还可以使用Collection的两种遍历方式。
三,ArrayList实现List接口
常用的方法跟List的一样,线程不安全
维护了Object[] elementData 初始值为0;
如果添加元素时容量不够首次会给10个空间,然后会扩容到原来的1.5倍。
由于ArrayList是有序的,查询的时候相对索引的速度较快,所以改查的效率较高。
四,Verto
实现
List
接口
跟ArrayList很像,线程安全,扩容到原来的2倍。
五,LinkedList
实现
List
接口
底层是双向链表。
维护first,和last 两个重要的属性
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node last;
每个节点(Node)维护三个属性item,prev,next 。
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
最终实现链表结构!
由于 LinkedList 是链表的结构,在删除元素的时候不需要把应删除元素后面的元素前移,只需将应删除元素前后元素的指针改变指向即可。
-----------综上所诉----------
如果考虑线程安全问题:Vector
不考虑线程安全问题:
查找较多:ArrayList
增删较多:LinkedList
六,Set集合
特点:
无序,不可重。
没有特定的方法,从Coolection继承。
遍历方式与Collection相同(两种)
七,HashSet实现Set接口
无序,不可重。
底层是基于HashMap的结构
通过调用HashCode方法得到元素的哈希码,然后通过equals比较两个相同哈希码的元素,实现去重。
如果想要自定义去重,只需重写HashCode,equals方法。
八,TreeSet
实现
Set接口
无序,不可重
底层是基于TreeMap,而TreeMap基于红黑树结构(二叉树的一种,可以尽可能的调整为平衡二叉树)实现元素的排序。
排序:
1.自然排序
必须让添加的元素实现Comparable接口,实现CompareTo方法
2.定制排序
创建TreeSet对象时,传入一个Comparator接口的对象,并实现里面的compare方法,同时 可以通过比较方法的返回值是否为0来判断是否重复
TreeSet set = new TreeSet(new Comparator(){
@Override
public int compare(Object o1, Object o2)
Book b1 = (Book) o1;
Book b2 = (Book) o2;
return Double.compare(b2.getPrice
(),b1.getPrice());
}
});
九,
Map
用于保存一组键值对的隐射关系,其中键不能重复,但是值可以重复。一个键对应一个值。
常见方法:
put 添加
remove删除
containsKey查找键
containsValue查找值
get根据键获取值
size获取键值对的个数
isEmpty判断元素是否为空
clear清除
entrySet 获取所有的关系
keySet获取所有的键
values获取所有的值
两种遍历方式:
都是先转换为Set对象 --> Set entrys = map.entrySet();
一种是使用迭代器Iterator的 hasNext, next 方法进行循环遍历。
另一种是使用foreac循环遍历
//方式1:通过entryset
Set entrys = map.entrySet();
for (Object object : entrys) {
Map.Entry entry = (Entry) object;
System.out.println(entry.getKey()+"的工资是:"+entry.getValue());
}
System.out.println("---------------------------");
//方式2:通过keySet
Set keys = map.keySet();
for (Object key : keys) {
System.out.println(key+"的工资是:"+map.get(key));
}
十,HashMap实现
Map接口
底层:哈希表。数组+链表+红黑树
维护Node类型的数组table
源码分析:
当HashMap创建对象时,初始化LoadFactor为0.75(最优的离散值),table数组默认为null;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
当第一次添加时,初始化table的容量为16,临界值为12(16*0.74)
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node[] resize() {
Node[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else
{
// zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
.
.
.
return newTab;
}
然后调用putVal方法
★★★★★
①先获取key的二次哈希值并进行取与运算,得出存放的位置
②判断该存放位置上是否有元素,如果没有直接存放
如果该存放位置上已有元素,则进行继续判断:
如果和当前元素直接相等,则覆盖
如果不相等,则继续判断是否是链表结构还是树状结构,按照对应结构的判断方式判断相等
③将size更新,判断是否超过了临界值,如果超过了,则需要重新resize()进行2倍扩容,并打乱原来的顺序,重新排列
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //二次哈希 取与 (二进制都为1,则结果为1)
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //判断数组是否为空或已满,空则进行初始赋值,满则扩容
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) //判断数组是否为空 空则直接添加节点
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p; //相同元素直接覆盖
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);
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;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold) //为了保证哈希表内元素间适当的离散性,当size达到临界点时需要扩容,并重新分配排列
resize();
afterNodeInsertion(evict);
return null;
}
红黑树相当耗费内存,影响程序效率,在HashMap中链表变成红黑树概率极小。
只有当一个桶中的链表的节点数>=8 && 桶的总个数(table的容量)>=64时,会将链表结构变成红黑树结构
了解:
jdk7和jdk8的区别
1.jdk7:创建HashMap对象时,则初始table容量为16
jdk8:创建HashMap对象时,没有初始table,仅仅只是初始加载因子。只有当第一次添加时才会初始table容量为16.
2.jdk7:table的类型为Entry
jdk8:table的类型为Node
3.jdk7:哈希表为数组+链表,不管链表的总结点数是多少都不会变成树结构
jdk8:哈希表为数组+链表+红黑树,当链表的节点数>=8 && 桶的总个数(table的容量)>=64时,会将链表结构变成红黑树结构
十一,Hashtable实现
Map接口
底层结构:哈希表
线程安全 不允许null键null值。
十二,TreeMap实现
Map接口
底层结构:红黑树,可以实现对添加元素的key进行排序
应用:
自然排序:要求key的元素类型实现
Comparable
,并实现里面的
compareTo
方法
定制排序:要求创建TreeMap对象时,传入
Comparator
比较器对象,并实现里面的
compare
方法
十三,Collections工具类的学习
常见方法:
reverse 反转
sort 排序
swap 两各索引处元素的交换
shuffle 随机打乱顺序
max 获取最大值
min 获取最小值
frequency 查找指定元素出现的次数
replaceAll 替换旧值为新值
copy 复制,注意:新集合的size>旧集合的size
十四,泛型
参数化的类型。可以将某个类型当做参数传递给类、接口或方法中。不传默认为Object类型
好处
1、编译时检查待添加的元素类型,提高了类型的安全性
2、减少了类型转换的次数,提高了效率
3、减少了编译警告