欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java面试通关要点汇总集和答案

程序员文章站 2022-07-11 11:40:51
...

原文地址:梁桂钊的博客

博客地址:http://blog.720ui.com

年前,我整理的 Java面试通关要点汇总集 获得了很多读者的肯定,谢谢大家支持。事实上,笔者结合自己过往的面试经验,整理了一些核心的知识清单,试图帮助读者更好地回顾与复习 Java 服务端核心技术。然而还有一部分内容,笔者没有更新上去,因为我认为和技术无关,随着「服务端思维」知识星球的探讨与解惑中,笔者觉得还是对大家有所帮助。此外,笔者还会补充一部分知识清单。因此,我重新整理一篇 Java面试通关要点汇总集【终极版】,致敬大家。

部分答案有链接,欢迎收藏与交流。

基本功

问题:

2、面向对象五大原则:
  • (1)单一职责原则(Single-Resposiblity-Principle):一个类应该仅有一个引起它变化的原因
  • (2)开放封闭原则(Open-Closed-Principle):对扩展开放,对更改时封闭的
  • (3)里氏替换原则(Liskov-Substituion Principle):子类可以替换父类,并且出现在父类能够出现的任何地方。GOF倡导面向接口编程
  • (4)接口隔离原则(Interface-Segregation Principle):使用多个接口比使用单个接口要好的多。
  • (5)依赖倒置原则(Dependecy-Invarsion Principle):让高层模块不要依赖低层模块。

3、final, finally, finalize 的区别?

1、final修饰符(关键字)

final用于控制成员、方法、或者是一个类是否可以被重写或者继承功能。

(1)、如果类被声明为final,意味着它不能被派生出新的子类,不能作为父类被继承。
(2)、将变量或方法声明为final,可以保证他们在使用中不会被改变,其初始化可以在两个地方:

  • 在final定义时直接给赋值,
  • 在构造函数中,二者只能选其一,在以后的引用中只能读取,不可修改

2、finally(用于异常处理)
一般是用于异常处理中,提供finally块来执行任何的清楚操作,try{}catch{}finally{}.finally结构使代码块总会执行,不管有无异常发生。使得finally可以维护对象的内部状态,并可以清理非内存资源。用于关闭文件的读写操作或者关闭数据库连接操作。

3、finalize(用于垃圾回收)

finalize这个是方法名。在Java中,允许使用finalize()方法在垃圾收集器将对象从内存中清理出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是Object中定义的。因此,所有类都继承了它,finalize方法是在垃圾收集器删除对象之前对这个对象调用的。

4、int 和 Integer 有什么区别?

两者最大的区别从大的方法来说就是基本类型与其包装类的区别

int是基本类型,直接存数值,而Integer是对象,用一个引用指向这个对象。

Java中的数据类型分为基本数据类型和复杂数据类型

int是前者,而Integer是后者(是一个类),在进行初始化时,int的变量被初始化为0,而Interger的变量则被初始化null。

5、重载和重写的区别?

重载(OverLoad)

(1)、方法重载是让类以统一的方式处理同步类型数据的一种手段。在多个同名函数同时存在,具有不同的参数个数、类型。重载(Overload)是一个类中多态性的一种表现。

(2)、Java方法的重载,就是在类中可以创建多个方法,他们具有相同的名字,但是有不同的参数,以及定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用那个方法,这就是多态。

(3)、重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同,也可以不相同。无法以返回值作为重载函数的区别标准

重写(Override)

(1)、父类和子类之间的多态性,对父类的函数进行重新定义。如果子类定义的方法和父类具有相同的方法名和参数一样,我们就说该方法被重写(Override).在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法,但有时子类并不想原封不动地继承父类方法,而是想做一定的修改,这就需要方法重写,方法重写又被称为方法覆盖。

(2)、若子类中方法与父类的某个方法具有相同的名称、返回值、和参数列表,则新方法 将覆盖原有的方法。如需父类中的原有方法,可以使用super关键字,该关键字引用了当前类的父类。

(3)、子类函数的访问修饰权限不能少于父类 。

两者之间的区别在于:

重写多态性起作用,对调用被重载过的方法可以大大减少代码的输入量,同一个方法名只要往里面传递不同的参数就可以拥有不同的功能或返回值

6、抽象类和接口有什么区别?

抽象类是用来捕捉子类的通用特性的。它不能被实例化,只能被用作子类的超类。抽象类是被用来创建继承层级里的子类的模板。

接口是抽象方法的集合,如果一个类实现类某个接口,那么他就继承了这个接口的抽象方法。这就像契约模式。如果实现了这个接口,那么就必须确保使用这些方法。接口只是一种形式,接口自身不能做任何事情。

参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口是完全抽象的,它根本不存在方法的实现  
实现 子类使用extends关键字来继承抽象类 子类使用implements来实现接口
构造器 抽象类可以有构造器 接口不能有构造器
修饰访问符 抽象方法可以有public,protected,和default 接口方法默认 修饰符是public
多继承 抽象方法可以继承一个类或实现多个接口 接口只可以继承一个或多个其他接口

什么时候使用抽象类和接口

  • 1、如果你拥有一些方法想让他们中的一些默认实现,那么使用抽象类。
  • 2、如果你想实现多重继承,那么你必须使用接口。由于java不支多继承,子类不能够继承多个类,但可以实现多个接口
  • 3、如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口 ,那么就需要改变所有实现了该接口的类。

JDK 8中的默认方法

向接口中引入了默认方法和静态方法,以此来减少抽象类和接口之间的差异。现在我们可以为接口提供默认实现的方法来,并且不用强制来实现它。

7、说说反射的用途及实现?
    1、什么是Java类中的反射?

       Reflection 是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类和对象的内部属性。

        通过反射,我们可以在运行时获得程序或程序集中每一个类型成员和成员变量的信息。

        程序中一般的对象类型都是在编译期就确定下来的,而Java 反射机制可以动态的创建对象并调用其属性,这样对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象即使这个对象在编译期是未知的,

反射的核心:是 JVM 在运行时 才动态加载的类或调用方法或属性,他不需要事先(写代码的时候或编译期)知道运行对象是谁。

    2、Java反射框架提供以下功能:

①、在运行时判断任意一个对象所属的类
②、在运行时构造任意一个类的对象
③、在运行时判断任意一个类所具有的成员变量和方法(通过反射设置可以调用 private)
④、在运行时调用人一个对象的方法

    3、反射的基本运用

以上我们提到了反射可以拟用于判断任意对象所属的类,获得 Class对象,构造人一个对象以及调用一个对象。这里我们介绍一下基本反射功能的事先(反射相关类一般都在 java.lang.relfect包里)。

①、获得 Class 对象

      (1)、使用 Class类的 forName() 静态方法:
                   public static Class<?> forName(String className)
                    ……
                    在JDBC开发中常用此方法加载数据库驱动:
                    ……java
                    Class.forName(driver)
        (2)、直接获取某一个对象的 class,比如:
                    Class<?> klass = int.class;
                    Class<?> classInt = Integer.TYPE;
        (3)、调用某个对象的getClass() 方法,比如:
                    StringBuilder str = new StringBuilder("123");
                    Class<?> klass = str.getClass();

②、判断是否为某个类的实例

一般地,我们用 instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的 isInstance()方法来判断是否为某个类的实例,它是一个 Native 方法:

  public native boolean isInstance(Object obj);

③、创建实例

通过反射来生成对象主要有两种方式。
(1)使用 Class 对象的 newInstance() 方法来创建对象对应类的实例。

Class<?> c  = String.calss;
Object str = c.getInstance();

(2)、先通过 Class 对象获取制定的 Constructor 对象,在调用 Constructor 对象的 newInstance() 方法来创建实例。这种方法可以用指定的构造器构造类的实例。

  //获取String所对应的Class对象
   Class<?> c = String.class;
  //获取String类带一个String参数的构造器
  Constructor constructor = c.getConstructor(String.class);
  //根据构造器创建实例
  Object obj = constructor.newInstance("23333");
  System.out.println(obj);

8、说说自定义注解的场景及实现推荐,播客也推荐

跟踪代码的依赖性,实现代替配置文件的功能。比较常见的是Spring等框架中的基于注解配置。

还可以生成文档常见的@aaa@qq.com@return等。如@override放在方法签名,如果这个方法 并不是覆盖了超类方法,则编译时就能检查出。

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节,在定义注解时,不能继承其他注解或接口。

9、HTTP 请求的 GET 与 POST 方式的区别参考
在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。

  • GET - 从指定的资源请求数据。
  • POST - 向指定的资源提交要被处理的数据

GET方法

请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:

/test/demo_form.asp?name1=value1&name2=value2
  • 请求可被缓存
  • 请求保留在浏览器历史记录中
  • 请求可被收藏为书签
  • 请求不应在处理敏感数据时使用
  • 请求有长度限制
  • 请求只应当用于取回数据

POST方法

请注意,查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的:

POST /test/demo_form.asp HTTP/1.1
Host: w3schools.com
name1=value1&name2=value2

比较 GET 与 POST

方法 GET POST
缓存 能被缓存 不能缓存  
编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符) 无限制。
对数据类型的限制 只允许 ASCII 字符 没有限制。也允许二进制数据。
安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。

其他 HTTP 请求方法

  • HEAD 与 GET 相同,但只返回 HTTP 报头,不返回文档主体。
  • PUT 上传指定的 URI 表示。
  • DELETE 删除指定资源。
  • OPTIONS 返回服务器支持的 HTTP 方法
  • CONNECT 把请求连接转换到透明的 TCP/IP 通道。

10、session 与 cookie 区别参考,不错

  1. cookie数据存放在客户的浏览器上,session数据放在服务器上。
  2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用COOKIE。
  4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
  5. 所以个人建议:
    将登陆信息等重要信息存放为SESSION
    其他信息如果需要保留,可以放在COOKIE中

11、session 分布式处理参考,不错

第一种:粘性session

粘性Session是指将用户锁定到某一个服务器上,比如上面说的例子,用户第一次请求时,负载均衡器将用户的请求转发到了A服务器上,如果负载均衡器设置了粘性Session的话,那么用户以后的每次请求都会转发到A服务器上,相当于把用户和A服务器粘到了一块,这就是粘性Session机制

第二种:服务器session复制

原理:任何一个服务器上的session发生改变(增删改),该节点会把这个 session的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要session,以此来保证Session同步。

第三种:session共享机制

使用分布式缓存方案比如memcached、Redis,但是要求Memcached或Redis必须是集群。

原理:不同的 tomcat指定访问不同的主memcached。多个Memcached之间信息是同步的,能主从备份和高可用。用户访问时首先在tomcat中创建session,然后将session复制一份放到它对应的memcahed上

第四种:session持久化到数据库

原理:就不用多说了吧,拿出一个数据库,专门用来存储session信息。保证session的持久化。 优点:服务器出现问题,session不会丢失 缺点:如果网站的访问量很大,把session存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库。

第五种terracotta实现session复制

原理:就不用多说了吧,拿出一个数据库,专门用来存储session信息。保证session的持久化。 优点:服务器出现问题,session不会丢失 缺点:如果网站的访问量很大,把session存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库

12、JDBC 流程[](http://www.cnblogs.com/lazyco...

注意:在此之前应该先把所有用到的对象设为null

(1)向DriverManager类注册驱动数据库驱动程序,

Class.forName( "com.somejdbcvendor.TheirJdbcDriver" );

(2)调用DriverManager.getConnection方法, 通过JDBC URL,用户名,密码取得数据库连接的Connection对象。

Connection conn = DriverManager.getConnection(
      "jdbc:somejdbcvendor:other data needed by some jdbc vendor", //URL
       "myLogin", // 用户名
      "myPassword" ); // 密码

(3)获取Connection后, 便可以通过createStatement创建Statement用以执行SQL语句。下面是一个插入(INSERT)的例子:

Statement stmt = conn.createStatement();
 stmt.executeUpdate( "INSERT INTO MyTable( name ) VALUES ( 'my name' ) " );

(4)有时候会得到查询结果,比如select,得到查询结果,查询(SELECT)的结果存放于结果集(ResultSet)中。

ResultSet rs = stmt.executeQuery( "SELECT * FROM MyTable" );

(5)关闭数据库语句,关闭数据库连接。

rs.close();
stmt.close();

13、MVC 设计思想

每当用户在Web浏览器中点击链接或提交表单的时候,请求就开始工作了。请求是一个十分繁忙的家伙,从离开浏览器开始到获取响应返回,它会经历很多站,在每站都会留下一些信息,同时也会带上一些信息。

Java面试通关要点汇总集和答案

Spring工作流程描述原文在这里

    1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
    1. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
    1. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
    1. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    • 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
    • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
    1. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
    1. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
    1. ViewResolver 结合Model和View,来渲染视图
    1. 将渲染结果返回给客户端。

图片参考这里

Java面试通关要点汇总集和答案

Spring工作流程描述

  • 为什么Spring只使用一个Servlet(DispatcherServlet)来处理所有请求?
  • 详细见J2EE设计模式-前端控制模式
  • Spring为什么要结合使用HandlerMapping以及HandlerAdapter来处理Handler?
  • 符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如HandlerAdapter可能会被用于处理多种Handler。

1、请求旅程的第一站是Spring的DispatcherServlet。与大多数基于Java的Web框架一样,Spring MVC所有的请求都会通过一个前端控制器(front contrller)Servlet.前端控制器是常用Web应用程序模式。在这里一个单实例的Servlet将请求委托给应用的其他组件来执行实际的处理。在Spring MVC中,DisPatcherServlet就是前端控制器。

2、DisPactcher的任务是将请求发送Spring MVC控制器(controller).控制器是一个用于处理请求的Spring组件。在典型的应用中可能会有多个控制器,DispatcherServlet需要知道应该将请求发送给那个哪个控制器。所以Dispactcher以会查询一个或 多个处理器映射(Handler mapping),来确定请求的下一站在哪里。处理映射器根据请求携带的 URL信息来进行决策。

3、一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。(实际上,设计良好的控制器 本身只是处理很少,甚至不处理工作,而是将业务逻辑委托给一个或多个服务器对象进行处理)

4、控制器在完成处理逻辑后,通常会产生一些信息。这些 信息需要返回给 用户,并在浏览器上显示。这些信息被称为模型(Model),不过仅仅给用户返回原始的信息是不够的----这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送一个视图(View),通常会是JSP。

5、 控制器做的最后一件事就是将模型打包,并且表示出用于渲染输出的视图名。它接下来会将请求连同模型和视图发送回DispatcherServlet。

6、这样,控制器就不会与特定的视图相耦合*传递给控制器的视图名并不直接表示某个特定的jsp。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑名称,这个名字将会用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器(View resolver),来将逻辑视图名称匹配为一个特定的视图实现,他可能也可能不是JSP

7、虽然DispatcherServlet已经知道了哪个驶入渲染结果、那请求的任务基本上也就完成了,它的最后一站是试图的实现。在这里它交付给模型数据。请求的任务就结束了。视图将使用模型数据渲染输出。这个输出通过响应对象传递给客户端(不会像听上去那样硬编码)

可以看到,请求要经过很多步骤,最终才能形成返回给客户端的响应,大多数的 步骤都是在Spirng框架内部完成的。

14、equals 与 == 的区别参考

  • 1、使用==比较原生类型如:boolean、int、char等等,使用equals()比较对象。
  • 2、==返回true如果两个引用指向相同的对象,equals()的返回结果依赖于具体业务实现
  • 3、字符串的对比使用equals()代替==操作符

其主要的不同是一个是操作符一个是方法,==用于对比原生类型而equals()方法比较对象的相等性。

集合

15、List 和 Set 区别

    1、List,Set都是继承自Collection接口。
           List特点:以线性方式存储,List可以有重复的对象,有序的,每个对象都带下标,list支持for循环,也就是通过下标来遍历,也可以用迭代器
         Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外,set只能用迭代,因为他无序,无法用下标来取得想要的值。
   Set和List对比: 
            Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 
            List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

        Set接口主要实现了两个类:HashSet,TreeSet。

           HashSet是按照哈希来存取元素的,因此速度较快。HashSet继承自抽象类AbstractSet,然后实现了Set、Cloneable、  Serializable接口。
         TreeSet也是继承自AbstractSet,不过不同的是其实现的是NavigableSet接口。而NavigableSet继承自SortedSet。SortedSet是一个有序的集合。其添加的元素必须实现了Comparable接口,因为其在添加一个元素的时候需要进行排序。

16、List 和 Map 区别

        List是继承自Collection接口,Map是Map接口。

        1、List是存储单列数据的集合,map是存储键和值这样的双列数据的集合,List中存储的数据是有顺序,并且允许重复;

        2、Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。

17、Arraylist 与 LinkedList 区别

     ArrayList与LinkedList都是List接口的实现类,因此都实现了List的所有未实现的方法,只是实现的方式有所不同.
    1.ArrayList实现了List接口,它是以可增长数组的方式来实现的,数组的特性是可以使用索引的方式来快速定位对象的位置,因此对于快速的随机取得对象的需求,使用ArrayList实现执行效率上会比较好. 其缺点是新项的插入和现有项的删除代价昂贵,所以插入和删除操作效率比较低。

    2.LinkedList是采用链表的方式来实现List接口的,它本身有自己特定的方法,如: addFirst(),addLast(),getFirst(),removeFirst()等. 由于是采用链表实现的,因此在进行insert和remove动作时在效率上要比ArrayList要好得多!适合用来实现Stack(堆栈)与Queue(队列),前者先进后出,后者是先进先出。

18、ArrayList 与 Vector 区别

(1):Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比;
(2):ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍;
(3):Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。

19、HashMap 和 Hashtable 的区别

  • HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
  • HashMap允许键和值是null,而Hashtable不允许键或者值是null。
  • Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
  • HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
  • 一般认为Hashtable是一个遗留的类。

20、HashSet 和 HashMap 区别

HashSet:
       HashSet是对HashMap的简单包装,对HashSet的函数调用都会转换成合适的HashMap方法。

  HashSet实现了Set接口,它不允许集合中出现重复元素。当我们提到HashSet时,第一件事就是在将对象存储在HashSet之前,要确保重写hashCode()方法和equals()方法,这样才能比较对象的值是否相等,确保集合中没有储存相同的对象。如果不重写上述两个方法,那么将使用下面方法默认实现:public boolean add(Object obj)方法用在Set添加元素时,如果元素值重复时返回 "false",如果添加成功则返回"true"

HashMap:

  HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许出现重复的键(Key)。Map接口有两个基本的实现TreeMap和HashMap。TreeMap保存了对象的排列次序,而HashMap不能。HashMap可以有空的键值对(Key(null)-Value(null))

HashMap是非线程安全的(非Synchronize),要想实现线程安全,那么需要调用collections类的静态方法synchronizeMap()实现。public Object put(Object Key,Object value)方法用来将元素添加到map中。

21、HashMap 和 ConcurrentHashMap 的区别

22、HashMap 的工作原理及代码实现 参考

        Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。

23、ConcurrentHashMap 的工作原理及代码实现

参考:http://www.importnew.com/28263.html
          http://www.importnew.com/23610.html
jdk1.7中采用Segment + HashEntry的方式进行实现。

    ConcurrentHashMap初始化时,计算出Segment数组的大小ssize和每个SegmentHashEntry数组的大小cap,并初始化Segment数组的第一个元素;其中ssize大小为2的幂次方,默认为16,cap大小也是2的幂次方,最小值为2,最终结果根据根据初始化容量initialCapacity进行计算,计算过程如下:

1
2
3
4
5
if (c * ssize < initialCapacity)
    ++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
    cap <<= 1;

其中Segment在实现上继承了ReentrantLock,这样就自带了锁的功能。

1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现。

初始化

1
2
3
4
5
6
7
8
9
10
11
// 这构造函数里,什么都不干
public ConcurrentHashMap() {
}
public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException();
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

这个初始化方法有点意思,通过提供初始容量,计算了 sizeCtl,sizeCtl = 【 (1.5 * initialCapacity + 1),然后向上取最近的 2 的 n 次方】。如 initialCapacity 为 10,那么得到 sizeCtl 为 16,如果 initialCapacity 为 11,得到 sizeCtl 为 32。

24、红黑树的实现原理 参考