-
速度更快(针对Java底层HashMap的改进的理解)
在java8以前,回顾一下HashMap的底层数据结构的使用原理:hashmap是以entry为主体的数组,entry是hashmap的基本单位。原始的entry实体对象在创建后在内存中的分布是无序的,当创建一个实例对象时就需要对已有的对象进行equals比对效率低下
于是使用到了数组型存储,在C语言的数据结构的介绍中hash结构用于数据的查找效率是极快的,在java中底层数组的默认长度为16,当程序调用该对象的hashcode方法时将该返回的hashcode的值传给哈希算法算出对应的数据的索引值然后将其放入数组中,若出现不同的对象调用哈希算法得到了相同的值时,需要在数组的后面接上链表来存储该实例。
注意:1.首先使用哈希算法出现哈希值一致但是实例对象不一样的情况是不可避免的,这叫做哈希冲突。因为在底层如果没有给定的初始化的initialCapacity值默认为16,但是当数据值比较大时必然会出现哈希冲突,解决哈希冲突的方法就是使用链表。
2.当出现哈希冲突是时我们称之为碰撞,我们需要将原来在数组里面的值碰撞出去将现在的值与之前的值进行链接
3.由于使用了链表如果出现极端情况(若出现存入的数据的hashcode值都一致,就会出现一条长链表,长链表极其的低效率),所以应该尽量的少的出现碰撞,在使用hashcode和equals的方法时应该要做好。
针对以上的情况,hashmap内部使用了一个负载因子loadFactory默认为0.75,当达到负载因子上限时将自动对数组进行扩容。但是当数据值变大时效率一般。
java1.8以后将该hashmap进行了改造,在1.7之前使用的数组-链表型,而在1.8以后使用了数组-链表-红黑树。
红黑树是二叉树的一种,具体表现请参照该链接。简单的描述在1.8里面将数据结构换为红黑树的情况,当碰撞的次数大于8次并且总容量的大小超过64时将自动进行结构转变,使用树形结构使得效率变得更高
当然对于HashMap的改进,也使得相应的ConcurrentHashMap得到了改进。都知道HashMap是线程不安全的,但是如果一定要使用HashMap也提供了线程安全的ConcurrentHashMap,java也提供了线程安全的HashTable(使用synchronized来实现线程安全)但是相对的hashtable没有hashmap效率高。在ConcurrentHashMap体系里面不是使用的synchronized来实现的,而是类似于将HashMap分成多个小的HashTable,具体实现就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中。
在1.8以后放弃了加锁机制,使用了CAS算法,在源码使用putval方法时未使用加锁(具体可以参照)
for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) // 初始化 tab = initTable(); // 获取对应下标节点,如果是kong,直接插入 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // CAS 进行插入 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } }
从jvm虚拟机(hotspot)的角度分析速度变快,在jdk1.7之前内存的分区模式是栈区、堆区,其中永久区(Perm)处于堆区。一般我们会将永久区当做方法区,因为方法区里面存放内容和永久区一致,但是方法区是jvm规范中的一部分,并不是实际的实现。也就是说在1.7以前Perm是属于堆的也就是说该部分是受到垃圾回收的管理的但是回收的条件是苛刻的。1.7以前可以通过参数PremGenSize和MaxPremGen来设置。
到了1.8以后移除了永久区,将永久区放到了所谓的“方法区”中了,这有一个专业的名称叫元空间(MetaSpace),可以看做是对方法区的实现。到了1.8以后出现元空间,这个元空间不属于堆区直接使用了物理内存(也就是我们电脑的RAM)自然当这个方法区的实现从堆区剥离之后显然就离开了垃圾回收区的管理,从而提升了效率。相对应的也可以通过调节参数MetaSpaceSize和MaxMetaSpace来进行控制
(注意使用条件:该表达式是对策略设计模式的改进,需要使用函数式接口,自定义的函数式接口可以使用注解@FunctionalInterface)
首先使用案例来体会一下Lambda表达式:
Java7排序语法:
-
代码简化,Lambda表达式的使用
Collections.sort(name,new Comparatot<String>(){
@override
public int compare(String s1,String s2){
return s1.compareTo(s2);
}
});
Java8排序语法:
Collections.sort(name,(s1,s2) -> s1.compareTo(s2);
Lambda表达式的基本语法:
左侧:Lambda表达式的参数
右侧:Lambda表达式的实现
Java8内置的四大核心函数式接口:
->消费型(无返回值带参数)Consumer<T> void accept(T t);
->供给型(有返回值不带参数)Supplier<T> T get();
->函数型(带参数带返回值) Function<T,R> R apply(T t);
->断言型(带参数返回布尔型)Predicate<T> boolean test(T t);
方法引用(若表达式中的方法实现了时使用)
主要的三种语法格式:
对象::实例方法名 System.out::println;
类::静态方法名 Integer::compare;
类::实例方法名 String::equals;
构造器引用--------与方法引用类似
-
强大的Stream(流)API
注:流(Stream)是数据渠道,用于操作数据源(集合,数组等)所生成的元素序列
->"集合讲的是数据,流讲的是计算!"
->stream本身是不会存储对象
->stream不会改变原对象,相反会返回一个持有结果的新stream
->stream操作是延迟执行的,这意味着他们会等到需要结果的执行
流的操作步骤:
->创建stream
->可以通过Collection的集合提供的Stream串型和Parallelstream并行方法
->通过Arrays中的静态方法stream方法获取数组流
->通过stream类中的静态方法of()
->创建无限流,迭代stream iterate()
->中间操作
filter,limit,skip,distinct(方法名表示了作用) map,flapmap Comparable ,Comparator
->终止操作
查找与匹配:AllMatch,anyMatch,noneMate,findFrist,findAny,count,max,min
归约:reduce
收集:collect
-
Optional容器的使用
用于对NULL的解决,常用与未知的空指针,使用该容器就可以准确知道具体空指针异常及问题(原谅我的懒惰。)