[Java面试]java笔试题总结

java笔试题总结

这篇博客是自己平时看到的一些或者有新知识点,或者旧知识重新学习之后有启发的一些内容,总结在这里,也是为了方便日后回顾,共勉。因为大部分知识点阅读起来需要些基础,所以初步读起来可能会有些困难,可根据此处的知识点再去搜索引擎做更详细的搜索。

1. JVM总结

@转自 牛客网:菜鸟葫芦娃 的回答
在这里插入图片描述
在这里插入图片描述
参考链接

  • 线程共享:
    • 方法区(method area):存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等
    • 堆(heap):存储对象信息,包括对象的属性、方法
  • 非线程共享
    • 程序计数器(Program Counter Register)
    • 虚拟机栈(VM Stack)
    • 本地方法栈 (Native Method Stack)

    大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区) ,Heap(堆) , Program Counter Register(程序计数器) , VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack ( 本地方法栈 ),其中Method Area 和 Heap 是线程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非线程共享的。为什么分为 线程共享和非线程共享的呢?请继续往下看。
     首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
     概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

2. Statement、PreparedStatement和CallableStatement

  1. Statement、PreparedStatement和CallableStatement都是接口(interface)。
  2. Statement继承自Wrapper、PreparedStatement继承自Statement、CallableStatement继承自PreparedStatement。
  3. Statement接口提供了执行语句和获取结果的基本方法;
    PreparedStatement接口添加了处理 IN 参数的方法;
    CallableStatement接口添加了处理 OUT 参数的方法。
  4. Statement:
    • Statement: 普通的不带参的查询SQL;支持批量更新,批量删除;
    • PreparedStatement:
      可变参数的SQL,编译一次,执行多次,效率高;
      安全性好,有效防止Sql注入等问题;
      支持批量更新,批量删除;
    • CallableStatement:
      继承自PreparedStatement,支持带参数的SQL操作;
      支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;
      Statement每次执行sql语句,数据库都要执行sql语句的编译 ,
      最好用于仅执行一次查询并返回结果的情形,效率高于PreparedStatement。

PreparedStatement是预编译的,使用PreparedStatement有几个好处

  1. 在执行可变参数的一条SQL时,PreparedStatement比Statement的效率高,因为DBMS预编译一条SQL当然会比多次编译一条SQL的效率要高。
  2. 安全性好,有效防止Sql注入等问题。
  3. 对于多次重复执行的语句,使用PreparedStament效率会更高一点,并且在这种情况下也比较适合使用batch;
  4. 代码的可读性和可维护性。

3. doGet和doPost

  • doget/dopost与Http协议有关,是在 javax.servlet.http.HttpServlet 中实现的。
  • GenericServlet 抽象类 给出了设计 servlet 的一些骨架,定义了 servlet 生命周期,还有一些得到名字、配置、初始化参数的方法,其设计的是和应用层协议无关的
    在这里插入图片描述

4. Servlet的生命周期

  1. 加载:容器通过类加载器使用Servlet类对应的文件来加载Servlet
  2. 创建:通过调用Servlet的构造函数来创建一个Servlet实例
  3. 初始化:通过调用Servlet的 init() 方法来完成初始化工作,这个方法是在Servlet已经被创建,但在向客户端提供服务之前调用。
  4. 处理客户请求:Servlet创建后就可以处理请求,当有新的客户端请求时,Web容器都会创建一个新的线程来处理该请求。接着调用Servlet的 Service() 方法来响应客户端请求(Service方法会根据请求的method属性来调用doGet()和doPost()
  5. 卸载:容器在卸载Servlet之前需要调用destroy()方法,让Servlet释放其占用的资源。

5. Servlet是否是进程安全的

servlet在多线程下其本身并不是线程安全的。
如果在类中定义成员变量,而在service中根据不同的线程对该成员变量进行更改,那么在并发的时候就会引起错误。最好是在方法中,定义局部变量,而不是类变量或者对象的成员变量。由于方法中的局部变量是在栈中,彼此各自都拥有独立的运行空间而不会互相干扰,因此才做到线程安全。

6. Struts1和Struts2的区别和对比:

  • Action 类:
    • Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口,而struts2的Action是接口。
    • Struts 2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。

  • 线程模式:
    • Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
    • Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)

  • Servlet 依赖:
    • Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。
    • Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。

  • 可测性:
    • 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。
    • Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。

  • 捕获输入:
    • Struts1 使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是,开发者可能是在重新描述(创建)已经存 在的JavaBean(仍然会导致有冗余的javabean)。
    • Struts 2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。Action属性能够通过 web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。这种 ModelDriven 特性简化了taglib对POJO输入对象的引用。

  • 表达式语言:
    • Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
    • Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--“Object Graph Notation Language” (OGNL).

7.封装

封装是把对象的属性和操作结合在一起,组成一个新的独立的对象,其内部信息对外界是隐藏的,外界只能通过有限的接口域对象发生关系

8. final关键字

  • final修饰的类:叫做最终类,不可被继承
  • final修饰的方法:不能被重写
  • final修饰的变量:final修饰的变量叫做常量,必须要初始化,且无法被修改

9. 类的初始化顺序

类的初始化顺序遵循两个规则:(方便记忆)
一、父类先于子类
二、静态先于非静态

我们根据这两个规则:有初始化顺序–>

  1. 基类静态代码块,基类静态成员字段 (并列优先级,按代码中出现先后顺序执行)(只有第一次加载类时执行)
  2. 派生类静态代码块,派生类静态成员字段 (并列优先级,按代码中出现先后顺序执行)(只有第一次加载类时执行)
  3. 基类普通代码块,基类普通成员字段 (并列优先级,按代码中出现先后顺序执行)
  4. 基类构造函数
  5. 派生类普通代码块,派生类普通成员字段 (并列优先级,按代码中出现先后顺序执行)
  6. 派生类构造函数

10. Java多线程

  1. sleep()、wait()的区别
    sleep()方法不释放锁,常被用于暂停执行、在sleep()执行完毕后线程继续执行。
    wait()释放线程锁、常被用于线程间的交互/通信,wait()后线程不会自动苏醒,需要其余线程执行notify()方法或者notifyAll()
  2. start()方法和run()方法解析
    运行start()方法会启动一个新的线程,并使该线程进入就绪态,等待cpu分配时间片,新线程会对run()方法进行调用。
    直接运行run方法则会把它当做main下普通的方法进行调用,没有启动新的线程
  3. 虚拟机栈和本地方法栈为什么是私有的?程序计数器为什么是私有的?
    程序计数器需要记录当前线程执行的位置,在cpu重新分配时间片后可从该位置继续向下执行。
    栈用于保证本线程的局部变脸的不被其余的线程访问到。

11. SSM核心知识

在这里插入图片描述

  • IOC依赖注入,基于反射机制
  • AOP面向切面,基于动态代理(jdk,cglib)

12. 依赖倒置原则、IOC、DI、IOC容器的关系

在这里插入图片描述

**解释:**其中依赖倒置原则是一种设计思想,该思想的核心理念是高层的代码不应该依赖于底层的代码。并基于此思想设计出了IOC控制反转,IOC的实现需要通过依赖注入的方法实现,即DI是实现IOC的一种方法。

**优势:**避免在各处使用new来创建对象,从而实现对象的统一维护
创建实例的时候不必了解其中的实现细节,通过接口进行实现。

IOC与普通创建对象过程上的差异:
在这里插入图片描述

13. Spring IOC 容器

在这里插入图片描述

Spring IOC容器的核心接口:

  • BeanFactory: 提供IOC的配置机制,包含Bean的各种定义,便于实例化Bean;建立Bean之间的依赖关系;控制Bean的生命周期
  • ApplicationContext :继承多个接口
    在这里插入图片描述
  • BeanDefinition: 用来描述Bean’的定义
  • BeanDefinitionRegistry: 提供向IOC容器注册BeanDefinition对象的方法

Spring源码 getBean方法的调用逻辑

  1. 转换beanName
  2. 从缓存中加载实例
  3. 初始化bean
  4. 检查parentBeanFactory
  5. 初始化依赖的bean
  6. 创建bean

Spring Bean的作用域

  • singleton: 默认作用域,容器里拥有唯一的bean实例(适合无状态的Bean)
    在Spring IoC容器中仅存在一个Bean的示例,Bean以单实例的方式存在,单实例模式是重要的设计模式之一,在Spring中对此实现了超越,可以对那些非线程安全的对象采用单实例模式。
  • prototype:针对每一个getBean请求,容器都会创建一个bean实例 (适合有状态的Bean)

当用户使用Spring的WebApplicationContext时,还可以使用另外3种Bean的作用域,即request,session和globalSession。

  • request:为每个http请求创建一个bean实例
  • session:为每个session创建一个bean实例
  • globalSession:为每个全局HttpSession创建一个Bean实例,该作用域仅对Portlet有效

Spring Bean的生命周期

在这里插入图片描述

理解Spring AOP

面向切面编程
关注点分离,不同的问题交给不同的部分去解决。
分离业务功能代码和切面代码,架构将变变得高内聚低耦合。为确保功能的完整性,切面最终要通过Weave被合并到业务中。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

AOP的实现

  • JDKProxy:通过Java内部的反射机制进行实现
  • Cglib:借助ASM进行实现
    区别:
  • JDK反射机制在生成类的过程中比较高效
  • ASM在生成类之后的执行过程比较高效
    设计模式: 代理模式==》接口+ 真实实现类+代理类
    getBean方法返回的实际上是Proxy的实例

在这里插入图片描述

14. Mysql数据库采用b+树较b树的优势

  1. 查询效率更加稳定。(b+树的具体信息都存储在叶子节点,每次查询都要遍历到叶子节点才能查找到数据,而b树要查找的数据可能存储在根节点,也可能存储在叶子节点上)
  2. b+树的磁盘读写代价更低。 同上面的叙述,非叶节点不存储具体数据数据,只存储查找所用的key值,所有的具体数据都存储在叶子节点上。因此在非叶节点上能够存储更多的key值和分支信息,这种存储结构能够有效减少磁盘的io次数,减小磁盘读写代价。

https://www.cnblogs.com/nullzx/p/8978177.html

-----------------------------------------------------------------2019.6.2---------------------------------------------------------------

15. switch语句带来的性能损耗问题

switch语句不加break会产生击穿现象,顺序执行case后面的内容,带来性能的损耗。
在switch语句对case判断成功后,不加break,其后的内容将会顺序执行,带来性能问题。

16 . Try catch以及throws异常的机制

  • 降低代码量。
    使用trycatch接收异常信息,就不需要在下层书写和传递过多的判断变量,优化代码的逻辑和代码量。
  • 不影响线程的继续执行
    若系统产生的异常交给java虚拟机进行处理,当前线程的执行会终止,其后的内容便无法执行。

17. 采用getter setter方法进行传值的分析

对于同时使用了getter和setter方法的域属性,除在

18. equals方法

Object类的equals方法
如上图是Object类的equals方法, 比较的是对象的引用地址。
String类重写的equals方法,比较字符串的值

默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。

19.StringBuilder和String类型在效率上的具体效率

20. 反射的理解

根据类的路径获取类的具体信息,并可通过获取的类型信息进行对象实例化

  • 对Class类的理解:
    网上对Class有各种各样的说法,其中有一大部分称之为 类类型 ,个人认为这种说法并不友好,也很难理解。根据反射的概念,我们通过反射获取到的是类的信息,如Class Student = Class.forName(“类的路径”);
    此时Class中包含的就是这个路径所代表的类的所有信息,我们还可通过Filed,Method分别获取类的域和方法信息。

21.各种内存区域

  • final修饰的数据的存储地址与是否final修饰无关,加final仅限定不可变,具体的存储地址与变量的类型有关。
  • 基本数据类型:栈
  • 对象:堆
  • 常量池:
    Java6和6之前,常量池是存放在方法区中的。
    Java7,将常量池是存放到了堆中,常量池就相当于是在永久代中,所以永久代存放在堆中。
    Java8之后,取消了整个永久代区域,取而代之的是元空间。没有再对常量池进行调整。

String a = “aaa”+“bbb”;
String b = “aaabbb”;
----->比较结果为true

String a= “aaa”;
String b = “bbb”;
String c = “aaabbb”;
System.out.println((a+b) == c);
----->比较结果为false
subString方法采用的是new的方式产生子串

22. 反射的性能问题

如对数组进行拷贝的时候使用for循环进行拷贝,和使用Arrays.copyof()方法进行数组拷贝哪个效率更高呢?在这里插入图片描述
我们可以看到copyof方法使用了反射的机制进行数组拷贝,而使用反射会带来极大的性能问题,因为反射需要获取类的信息。所以对于引用数据类型,使用for循环进行拷贝是一种更好的选择。

23. 类继承

子类继承父类,使用多态进行实现,Person person = new Student()
若想调用子类的方法,需要将person强转为Student类型

24. 抽象类和接口在设计上的区别

抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

25. 自动装箱和拆箱

Java包装类及自动装箱、拆箱

java会自动调用装箱和拆箱操作,Integer.valueOf(3)和 i.intValue()

  • 128陷阱
    超过128会返回 new Integer();
public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }

26. 理解字符串常量池和运行时常量池

  • 运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加,符号引用可以被解析为直接引用
  • JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

27. MyISAM和Innodb的区别

MyISAM不支持外键,不是事务安全的,索引时使用非聚簇索引,数据锁是表级锁。
相反,Innodb支持外键约束,具有事务的提交、回滚和崩溃恢复能力,在索引时使用聚簇索引,数据锁为行锁(并非所有操作都是行锁,在一些条件下仍会锁定全表)。

28. Innodb什么时候会查询全表(大数据量数据库优化方案)

  1. 首先考虑在涉及到where或order by的字句上建立索引
  2. where 中存在 is null
  3. 使用like进行查询
  4. 避免在where字句中使用!= 或<>操作符
  5. 避免在where字句中使用or进行连接
  6. 能使用between尽量减少in字句的使用,以及not in
  7. .不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
  8. select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
  9. 对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。
  10. Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
  11. 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
  12. 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
  13. 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
  14. 尽量避免大事务操作,提高系统并发能力。

29. OpenJdk和OracleJDK的区别

30. switch穿透

31. Mybatis相关

  1. #和$的区别
  2. 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
  3. 如何获取自动生成的(主)键值?
  4. 在mapper中如何传递多个参数?
    • 在映射文件中使用#{0},#{1}代表传递进来的第几个参数
    • 使用@param注解:来命名参数
    • 使用Map集合作为参数来装载
  5. 接口绑定有几种实现方式,分别是怎么实现的?
  6. 简述Mybatis的插件运行原理,以及如何编写一个插件
    Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
    实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
  7. Mybatis都有哪些Executor执行器?它们之间的区别是什么?
    • SimpleExecutor
    • ReuseExecutor
    • BatchExecutor

32. 为什么重写了equals方法还要重写hashCode方法

equals方法原本比较的是对象的地址,此时hashCode也是使用地址进行的比较。
若对equals方法进行了重写,如对其中的某个属性进行比较,但并未重写hashCode方法,就会产生equals方法返回true但hashCode中比较为false的现象。这会在集合框架中,hashMap产生严重的问题

保证equals判断结果为true,那么他们的hashCode必然相同。

33. 对象的内存存储

对象的实例(instantOopDesc)存储在堆中,对象的元数据(instantKlass)存储在方法区中,对象的引用存储在虚拟机栈中。
在这里插入图片描述

方法区用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 所谓加载的类信息,其实不就是给每一个被加载的类都创建了一个 instantKlass对象么。

在HotSpot虚拟机中,使用oop-klass模型来表示对象。每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass,保存在方法区,用来在JVM层表示该Java类。当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头以及实例数据。

https://www.hollischuang.com/archives/1910

34. Java对象头

在这里插入图片描述
java对象头使用3位来标记锁,其中1 bit 用于标记偏向锁,剩余 2 bit 共标记四种锁,其中11标记GC。

https://www.hollischuang.com/archives/1953

35. 线程池的正确使用姿势

  • 使用Executors创建线程池
  • 使用ThreadPoolExecutors创建线程池
    **使用Executors创建线程池可能会导致OOM的问题。**java开发手册规定:
    FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能导致大量请求堆积,导致OOM。
    CachedThreadPool和ScheduledThreadPool:允许创建的线程数量为Integer。MAX_VALUE,可能会创建大量的线程,从而导致OOM。

避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。

除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。

作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。

public class ExecutorsDemo {

    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();

    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            pool.execute(new SubThread());
        }
    }
}z`

36. Java为什么要将字符串设计为final类型的

  1. **缓存hashCode。**提升集合框架的性能。hashcode进行缓存,后面的调用可以直接使用缓存中的值,能够提升集合数据结构的性能。
  2. 节约堆内存空间,方便其他对象的使用。
  3. 天然的线程安全。将变量设计为final类型的,由于其不可变性,在并发操作的时候不会存在多线程操作的问题。
  4. 安全性:String被广泛的使用在其他Java类中充当参数。比如网络连接、打开文件等操作。如果字符串可变,那么类似操作可能导致安全问题。因为某个方法在调用连接操作的时候,他认为会连接到某台机器,但是实际上并没有(其他引用同一String对象的值修改会导致该连接中的字符串内容被修改)。可变的字符串也可能导致反射的安全问题,因为他的参数也是字符串。

http://www.hollischuang.com/archives/1246

37. 生产唯一token

雪花算法
1bit留空 41bit时间戳 10bit机器码 12bit自增序号

38. 导致事务回滚的异常

RuntimeTimeException Error 也可使用@Transactional(rollBackFor=“xxException”)注解指定抛出回滚的异常类型

39. 线上服务器CPU占用率高如何排查定位问题?

  1. 问题定位
  2. 定位进程 使用top命令查看cpu占用情况
  3. 定位线程 top -Hp 1893 查看进程中各个线程的CPU使用情况
  4. 定位代码 使用printf %x将线程转为16进制 使用jstack命令查看栈信息
  5. 发现问题,解决问题

40. SimpleDateFormate线程安全

若使用SimpleDateformate用作成员变量且声明为static,则会产生线程安全的问题。如下:

public class Main {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime()));
    }
}

因为simpleDateFormate中存在着一个私有的calendar属性,在并发的情况下就是一个共享变量,会产生并发问题。

解决方式:

  1. 使用局部变量
  2. 加同步锁 synchronized
  3. 使用ThreadLocal :ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么自然也就不存在竞争问题了。
  4. Java8以上可采用DateTimeFormatter

41. 为什么不能再增强for循环内使用remove、add方法

在foreach中使用remove和add方法会导致ConcurrentModificationException的抛出。
原因:

  • foreach的底层源码为iterator。
  • modCount是ArrayList的一个成员变量
  • expectedModCount是ArrayList的一个内部类,表示这个迭代器预期该集合被修改的次数。其值随着Itr被创建而初始化。只有通过迭代器对集合进行操作,该值才会改变。

remove仅改变了modCount但未改变expectedModCount的值,因此会导致比较异常的抛出。

42. Http长连接与短连接

短连接: 每次Http请求建立一次连接
长连接: 一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。

43. fail-fast机制

Fail-fast机制是java集合框架中的一种异常检测机制,当多线程对集合进行结构性改变时,有可能会产生fail-fast机制。

  • 如:
    • 在for each(增强for循环)中进行add和remove操作会触发fail-fast机制,抛出ConcurrentModificationException异常。

异常原理:

  • foreach其实是依赖了while循环和Iterator实现的。
  • 该方法是在iterator.next()方法中调用的。我们看下该方法的实现:
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

如上,在该方法中对modCount和expectedModCount进行了比较,如果二者不想等,则抛出CMException。

那么modCount和expectedModCount又是什么呢?

  • modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。
  • expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。 expectedModCount表示这个迭代器预期该集合被修改的次数。其值随着Itr被创建而初始化。只有通过迭代器对集合进行操作,该值才会改变。

看下面remove的源码:

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

仅对modCount的值进行了修改,但并未对内部类中的expectedModCount进行修改。因而在对值进行的比较的时候触发了fail-fast机制,导致抛出ConcurrentModificationException异常。

总结简单总结一下,之所以会抛出CMException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示用户,可能发生了并发修改!

所以,在使用Java的集合类的时候,如果发生CMException,优先考虑fail-fast有关的情况,实际上这里并没有真的发生并发,只是Iterator使用了fail-fast的保护机制,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常

https://www.hollischuang.com/archives/3542

44. 为什么阿里巴巴要求谨慎使用ArrayList中的subList方法

在这里插入图片描述

底层原理:
首先,我们看下subList方法给我们返回的List到底是个什么东西,这一点在JDK源码中注释是这样说的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive

也就是说subList 返回是一个视图,那么什么叫做视图呢?

我们看下subList的源码:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

这个方法返回了一个SubList,这个类是ArrayList中的一个内部类

SubList这个类中单独定义了set、get、size、add、remove等方法。

当我们调用subList方法的时候,会通过调用SubList的构造函数创建一个SubList,那么看下这个构造函数做了哪些事情:

SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset + fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}

可以看到,这个构造函数中把原来的List以及该List中的部分属性直接赋值给自己的一些属性了。

也就是说,SubList并没有重新创建一个List,而是直接引用了原有的List(返回了父类的视图),只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。

**总结:**我们简单总结一下,List的subList方法并没有创建一个新的List,而是使用了原List的视图(fromIndex和toIndex),这个视图使用内部类SubList表示。

所以,我们不能把subList方法返回的List强制转换成ArrayList等类,因为他们之间没有继承关系。

另外,视图和原List的修改还需要注意几点,尤其是他们之间的相互影响:

  1. 对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。

  2. 对子List做结构性修改,操作同样会反映到父List上。

  3. 对父List做结构性修改,会抛出异常ConcurrentModificationException。

https://www.hollischuang.com/archives/3775

45. 数据库死锁问题的排查

  1. 查看数据库版本:
select version();
  1. 引擎查询方法:
show engines
  1. 查询事务隔离级别:
    8.0版本之前:
select @@tx_isolation;

8.0版本

select @@transaction_isolation
  1. 查询日志
show engine innodb status
  1. 查看锁情况
  2. 修改代码或索引

46. 除StringBuilder和String Buffer以及String外,Java 8新增StringJoiner进行字符串拼接

StringJoiner sj = new StringJoiner("Hollis");

        sj.add("hollischuang");
        sj.add("Java干货");
        System.out.println(sj.toString());

        StringJoiner sj1 = new StringJoiner(":","[","]");

        sj1.add("Hollis").add("hollischuang").add("Java干货");
        System.out.println(sj1.toString());

根据此特性,可使用StringJoiner通过一个LIst进行字符串拼接

47. 线程池

所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。

只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。

线程池提供了两个钩子(beforeExecute,afterExecute)给我们,我们继承线程池,在执行任务前后做一些事情。

线程池原理关键技术:锁(lock,cas)、阻塞队列、hashSet(资源池)

使用ThreadPoolExecutor创建线程池,不允许使用Executors去创建
在这里插入图片描述
原理

Java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue 和 LinkedBlockingQueue。

  • ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。

  • LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。
    这里的问题就出在:不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。也就是说,如果我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE。

而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。

创建线程池的正确姿势:

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。

48. ThreadLocal

ThreadLocal
ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些实际问题。

49. 创建线程的四种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 通过Callable和FutureTask创建线程
public class MultiThreads {
    public static void main(String[] args) throws InterruptedException {
        CallableThread callableThread = new CallableThread();
        FutureTask futureTask = new FutureTask<>(callableThread);
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
}

class CallableThread implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return "Hollis";
    }

}
  1. 通过线程池创建线程
public class MultiThreads {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println(Thread.currentThread().getName());
        System.out.println("通过线程池创建线程");
        ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(10));
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
    }
}

50. SimpleDateFormat

SimpleDateFormat不是一个线程安全的类。见阿里巴巴编码规范:
在这里插入图片描述

如何解决:

  1. 使用局部变量
  2. 进行加锁
for (int i = 0; i < 100; i++) {
    //获取当前时间
    Calendar calendar = Calendar.getInstance();
    int finalI = i;
    pool.execute(() -> {
        //加锁
        synchronized (simpleDateFormat) {
            //时间增加
            calendar.add(Calendar.DATE, finalI);
            //通过simpleDateFormat把时间转换成字符串
            String dateString = simpleDateFormat.format(calendar.getTime());
            //把字符串放入Set中
            dates.add(dateString);
            //countDown
            countDownLatch.countDown();
        }
    });
}
  1. 使用ThreadLocal
/**
 * 使用ThreadLocal定义一个全局的SimpleDateFormat
 */
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

//用法
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());

ThreadLocal保证每个线程都有一个独享的对象,避免了频繁创建的对象,也避免了多线程的竞争。

  1. 使用DateTimeFormatter
    使用DateTimeFormatter代替SimpleDateFormat,他是一个线程安全的类。

51. 不要直接使用Log4J等日志工具

使用Slf4j等门面日志而不直接使用Log4j等日志。

52. Spring源码

  • Application接口
    ApplicationContext接口继承自BeanFactory接口(非直接继承),就是我们常说的bean容器。在这里插入图片描述

有实现类 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext Spring提供的两个常用的ApplicationContext实现类,前者默认从类路径加载配置文件,后者从文件系统加载。 然后初始化一个ApplicationContext。

ApplicationContext ac =new ClassPathXmlApplicationContext("application.xml");
ApplicationContext ac =new FileSystemXmlApplicationContext(/User/Desktop/application.xml);

@Autowired

Spring通过BeanPostProcessor处理Autowired注解,所以不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型应用这些注解,这些类型必须通过xml或者Spring的@Bean注解进行加载。

在这里插入图片描述

53. StringBuilder和StringBuffer

String是final类型的数据,因此+号拼接需要重新创建String对象。
StringBuilder类似String内部也是char[] 但并不是final类型,因此使用append添加内容时会进行扩容,并添加元素。
StringBuffer内部使用synchronized保证线程安全。

54. 内存泄漏

简单的理解内存泄漏的问题就是不使用的对象没有被回收(没有被GC的对象。)

解决方案:

  • 尽量减小变量的作用域,如能使用局部变量的就不要使用全局变量。
  • 明确之后不会继续使用的对象可手动赋null值,使对象区域更容易被判为不可达。

55. AOP

  • 配置切面aspect

  • 配置切入点pointcut 配置expression
    在这里插入图片描述
    如何声明?
    在这里插入图片描述

  • before

  • after returning

  • after throwing

  • after

  • around

56. AQS

AQS的英文全称为AbstractQueuedSynchronizer 队列同步器。
AQS实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等等一些底层的实现处理。AQS的核心也包括了这些方面:同步队列,独占式锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现,而这些实际上则是AQS提供出来的模板方法

其含义为当共享资源被某个线程占有,其他请求该资源的线程将会阻塞,从而进入同步队列。(队列有两种实现形式 一种为数组,一种为链表)AQS底层采用双向链表队列的形式进行实现,包含共享锁和独占锁。

57. Spring Bean的生命周期

在这里插入图片描述

借用网上广为流传的一个图。
Spring Bean的声明周期分为对象的实例化,和销毁两个过程。在初始化Spring ApplicationContext后进行Bean的实例化,对Bean的实例化有域注入,setter注入和构造器注入三种形式。

  • Bean的销毁:实现DisposableBean接口并覆盖destory方法
  • Aware接口:Spring中提供了一些以aware结尾的几口,实现了Aware接口的bean在被初始化后,可以获取相应的资源。 如ApplicationContextAware接口
  • BeanPostProcessor:后置处理器。bean对象已经被正确的构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。 接口包含postProcessorBeforeInitialzation和postProcessorAfterInitialzation两个方法
  • **初始化:**初始化时可实现InitializingBean接口,并实现afterPropertiesSet方法。在对Bean进行实例化是若实现了此接口则会提前调用afterPropertiesSet方法

更详细的过程可参考 https://www.zhihu.com/question/38597960

58. 数据库

理解数据库的架构需要从他的物理存储和逻辑存储两方面来理解,参照下图:
在这里插入图片描述
物理存储指的是我们的文件存储结构,数据库中的数据一定是存储在磁盘上,而我们常说的索引树之类的内容,都是逻辑上的结构。可参照上图进行理解,上图包含了数据库相关的全部架构和知识体系。

59.计算机结构——CPU

60.列式存储结构

不同于Mysql的行式存储结构,列式存储对于 OLTP 不友好,一行数据的写入需要同时修改多个列。但对 OLAP 场景有着很大的优势:

  • 当查询语句只涉及部分列时,只需要扫描相关的列
  • 列式存储的每一列数据类型都是相同的,彼此之间的相关性更大,对列数据压缩的效率较高

OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,记录即时的增、删、改、查,比如在银行存取一笔款,就是一个事务交易。OLAP即联机分析处理,是数据仓库的核心部心,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。典型的应用就是复杂的动态报表系统。

61. 详解内部类

内部类总共有四种,先来看一下他们在表层上或者说是写法上的差异:

  • 成员内部类:普通的定义在一个类内部的类
  • 局部内部类:可以定义在一个方法或者代码块内
  • 匿名内部类:类似局部内部类,但是并未进行显式声明,而是采用匿名的方式
  • 静态内部类:使用static关键字修饰的内部类

那么内部类存在的意义究竟?

众所周知,我们的C++设计是多继承的,而多继承的好处就是我们只需要写很少的代码即可完成一个尅的的定义,可以通过集成其他类来获取其他类的实现。
Java在设计继承机制时认为多继承会是一个麻烦,所以Java采用了单继承的方式进行实现,其思想是is-a的原则,(如果一个类是A,那么就不能是B,因此是单继承的设计)。

但是多继承的方式也并不是一无是处,在一些需要大量复用代码的情况下,也不失为一个好的解决方式。因此采用内部类的设计方式我们可以很好的解决大量复用的问题。采用内部类的方式,我们可以很轻松地在一个类的内部定义多个内部类,并分别继承多个类。因此我们可以说Java的内部类完善了他的多继承机制。

通过例子来理解一下:

public interface Contents {
	int value();
}
public interface Destination {
	String readLabel();
}
public class Goods {
	private class Content implements Contents {
		private int i = 11;
		public int value() {
			return i;
		}
	}
	protected class GDestination implements Destination {
		private String label;
		private GDestination(String whereTo) {
			label = whereTo;
		}
		public String readLabel() {
			return label;
		}
	}
	public Destination dest(String s) {
		return new GDestination(s);
	}
	public Contents cont() {
		return new Content();
	}
}
class TestGoods {
	public static void main(String[] args) {
		Goods p = new Goods();
		Contents c = p.cont();
		Destination d = p.dest("Beijing");
	}
}

这是最普通的使用方式。

在这个例子里类Content和GDestination被定义在了类Goods内部,并且分别有着protected和private修饰符来控制访问级别。Content代表着Goods的内容,而GDestination代表着Goods的目的地。它们分别实现了两个接口Content和Destination。在后面的main方法里,直接用 Contents c和Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了 隐藏你不想让别人知道的操作,也即封装性。
同时,我们也发现了在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。上例中的cont()和dest()方法就是这么做的。那么还有没有别的方法呢?当然有,其语法格式如下:
outerObject=new outerClass(Constructor Parameters);
outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);
注意在创建非静态内部类对象时,一定要先创建起相应的外部类对象。至于原因,也就引出了我们下一个话题 非静态内部类。

  1. 成员内部类(非静态内部类)

类对象有着指向其外部类对象的引用 对刚才的例子稍作修改:

public class Goods {
	private int valueRate = 2;
	private class Content implements Contents {
		private int i = 11 * valueRate;
		public int value() {
			return i;
		}
	}
	protected class GDestination implements Destination {
		private String label;
		private GDestination(String whereTo) {
			label = whereTo;
		}
		public String readLabel() {
			return label;
		}
	}
	public Destination dest(String s) {
		return new GDestination(s);
	}
	public Contents cont() {
		return new Content();
	}
}

在这里我们给Goods类增加了一个private成员变量valueRate,意义是货物的价值系数,在内部类Content的方法value()计算价值时把它乘上。我们发现,value()可以访问valueRate,这也是内部类的第二个好处 一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外部类对象的引用。Java编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。
有人会问,如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽了,怎么办?没事,Java里用如下格式表达外部类的引用:
outerClass.this
有了它,我们就不怕这种屏蔽的情况了。

  1. 静态内部类
    和普通的类一样,内部类也可以有静态的。不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。

public class Goods {
	private int valueRate = 2;
	private class Content implements Contents {
		private int i = 11 * valueRate;
		public int value() {
			return i;
		}
	}
	protected class GDestination implements Destination {
		private String label;
		private GDestination(String whereTo) {
			label = whereTo;
		}
		public String readLabel() {
			return label;
		}
	}
	public Destination dest(String s) {
		return new GDestination(s);
	}
	public Contents cont() {
		return new Content();
	}
}

这实际上和C++中的嵌套类很相像了,Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用这一点上,当然从设计的角度以及以它一些细节来讲还有区别。
除此之外,在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切。这也算是两者的第二个区别吧。

  1. 局部内部类

是的,Java内部类也可以是局部的,它可以定义在一个方法甚至一个代码块之内。

public class Goods1 {
	public Destination dest(String s) {
		class GDestination implements Destination {
			private String label;
			private GDestination(String whereTo) {
				label = whereTo;
			}
			public String readLabel() {
				return label;
			}
		}
		return new GDestination(s);
	}
	public static void main(String[] args) {
		Goods1 g = new Goods1();
		Destination d = g.dest("Beijing");
	}
}

  1. 匿名内部类
    匿名内部类类似于我们的匿名对象的思想。是对类的定义采用了一种匿名的方式。

java的匿名内部类的语法规则看上去有些古怪,不过如同匿名数组一样,当你只需要创建一个类的对象而且用不上它的名字时,使用内部类可以使代码看上去简洁清楚。它的语法规则是这样的:
new interfacename(){…}; 或 new superclassname(){…};
下面接着前面继续举例子:

public class Goods3 {
	public Contents cont() {
		return new Contents() {
			private int i = 11;
			public int value() {
				return i;
			}
		};
	}
}

这里方法cont()使用匿名内部类直接返回了一个实现了接口Contents的类的对象,看上去的确十分简洁。
在java的事件处理的匿名适配器中,匿名内部类被大量的使用。例如在想关闭窗口时加上这样一句代码:

frame.addWindowListener(new WindowAdapter(){
	public void windowClosing(WindowEvent e){
	   System.exit(0);
	}
}); 

62. Mysql的连接

  • 对于三类连接查询,MySQL只支持其中的:内连接 和 外连接
  • 对于所有的连接类型而言,就是将符合关键字 ON 后条件匹配的对应组合都成为一条记录出现在结果集中,对于两个表中的某条记录可能存在:一对多 或者 多对一 的情况会在结果集中形成多条记录,只是另外一个表中查询的字段信息相同而已;千万不要误以为:左外连接查询到的记录数是和左表的记录数一样,对于其他连接一样,不能形成这个误区。
  • 对于内连接查询到的是一个符合 ON 条件匹配的在两个表中都存在记录的结果集
    -对于外连接,例如左外连接查询到的是一个包含所有左表记录且在右表符合ON匹配时的右表信息的结果集,如果左表的某条记录和右表的多条记录匹配,结果集中就存在同一个左表记录对应多个右表记录的多条记录,此时的结果集记录数是大于左表的记录数的。对于右外连接来说同理。
  • 对于全连接,就是把左表右表都包含在内,如果符合ON条件的匹配在结果集中形成一条记录,如果对左表或者右表中的某一条记录在对方表中不存在ON匹配时,就以单独一条记录出现在结果集中,对方表不存在的信息以NULL补充。

63. 数据库范式

  • 第一范式:数据库某个属性不能有多个值->第一范式就是无重复的列
  • 第二范式:属性完全依赖于主键-> 消除部分子函数依赖
  • 第三范式:属性不依赖其他非主属性-> 消除传递依赖

64. 三次握手 四次挥手

三次握手 四次挥手

65. 双亲委派模型

类加载器加载类,首先会把这个类请求委派给父类加载器去进行类加载,每一层都是如此,一直递归到顶层。
在这里插入图片描述
存在原因:

双亲委派有啥好处呢?它使得类有了层次的划分。就拿java.lang.Object来说,你加载它经过一层层委托最终是由Bootstrap ClassLoader来加载的,也就是最终都是由Bootstrap ClassLoader去找<JAVA_HOME>\lib中rt.jar里面的java.lang.Object加载到JVM中。

这样如果有不法分子自己造了个java.lang.Object,里面嵌了不好的代码,如果我们是按照双亲委派模型来实现的话,最终加载到JVM中的只会是我们rt.jar里面的东西,也就是这些核心的基础类代码得到了保护。

JDBC的SPI机制:
在rt.jar里面定义了SPI。
mysql有mysql的jdbc实现,oracle有oracle的jdbc实现,反正我java不管你内部如何实现的,反正你们都得统一按我这个来,这样我们java开发者才能容易的调用数据库操作。所以因为这样那就不得不违反这个约束啊,Bootstrap ClassLoader就得委托子类来加载数据库厂商们提供的具体实现。因为它的手只能摸到<JAVA_HOME>\lib中,其他的它无能为力。这就违反了自下而上的委托机制了。
Java就搞了个线程上下文类加载器,通过setContextClassLoader()默认情况就是应用程序类加载器然后Thread.current.currentThread().getContextClassLoader()获得类加载器来加载。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章