站在巨人们的肩膀上,此前已经有非常多这类的博文,再次感谢他们为我写这篇文章提供了创作动力和知识储备。本文在个人认为合理的答案后添加认为需要补充的知识点,若有错误希望指正,另外由于知识点多并且每个小点都需要翻阅大量资料故目前整理的知识点数量不是很多但是坚持每天至少更新一点。

侵删

一、Java基础

1. JDK 和 JRE 有什么区别?

两者是包含关系,JDK包含JRE,适用对象的不同,JDK是开发人员使用的开发工具,JRE是使用者需要的软件环境。

JDK包含JRE,是开发人员使用的软件开发工具,还包含了编译器javac,用于Java程序调试和分析的工具jconsole、jvisualvm等 。
JRE是Java程序使用者需要的运行环境,包含了Java虚拟机(jvm)和Java基础类库(lang,util等)的class文件,并且将基础类库打包成jar放在其目录的lib目录下。

扩展:
JDK(Java Development Kit):Java开发工具。
JRE(Java Runtime Environment):Java运行环境。
JConsole和VisualJVM都是JDK自带的性能分析工具,.都可用来分析查看堆内存使用量、cpu占用率、线程数、已加载类数、VM概要。

2. == 和 equals 的区别是什么?

两者都用来比较两个变量是否相同,但是==是地址对比,equals默认是地址对比但可以重写hashcode和equals方法进行值对比(例如String,Math,Integer的equals方法都进行了重写)

int a = 1;
int b = 1;
System.out.println(a == b);  // true,基本数据类型对比值

String c = "c";
String d = c;
System.out.println(c == d);  // true,两者引用地址相同

String e = new String("c");
System.out.println(c == e);  // false,e使用new开辟了新的地址存储值

String f = new String("c");
System.out.println(e == f);  // false,e和f都使用new
System.out.println(e.equals(f));  // true,String方法重写了equals方法,在对比地址值的基础上继续判断内容是否相同

扩展:

  1. 没有重写的情况下equals的实现方法就是==
  2. 对于基本数据类型和对象==都是对比地址的值是否相等
  3. 对于对象最好用equals来比较两个对象的内容是否相等,因为这两个对象可能指向不同的地址但是存储内容一致
  4. 成员变量,new生成的对象存在堆(heap)里
  5. 局部变量存在栈(stack)里
  6. 直接赋值和new区别:
    以String为例,String a = “a”; 这段话可能不创建对象也可能创建一个对象,因为JVM中存在一个常量池,这个常量池存放直接赋值的对象,所以这段话在常量池已经有"a"时将其地址赋予a,若没有则在池中创建一个对象再将他的地址赋予a。
    String a = new String(“a”);这段话至少创建一个对象,因为使用了new关键字,至少会在堆中创建一个a的String对象,也可能创建两个对象,若"a"不在常量池中则会在池中创建一个对象“a”
  7. 对于整形数据-128~127之间会存在常量池,其他数值都是用new生成对象

3. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

不对,正确的说法是两个对象equals为true则hashCode()一定相等,两个对象equals为false但hashCode()不一定不相等。

扩展:

  1. HashCode的存在主要是用于提高查找的快捷性
  2. hash算法尽量保证每个变量算出来的hash值不同但是还是存在相同情况
  3. 两个对象的hashcode相同不代表两个对象就相同,只能说明这两个对象在散列存储结构中存放于同一个位置
  4. 在String的equals方法中,判断完两个对象地址不相同后才开始值比对
  5. 假如一个拥有10000个字符串的数组,现在你要插入一个不重复的字符串,这个时候最坏的情况是需要遍历所有元素,每次遍历调用equals方法,在使用hashCode后每次遍历前先对比是否hash值是否相同,相同再调用equals方法,这样大大减少了查找的时间

4. 什么是Java虚拟机?为什么Java被称为平台无关的编程语言

Java虚拟机是一个可以执行Java字节码的虚拟机进程
Java被称为平台无关的编程语言归结于不同的平台装有不同的Java虚拟机,各个平台对应的Java虚拟机能够将相同的.class文件解释成各自平台所需要的机器码

扩展

  1. 平台是CPU和操作系统的总称
  2. C语言为例:VC编译出来的C语言可执行文件exe能够在windows上运行而不能在linux上运行,linux上使用gcc编译的的执行文件也不能在windows运行

5. final 有什么作用?

凡是引用final关键字的地方皆不可修改,可用于修饰类、类变量和类方法
修饰类时该类不可继承
修饰类变量时该变量赋值后不可再进行更改,子类也不能重新赋值
修饰类方法时该方法不能被重写

6. Java有什么特征

有三大特征封装、继承、多态
封装:封装的思想保证了类内部数据结构的完整性,使用户无法轻易直接操作类的内部数据,这样降低了对内部数据的影响,提高了程序的安全性和可维护性。

继承: 提高代码复用性,选择继承你就不需要再写一个拥有相同成员变量和相同方法的类,但缺点是类的耦合性增强了,这样以后你改动父类时就需要考虑子类会不会因为改而出现问题。

多态是在有继承的情况下出现的,使子类父类相同方法(名称参数都一致)在调用对象(子类对象或父类对象)不同的情况下执行相应的操作

以下方例子说明

扩展:
多态一个简单实例

class Animal{
	public void shout(){
		System.out.println("...");
	}
}
class Cat extends Animal {
	pulic void shout(){
		System.out.println("喵喵喵");
	}
}
public void main(String[] args){
	Animal cat = new  Cat();
	cat.shout();		// 输出 "喵喵喵"
}

7. 什么是面向对象,面对对象和面对过程的区别

什么是面向对象(个人认为):面对对象是面向过程的集合。面向对象的本质其实还是面向过程,把面向过程实现用到的变量抽象成类属性,实现的过程抽象成类方法,然后整合封装成一个类,之后在编程过程中只需要使用该类的方法就能实现一个功能而不是再编写一段程序。

面对对象和面对过程的区别:面向过程是解决一个问题的步骤,面向对象时解决问题步骤的

扩展:
面向对象编程(OOP):Object Orinted Programming
实例讲解面对对象和面对过程(大概):把大象装进冰箱。 把猴子装入冰箱
面对过程:打开冰箱,把大象塞进去,关上门。 打开冰箱,把猴子塞进去,关上门
面对对象:调用动物装进冰箱的方法,传入参数大象。 调用动物装进冰箱的方法,传入参数猴子

8. 重载和重写的区别

重载:在一个类中声明了同名但是参数类型和参数数量不同的方法
重写:在一子类中声明了和父类同名且参数类型参数数量相同的方法

扩展:
重载(Overwrite)实现条件:
两(多)个方法同名,返回类型可相同可不同,参数类型和个数不可一致
重写(Override)实现条件:
子类中的方法和父类方法一致(名称,返回类型,参数类型,参数个数)

class father {
	public Stirng hello(){
		return "hello";
	}

	// 重载
	public String hello(String context){
		return context;
	}
}

class child extend father {
	// 重写
	@Override
	public String hello(){
		return "world";
	}
}

9. 什么是Java 应用程序的主类

Java 应用程序的主类(main方法)是Java 应用程序的入口点,Java虚拟机实例通过调用某个初始类的main()方法来运行一个Java程序

10. String、StringBuilder和StringBuffer的区别是什么?

String的值是不可变的,每次对String的操作都会生成新的String对象,运行速度三者中最慢
StringBuilder 的值是可变的,其方法不是线程安全的,但是运行速度三者中最快
StringBuffer的值是可变的,其方法是线程安全的,运行速度位于其他两个之间

扩展:
运行速度不是绝对的,有些特殊情况(常量池存在需要用到的字符串)会有不同结果
StringBuilder和StringBuffer底层实现是没有用final修饰的字符数组:char[]

11. String 为什么是不可变的?

String源码中观察到的实现方式是一个用final修饰的字符数组(char[]),并且在接口中没有提供修改的方法,所以String是不可修改的

12. (数据库)简述乐观锁和悲观锁

乐观锁:是一种思想,假设要进行的操作不会受到干扰,在多读取的场景可以考虑使用该思想以提高吞吐量
每次获取数据时默认外界没有对该数据的其他操作故不上锁,但在更新时会判断在更新间是否有他人更新此数据
可以使用版本号机制和CAS算法实现

悲观锁:也是一种思想,假设进行的操作总是会受到干扰,在多写入的场景可以考虑使用该思想以提高写入的正确性
每次获取数据时都认为别人会修改该数据故当他获取数据的时候会上锁,这样别人想对这个数据进行操作就会阻塞直到他拿到锁

扩展:
乐观锁实现的两种方法。

  1. 版本号机制
    一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
  2. CAS
    CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B
    CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

13. Java有哪些GC算法

引用记数法、根搜索算法、标记清除算法、标记整理算法和复制算法

扩展:
GC(Garbage Collection):垃圾回收

引用计数法(Reference Counting):给对象添加一个引用次数计数器,每过一个引用计数器值就+1,少一个引用就-1。当它的引用变为0时,该对象就不能再被使用。它的实现简单,但是不能解决互相循环引用的问题。

根搜索算法(GC Roots Tracing):以一系列“GC Roots”的对象为起点开始向下搜索,搜索经过的路径称为引用链(Reference Chain),当一个对象没有和任何引用链相连时,证明对象是无任何引用的,即可判定判定为可回收的对象。

Java里可作为GC Roots的对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(即Native方法)的引用的对象

标记-清除算法(Mark-Sweep):这是一个非常基本的GC算法,它是现代GC算法的思想基础,分为标记和清除两个阶段:先把所有活动的对象标记出来,然后把没有标记的对象统一清理。

但是它有两个问题,一是效率问题,两个过程的效率都不高。二是空间问题,清除之后会产生大量不连续的内存(内存碎片)。

java后端设计文档 java后端基础知识_Java


标记整理(Mark-Compact):经过可达性分析将对象进行标记之后,全部存活的对象向内存的一端移动,然后清理掉存活对象边界以外的所有内存

缺点:当存在大量对象时消耗性能

复制算法(Copying):把堆空间一分为二,使用一半A空间,空闲一半B空间,把经过可达性分析可以活着的对象,从A复制到B,然后把A空间清空,同理后一次的清理时把B空间活着的对象复制到A空间,然后再清空B空间。
缺点浪费空间,对象多的时候效率受影响

14. JVM内存有哪几个区域

JVM 内存共分为堆、方法区、虚拟机栈、程序计数器、本地方法栈五个区域

扩展:
参考文献
Ruheng的JVM内存区域与内存模型__Meng的JVM内存区域的划分(内存结构或者内存模型) 堆(公有):是JVM所管理的内存中最大的一块。被所有线程共享的一块内存区域,在虚拟机启动时创建,唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。当堆中没有内存可以分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。

方法区(公有):被所有线程共享的一块内存区域。用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据等。这个区域的内存回收目标主要针对常量池的回收和对类型的卸载。在HotSpot虚拟机中,用永久代来实现方法区,将GC分代收集扩展至方法区,但是这样容易遇到内存溢出的问题。JDK1.7中,已经把放在永久代的字符串常量池移到堆中。JDK1.8撤销永久代,引入元空间。
  其中包含常量池:用户存放编译器生成的各种字面量和符号引用。当方法区无法满足内存分配需求时,则抛出OutOfMemoryError异常。

虚拟机栈(线程私有):描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈帧,用户存储局部变量表,操作数栈,动态连接,方法出口等信息。每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 通常所说的栈,一般是指虚拟机栈中的局部变量表部分。局部变量表所需的内存在编译期间完成分配。

对这个区域定义了两种异常状态,如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,报 OutOfMemoryError 如果线程请求的栈深度大于虚拟机所允许的深度报StackOverflowError

本地方法栈(线程私有):与虚拟机栈所发挥的作用相似。它们之间的区别不过是虚拟机栈为虚拟机执行java方法,而本地方法栈为虚拟机使用到的Native方法服务。也会抛出StackOverflowError和OutOfMemoryError。

程序计数器(线程私有):一块较小的内存,当前线程所执行的字节码的行号指示器。字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Native方法,则为空。这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError情况的区域。

15. Java有哪些引用类型

强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)

扩展:
从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。
强引用:如果一个对象具有强引用,那么垃圾回收器绝不会回收这个对象。即使在内存不足时也不会回收二是抛出当内存不足时OutOfMemeryError异常,因为JVM认为强引用的对象是用户正在使用的对象,它无法分辨出到底该回收哪个,强行回收有可能导致系统严重错误。

软引用:如果一个对象只具有软引用,在垃圾回收时若内存空间不够则会回收该对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。软引用可用来实现内存敏感的高速缓存如网页缓存,图片缓存,防止内存溢出等。

弱引用:如果一个对象只具有弱引用,在垃圾回收时无论内存空间是否足够都会回收该对象,但是垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用:如果一个对象仅持有虚引用,那么在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。对象销毁前的一些操作,比如说资源释放等。

16. 线程有几个状态

NEW(新建)、RUNNABLE(运行)、BLOCKED(锁池)、TIMED_WAITING(定时等待)、WAITING(等待)、TERMINATED(终止、结束),据官方源码,一个线程有这六个状态,没有阻塞状态,没有可运行,没有挂起状态。

扩展:
内容摘自Rock.Jiang的文章:Java线程的六种状态 当一个线程创建以后,就处于NEW(新建)状态
RUNNABLE:就绪状态,可运行状态,调用了线程的start方法,已经在java虚拟机中执行,等待获取操作系统资源如CPU,操作系统调度运行。
当一个持有对象锁的线程获得CPU时间片以后,开始执行这个线程,此时叫做运行状态。
BLOCKED:堵塞状态。线程等待锁的状态,等待获取锁进入同步块/方法或调用wait后重新进入需要竞争锁
WAITING:等待状态。等待另一个线程以执行特定的操作。调用以下方法进入等待状态。 Object.wait(), Thread.join(),LockSupport.park
TIMED_WAITING: 线程等待一段时间。调用带参数的Thread.sleep, objct.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil
TERMINATED:进程结束状态。

17. Spring是什么

节选自知乎用户桑大宝贝儿的Spring 那些烦人的事

Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。由以下几个模块组成:

Spring Core:核心类库,提供IOC服务;
Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
Spring AOP:AOP服务;
Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;
Spring ORM:对现有的ORM框架的支持;
Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;
Spring MVC:提供面向Web应用的Model-View-Controller实现。

IoC:控制反转(Inversion of Control),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

AOP:面向切面编程(Aspect Oriented Programming),通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP(Object Aspect Oriented Programming面向对象编程)的延续,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

18. 简述JAVA集合类

Java的集合类分为Collection接口和Map接口两大类,Collection又分为Set、List、Queue(相对不常用)。其中Set代表无序集合,且不允许重复,List代表有序集合,可重复,Map集合存储键值对,不允许重复key
Set包含HashSet,LinkedHashSet,TreeSet
List包含ArrayLsit,LinkedList和Vector
Queue包含LinkedList和PriorityQueue
Map包含HashMap,TreeMap,LinkedHashMap以及HashTable,ConcurrentHashMap

19. Arraylist 与 LinkedList 区别

Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList底层使用的是双向循环链表数据结构(插入,删除效率特别高)。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。、但是一个线程访问Vector ,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist

20. HashMap 和 ConcurrentHashMap 的区别

ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

21. 什么是反射

在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性

22. 反射的三种形式

  1. 通过对象的class属性
    Class class1 = Object.class;
  2. 通过对象的getClass方法
    Object object =new Object();
    Class class2 = object .getClass();
  3. 通过包名,调用class的forName方法
    Class class3 = Class.forName(“java.util.String”);

23. 实现线程的几种方式

继承Thread类创建线程
实现Runnable接口创建线程
实现Callable接口创建新线程(可用Future返回结果)

24. Thread中的start和run方法的区别

调用start()方法会创建一个新的子线程并启动
run()方法只是Thread的一个普通方法的调用,还是在主线程中执行。

25. sleep和wait的区别

sleep是Thread类的方法,wait是Object类中定义的方法
sleep()方法可以在任何地方使用
wait()方法只能在synchronized方法或synchronized块中使用
Thread.sleep只会让出CPU,不会导致锁行为的改变
Object.wait不仅让出CPU,还会释放已经占有的同步资源锁