注:

1.笔记为个人归纳整理,尽力保证准确性,如有错误,恳请指正

2.写文不易,转载请注明出处

3.本文首发地址 https://blog.leapmie.com/archives/b8fe0da9/

4.本系列文章目录详见《Java八股文纯享版——目录》

5.文末可关注公众号,内容更精彩

JDK8对比JDK7的差别

1.HashMap的实现差别

2.支持Lambda表达式语法(如创建线程,对于接口只有一个方法需要重写的类可以用lambda方式简洁创建对象)

3.支持Stream流操作。Stream提供一种对 Java 集合的流式操作,比如filter, map, reduce, find, match, sorted等。创建Stream有两种方式:stream() 创建串行流、parallelStream() 创建可以并行计算的并行流。

List<String> stringList = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = stringList .parallelStream()
.filter(string > !string.isEmpty()) //过滤
.map(i -> i*i) // 映射
.sorted() // 排序
.limit(10) // 分页
.collect(Collectors.toList()); // 返回结果集

4.接口支持默认方法(如果实现多个接口同时都定义了相同的默认方法,则实现类必须重写该方法)

public interface Interface1{
default void helloWorld() {
System.out.println("hi i'm from Interface1");
}
} public class MyImplement implements Interface1{
public static void main(String[] args) {
MyImplement myImplement = new MyImplement();
myImplement.helloWorld();
}
}

HashMap结构

Jdk7的实现

数组+链表组成,数组是HashMap的主体,链表用于解决Hash冲突。

Jdk8的实现

数组+红黑树。JDK8中当HashMap链表长度大于8的时候,改为红黑树结构,解决链表过长的问题,当小于6时会转换回链表。

转换阈值为什么是8

Java源码的贡献者在进行大量实验分析,hashcode碰撞次数符合泊松分布,在负载因子0.75(HashMap默认值)的情况下,单个hash槽内元素个数为8的概率为0.00000006,概率小于百万分之一,所以发生红黑树转换的情况其实并不多,设置为8可以大幅减少转换的代价。

从红黑树转换为链表的阈值为6,是为了避免元素数量在临界点来回变化导致的结构频繁转换。

以下为源码注释中的概率说明:

0: 0.60653066

1: 0.30326533

2: 0.07581633

3: 0.01263606

4: 0.00157952

5: 0.00015795

6: 0.00001316

7: 0.00000094

8: 0.00000006

为什么是红黑树而不是其他树?

普通二叉树可能会出现单边长度过长的问题,红黑树属于平衡二叉树,保证树的合理高度,而相比AVL平衡二叉树具备更好的插入、删除效率。(红黑树允许局部少量的不完全平衡,这样对于效率影响不大,但省去了很多没有必要的调平衡操作,avl树调平衡有时候代价较大,所以效率不如红黑树)。

HashMap的扩容机制

当HashMap中的元素越来越多的时候,碰撞的几率也就越来越高,为了提高查询的效率,就要对HashMap的数组进行扩容(resize)。

当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75。

扩容的大小为原数组长度的一倍。

ConcurrentHashmap实现原理

Jdk7的实现

HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,性能低下。

ConcurrentHashMap内部分为很多个Segment,每一个Segment拥有一把锁,每个段相当于一个小的Hashtable。当一个线程占用锁访问其中一个数据段时不影响其他段的访问,提高并发效率。

Jdk8的实现

table数组+单向链表+红黑树的结构

jdk8中取消segments字段,直接采用transient volatile HashEntry<K,V>[] table 保存数据,采用 table 数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率,代替原来的每一段加锁。

因为段的隔离级别不太容易确定,默认是16,但是很多情况下并不合适,如果太大很多空间就浪费了,如果太小每个段中可能元素过于多,所以取消segments,改成了CAS算法

ArrayList与LinkedArrayList的区别

  • Array(动态数组)的数据结构,一个是Link(链表)的数据结构
  • 当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高
  • 当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高

List的安全实现

ArrayList不是线程安全的,有以下几种方案实List的现线程安全:

1. Vector类

Vector实现方式比较笨重,add等每个方法使用Synchronized修饰

Vector v = new Vector(3, 2);
v.addElement(new Integer(1));
v.addElement(new Integer(2));
Enumeration en=v.elements();
while(en.hasMoreElements()){
Object object=en.nextElement();
System.out.println(object);
}

2. Collections.synchronizedList

Collections.synchronizedList(List() list),内部使用同步代码块的方式实现同步,用SynchronizedCollection这个静态内部类作为锁。

List<String> list = Collections.synchronizedList(new ArrayList<>());

3.CopyOnWriteArrayList

List<String> list =new CopyOnWriteArrayList<String>();
list.add("1");
list.add("2");
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
String o = iter.next();
    System.out.println(o);
}

内部在add等方法通过ReentrantLock加锁实现。

缺点:

1.因为CopyOnWrite的写是复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。

2.CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

Java的异常类别

异常分为Error和exception,其中exception分为CheckedException和RuntimeException

Error

error表示系统级的错误,是java运行环境内部错误或硬件问题,由Java虚拟机抛出,除了退出运行别无选择,如OOM(OutOfMemoryError)。

CheckedException(检查异常)

检查异常主要是指IO异常、SQL异常等。对于这种异常,JVM要求我们必须对其进行catch处理,如FileNotFoundException。

RuntimeException(运行时异常)

运行时异常一般不处理,比如NullPointerException,对于运行时异常,程序会将异常一直向上抛,一直抛到处理代码,如果没有catch块进行处理,到了最上层,如果是多线程就有Thread.run()抛出,如果不是多线程就由main.run抛出,抛出异常后线程终止。

Iterator

如有ArrayList a,内容为["a","b","c","d"]

在for 循环里遍历List,删除元素会怎样?

for (int i = 0; i < a.size(); i++) {
if (i == 1) {
a.remove(i);
} else {
System.out.println(i + a.get(i));
}
}

最终输出0a,2d,因为元素b被删除,然后c往前移位对应i=1,所以c也被跳过输出。

在iterator 循环里遍历List,删除元素会怎样?

Iterator<String> iterator = a.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if ("b".equals(s)) {
a.remove(1);
} else {
System.out.println(s);
}
}

抛出异常ConcurrentModificationException,要避免抛异常应该使用iterator.remove()进行删除。

Iterator实现原理

Iterator的实现中主要有几个变量cursor,lastRest, expectedModCount三个变量,其中cursor将记录下一个位置,lastRet记录当前位置,expectedModCount记录没有修改的List的版本号。

ArrayList作了添加或删除操作都会增加modCount版本号,这样的意思是在迭代期间,会不断检查modCount和迭代器持有的expectedModCount两者是不是相等,如果不想等就抛出异常了

Java的继承有什么缺点

  1. 父类向子类暴露了实现细节
  2. 父类更改之后子类也要同时更改
  3. 子类覆盖了一些方法,可能会导致其他调用了该方法的方法错误

包装类

《阿里巴巴Java手册》规定如下

【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。

说明:对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产 生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数 据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

对于以下语句:

Integer i01 = 59;
int i02 = 59;
Integer i03 =Integer.valueOf(59);
Integer i04 = new Integer(59);

以下输出结果为false的是:

A System.out.println(i01 == i02);

B System.out.println(i01 == i03);

C System.out.println(i03 == i04);

D System.out.println(i02 == i04);

答案为C

JVM中一个字节以下的整型数据会在JVM启动的时候加载进内存,除非用new Integer()显式的创建对象,否则都是同一个对象

所以只有i04是一个新对象,其他都是同一个对象。所有A,B选项为true

C选项i03和i04是两个不同的对象,返回false

D选项i02是基本数据类型,会触发i04自动拆箱,比较的时候比较的是数值,返回true

重写hashCode方法

为什么重写equals方法要重写hashCode方法?

当equals方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规约定:值相同的对象必须有相同的hashCode。

  • hashCode不同时,object1.equals(object2)为false;
  • hashCode相同时,object1.equals(object2)不一定为true

因为hashCode效率更高(仅为一个int值),比较起来更快,对于HashMap等很多结构是先通过对象的hashCode方法判断是否一致,然后再继续操作。

例如类Person中有属性name、idcard等字段,如果重写equals方法希望通过name、idcard字段值一致则代表该对象相等,必须同时重写hashCode方法。

class Person {
String name;
String idcard;
String sex; @Override
public int hashCode() {
int result = 17;  //任意素数  
// 31 有个很好的性能,即用移位和减法来代替乘法,通常*31
result = 31*result +name.hashCode(); 
result = 31*result +idcard.hashCode();  
return result;
}

摘自《Effective Java》中关于重写hashCode方法的习惯步骤如下:

“之所以选择31,是因为它是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于位移运算。使用素数的好处并不很明显,但是习惯上都使用素数来计算三列结果。31有个很好的特性,即用移位和减法来代替乘法,可以得到更好的性能:31 * i 等于 (i << 5) - i”。

对象引用类型及回收时机

从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

(1)强引用(StrongReference)

强引用是我们使用的最广泛,也是最普遍的一种引用类型。即

A a = new A();

只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

⑵软引用(SoftReference)

软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。

软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。

对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

⑶弱引用(WeakReference)

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

在java中,用java.lang.ref.WeakReference类来表示。

WeakReference<String> sr = new WeakReference<String>(new String("aaa"));

不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。弱引用也可以和一个引用队列(ReferenceQueue)联合使用。

⑷虚引用(PhantomReference)

如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。在java中用java.lang.ref.PhantomReference类表示。

ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(new String("aaa"), queue);

虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。


[目录]《Java八股文纯享版——目录》


Java八股文纯享版——篇①:Java基础的更多相关文章

  1. Java八股文纯享版——篇②:并发编程

    注: 1.笔记为个人归纳整理,尽力保证准确性,如有错误,恳请指正 2.写文不易,转载请注明出处 3.本文首发地址 https://blog.leapmie.com/archives/c02a6ed1/ ...

  2. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  3. [转]深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

  4. Java快速入门-01-基础篇

    Java快速入门-01-基础篇 如果基础不好或者想学的很细,请参看:菜鸟教程-JAVA 本笔记适合快速学习,文章后面也会包含一些常见面试问题,记住快捷键操作,一些内容我就不转载了,直接附上链接,嘻嘻 ...

  5. 深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

    转载:http://zh.lucida.me/blog/java-8-lambdas-inside-out-library-features/ 关于 深入理解 Java 8 Lambda(语言篇——l ...

  6. 这篇 Java 基础,我吹不动了

    Hey guys,这里是程序员cxuan,欢迎你收看我最新一期的文章,这篇文章我补充了一些关于<Java基础核心总结>的内容,修改了部分错别字和语句不通顺的地方,并且对内部类.泛型等内容进 ...

  7. java 学习第一篇简单基础

    Java基础 Java Java 和C#有着极为相似的语法. 和C#都是面向对象的高级程序语言. JAVA是一个开源,公开的语言,有着极其丰富的开源库和其他资源. JAVA分类 JAVA分SE EE ...

  8. 大数据学习笔记——Java篇之基础知识

    Java / 计算机基础知识整理 在进行知识梳理同时也是个人的第一篇技术博客之前,首先祝贺一下,经历了一年左右的学习,从完完全全的计算机小白,现在终于可以做一些产出了!可以说也是颇为感慨,个人认为,学 ...

  9. 《手把手教你》系列基础篇(九十七)-java+ selenium自动化测试-框架设计篇-Selenium方法的二次封装和页面基类(详解教程)

    1.简介 上一篇宏哥介绍了如何设计支持不同浏览器测试,宏哥的方法就是通过来切换配置文件设置的浏览器名称的值,来确定启动什么浏览器进行脚本测试.宏哥将这个叫做浏览器引擎类.这个类负责获取浏览器类型和启动 ...

随机推荐

  1. 最强肉坦:RUST多线程

    Rust最近非常火,作为coder要早学早享受.本篇作为该博客第一篇学习Rust语言的文章,将通过一个在其他语言都比较常见的例子作为线索,引出Rust的一些重要理念或者说特性.这些特性都是令人心驰神往 ...

  2. Kubernetes client-go 源码分析 - ListWatcher

    概述ListWatch 对象的创建GetterListWatchList() & Watch() 概述 源码版本信息 Project: kubernetes Branch: master La ...

  3. 解决python 导入selenium 库后自动化运行成功但是报错问题

    本章节开始进入自动化的基础教学了,首先我们要对我们的工具有一定的熟练使用程度,做自动化常用的工具一个是搭建 RobotFramework自动化框架,另外一个便是我们最常用的python 工作原理是比较 ...

  4. 如何在Uniapp中访问CabloyJS后端API管理系统

    介绍 CabloyJS是一款免费开源的NodeJS全栈开发框架,采用前后端分离设计,具备开箱即用的后台管理系统 Cabloy-SDK是专门为Uniapp应用量身定制的前端SDK,用于便捷的访问Cabl ...

  5. npm init cabloy背后的故事

    背景 我们知道许多框架会提供一个脚手架工具,我们先下载安装脚手架工具,然后再通过脚手架命令行来创建项目.在npm@6.1.0中引入了npm init <initializer>的语法.简单 ...

  6. 网络协议之:memcached binary protocol详解

    目录 简介 memcached的协议包 memcached命令举例 总结 简介 前面讲到了memcached的文本协议,虽然文本协议看起来非常简单,但是对于客户端来说一般还是会选择效率更高的二进制协议 ...

  7. 30.Mysql主从复制、读写分离

    Mysql主从复制.读写分离 目录 Mysql主从复制.读写分离 读写分离 读写分离概述 为什么要读写分离 什么时候要读写分离 主从复制与读写分离 mysql支持的复制类型 主从复制的工作过程 初始环 ...

  8. C语言学习之我见-strlen()字符串长度函数

    strlen()函数,负责给出字符串的长度.注意是字符串的长度,不是字符数组的长度. (1)函数原型: size_t __cdecl strlen(const char *_Str); (2)头文件` ...

  9. 基于web3D展示技术的煤矿巷道3D可视化系统

    地下开采离不开巷道工程.煤矿的生产.运输.排水.通风等各个环节都少不了巷道的支持.在煤矿智能化建设被提上日程的今天,巷道工程的智能化.可视化建设也成了行业趋势.尤其是复杂的井下作业环境,人员信息安全问 ...

  10. Vue.js与ElementUI搭建无限级联层级表格组件

    前言 今天,回老家了.第一件事就是回家把大屏安排上,写作的感觉太爽了,终于可以专心地写文章了.我们今天要做的项目是怎么样搭建一个无限级联层级表格组件,好了,多了不多说,赶快行动起来吧!项目一览 到底是 ...