Java基础知识总结
Java基础知识清单
主要包括面向对象、基础语法、高级进阶三部分
面向对象
- 面向对象的三大特征?
提示封装、继承、多态
- 接口和抽象类的区别?
提示抽象类是为了提供继承; 接口是为了提供一种规范、降低耦合,接口比抽象类的抽象层度更高
基础语法
-
Java基本数据类型和包装类型有哪些?
提示基本类型:
boolean,char,int,byte,short,long,float,double
包装器类型: Boolean,Character,Integer,Byte,Short,Long,Float,Double -
final,finally,finalize三者的含义和区别?
提示final:表示不可变,用来修饰类、方法和变量;
finally:是异常处理中的关键字,表示处理完异常后最后的执行操作,但又不一定会执行,不会执行的情况如下:- 进入try块之前程序发生异常
- 在try块中调用了System.exit(0)终止了虚拟机的运行
- 在try块或catch块中程序被中断,比如说死机。
finalize:是Object类中的一个方法,所有的类都有finalize方法,垃圾收集器在调用前会调用finalize方法,做一些内存清理工作。
-
String、StringBuilder、StringBuffer三者的区别?
提示- String:不可变对象,使用在常量以及少量的变量运算环境下。
字符串常量,一旦创建,不可改变。这里的不可改变指字符串对应堆内存,当我们执行字符串的加操作时,实际上是复制被加字符串的内容到新开辟的字符串空间中,原来的作废。
String 是线程安全的,String 类是 final 类,不可以被继承。
String 的长度是不变的,适用于少量的字符串操作。 - StringBuffer:可变字符串对象,线程不安全,使用在频繁进行字符串运算的单线程场景下。
字符串变量,长度可变, 线程安全 。适用于多线程下在字符缓冲区进行大量字符串操作 - StringBuilder:可变字符串对象,线程安全,应用在频繁进行字符串运算(拼接、替换、删除等)多线程场景下。
字符串变量,长度可变, 线程不安全 。适用于 单线程 下在字符缓冲区进行大量字符串操作
字符串操作在执行速度:
StringBuilder > StringBuffer > String
- String:不可变对象,使用在常量以及少量的变量运算环境下。
-
HashMap的底层原理?
提示HashMap的数据结构为数组+(链表或红黑树) -
ThreadLocal和synchronized的区别
提示Synchronized牺牲时间
ThreadLocal牺牲空间 -
volatile关键字的用法和含义
提示 -
内部类的作用
提示首先Java类的作用是功能内聚,将相近特性的功能内聚到一个类中,例如StringUtil类内聚String相关的功能,DateUtil类内聚Date相关的功能,那么内部类自然也有功能内聚的作用,与一般类不同的是,定义一个内部类通常意味者既要功能内聚,又要对外屏蔽可见性,即不希望外部可见,减少对外暴露的接口,这样从源码结构上来看,需要了解的类信息更少,更简洁。 -
为什么不用内部方法,而要用内部类
提示既然内部类是为了对外屏蔽可见性,那么内部类的功能不能直接用内部方法实现么? 内部类可以通过内部方法实现,但有的场景更适合用内部类来实现:
1.有一组相近的功能,可以内聚,归属到一个特性,如果都用方法实现,那么原来这个类的方法会很多
2.需要继承或者实现某个接口,此时通过方法就无法做到。 -
内部类和静态内部类的区别
提示从static关键字的一般用法出发很容易理解两者的区别: 1.static方法不能访问非static成员,同理静态内部类也一样。 2.非static方法能访问非static成员,同理静态内部类也一样
3.非静态成员,需要通过类实例去访问,同理访问内部类也一样
4.静态成员,可以通过类而非类实例来访问,同理访问静态内部类也一样。 -
何时使用内部类和静态内部类
提示这个也可以结合static关键字的一般用法出发来考虑:
1.外部其他类需要如何访问内部类?例如是通过外部类的实例访问,还是通过外部类访问。如果通过类访问,那么就使用静态内部类,否则使用内部类。
2.是否需要访问类的非静态成员,如果需要就用内部类,否则就用静态内部类。 -
内部类带来的增强能力–使得多重继承变得可能
提示Java中不支持继承多个类,但是在一个类中可以定义多个内部类,不同的内部类又可以继承不同的类,这样就等于同一个类可以继承多个类。不过这样做跟使用组合也差不多了,都是在内部持有一个新的类,而继承和组合的区别是继承可以继承一个抽象类,而组合不能。 -
ArrayList和Vector的区别:
提示从内部实现机制来讲,ArrayList和Vector都是使用数组来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来已被的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。 -
同步代码块和同步代码方法的区别:
提示同步代码块在锁定的方位上可能比同步方法药效,一般来说锁的范围大小和性能是成反比的。同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。 -
SynchronizedList和Vector的区别:
提示SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无区别。在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象。SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。 目前为止有两点,如果使用add方法,那么他们的扩容机制不一样;SynchronizedList可以指定锁定的对象。 SYnchronizedList中实现的类并没有都使用synchronzied同步代码块。其中有listIiterator和listIterator(int index)并没有做同步处理,但是Vector却对该方法加了方法锁,在使用SynchronizedList进行遍历的时候要手动加锁。 将ArrayList转成SynchronizedList,如果我们想把LinkedList变成线程安全的,或者说我想要方便在中间插入和删除的同步的链表,可以将已有的LinkedList之间转成SynchronizedList,不用改变它的底层数据结构,Vector无法做到这一点,他的底层架构是使用数组实现的,这个是无法更改的。 -
HashMap/HashTable/ConcurrentHashMap区别:
提示HashTable:同步,key和value都不允许出现null值。 HashMap:非同步,null可以作为键且只能出现一次,可以有一个或多个键所对应的值为null。 ConcurrentHashMap: ConcurrentHashMap和HashMap的实现方式不一样,虽然都是使用桶数组实现的,但是还是有区别,ConcurrentHashMap对桶数组进行了分段,而HashMap并没有。 ConcurrentHashMap在每一个分段上都用锁进行了保护。HashMap没有锁机制。所以ConcurrentHashMap是线程安全的,HashMap线程不安全。
高级进阶
代理
- JDK和CGLib动态代理实现和区别?
提示
静态代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
public interface HelloService{ public void say(); } public class HelloServiceImpl implements HelloService{ @Override public void say() { System.out.println("Hello World"); } } public class HelloServiceProxy implements HelloService{ private HelloService target; public HelloServiceProxy(HelloService target){ this.target = target; } @override public void say(){ System.out.println("记录日志"); target.say(); System.out.println("清理数据"); } } public class Main{ @Test public void testProxy(){ HelloService target = new HelloServiceImpl(); HelloServiceProxy proxy = new HelloServiceProxy(target); proxy.say(); } }
JDK动态代理:面向接口的代理模式,只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。Spring中通过Java的反射机制生产被代理接口的新的匿名实现类,重写其中AOP的增强方法,具体操作步骤如下:
- 通过InvocationHandler接口创建自己的调用处理器
- 通过Proxy类指定ClassLoader对象和一组interface来创建动态代理
- 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数
CGLib动态代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
JVM
- JVM运行时数据区包括那些?
提示
- 方法区
- 虚拟机栈
- 本地方法栈
- 堆
- 程序计数器
- 类的生命周期包括哪几个阶段?
提示
- 加载
- 验证
- 准备
- 解析
- 初始化
- 使用
- 卸载
- 垃圾回收
提示
CMS收集器:老年代收集器, 一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。 CMS收集器就非常符合这类应用的需求。 CMS收集器是基于标记-清除算法实现的,他的运作过程相对于其他收集器来说复杂一些: 包括: 1.初始标记 2.并发标记 3.重新标记 4.并发清除 其中初始标记、重新标记这两个步骤仍然需要“Stop The World”,初始标记仅仅知识标记以下GCRoots能直接关联到的对象,速度很快;并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾手机线程一起并发运行;而重新标记阶段是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短;最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,多以这个阶段也是可以与用户线程同时并发的。 由于整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程一起工作,所以从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。 CMS(并发低停顿收集器) CMS三个明显的缺点: 1.CMS收集器对处理器资源非常敏感 2.CMS收集器无法处理“浮动垃圾",有可能出现”Concurrent Mobile failure"失败而导致另一次完全“Stop The World”的FullGC的产生。 3.CMS是一款基于“标记-清除”算法实现的收集器,会产生大量的空间碎片。
G1收集器: 收集器面向局部收集的设计四路和基于Region的内存布局形式, Java9发布之后,G1宣告取代Parallel Scavenge和ParallelOld组合,称为服务端模式下的默认垃圾收集器, G1垃圾回收时,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。 G1开创的基于Region的堆内存布局是他能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域,每个Region都可以根据需要,扮演新生代的Eden空间,Survior空间或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间,熬过多次手机的就对象都能获取很好的手机效果。
G1之所以能建立可预测的停顿时间模型,是因为他将Region作为单词回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾手机。 优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来 , G1从整体来看是基于“标记-整理”算法实现的收集器,但从局部(两个Region之间)上看又是基于“标记-复制”算法实现,运作期间不会产生内存空间碎片,垃圾收集完成之后能提供规整的可用内存。这种特性有利于程序长时间运行,在程序为大对象分配内存时不容易因无法找到连续内存空间而提前出发下一次收集 。 用户运行过程中,G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS要高。 小内存应用上,CMS的表现大概率优于G1 大内存应用上,G1则大多能发挥其优势
Java1.7之前运行时常量池逻辑包含字符串常量池,存放在方法区,从此hotspot虚拟机对方法区的实现为永久代 Java1.7中,字符串常量池被从方法去拿到了堆区,这里没有提到运行时常量区,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区,也就是hotspot中的永久代。 Java1.8中hostspot移除了永久代用原空间取而代之,这时候字符串常量池还在堆,运行时常量池还在方法去,只不过方法区的实现从永久代变成了元空间。
Spring
-
SpringMVC工作原理:
提示1.客户端发送请求,直接请求到DispatcherServlet 2.DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler 3.解析到对应的Handler(也就是我们平常说的Controller控制器) 4.HandlerAdapter会根据Handler来调用真正的处理器来处理请求和执行相对应的业务逻辑 5.处理器处理完业务后,会返回一个ModelandView对象,Model是返回的数据对象,View是逻辑上的View. 6.ViewResolver会根据逻辑View去查找实际的View。 7.DispatcherServlet把返回的Model传给View(视图渲染) 8.把View返回给请求者 -
Spring框架中用到了哪些设计模式?
提示- 工厂设计模式: BeanFActory用来创建对象的实例
- 代理设计模式: AOP和Remoting中被用的比较多。
- 单例设计模式: 在spring配置文件中定义的bean默认为单例模式
- 模板方法模式
- 包装器设计模式
- 观察者模式
- 适配器模式
-
自动装配模式的区别:
提示spring框架中共有5种自动装配 1.no:这是Spring框架的默认设置 2.byName:该选项可以根据bean定义中用标签明确的设置依赖关系,当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错 3.byType:该选项可以根据bean类型设置依赖关系。当像一个bean中自动装配一个属性时,容器将根据bean的类型自动在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。 4.constructor:构造器的自动装配和byType模式类似,但是仅仅适用于与构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。 5.autodetect:该模式自动探测使用构造器自动装配或者byType自动装配。首先,会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到响应的构造器或者是无参构造器,容器就会自动选择byType的自动装配方式。 @Qualifier注解意味着可以在被标注bean的字段上可以自动装配。Qualifier注解可以用来取消Spring不能取消的bean应用。
如果你有更好的想法和建议,欢迎留言。
参考
- https://toutiao.io/subjects/260946?page=2
- https://github.com/huangliangyun/MindManager
- https://github.com/HusyCoding/JNotes
- https://github.com/topics/algorithm
- RFC7450-HTTP/2
- http2讲解中文版
- java 个人知识小仓库
https://github.com/BruceEckel/OnJava8-Examples/issues
https://xie.infoq.cn/article/d49e5aa412e022cb6afc48656