一. 设计模式

java的设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案,具有普遍性,可以反复使用。其目的是为了提高代码的可重用性可读性可靠性
常见的设计模式有:

1.重构原则

重构:对软件内部结构的一种调整,目的是在不改变软件客观察行为的前提下,提高其可理解性,降低修改成本,提高编程速度,改善系统性能。

何时重构:

  1. 三次法则,事不过三,三则重构
  2. 添加新功能的时候重构
  3. 修补错误的时候重构
  4. 复审代码的时候重构

永远不要害怕重构,仅当这些情况出现时除外:

  1. 现有代码过于混乱,重构的成本大于重写
  2. 代码在大部分情况下无法正常运行,稍作改动,就报很多错误,无法稳定运作;
  3. 项目临近交付的时候;

2. 开口合里最单依原则

  • 开闭原则
    通过抽象类和接口,实现对扩展开放,面对修改关闭,可以提高可复用性和可维护性,便于测试。
  • 接口隔离原则
    一个类对另外一个类的依赖是建立在最小的接口上,宁可尽可能多的接口,而不要一个大而全的接口

    接口数量要规划合理,同时要符合单一职责原则

  • 组合复用原则
    组合复用原则要求在软件复用时,要尽量
    先使用组合或者聚合等关联关系来实现,
    其次才考虑使用继承关系来实现。

    继承复用的耦合度高,并且破坏了类的封装性(暴露父类),限制了使用的灵活性 如果一定要使用继承关系,则必须严格遵循里氏替换原则

  • 里 式替换原则
    子类可以扩展父类的功能,但不能改变父类原有的功能

    如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。

  • 最少知识原则
    最少知识原则又称迪米特法则一个对象应当对其他对象有尽可能少的了解. 就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果要调用的话可以通过第三方来调用

  • 单一职责原则
    一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。如果类有多个引起它变化的原因,他就可能有多个职责,应该将职责分离出去。

    这样做可以降低类的复杂度,提高类的可读性和可维护性,但是会使得可复用行降低,酌情选择。

  • 依依赖倒置原则
    高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
    简单来说,就是要面向接口编程,不要面向实现编程。

    采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定,降低并行开发引起的风险,提高代码的可读性和可维护性。

3. GOF 23 种设计模式

设计模式 介绍 要点
单例模式 一个类只有一个实例,且该类能自行创建这个实例 懒汉、恶汉、双重检测
原型模式 用一个已经创建的实例作为原型,通过复制来创建一个相同或相似的新对象 clone方法,Cloneable接口
工厂方法模式 用工厂类创建自定义对象 根据参数,创建不同的对象
生成器模式 将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成,步骤可变 注重零件组装的过程
代理模式 由于某些原因需要给某对象提供一个代理以控制对该对象的访问 隔离,保护,扩展
适配器模式 将一个类的接口适配成指定的接口(继承或聚合后实现接口) Arrays.asList方法转化数组为列表,得到的ArrayList只是以个内部类,是对数组的适配,集成了AbstaractList将数组模拟成list
桥接模式 其实就是面向接口编程 低耦合
装饰模式 装饰者包含并继承了原接口,通过多级包含,来多级填加属性 多级装饰return,不改变类结构
外观模式 为多个复杂的子系统提供一个一致的接口,相当于填加了一个控制层 封闭,依旧违背开闭原则
策略模式 该模式定义了一系列算法,它们可以相互替换 接口,抽象类,换句话说可以是面向抽象开发
迭代器模式 自定义Iterator迭代器,实现next,hasNext,然后在该类内填加getIterator方法 不暴露聚合对象,统一的接口
模板方法模式 定义并固定了一套流程模板,将其中一个步骤设置为abstract方法,来对接不同的对象不同场景 抽象类,重写抽象方法

1.3.1 工厂模式

在实际业务中经常用到,也是面试的主要考察点.是创建不同类型实例常用的方式.

spring中的bean都是由不同工厂类创建的.

1.3.2 代理模式

在不适合或不能直接引用另一个对象的场景,可以用代理模式对被代理的队形进行访问行为的控制.Java的代理模式分为静态代理和动态代理,静态代理是指在编译时就创建好的代理类,例如在源代码中编写的类.动态代理指在JVM运行过程中动态创建的代理类,如JDK动态代理,CDLIB,javaasist等.

例如,在Mybatis中getMapper时会通过MapperProxyFactory及配置文件动态生成的Mapper代理对象,代理对象会拦截Mapper接口的方法调用,创建对应方法的MapperMethod类并执行execute方法,然后返回结果.

1.3.3 责任链模式

类似工厂流水线,其中的每个节点完成对对象的某一种处理.

Netty框架的处理消息的Pipeline就是采用的责任链模式.

1.3.4 适配器模式

类似于转接头,将两种不匹配的对象进行适配,也可以起到对两个不同的对象进行解耦的作用.

SLF4J可使项目与Log4、logback等具体日志实现框架进行解耦,其通过不同适配器与不同框架进行适配,完成日志功能的使用.

1.3.5 观察者模式

也可称为发布订阅模式,适用于一个对象某个行为需要触发一系列操作的场景.

GRPC中stream流式请求的处理.

1.3.6 构造者模式

适用于一个对象拥有很多复杂的属性,需要根据不同情况创建不同的具体对象.

创建Protocol Buffer对象时,需要用到Builder

4 并行设计模式

4.1 Future模式

Future模式的核心在于:去除了主函数的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑,将同步改为异步
定义了一个Data接口,两个 FutureDate 和 RealDate实现类。主线程请求的时候先返回一个FutureDate ,然后子线程去请求RealDate并写入请求结果。如果再RealDate写入前主线程要访问,访问到的是FutureDate。

4.2 Master-worker模式(有空研究)

4.3 生产者-消费者模式

用lock,linkedblockingQueue阻塞队列,synchronized和volatile等实现

5 Spring中的设计模式?

5.1 工厂模式

Spring的bean创建用到了工厂模式
Spring使用ICO控制反转创建bean,我们无需关心对象的具体创建,只需要提供参数,Spring的bean工厂就会在Spring启动后创建,同时可以指定对象的生命周期。如果配置了懒加载,那么只有在用到bean的时候才会创建。

Spring使用工厂模式可以通过2种方式创建bean对象

  • BeanFactory延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
  • ApplicationContext容器启动的时候,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用ApplicationContext会更多。

ApplicationContext的三个实现类:

  1. ClassPathXmlApplication:从classPath 根路径加载XML 文件(相对路径)
  2. . FileSystemXmlApplication:根据绝对路径加载XML 文件(灵活性差)
  3. XmlWebApplicationContext:从web根路径中加载XML 文件(web项目)

5.2 单例模式

Spring 中 bean 的默认作用域就是 singleton(单例)的,节省系统开销,减少GC次数。 除了 singleton 作用域,Spring 中 bean 还有下面几种作用域(scop属性):

  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效
  • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效
  • global-session全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

5.3 代理模式

代理的模式主要有 静态代理, jdk动态代理, cglib动态代理
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib

5.3.1 JDK动态代理

jdk的动态代理调用了Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法,动态的创建了一个代理类,通过反射调用类的方法,这里使用到聚合设计模式中的继承聚合和组合聚合。代理类继承了porxy所以无法再继承被代理的类,所以只能通过接口来代理。

参数说明:
假设被代理的类为A

  • ClassLoader loader: 要求传入一个类加载器,A.getClass().getClassLoader()
  • Class<?>[] interfaces:被代理对象实现的所有接口,A.getClass().getInterfaces()
  • InvocationHandler h:实现InvocationHandler 接口,聚合被代理的A并实现invoke方法处理调用前后的逻辑

5.3.1 Cglib代码生成库

CGLIB代理主要通过对字节码的操作,创建代理对象以控制对象的访问,支持类的代理。

注意点:
两者都是对Java字节码进行操作来生产代理类,这样生成的类会在Java的永生代中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。

CGLIB和Java动态代理的区别

  • Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
  • Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

5.4 责任链模式

Spring mvc中,HandlerExecutionChain 实现了责任链模式。HandlerMaping的请求到达
HandlerExecutionChain 后,HandlerExecutionChain 本身并不处理,而是将它按照代码执行的先后顺序依次交给 intercepter集合 -> controller - >intercepter集合来处理,intercept和controller都可以处理请求,形成了一个责任链。

5.5 模板模式

模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。比如JDBCTemplate:封装了 获得数据库连接,处理事务,处理异常,关闭资源等等通用方法,执行完之后,调用callback的不一样的业务逻辑

5.6 观察者模式

观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,或者发布消息通知。
Spring 事件驱动模型中的三种角色
事件角色
ApplicationEvent(org.springframework.context包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject并实现了 java.io.Serializable接口。
Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent 的实现(继承自ApplicationContextEvent),可以用来监听Spring上下文

  • ContextStartedEvent:ApplicationContext 启动后触发的事件;
  • ContextStoppedEvent:ApplicationContext 停止后触发的事件;
  • ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件;
  • ContextClosedEvent:ApplicationContext 关闭后触发的事件。

事件监听者角色
ApplicationListener 充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()方法来处理ApplicationEvent事件。

只要实现 ApplicationListener 接口实现 onApplicationEvent() 方法即可完成监听事件

事件发布者角色
ApplicationEventPublisher 充当了事件的发布者,它也是一个接口
ApplicationEventPublisher 接口的publishEvent()这个方法在AbstractApplicationContext类中被实现,我们使用的时候只需要获取spring上下文ApplicationContext调用publishEvent()方法即可

获取spring上下文
实现 ApplicationContextAware 并交由Spring管理即可

Spring 的事件流程总结

  1. 定义一个事件: 实现一个继承自 ApplicationEvent的事件类,并且写相应的构造函数;
  2. 定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方法;
  3. 使用事件发布者发布消息: 可以通过 ApplicationEventPublisher 的 publishEvent() 方法发布消息。

5.7 适配器模式

适配器模式(Adapter Pattern) 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。

5.7.1 spring AOP中的适配器模式

我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口AdvisorAdapter
Advice 常用的类型有:

  • BeforeAdvice(目标方法调用前,前置通知)|
  • AfterAdvice(目标方法调用后,后置通知)
  • AfterReturningAdvice(目标方法执行结束后,return之前)等等。

每个类型Advice(通知)都有对应的拦截器:
MethodBeforeAdviceInterceptorAfterReturningAdviceAdapterAfterReturningAdviceInterceptor

Spring预定义的通知要通过对应的适配器,适配成 MethodInterceptor接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)。

5.7.2 spring MVC中的适配器模式

在Spring MVC中,DispatcherServlet调度器 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式? Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断

5.8 装饰者模式

装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如java对字符字节流的处理InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。

Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式(这一点我自己还没太理解具体原理)。Spring 中用到的包装器模式在类名上含有 Wrapper或者 Decorator。这些类基本上都是动态地给一个对象添加一些额外的职责。
spring datesource和datesourcefactory就用到了这点。

5.9 总结

Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

6 MVC设计模式

Model view controller 模型视图控制器

  • Model是业务功能的实现的核心
  • Controller用来处理传入数据,分配相应页面
  • View用来显示页面

优点:

  • MVC分层简化来分组开发,减少了程序的耦合
  • c

常见框架:

  • Struts2
  • SpringMVC

二. Java的语言特性

1. JAVA的基础特性

C语言是面向过程编程,Java面向对象编程(黑白棋问题)。
万物皆是对象,万物皆属于类,对象是经由类这个模板创建出来的一个具体实例

1.1 面对现象三大特征

  • 封装,将对象的数据和行为封装为一个整体,并尽可能地隐藏对象的内部实现细节

    数据私有化,行为公开化,

  • 继承,为了提高代码的复用率,子类可以继承父类

    单继承,传递性,构造方法无法继承,super调用父类方法,final方法无法被重写(运行时绑定,访问控制修饰符比父类大)

  • 多态, 同一方法名的方法可以有不同的解释,产生不同的执行结果

    参数顺序/参数数目/参数类型不同,任何方法都可以被重载(编译时绑定)

1.2 构造方法

  • 默认拥有一个无参构造方法,添加有参构造方法后消失
  • 无参构造方法只能有一个,有参可以有多个
  • 无法继承父类构造方法,只能super调用

1.3 变量的生命周期

  • 局部变量生命周期:随着方法执行结束而结束
  • 成员变量的生命周期:和对象生命周期一致
  • 对象的生命周期:可达性分析,GC回收

1.4 引用类型数组

  • 引用类型存放的是对象存放的地址
  • final修饰只能保证指向的对象不变,对象的属性可变
  • 二维数组 int[][] ary = new int[len][len1](井表)
  • Sting的字符串优化拼接(字符串常量池)

1.4 Final finally finalize的区别

  • final用来修饰变量表示无法被修改(成员变量需初始化),修饰方法无法被重写,修饰类无法被继承
  • finally用来表示try-catch中最后且必将执行的代码块,在return返回值前执行
  • finalize是Object的方法,在GC回收前执行

1.5 访问控制修饰符

Public > Protected > 默认的 > Protected

  • Public 所有可用
  • Protected 仅本项目可用
  • 默认的 仅本包可用
  • Private 仅本类可用

1.6 abstract 抽象 和 interface 接口

  • 有abstract方法的类一定是抽象类,abstract方法无实体被继承后要重写
  • interface可以被实现和继承,多实现/继承 : implements/extends 接口1,接口2{}
  • instanceof运算符可以返回是否是接口的实现

1.7 多态和造型

子类转为父类为向上造型,但是父类无法向下造型成子类

Father f = Father(son);

1.8 内部类和匿名内部类

  • class内定义的类即为内部类不能用private修饰,Inner可以直接调用Outer的所有成员及方法。
  • 定义在方法体内部的类叫局部内部类方法中的变量必须加final才能访问,可以访问outer所有变量

1.9 数据类型

java有8种基本的数据类型

基本类型 封装类型 大小 取值范围 说明
byte Byte 1字节(2^8) -128~127
short Short 2字节(2^16)
int Integer 4字节(2^32)
long Long 8字节(2^64)
float Float 4字节(2^32) 浮点数不能用来表示精确的值
double Double 4字节(2^32) 浮点数不能用来表示精确的值
char Character 1字节(2^8) \u0000~\uffff
boolean Boolean 1字节(2^8)

要点:

  • 自动装箱拆箱
  • Long/Double无法保证运算的原子性
  • 超过最大值会数据溢出
  • 运算自动转型为参与运算的最大类型,并保存为该类型
  • char类型的编码
  • 浮点数float和double运算精度不足,必须使用Bigdecimal精度数类
  • 封装类型都被final修饰,无法继承

引申:

  1. 判断是否是基础类型

    1. /**
    2. * 判断是否是基础数据类型,即 int,double,long等类似格式
    3. */
    4. public static boolean isCommonDataType(Class clazz){
    5. return clazz.isPrimitive();
    6. }
  2. 判断是否是基础类型的包装类

    1. /**
    2. * 判断是否是基础数据类型的包装类型
    3. *
    4. * @param clz
    5. * @return
    6. */
    7. public static boolean isWrapClass(Class clz) {
    8. try {
    9. return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
    10. } catch (Exception e) {
    11. return false;
    12. }
    13. }
  3. 判断是否是基础类型或其包装类

    1. return isCommonDataType(clazz) || isWrapClass(clazz);

TODO : Unicode码,Utf-8,ASCII码,GBK的不同?(有时间分析)

1.10 枚举

枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,是一种特殊的class类型,会在编译后继承Enum类。
枚举类在定义的时候会自动为每个变量添加一个序号,从0开始,可以用ordinal()获取

  1. public enum Weekday {
  2. SUN(0),MON(1),TUS(2),WED(3),THU(4),FRI(5),SAT(6);
  3. private int value;
  4. private Weekday(int value){
  5. this.value = value;
  6. }
  7. }

同时,也给每个变量取了一个名字,默认是变量名的驼峰格式,可以用name()获取

1.11 运算符

详见,待整理 https://blog.csdn.net/fzbeiqing/article/details/79253983

运算符 运算符
! 非 >> 带符号右移 除法
^ 异或 << 带符号左移 乘法
~ 求反 >>> 不带符号右移 补0
%求模 <<< 不带符号左移 补0
  • a+=b 会自动转型,而a=a+b在类型不匹配的时候会报错。
  • a 先对a自增,a 运算后再对a自增

运算优先级:

括号 > 自增自减 > 乘除取模 > 加减 > 位移 > 比较 > 位运算 > 逻辑运算 > 赋值 > 位赋值 >

1.12 循环和流程控制

  • if esle if 分支语句
  • Switch case break default 分支语句

    switch条件可以存放 int、String、enum枚举,以及支持类型转换的数据类型 如果不加break会穿透,自动执行符合条件的case下的所有case代码。 dufault语句最后执行,如果将之放在中间位置,则之后的所有case都会执行,后面必须加上break

  • while 循环语句

  • do while 先执行的循环语句
  • for 指定次数循环
  • 增强for循环
  • continue 跳过当次循环 (可以指定跳过的循环)
  • break 跳出当次循环(可以指定跳出的循环)、

1.13 字符集编码

为什么有编码?

我们知道计算机中最小的存储单位是字节(byte),一个字节所能表示的字符数又有限,1byte=8bit,一个字节最多也只能表示255个字符,而世界上的语种又多,都有各种不同的字符,无法用一个byte表示,所以java中的char表示字符就是来解决这种编码问题的,一个char占两个字节,所以从char到最小单位byte之间必须经过编码。

通用性向下递增

编码 大小 支持语言
ASCII / ISO-8859-1 1个字节 英文
Unicode 2个字节(生僻字4个) 所有语言
GBK 英文字母1个字节,汉字2个字节 大部分亚洲语言和英文
UTF-8 英文字母1个字节,汉字3个字节,生僻字4-6个字节 所有语言

char是按照字符存储的,不管英文还是中文,固定占用占用2个字节,用来储存Unicode字符。范围在0-65536。

2. JAVA的集合框架

在Java的集合容器框架中,主要有四大类别:List、Set、Queue、Map。
List、Set、Queue接口分别继承了Collection接口,Map本身是一个接口。
像ArrayList、LinkedList、HashMap这些容器都是非线程安全的。

  • List:
    1、可以允许重复的对象
    2、可以插入多个null元素
    3、是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺
    4、常用的实现类有ArrayList、LinkedList、Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
  • Set:
    1、不允许重复对象
    2、无序容器,无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
    3、只允许一个null元素
    4、Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
  • Map:
    1、 Map不是collection的子接口或者实现类。Map是一个接口。
    2、Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,键唯一
    3、TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
    4、Map 只能有一个 null 键
    5、Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)

面试准备(三) : 设计模式与Java语言特性 - 图1

2.1 ArrayList

2.2 LinkedList

面试准备(三) : 设计模式与Java语言特性 - 图2

2.1 HashMap

面试准备(三) : 设计模式与Java语言特性 - 图3
关键参数:(默认值)

  • 默认大小是16(1<<4)
  • 最大扩容大小(1<<30)

    由于key的hash是 hash & ( length-1)来进行计算,只有当length为2的指数幂的时候才能较均匀的分布元素。 HashMap内部由Entry[]数组构成,Java的数组下标是由Int组成,最大值为2的31次幂-1,左移后成为补码0x80000000,所以最大只能为2的30次幂。

  • 加载因子 0.75

    当容量到达阀值或者储存总量大于64时,会rehash,并将map扩容1倍,此时链表会倒排

  • 阀值 12

    触发rehash的容量,= 加载因子*当前大小

  • 单个链表大于8个且总容量大于64后会转为红黑树

Hashmap的数据其实是成员变量table中,存放的是entry数组
面试准备(三) : 设计模式与Java语言特性 - 图4
每个entry对象中都有next属性,存放下一个node,形成单向链表
每个entry都有key和value属性,可以保存键值和内容
还有一个唯一的hash码用来识别

put原理:

  • 首先通过hash方法将key转为hash码,如果key=null则调永putForNullKey()放入table[0]中,hash码设为0
  • 然后通过indexFor方法 h & (length-1)获取存入桶的下标 i

    由于长度是2的N次幂,所以lenth-1就相当于与11111低位掩码,与h向与后保留h的低位,既待插入node在数组中的位置) &与的字节码比%求模少很多,速度快

  • 遍厉Node[i]链表,如果Node中有key相同的(hash,==,equal三种判断),则替换value,否则填加到链表后面

    for (Entry e = table[i]; e != null; e = e.next)

  • key为null时,会调用的是 putForNullKey(V value) 方法,会存入第0个Node并且分配一个key给它

get原理:

调用hash和indexFor获取散列桶下标,如果存在,则遍厉Node取值

多线程风险:

  1. 多线程的key 可能发生碰撞,产生覆盖
  2. Node数组扩容时,其他线程的Node丢失,(1.7扩容时由于头插法引起的环形引用会产生死循环)

    头插法,将每次插入的Node作为头 尾插法,将每次插入的Node作为Next

HashMap和HashTabe的区别?

  1. HashMap线程不安全效率高,HashTable利用synchronized实现了线程安全,效率低
  2. HashMap允许null值 而 HashTable 不允许
  3. HashMap用与操作hash,而HashTable 采用 % 取模,效率低
  4. HashMap扩容后2倍,HashTable 为2倍+1

2.2 hashMap的红黑树

要了解红黑树,先要知道avl树,要知道av树,首先要知道二叉树
二叉树

二叉树就是每个父节点下面有零个一个或两个子节点,如果数据本身就是有序的,那么会偏向一边导致二叉树的不平衡,甚至退化成链表 面试准备(三) : 设计模式与Java语言特性 - 图5

avl树(二叉平衡树)
通过左旋和右旋平衡二叉树,使得每个节点对应的左子树和右子树的树高度差不超过1
左旋:将Y上升为父节点,此时Y的右子节点不变,而左子节点变成pr,同时将b给予pr作为右节点,右旋也一样,将右子节点变为pr。
面试准备(三) : 设计模式与Java语言特性 - 图6
二三树

二三数的节点有两种,一种是1个值2子节点,一是2个值3子节点 如果ti一个2个值的节点再插入一个值的话,会将中间的值向上进行左旋或右旋。

面试准备(三) : 设计模式与Java语言特性 - 图7
红黑树

红黑树就是基于二三数来实现的,拥有五大特征:

  1. 每个节点或者是黑色,或者是红色。
  2. 根节点是黑色。
  3. 每个叶子节点是黑色。(空或NULL)
  4. 如果一个节点是红色的,则它的子节点必须是黑色的。
  5. 完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。‘

更多:
B树,B-树,B+树,B*数树(以后有时间整理)

3. JAVA的同步类容器和并发类容器

为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列、Synchronizer(比如CountDownLatch)。今天我们就来讨论下同步容器和并发类容器。

3.1 同步类容器

在Java中,同步容器主要包括2类:

  1. VectorStackHashTable

    • Vector矢量队列实现了List接口,实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。
    • Stack栈也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。
    • HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。
  2. Collections类中提供的静态工厂方法创建的类

    Collections类是一个工具提供类,注意,它和Collection不同,Collection是一个顶层的接口。在Collections类中提供了大量的方法,比如对集合或者容器进行排序、查找等操作。最重要的是,在它里面提供了几个静态工厂方法来创建同步容器类, synchronizedList(), synchronizedMap(), synchronizedSet()等。

同步类容器的缺陷:

  • 同步容器中的方法采用了synchronized进行了同步,显然这必然会影响到执行性能
  • 同步类容器并不是所有操作都是线程安全的,他只能保证单个方法的操作是同步的,但是对整个操作来说并不保证原子性,一边读一边删容易抛数组下标越界异常

3.2 并发类容器

由于同步类容器的状态都是串行化的,虽然实现了线程安全,但是严重降低了并发性。在JDK1.5以后,

3.2 ConcurrentHashMap

面试准备(三) : 设计模式与Java语言特性 - 图8
在ConcurrentHashMap中,定义了多个Segment[]数组来将Hash表实现分段存储,从而实现分段加锁;而么一个Segment元素则与HashMap结构类似,其包含了一个HashEntry数组,用来存储Key/Value对。Segment继承了ReetrantLock,表示Segment是一个可重入锁,因此ConcurrentHashMap通过可重入锁对每个分段进行加锁。

默认拥有16个segment元素,每个元素可以存放16条数据,加载因子0.75

  • 分段锁思想
    1.7中采用segment分段加锁,Segment继承了ReentrantLock重入锁
  • CAS自旋锁
    1.8中取消了分段,采用类似1.8HashMap的设计,同时使用了自旋锁(加强性能)
  • 本地缓存
    可以实现简单的本地缓存,结合LinkedhashMap可以实现LRU缓存,或者直接用LRUMap实现

4. Java的流

文件操作类

  • File
  • File(path)
  • File(path,codeType)
  • File(fatherPath,son)

文件字节流

  • FileInputStream
  • FileOutputStream

字节缓冲流

  • BufferdInputStream( )
  • BufferdOutputStream(FileOutputStream)

字符流
OutStreamWriter(File)
InputStreamReader(File)

字符缓冲流
BufferedReader(InputStreamReader(File))
PrintWriter
*BufferedWriter(OutputStreamReader(File))


对象流
ObjectInputStream(File)
ObjectOutPutStream(File)

被序列化对象必须应用Serealizeable接口 序列号常量serialVersionUID Transient短暂修饰符,忽略序列化校验


5 反射

Java种的反射非常强大,编译器在运行期间打开和检查.class文件,
在程序中通过获取类对象的信息,可以绕过java的访问控制创建对象,可以获取类的属性和方法,甚至获取父类的属性和方法及其接口里的内容。

获取类对象有三种方法:

  • 对象.class
  • 对象.getClass()
  • Class.forName(‘java.util.concurrent’)

IOC控制反转,AOP,

三. 真题汇总

  1. 问:进程和线程的区别和联系
    答:从资源占用,切换效率,通信方式等方面解答
  2. 问:简单介绍一下进程的切换过程
    答: 线程上下文的切换代价,要回答,切换会保存寄存器,栈等线程相关的现场,需要由用户态切换到内核态,可以用vmstat命令查看线程上下文的切换状况
  3. 问:你经常使用哪些Linux命令,主要用来解决哪些问题?
    答:参考之前操作系统汇总中提到的命令
  4. 问:为什么TCP建连需要3次握手而断连需要4次?
    答: 参考之前内容
  5. 问:为什么TCP关闭链接时需要TIME_WAIT状态,为什么要等2MSL?
    答: 参考之前内容
  6. 问:一次完整的HTTP请求过程是怎样的?
    答: DNS解析,TCP建连,HTTP请求,HTTP响应等.
  7. 问:HTTP2和HTTP的区别有哪些?
  8. 问:在你的项目中你使用过那些设计模式?主要用来解决哪些问题?
  9. 问:Object中的equals和hashcode的作用分别是什么?
  10. 问: final,finally,finalize的区别与使用场景
  11. 问:简单表述一下Java的异常机制
  12. 问:先上使用的那个版本jdk,为什么使用这个版本(有什么特色)?
  13. 问:对象创建有几种方式?
    对象流反序列化,“字符串”,new ,构造方法,clone方法,反射 ,class对象可由JDK动态代理/Cglib创建
  14. 问:Object中有哪些方法?
    • 构造函数
    • hashCode和equale函数用来判断对象是否相同,
    • wait(),wait(long),wait(long,int),notify(),notifyAll()
    • toString()和getClass,
    • clone() 克隆(对于成员对象仅克隆引用地址)
    • finalize()用于在垃圾回收
  15. 问:String,StringBuffer和StringBuilder的区别?
    • String被final修饰,任何对String的操作都会创建一个新的String,优化拼接除外
    • 用“abc”直接创建的Stirng存放在字符串常量池,其存放在堆中
    • StringBuilder线程不安全,但是效率高,StringBuffer线程安全弃用
  • HashMap在JDK1.8中的实现方式
  • 单例模式有哪几种实现方式,什么场景该使用静态方法实现,什么场景该使用双检锁实现

    • ==与equals区别是什么
  • 对象强引用使用不当会导致内存泄露,考察不同引用方式和作用的理解

四. 加分项

  1. 知识点与典型的业务场景关联.
    如,谈到设计模型时,可以讲XX框架在解决XX问题时使用了那种设计模式.
  2. 以反例来描述实际场景中误用的危害.
    如,大量使用反射会影响性能.
  3. 与知识点相关的优化点.
    如,讲到tcp建连和断连时,如遇到洪水攻击或大量TIME_WAIT时,可以调整系统参数预防.
  4. 与知识点相关的最新技术趋势.
    如,讲到ConcurrentHashMap,可以介绍1.8的改进细节.
    或,讲到HTTP时,能说出HTTP2和QUIC的特点和实现.
  5. 在了解的前提下,尽量增加回答内容深度.
    如,讲到tcp的滑动窗口时,能讲到流量与拥塞控制,进一步能指出解决拥塞的不同算法.

ps.面试官可能会顺着细节追问,回答不上来会适得其反.