java----集合(Map)
HashMap:
基于哈希表(数组+链表+二叉树(红黑树)) 在jdk1.8之后出现的二叉树 (链表长度大于8后,就排布成二叉树的形式,如果二叉树的节点减少到小于6,有会转变成链表) ,默认数组大小16,使用链表和红黑树的原因就是hash冲突(如果hash不冲突是不用使用链表和红黑树的)
当数组容量>75%(默认),需要扩充,扩充大小是元素组的两倍(2的整数倍),可以通过源码观察),一旦扩充,里面的数据就需用重新排列,非常耗性能,开发中尽量减少扩充次数,可以指定初始容量(tab[i = (n - 1) & hash]) n:数组的长度)
线程不安全;适合单线程使用,其中ConcurrentHashMap在putval内部实现synchronized 锁,并且支持红黑树,而HashTable在put方法上加锁,不支持红黑树。
注意我们所说的线程不安全是指当所有的线程公用一个hashmap的时候,比如在成员变量中定义的hashMap,而hashMap中的put操作没有加锁,就不安全。
如果在方法中定义hashmap,不用考虑安全的问题,方法是在栈中运行的,栈属于线程私有的。
hashMap是无序的,但是按照key遍历出来的值却是按照升序排列(所谓的无序是指不是按照插入的顺序排序)
class Test{ //所有的线程公用 HashMap hashMap = new HashMap(); public void test(){ //hashMap线程私有 HashMap<Object, Object> hashMap = new HashMap<>(); } }
import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class Test { public static void main(String[] args) { Map<Integer, String> map = new HashMap<>(); map.put(1, "小明"); map.put(2, "小花"); map.put(3, "小草"); //通过key取值 //System.out.println(map.get(1)); //遍历map 第一种遍历 Set<Entry<Integer,String>> set = map.entrySet(); for(Entry e:set){ System.out.println(e+","+e.getKey()+","+e.getValue()); } //第二种遍历键 Set<Integer> Set = map.keySet(); for(Integer x:Set){ System.out.println(x+"->"+map.get(x)); } //第三种遍历value Collection<String> str = map.values(); for(String x:str){ System.out.println(x); } //第四种使用foreach map.forEach((key,value)->System.out.println(key+"-->"+value)); } }
HashMap<Object, Object> hashMap = new HashMap<>(); Set<Map.Entry<Object, Object>> entries = hashMap.entrySet(); Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
源码分析
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) /*tab是一个数组,resize初始化数组*/ n = (tab = resize()).length; /*(n - 1) & hash让key 永远小于n-1(数组长度-1,table[15]就是数组最后一位)*/ /*数组的第一个元素为空*/ if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); /*数组元素table[i]不为空*/ else { Node<K,V> e; K k; /*如果hash值相等,key相等 p时候tab中的元素*/ if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) /*将老的元素赋值给e,返回给用户*/ e = p; /*如果table[i]和添加的值key不一样,那么就开始遍历了,判断p是否属于红黑树*/ else if (p instanceof TreeNode) e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); /*如果p属于链表链表,那么就开始遍历每一个链表*/ else { for (int binCount = 0; ; ++binCount) { /*给e赋值*/ 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添加一个值*/ p = e; } } /*如果e为空返回null,如果不为空,返回被覆盖元素的value*/ 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>0.75*容量,就扩增 resize(); afterNodeInsertion(evict); return null; }
final Node<K,V>[] resize() { Node<K,V>[] 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); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; /*第一次初始化的时候,oldTab为null,之后当扩增的时候oldTab就有值了*/ /*扩增后将oldTab转移到新的newTab中*/ if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; /*如果e下面没有字数据,直接将e添加到newTab中*/ if (e.next == null) newTab[e.hash & (newCap - 1)] = e; /*如果e是红黑数的数据结构*/ else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); /*如果e是链表的数据结构*/ else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; /*核心(e.hash & oldCap), 利用它来判断它在newTab中的位置*/ do { next = e.next; /*原hash值(二进制)倒数第5位如果是0,在newTab中位置不变(这个数字5是根据oldCap得来的(当oldCap为16(10000),就是5))*/ if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } /*原hash值(二进制)倒数第5位如果是1,则位置=原来位置+oldCap*/ else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
HashTable:
基于hash表实现(数组加链表,没有实现红黑树)
默认数组大小为11,加载因子为0.75
扩充方法:原数组大小 *2 + 1,原因就是减少hash碰撞
添加数据的时候,首先是遍历tab[x]的一条链表,如果插入数据key重复,直接替换Entry中的value,返回oldvalue,如果没有重复数据,从链表的头部插入数据。
线程安全
import java.util.Hashtable; import java.util.Map; public class Test { public static void main(String[] args) { Map<Integer,String> table = new Hashtable<>(); table.put(1, "one"); table.put(2, "two"); table.put(3, "three"); table.forEach((key,value)->System.out.println(key+","+value)); } }
源码分析
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Hashtable.Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Hashtable.Entry<K,V> entry = (Hashtable.Entry<K,V>)tab[index]; /*遍历链表(hash发生碰撞的链表),看看是否有重复的key*/ for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } /*插入一个新的数据*/ addEntry(hash, key, value, index); return null; }
private void addEntry(int hash, K key, V value, int index) { modCount++; Hashtable.Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded /*开始扩增*/ rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Hashtable.Entry<K,V> e = (Hashtable.Entry<K,V>) tab[index]; /*从链表的头部添加数据,原来的数据e,放在新的数据后面*/ tab[index] = new Hashtable.Entry<>(hash, key, value, e); count++; }
LinkedHashMap:
帮我们保持插入是的顺序
LinkedHashMap是HashMap的子类
此类使用双重链表来维护元素的添加的顺序,HashMap执行put后,会调用afterNodeInsertion方法,LinkedHashMap重载了这个方法,用来put数据后的处理的。
import java.util.LinkedHashMap; import java.util.Map; public class Test { public static void main(String[] args) { Map<Integer,String> table = new LinkedHashMap<>(); table.put(1, "one"); table.put(2, "two"); table.put(3, "three"); table.forEach((key,value)->System.out.println(key+","+value)); } }
TreeMap:
只基于二叉树的红黑树(平衡二叉树)
注意,如果遇到重复的数据,key不变,新的value会替换老的value(默认情况)
import java.util.Map; import java.util.TreeMap; public class Test { public static void main(String[] args) { Map<Dog,String> table = new TreeMap<>(); // table.put(1, "one"); // table.put(3, "three"); // table.put(2, "two"); table.put(new Dog(10,"1哈"), "dog1"); table.put(new Dog(30,"2哈"), "dog1"); table.put(new Dog(20,"3哈"), "dog1"); table.forEach((key,value)->System.out.println(key+","+value)); } } class Dog implements Comparable<Dog>{ private int age; private String name; public Dog(int age, String name) { super(); this.age = age; this.name = name; } @Override public int compareTo(Dog o) { return this.age-o.age; } @Override public String toString() { return "Dog [age=" + age + ", name=" + name + "]"; } }
更多HashMap
ConcurrentHashMap:
线程安全,效率较高
内部实现原理:分段锁
补充
Map接口 新特性(1.8之后)
import java.util.HashMap; import java.util.Map; public class Test { public static void main(String[] args) { Map<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); //1 //String value = map.getOrDefault(4, "null"); //"null",表示为字符串null //System.out.println(value); //2 //map.putIfAbsent(3, "test"); //此时的value不会覆盖three; //map.put(3, "test"); //test会覆盖three; //3 //map.remove(4);//只要key存在就删除,不存在不报错 //map.remove(1, "test"); //必须key和value同时存在才可以删除; //4 //map.replace(5, "test");//只要key存在就替换,不存在不报错 //map.replace(1, "one", "test"); //只有key和value同时存在,test才可以替换one; //5 //map.compute(1, (k,v)->v+"one大礼包");//将"v+"one大礼包""这个值重新赋值给value; //map.computeIfAbsent(5,(k)->k+"test");//当5不存在的时候,才执行; //6 //map.merge(1, "new_val", (old_val,new_val)->old_val=new_val);//如果key=1不存在,就创建一个新的key=1,value="new_val" map.forEach((k,v)->System.out.println(k+","+v)); }
LinkedMultiValueMap
//key 对应多个value //底层:key LinkedList LinkedMultiValueMap<String, String> header = new LinkedMultiValueMap<>(); header.add("Authorization",httpBasic);
Optional类的使用
import java.util.Optional; public class Test { public static void main(String[] args) { //创建Optional对象的方式 Optional<String> opt = Optional.of("bin"); //Optional<String> opt1 = Optional.ofNullable("bin"); //Optional<String> opt2 = Optional.empty(); //判断opt是不是为空 System.out.println(opt.isPresent()); //取出opt中的值 System.out.println(opt.get()); //如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。 opt.ifPresent((val)->System.out.println(val)); //跟多查看api } }
表格数据的存储
方式1:每一个行数据用hashMap存储,整张表用list存储
方式2:orm思想,每一行数据对应一个javabean,整个表的数据用list存储
重写equals还要重写hashcode?
首先我们知道hashcode。在hash表中决定了这个对象在数组中的存放位置。(通过计算key的hash值经过与元算,就能第一时间找到对象,所以hashMap的查询非常快)