1:Set
Set适合
动态查找
的集合容器。
set底层是纯K模型
- HashSet:作为set接口的主要实现类,线程不安全,可以存储null值
- LinkedHashSet:HashSet的子类
- TreeSet:使用红黑树存储
HashSet:
底层为数组+链表
HashSet:存储元素
不重复
,且无序
(存储数据并非按照底层数组的索引顺序添加)
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable,Serializable
{
static final long serialVersionUID = -5024744406713321676L;
//HashSet底层存在一个HashMap,而set存入的值被作为map的key,所以不能存入重复元素
private transient HashMap<E,Object> map;
}
//HashSet的add方法
public boolean add(E e) {
//如果未包含此次存入的数据返回true,如果要存入的数据已存在返回false
return map.put(e, PRESENT)==null;
}
HashSet的遍历方式:
- foreach
- 迭代器
- toArray
Set<String> set=new HashSet<>();
//可以存储null
set.add(null);
//即使添加顺序为cba但输出为abc
set.add("c");
set.add("b");
set.add("a");
//foreach
for (String s :
set) {
System.out.println(s);
}
//toArray后遍历
Object[] objects = set.toArray();
for(int i=0;i<objects.length;i++){
System.out.println(objects[i]);
}
//迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
LinkedHashSet:
作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个和后一个数据,因此对于频繁的遍历操作,LinkedHashSet效率高于HashSet,且可以按照添加顺序遍历。
Set<Object> set=new LinkedHashSet<>();
set.add(null);
set.add("c");
set.add("b");
set.add("a");
//[null, c, b, a]
System.out.println(Arrays.toString(set.toArray()));
TreeSet:
底层为红黑树,可以按照对象属性排序输出(Comparable,Comparator)TreeSet不可以存入空值,会报
NullPointerException
排序方式1:自然排序
//自定义类需要实现Comparable,Comparator接口
class u implements Comparable{
int id;
public u(int id) {
this.id = id;
}
@Override
public int compareTo(Object o) {
if(o instanceof u){
//按照id从大到下
return ((u) o).id-this.id;
}else {
return 0;
}
}
}
public static void main(String[] args) {
Set<Object> set=new TreeSet<>();
set.add(new u(1));
set.add(new u(2));
set.add(new u(3));
set.add(new u(4));
Iterator<Object> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
/*
u{id=4}
u{id=3}
u{id=2}
u{id=1}
*/
}
}
排序方式2:定制排序
Comparator<u> comparator=new Comparator<u>() {
@Override
public int compare(u o1, u o2) {
return o1.id-o2.id;
}
};
Set<u> set=new TreeSet<>(comparator);
set.add(new u(1));
set.add(new u(2));
set.add(new u(3));
set.add(new u(4));
Iterator<u> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
2:Map
Map
:Key-Value模型
- HashMap:作为Map的主要实现类,线程不安全效率高,可以存储null的key和value。
- LinkedHashMap:HashSet的子类,保证在遍历Map元素按照添加顺序进行遍历。
- TreeMap:底层采用红黑树保证按照添加的key-value(键值对)进行遍历,实现排序遍历,此时考虑使用key的自然或者定制排序,不能存储null的key和value。
- Hashtable:作为Map的古老实现类,线程安全效率低,不能存储null的key和value。(多线程推荐使用ConcurrentHashMap)
HashMap:
jdk7的底层结构只有数组+链表,jdk8的底层结构是数组+链表+红黑树,树化是为了优化查找效率。
HashMap底层重要常量JDK8:
> static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 默认长度为16
> static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子0.75
> static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量2的30次方
> static final int TREEIFY_THRESHOLD = 8; //树化条件之一,链表长大于8
> static final int MIN_TREEIFY_CAPACITY = 64; //树化条件之一,数组长大于64
> static final int UNTREEIFY_THRESHOLD = 6; //红黑树变为链表条件,链表长为6
HashMap构造方法JDK8:
// initialCapacity:指定大小 loadFactor:负载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
//指定大小
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//负载因子为给定的0.75
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
HashMap的创建JDK1.8:
当new一个HashMap对象且没有指定容量时,底层数组长度为0,当第一次put元素时,数组长默认为16
> //部分源码
> final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
> boolean evict) {
> Node<K,V>[] tab; Node<K,V> p; int n, i;
> if ((tab = table) == null || (n = tab.length) == 0)
> n = (tab = resize()).length;//此时底层数组的长度为0
> ......
>
> //第一次put的时候数组长度设置为16
> else { // zero initial threshold signifies using defaults
> newCap = DEFAULT_INITIAL_CAPACITY;//16
> newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
> }
HashMap的扩容机制JDK1.8:
2倍扩容
> 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 二倍扩容
> }
HashMap的put流程JDK1.8:
- 计算哈希值,确认索引位置
- 该位置没有元素则直接添加
- 该位置有元素,使用equals方法判断是否相对,equals相等则替换,equals不等则插入链表
- 插入元素可能发生扩容和树化,如果此时链表节点超过8个需要树化或者扩容,如果数组长小于64则2倍扩容,如果数组大于64则树化。
- JDK1.8插入链表使用的尾插法,JDK1.7是头插法
hash()和equals():
- 计算哈希值,确定索引位置,如果此位置为空,则插入数据(
插入成功情况一
)- 如果此位置不为空,代表此位置存在一个或者多个数据(多个数据以链表或者红黑树的形式存在),比较要插入的数据的哈希值和已存在的数据的哈希值:
- 如果哈希值不同,则插入数据(
插入成功情况二
)- 如果哈希值相同,调用插入数据所在类的equals()方法比较数据是否相同
- 如果equals()返回false,则插入数据(
插入成功情况三
)- 如果equals()返回true,则替换之前的数据(
修改功能
)
//put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//计算哈希值
static final int hash(Object key) {
int h;
//右移16位和本身进行疑惑,为了让32位数据都参与到哈希值的计算中可以减少哈希冲突
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//数组长度n-1 与 哈希值获取index下标 这样计算方式比模运算快
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> 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<K,V>)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链表的结点超过8
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//treeifyBin(tab, hash); 如果数组长度小于64则扩容无需树化
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
HashMap的遍历方式:
- key和value分开遍历
HashMap<String,Integer> map=new HashMap<>();> map.put("1",1);
map.put("2",2);
map.put("3",3);
map.put("4",4);
//遍历key
Set<String> keySet = map.keySet();
Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//遍历value
Collection<Integer> values = map.values();
for(Integer value:values){
System.out.println(value);
}
- 使用entrySet( )
HashMap<String, Integer> map = new HashMap<>();
map.put("1", 1);
map.put("2", 2);
map.put("3", 3);
map.put("4", 4);
//遍历key value
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> next = iterator.next();
System.out.println(next.getKey()+"-->"+next.getValue());
}
LinkedHashMap:
在HashMap的底层结构的基础上,添加了一对指针,指向前一个和后一个元素,对于频繁遍历的操作,此类执行效率高于HashMap,而且可以保证遍历顺序为添加顺序。
LinkedHashMap的内部类Entry:
添加了before after 知道了前后的Entry 记录了添加顺序static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
TreeMap:
底层为红黑树,保证按照添加的key-value(键值对)进行遍历,实现排序遍历,此时考虑使用key的自然或者定制排序。(Comparable,Comparator)
定制排序:Comparator
Comparator<String> comparator=new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);//key从大到小
}
};
Map<String,Integer> map=new TreeMap<>(comparator);
map.put("1", 1);
map.put("2", 2);
map.put("3", 3);
map.put("4", 4);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
自然排序:Comparable
//实体类
class U implements Comparable<U>{
int id;
@Override
public int compareTo(U o) {
return o.id-this.id;//id从大到小
}
//toString() hashCode() equals()...
}
//按照id从大到小遍历
Map<U,String> map=new TreeMap<>();
map.put(new U(1), "一号");
map.put(new U(2), "二号");
map.put(new U(3), "三号");
map.put(new U(4), "四号");
Set<Map.Entry<U, String>> entries = map.entrySet();
Iterator<Map.Entry<U, String>> iterator = entries.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}