精华内容
下载资源
问答
  • java集合框架总结 Collection体系结构 ArrayList源码解读 HashMap HashSet 深入讲解java集合框架
  • oct 是用于开放容器的测试用例和框架,用 Go 语言开发。ocp-testing 项目的目标是,通过提供全局测试框架给所有的容器项目,来提高开放容器项目。测试包括,ocp 规范,容器功能和容器性能。框架架构:Open Container...
  • Java框架总结

    万次阅读 多人点赞 2020-01-17 14:14:13
    本系列用来记录常用java框架的基本概念、区别及联系,也记录了在使用过程中,遇到的一些问题的解决方法,方便自己查看,也方便大家查阅。 欲速则不达,欲达则欲速! 一、SSH 1、基本概念 SSH框架是JAVA EE中三种...

    🍅 Java基础教程系列: Java基础教程系列

    🍅 Java学习路线总结:搬砖工逆袭Java架构师

    🍅 Java经典面试题大全:10万字208道Java经典面试题总结(附答案)

    🍅 简介:Java领域优质创作者🏆、CSDN哪吒公众号作者✌ 、Java架构师奋斗者💪

    🍅 扫描主页左侧二维码,加入群聊,一起学习、一起进步 

    🍅 欢迎点赞 👍 收藏 ⭐留言 📝   

    本系列用来记录常用java框架的基本概念、区别及联系,也记录了在使用过程中,遇到的一些问题的解决方法,方便自己查看,也方便大家查阅。

    一、SSH

    1、基本概念

    SSH框架是JAVA EE中三种框架所集成,分别是Struts,Spring,Hibernate框架所组成,是当前比较流行的java web开源框架。

    集成SSH框架的系统从职责上分为(Struts2--控制;spring--解耦;hibernate--操作数据库),以帮助开发人员在短期内搭建结构清晰、可服用好、维护方便的web应用程序。使用Struts作为系统的整体基础框架,负责MVC的分离,在Struts框架的模型部分,控制业务跳转,利用hibernate框架对持久层提供支持,spring做管理,管理Struts和hibernate。

    2、Struts2

    (1)基本概念

    Struts2是一个基于MVC设计模式的web应用框架,相当于一个servlet,在MVC设计模式中,Struts2作为控制器(controller)来建立模型与视图的数据交互。Struts2在Struts1融合webwork。struts2以webwork为核心,采用拦截器的机制来处理用户的请求,这样的设计使得业务逻辑控制器能够与servletAPI完全脱离。

    (2)Struts2框架的运行结构

    解析:客户端发送请求(HttpServletRequest)到服务器,服务器接收到请求就先进入web.xml配置文件看看有没有配置过滤器,发现有有Struts2的过滤器,然后找到struts.xml配置文件,struts.xml配置文件里定义一个action,然后就去找到action类,此类继承ActionSupport接口,并且实现了execute()方法,返回一个字符串“success”给struts.xml配置文件,struts.xml配置文件的action会默认调用action类的execute()方法,result接收到返回的字符串,result就会调用你指定的jsp页面将结果呈现,最后响应给客户端。

    (3)Struts2的优势

    • 实现了MVC模式,层次结构清晰,使程序员只需要关注业务逻辑的实现。
    • 丰富的标签库,大大提高了开发的效率。
    • Struts2提供丰富的拦截器实现。
    • 通过配置文件,就可以掌握整个系统各个部分之间的关系。
    • 异常处理机制,只需在配置文件中配置异常的映射,即可对异常做响应的处理。
    • Struts2的可扩展性高。
    • 面向切面编程的思想在Struts2中也有了很好的体现。
    • 体现了拦截器的使用,拦截器是一个一个的小功能模块,用户可以将这些拦截器合并成一个大的拦截器,这个合成的拦截器就像单独的拦截器一样,只要将它配置到一个Action中就可以。

    (4)Struts2的缺点:

    • 校验较繁琐,多字段出错返回不同。
    • 安全性太低
    • 获取传参时较麻烦

    2、Spring

    (1)基本概念

    spring是一个开源开发框架,是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。

    spring主要用来开发java应用,构建J2EE平台的web应用。其核心就是提供一种新的机制管理业务对象及其依赖关系。

    (2)spring的流程图

    解析:上面是在Struts结构图的基础上加入了spring流程图,在web.xml配置文件中加入了spring的监听器,在struts.xml配置文件中添加

    “<constant name="struts.objectFactory" value="spring" />”

    是告知Struts2运行时使用spring来管理对象,spring在其中主要做的就是注入实例,所有需要类的实例都由spring管理。

    (3)spring的优点

    • 容器:spring是一个容器,包含并管理对象的生命周期和配置。可以配置每个bean如何被创建,基于一个可配置原型prototype,你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例。
    • 支持AOP:spring提供对AOP的支持,它允许将一些通用任务,如安全、事物、日志等进行集中式处理,从而提高了程序的复用性。
    • 轻量级框架:spring是轻量级框架,其基本的版本大约2M。
    • 控制反转:spring通过控制反转实现松耦合。对象们给他们依赖,而不是对象本身,方便解耦,简化开发。
    • 方便程序测试:spring提供了Junit4的支持,可以通过注解方便的测试spring程序。
    • 降低java EE API的使用难度:spring对java EE开发中非常难用的一些API(比如JDBC),都提供了封装,使这些API应用难度大大降低。
    • 方便集成各种优秀框架:spring内部提供了对各种优秀框架(如Struts、mybatis)的直接支持。
    • 支持声明式事务处理:只需要通过配置就可以完成对事务的管理,而无须手动编程。

    (4)spring的缺点

    • 依赖反射,反射影响进程。
    • 太过于依赖设计模式。
    • 控制器过于灵活。
    • 不支持分布式应用。

    Spring常用注解(绝对经典)

    Spring视频教程--颜群

    3、hibernate

    (1)基本概念

    Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久化的重任。

    (2)hibernate的核心构成和执行流程图

    (3)hibernate的优点

    • 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
    • Hibernate是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作,将软件开发人员从大量相同的数据持久层相关编程工作中解放出来,使开发更对象化了。
    • 透明持久化(persistent)带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。这些对象可能是普通的javabeans/POJO,(POJO概念,plain ordinary java object,简单的java对象,可以简单理解为简单的实体类entity。)这个对象没有实现第三方框架或接口,唯一特殊的是他们正与session关联。一旦这个session被关闭,这些对象就会脱离持久化状态,这样就可被应用程序的任何层自由使用。
    • 事务transaction应用程序用来指定原子操作单元范围的对象,它是单线程的,生命周期很短。它通过抽象将应用从底层具体的JDBC、JTA(java transaction API,JTA允许应用程序执行分布式事务处理,在两个或多个网络计算机资源访问并且更新数据,JDBC驱动程序的JTA支持极大地增强了数据访问能力)以及CORBA(公用对象请求代理程序体系结构,common object request broker architecture,简而言之,CORB允许应用程序和其它的应用程序通讯)事务隔离开。某些情况下,一个session之内可能包含多个transaction对象,事务边界的开启与关闭时必不可少的。
    • 它没有侵入性,是轻量级框架。
    • 移植性好,支持各种数据库,如果换个数据库只要在配置文件中变换配置就可以了,不用改变hibernate代码。
    • 缓存机制,提供一级缓存和二级缓存。

    一级缓存:是session级别的缓存,一个session做了一个查询操作,它会把这个操作的结果放到一级缓存中,如果短时间内这个session又做了同一个操作,那么hibernate直接从一级缓存中拿出,而不会去连数据库取数据。

    二级缓存:是sessionFactory级别的缓存,就是查询的时候会把结果缓存到二级缓存中,如果同一个sessionFactory创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿出结果,而不会再去连接数据库。

    (4)hibernate的缺点

    • 持久层封装过于完整,导致开发人员无法对SQL进行优化,无法灵活应用原生SQL。
    • 批量数据处理的时候较为弱势。
    • 框架中使用ORM原则,导致配置过于复杂,遇到大项目,维护问题不断。

    Hibernate实现CRUD(附项目源码)

    为什么很多人不愿意用hibernate了?

    尚硅谷Java视频_SSH整合&综合案例 视频教程

    手动实现教程源码:

    链接: https://pan.baidu.com/s/1BK0V1wxA-GQrWco10WEzeg 提取码: 2e3e 

    二、SSM

    SSM架构,是三层结合所成的框架,分别是Spring、SpringMVC、MyBatis所组成。Spring依赖注入来管理各层,面向切面编程管理事务,日志和权限。SpringMVC代表了model、view、controller接收外部请求,进行开发和处理。mybatis是基于jdbc的框架,主要用来操作数据库,并且将业务实体和数据表联系起来。

    1、spring

    详细介绍见SSH中spring。

    2、SpringMVC

    (1)基本概念

    属于spring框架的一部分,用来简化MVC架构的web应用程序开发。

    (2)SpringMVC的优点

    • 拥有强大的灵活性,非侵入性和可配置性
    • 提供了一个前端控制器dispatcherServlet,开发者无需额外开发控制器对象
    • 分工明确,包括控制器、验证器、命令对象、模型对象、处理程序映射视图解析器,每一个功能实现由一个专门的对象负责完成
    • 可以自动绑定用户输入,并正确的转换数据类型
    • 可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。

    (3)SpringMVC的缺点

    • servlet API耦合难以脱离容器独立运行
    • 太过于细分,开发效率低

    SpringMVC中put和post如何选择

    GET和POST的区别

    @RequestParam、@ModelAttribute、@RequestBody的区别

    HttpServletResponse response实现文件上传、下载

    3、mybatis

    (1)基本概念

    mybatis是一个简化和实现了java数据持久层的开源框架,它抽象了大量的JDBC冗余代码,并提供了一个简单易用的API和数据库交互。

    (2)mybatis的优点

    • 与JDBC相比,减少了50%以上的代码量。
    • mybatis是最简单的持久化框架,小巧并且简单易学。
    • mybatis灵活,不会对应用程序或者数据库的限售设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,降低耦合度,便于统一管理和优化,可重用。
    • 提供XML标签,支持编写动态SQL语句(XML中使用if,else)。
    • 提供映射标签,支持对象与数据库的ORM字段关系映射(在XML中配置映射关系,也可以使用注解)

    (3)mybatis的缺点

    • SQL语句的编写工作量较大,对开发人员的SQL语句编写有一定的水平要求。
    • SQL语句过于依赖数据库,不能随意更换数据库。
    • 拼接复杂SQL语句时不灵活。

    【MyBatis 基础知识总结 1】SQL注入

    【MyBatis 基础知识总结 2】MyBatis-Plus

    MyBatis常用标签和注解(绝对经典)

    MyBatis事务管理

    MyBatis逆向工程(Example + Criteria简介)

    MyBatis xml配置文件详解

    Spring JdbcTemplate简介

    纯干货,Spring-data-jpa详解,全方位介绍。

    尚硅谷SSM整合视频教程雷丰阳雷大神讲解

    SpringMVC视频教程--颜群

    颜群版SSM整合示例

    示例源码:

    链接:https://pan.baidu.com/s/1NIDjQ5wRBN9hNc_4G1Nhng 
    提取码:18vi

    三、Springboot

    1、springboot基本概念

    springboot是一个全新的框架,简化Spring的初始搭建和开发过程,使用了特定的方式来进行配置,让开发人员不再需要定义样板化的配置。此框架不需要配置xml,依赖于maven这样的构建系统。

    2、Springboot的优点

    (1)减少了大量的开发时间并提高了生产力

    (2)避免了编写大量的样板代码,注释和XML配置

    (3)解决了spring的弊端

    (4)代码少了、配置文件少了、不需要对第三方框架烦恼了、项目精简了,对整个团队的开发和维护来说,更大的节约了成本。

    3、springboot的缺点

    (1)修复bug较慢,报错时难以定位。

    (2)集成度较高,不易于了解底层。

    4、springboot总结

    简单、快速、方便的搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效率。

    5、springboot和spring的区别

    (1)springboot可以建立独立的spring应用程序。

    (2)内嵌了如tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作。

    (3)无需再像spring那样写一堆繁琐的XML配置文件

    (4)可以自动配置spring

    (5)提供的POM可以简化maven的配置

    6、springboot和springMVC的区别

    (1)SpringMVC是基于spring的一个MVC框架。

    (2)springboot的基于spring的条件注册的一套快速开发整合包。

    🍅 Java基础教程系列: Java基础教程系列

    🍅 Java学习路线总结:搬砖工逆袭Java架构师

    🍅 Java经典面试题大全:10万字208道Java经典面试题总结(附答案)

    🍅 简介:Java领域优质创作者🏆、CSDN哪吒公众号作者✌ 、Java架构师奋斗者💪

    🍅 扫描主页左侧二维码,加入群聊,一起学习、一起进步 

    🍅 欢迎点赞 👍 收藏 ⭐留言 📝   

    添加微信,备注1024,赠送Java学习路线思维导图 

    展开全文
  • SSM框架概念(什么是SSM框架

    万次阅读 多人点赞 2019-08-21 13:36:53
    SSM框架 全称为Spring+SpringMVC+MyBatis ,这个是继SSH之后,目前比较主流的Java EE企业级框架,适用于搭建各种大型的企业级应用系统。 由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容...

            SSM框架

            全称为Spring+SpringMVC+MyBatis ,这个是继SSH之后,目前比较主流的Java EE企业级框架,适用于搭建各种大型的企业级应用系统。

            由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。常作为数据源较简单的web项目的框架。

            Spring

            Spring就像是整个项目中装配bean的大工厂,在配置文件中可以指定使用特定的参数去调用实体类的构造方法来实例化对象。也可以称之为项目中的粘合剂。
      Spring的核心思想是IoC(控制反转),即不再需要程序员去显式地'new'一个对象,而是让Spring框架帮你来完成这一切。

           简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
      SpringMVC
      SpringMVC在项目中拦截用户请求,它的核心Servlet即DispatcherServlet承担中介或是前台这样的职责,将用户请求通过HandlerMapping去匹配Controller,Controller就是具体对应请求所执行的操作。SpringMVC相当于SSH框架中struts。

            Mybatis
      Mybatis是对jdbc的封装,它让数据库底层操作变的透明。mybatis的操作都是围绕一个sqlSessionFactory实例展开的。mybatis通过配置文件关联到各实体类的Mapper文件,Mapper文件中配置了每个类对数据库所需进行的sql语句映射。在每次与数据库交互时,通过sqlSessionFactory拿到一个sqlSession,再执行sql命令。

           页面发送请求给控制器,控制器调用业务层处理逻辑,逻辑层向持久层发送请求,持久层与数据库交互,后将结果返回给业务层,业务层将处理逻辑发送给控制器,控制器再调用视图展现数据。

    展开全文
  • 前面的文章中详细介绍了Java的容器框架,在此基础上,本文对Java中的同步容器与并发容器做一些介绍。fail-fast机制快速报错机制(fail-fast)能够防止多个进程同时修改同一个容器的内容。如果在你迭代遍历某个容器的...

    前面的文章中详细介绍了Java的容器框架,在此基础上,本文对Java中的同步容器与并发容器做一些介绍。

    fail-fast机制

    快速报错机制(fail-fast)能够防止多个进程同时修改同一个容器的内容。如果在你迭代遍历某个容器的过程中,另一个进程接入其中,并且插入、删除或者修改此容器内的某个对象,就会出现问题:也许迭代过程已经处理过容器中的该元素了,也许还没处理,也许在调用size()之后尺寸缩小了等等。fail-fast机制会探查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其他进程修改了容器,立刻抛出ConcurrentModificationException异常,即快速报错——不适用复杂的算法在时候进行检查。

    同步容器

    同步容器可以简单地理解为通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。

    同步容器将它们的状态封装起来,并对每一个公有方法进行同步。主要包括:

    • Vector
    • Stack
    • HashTable
    • Collections.synchronized方法生成,例如:
      • Collectinons.synchronizedList()
      • Collections.synchronizedSet()
      • Collections.synchronizedMap()
      • Collections.synchronizedSortedSet()
      • Collections.synchronizedSortedMap()

    其中Vector(同步的ArrayList)和Stack(继承自Vector,先进后出)、HashTable(继承自Dictionary,实现了Map接口)是比较老的容器,Thinking in Java中明确指出,这些容器现在仍然存在于JDK中是为了向以前老版本的程序兼容,在新的程序中不应该在使用。Collections的方法时将非同步的容器包裹生成对应的同步容器。

    同步容器在单线程的环境下能够保证线程安全,但是通过synchronized同步方法将访问操作串行化,导致并发环境下效率低下。而且同步容器在多线程环境下的复合操作(迭代、条件运算如没有则添加等)是非线程安全,需要客户端代码来实现加锁。

    代码示例:

    public static Object getLast(Vector list) {
        int lastIndex = list.size() - 1;
        return list.get(lastIndex);
    }
    
    public static void deleteLast(Vector list) {
        int lastIndex = list.size() - 1;
        list.remove(lastIndex);
    }

    上面的代码取最后一个元素或者删除最后一个元素,使用了同步容器Vector。如果有两个线程A,B同时调用上面的两个方法,假设list的大小为10,这里计算得到的lastIndex为9,线程B首先执行了删除操作(多线程之间操作执行的不确定性导致),而后线程A调用了list.get方法,这时就会发生数组越界异常,导致问题的原因就是上面的复合操作不是原子操作,这里可以通过在方法内部额外的使用list对象锁来实现原子操作。

    在多线程中使用同步容器,如果使用Iterator迭代容器或使用使用for-each遍历容器,在迭代过程中修改容器会抛出ConcurrentModificationException异常。想要避免出现ConcurrentModificationException,就必须在迭代过程持有容器的锁。但是若容器较大,则迭代的时间也会较长。那么需要访问该容器的其他线程将会长时间等待。从而会极大降低性能。

    此外,隐式迭代的情况,如toString,hashCode,equalse,containsAll,removeAll,retainAll等方法都会隐式的Iterate,也可能抛出ConcurrentModificationException。

    并发容器

    由上面的分析我们知道,同步容器并不能保证多线程安全,而并发容器是针对多个线程并发访问而设计的,在jdk5.0引入了concurrent包,其中提供了很多并发容器,极大的提升同步容器类的性能。

    ConcurrentHashMap

    CopyOnWriteArrayList

    • 对应的非并发容器:ArrayList
    • 目标:代替Vector、synchronizedList
    • 原理:利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。

    关于这一部分可参考【JDK】:CopyOnWriteArrayList、CopyOnWriteArraySet 源码解析

    CopyOnWriteArraySet

    • 对应的费并发容器:HashSet
    • 目标:代替synchronizedSet
    • 原理:基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法,其遍历当前Object数组,如Object数组中已有了当前元素,则直接返回,如果没有则放入Object数组的尾部,并返回。

    关于这一部分可参考【JDK】:CopyOnWriteArrayList、CopyOnWriteArraySet 源码解析

    ConcurrentSkipListMap

    • 对应的非并发容器:TreeMap
    • 目标:代替synchronizedSortedMap(TreeMap)
    • 原理:Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过”空间来换取时间”的一个算法。ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上能够在O(log(n))时间内完成查找、插入、删除操作。

    ConcurrentSkipListSet

    • 对应的非并发容器:TreeSet
    • 目标:代替synchronizedSortedSet
    • 原理:内部基于ConcurrentSkipListMap实现

    ConcurrentLinkedQueue

    不会阻塞的队列

    • 对应的非并发容器:Queue
    • 原理:基于链表实现的FIFO队列(LinkedList的并发版本)

    LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue

    • 对应的非并发容器:BlockingQueue
    • 特点:拓展了Queue,增加了可阻塞的插入和获取等操作
    • 原理:通过ReentrantLock实现线程安全,通过Condition实现阻塞和唤醒
    • 实现类:
      • LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列
      • ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列
      • PriorityBlockingQueue:按优先级排序的队列

    参考:
    1、Java并发:同步容器&并发容器
    2、Java并发:线程安全的容器-同步和并发

    展开全文
  • Java集合容器面试题(2020最新版)

    万次阅读 多人点赞 2020-03-01 11:08:34
    文章目录集合容器概述什么是集合集合的特点集合和数组的区别使用集合框架的好处常用的集合类有哪些?List,Set,Map三者的区别?List、Set、Map 是否继承自 Collection 接口?List、Map、Set 三个接口存取元素时,各...

    Java面试总结(2021优化版)已发布在个人微信公众号【技术人成长之路】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试题,对文章进行了精简优化,欢迎大家关注!😊😊

    【技术人成长之路】,助力技术人成长!更多精彩文章第一时间在公众号发布哦!

    文章目录

    Java面试总结汇总,整理了包括Java基础知识,集合容器,并发编程,JVM,常用开源框架Spring,MyBatis,数据库,中间件等,包含了作为一个Java工程师在面试中需要用到或者可能用到的绝大部分知识。欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感激不尽。文章持续更新中…

    序号内容链接地址
    1Java基础知识面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390612
    2Java集合容器面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588551
    3Java异常面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390689
    4并发编程面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104863992
    5JVM面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390752
    6Spring面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397516
    7Spring MVC面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397427
    8Spring Boot面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397299
    9Spring Cloud面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397367
    10MyBatis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/101292950
    11Redis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/103522351
    12MySQL数据库面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104778621
    13消息中间件MQ与RabbitMQ面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588612
    14Dubbo面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390006
    15Linux面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588679
    16Tomcat面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397665
    17ZooKeeper面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397719
    18Netty面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104391081
    19架构设计&分布式&数据结构与算法面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/105870730

    集合容器概述

    什么是集合

    集合框架:用于存储数据的容器。

    集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。
    任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。

    接口:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现,从而达到“多态”。在面向对象编程语言中,接口通常用来形成规范。

    实现:集合接口的具体实现,是重用性很高的数据结构。

    算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。事实上,算法是可复用的函数。
    它减少了程序设计的辛劳。

    集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而不是为了让程序能正常运转而将注意力于低层设计上。
    通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以便联合这些API而去写大量的代码。 它提高了程序速度和质量。

    集合的特点

    集合的特点主要有如下两点:

    • 对象封装数据,对象多了也需要存储。集合用于存储对象。

    • 对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的。

    集合和数组的区别

    • 数组是固定长度的;集合可变长度的。

    • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

    • 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

    数据结构:就是容器中存储数据的方式。

    对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。

    集合容器在不断向上抽取过程中,出现了集合体系。在使用一个体系的原则:参阅顶层内容。建立底层对象。

    使用集合框架的好处

    1. 容量自增长;
    2. 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
    3. 允许不同 API 之间的互操作,API之间可以来回传递集合;
    4. 可以方便地扩展或改写集合,提高代码复用性和可操作性。
    5. 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。

    常用的集合类有哪些?

    Map接口和Collection接口是所有集合框架的父接口:

    1. Collection接口的子接口包括:Set接口和List接口
    2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
    3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
    4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

    List,Set,Map三者的区别?List、Set、Map 是否继承自 Collection 接口?List、Map、Set 三个接口存取元素时,各有什么特点?

    img

    Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是collection的子接口。

    Collection集合主要有List和Set两大接口

    • List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
    • Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

    Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

    Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap

    集合框架底层数据结构

    Collection

    1. List
    • Arraylist: Object数组
    • Vector: Object数组
    • LinkedList: 双向循环链表
    1. Set
    • HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
    • LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
    • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

    Map

    • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
    • LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
    • HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
    • TreeMap: 红黑树(自平衡的排序二叉树)

    哪些集合类是线程安全的?

    • vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
    • statck:堆栈类,先进后出。
    • hashtable:就比hashmap多了个线程安全。
    • enumeration:枚举,相当于迭代器。

    Java集合的快速失败机制 “fail-fast”?

    是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

    例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

    原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

    解决办法:

    1. 在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。

    2. 使用CopyOnWriteArrayList来替换ArrayList

    怎么确保一个集合不能被修改?

    可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

    示例代码如下:

    List<String> list = new ArrayList<>();
    list. add("x");
    Collection<String> clist = Collections. unmodifiableCollection(list);
    clist. add("y"); // 运行时此行报错
    System. out. println(list. size());
    

    Collection接口

    List接口

    迭代器 Iterator 是什么?

    Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。

    Iterator 怎么使用?有什么特点?

    Iterator 使用代码如下:

    List<String> list = new ArrayList<>();
    Iterator<String> it = list. iterator();
    while(it. hasNext()){
      String obj = it. next();
      System. out. println(obj);
    }
    

    Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。

    如何边遍历边移除 Collection 中的元素?

    边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:

    Iterator<Integer> it = list.iterator();
    while(it.hasNext()){
       *// do something*
       it.remove();
    }
    

    一种最常见的错误代码如下:

    for(Integer i : list){
       list.remove(i)
    }
    

    运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用 foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。

    Iterator 和 ListIterator 有什么区别?

    • Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
    • Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
    • ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

    遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?

    遍历方式有以下几种:

    1. for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。

    2. 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。

    3. foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。

    最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。

    • 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。
    • 如果没有实现该接口,表示不支持 Random Access,如LinkedList。

    推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。

    说一下 ArrayList 的优缺点

    ArrayList的优点如下:

    • ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。
    • ArrayList 在顺序添加一个元素的时候非常方便。

    ArrayList 的缺点如下:

    • 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
    • 插入元素的时候,也需要做一次元素复制操作,缺点同上。

    ArrayList 比较适合顺序添加、随机访问的场景。

    如何实现数组和 List 之间的转换?

    • 数组转 List:使用 Arrays. asList(array) 进行转换。
    • List 转数组:使用 List 自带的 toArray() 方法。

    代码示例:

    // list to array
    List<String> list = new ArrayList<String>();
    list.add("123");
    list.add("456");
    list.toArray();
    
    // array to list
    String[] array = new String[]{"123","456"};
    Arrays.asList(array);
    

    ArrayList 和 LinkedList 的区别是什么?

    • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
    • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
    • 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
    • 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
    • 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

    综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

    补充:数据结构基础之双向链表

    双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

    ArrayList 和 Vector 的区别是什么?

    这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合

    • 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
    • 性能:ArrayList 在性能方面要优于 Vector。
    • 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

    Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

    Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。

    插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?

    ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。

    Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差

    LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快

    多线程场景下如何使用 ArrayList?

    ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:

    List<String> synchronizedList = Collections.synchronizedList(list);
    synchronizedList.add("aaa");
    synchronizedList.add("bbb");
    
    for (int i = 0; i < synchronizedList.size(); i++) {
        System.out.println(synchronizedList.get(i));
    }
    

    为什么 ArrayList 的 elementData 加上 transient 修饰?

    ArrayList 中的数组定义如下:

    private transient Object[] elementData;
    

    再看一下 ArrayList 的定义:

    public class ArrayList<E> extends AbstractList<E>
         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    

    可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现:

    private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
        *// Write out element count, and any hidden stuff*
            int expectedModCount = modCount;
        s.defaultWriteObject();
        *// Write out array length*
            s.writeInt(elementData.length);
        *// Write out all elements in the proper order.*
            for (int i=0; i<size; i++)
                s.writeObject(elementData[i]);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
    }
    

    每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。

    List 和 Set 的区别

    List , Set 都是继承自Collection 接口

    List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。

    Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

    另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。

    Set和List对比

    Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
    List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变

    Set接口

    说一下 HashSet 的实现原理?

    HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

    HashSet如何检查重复?HashSet是如何保证数据不可重复的?

    向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。
    HashSet 中的add ()方法会使用HashMap 的put()方法。

    HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。

    以下是HashSet 部分源码:

    private static final Object PRESENT = new Object();
    private transient HashMap<E,Object> map;
    
    public HashSet() {
        map = new HashMap<>();
    }
    
    public boolean add(E e) {
        // 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
    	return map.put(e, PRESENT)==null;
    }
    

    hashCode()与equals()的相关规定

    1. 如果两个对象相等,则hashcode一定也是相同的
    2. 两个对象相等,对两个equals方法返回true
    3. 两个对象有相同的hashcode值,它们也不一定是相等的
    4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
    5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

    ==与equals的区别

    1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
    2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.==指引用是否相同 equals()指的是值是否相同

    HashSet与HashMap的区别

    HashMapHashSet
    实现了Map接口实现Set接口
    存储键值对仅存储对象
    调用put()向map中添加元素调用add()方法向Set中添加元素
    HashMap使用键(Key)计算HashcodeHashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
    HashMap相对于HashSet较快,因为它是使用唯一的键获取对象HashSet较HashMap来说比较慢

    Queue

    BlockingQueue是什么?

    Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。

    在 Queue 中 poll()和 remove()有什么区别?

    • 相同点:都是返回第一个元素,并在队列中删除返回的对象。
    • 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。

    代码示例:

    Queue<String> queue = new LinkedList<String>();
    queue. offer("string"); // add
    System. out. println(queue. poll());
    System. out. println(queue. remove());
    System. out. println(queue. size());
    

    Map接口

    说一下 HashMap 的实现原理?

    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

    HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

    HashMap 基于 Hash 算法实现的

    1. 当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
    2. 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
    3. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
    4. 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

    需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

    HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现

    在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。

    JDK1.8之前

    JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

    jdk1.7中HashMap数据结构

    JDK1.8之后

    相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。

    jdk1.8中HashMap数据结构

    JDK1.7 VS JDK1.8 比较

    JDK1.8主要解决或优化了一下问题:

    1. resize 扩容优化
    2. 引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考
    3. 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。
    不同JDK 1.7JDK 1.8
    存储结构数组 + 链表数组 + 链表 + 红黑树
    初始化方式单独函数:inflateTable()直接集成到了扩容函数resize()
    hash值计算方式扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
    存放数据的规则无冲突时,存放数组;冲突时,存放链表无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
    插入数据方式头插法(先讲原位置的数据移到后1位,再插入数据到该位置)尾插法(直接插入到链表尾部/红黑树)
    扩容后存储位置的计算方式全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1))按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)

    HashMap的put方法的具体流程?

    当我们put的时候,首先计算 keyhash值,这里调用了 hash方法,hash方法实际是让key.hashCode()key.hashCode()>>>16进行异或操作,高16bit补0,一个数和0异或不变,所以 hash 函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞。按照函数注释,因为bucket数组大小是2的幂,计算下标index = (table.length - 1) & hash,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高16bit和低16bit异或来简单处理减少碰撞,而且JDK8中用了复杂度 O(logn)的树结构来提升碰撞下的性能。

    putVal方法执行流程图

    putVal方法执行流程图

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    //实现Map.put和相关方法
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 步骤①:tab为空则创建 
        // table未初始化或者长度为0,进行扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 步骤②:计算index,并对null做处理  
        // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        // 桶中已经存在元素
        else {
            Node<K,V> e; K k;
            // 步骤③:节点key存在,直接覆盖value 
            // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                    // 将第一个元素赋值给e,用e来记录
                    e = p;
            // 步骤④:判断该链为红黑树 
            // hash值不相等,即key不相等;为红黑树结点
            // 如果当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node, e可能为null
            else if (p instanceof TreeNode)
                // 放入树中
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 步骤⑤:该链为链表 
            // 为链表结点
            else {
                // 在链表最末插入结点
                for (int binCount = 0; ; ++binCount) {
                    // 到达链表的尾部
                    
                    //判断该链表尾部指针是不是空的
                    if ((e = p.next) == null) {
                        // 在尾部插入新结点
                        p.next = newNode(hash, key, value, null);
                        //判断链表的长度是否达到转化红黑树的临界值,临界值为8
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //链表结构转树形结构
                            treeifyBin(tab, hash);
                        // 跳出循环
                        break;
                    }
                    // 判断链表中结点的key值与插入的元素的key值是否相等
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        // 相等,跳出循环
                        break;
                    // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                    p = e;
                }
            }
            //判断当前的key已经存在的情况下,再来一个相同的hash值、key值时,返回新来的value这个值
            if (e != null) { 
                // 记录e的value
                V oldValue = e.value;
                // onlyIfAbsent为false或者旧值为null
                if (!onlyIfAbsent || oldValue == null)
                    //用新值替换旧值
                    e.value = value;
                // 访问后回调
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }
        // 结构性修改
        ++modCount;
        // 步骤⑥:超过最大容量就扩容 
        // 实际大小大于阈值则扩容
        if (++size > threshold)
            resize();
        // 插入后回调
        afterNodeInsertion(evict);
        return null;
    }
    

    ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

    ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

    ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

    ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

    ⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

    ⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

    HashMap的扩容操作是怎么实现的?

    ①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;

    ②.每次扩展的时候,都是扩展2倍;

    ③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。

    在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7中,扩容之后需要重新去计算其Hash值,根据Hash值对其进行分发,但在1.8版本中,则是根据在同一个桶的位置中进行判断(e.hash & oldCap)是否为0,重新进行hash分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//oldTab指向hash桶数组
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {//如果oldCap不为空的话,就是hash桶数组不为空
            if (oldCap >= MAXIMUM_CAPACITY) {//如果大于最大容量了,就赋值为整数最大的阀值
                threshold = Integer.MAX_VALUE;
                return oldTab;//返回
            }//如果当前hash桶数组的长度在扩容后仍然小于最大容量 并且oldCap大于默认值16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold 双倍扩容阀值threshold
        }
        // 旧的容量为0,但threshold大于零,代表有参构造有cap传入,threshold已经被初始化成最小2的n次幂
        // 直接将该值赋给新的容量
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        // 无参构造创建的map,给出默认容量和threshold 16, 16*0.75
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 新的threshold = 新的cap * 0.75
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        // 计算出新的数组长度后赋给当前成员变量table
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//新建hash桶数组
        table = newTab;//将新数组的值复制给旧的hash桶数组
        // 如果原先的数组没有初始化,那么resize的初始化工作到此结束,否则进入扩容元素重排逻辑,使其均匀的分散
        if (oldTab != null) {
            // 遍历新数组的所有桶下标
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    // 旧数组的桶下标赋给临时变量e,并且解除旧数组中的引用,否则就数组无法被GC回收
                    oldTab[j] = null;
                    // 如果e.next==null,代表桶中就一个元素,不存在链表或者红黑树
                    if (e.next == null)
                        // 用同样的hash映射算法把该元素加入新的数组
                        newTab[e.hash & (newCap - 1)] = e;
                    // 如果e是TreeNode并且e.next!=null,那么处理树中元素的重排
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    // e是链表的头并且e.next!=null,那么处理链表中元素重排
                    else { // preserve order
                        // loHead,loTail 代表扩容后不用变换下标,见注1
                        Node<K,V> loHead = null, loTail = null;
                        // hiHead,hiTail 代表扩容后变换下标,见注1
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        // 遍历链表
                        do {             
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    // 初始化head指向链表当前元素e,e不一定是链表的第一个元素,初始化后loHead
                                    // 代表下标保持不变的链表的头元素
                                    loHead = e;
                                else                                
                                    // loTail.next指向当前e
                                    loTail.next = e;
                                // loTail指向当前的元素e
                                // 初始化后,loTail和loHead指向相同的内存,所以当loTail.next指向下一个元素时,
                                // 底层数组中的元素的next引用也相应发生变化,造成lowHead.next.next.....
                                // 跟随loTail同步,使得lowHead可以链接到所有属于该链表的元素。
                                loTail = e;                           
                            }
                            else {
                                if (hiTail == null)
                                    // 初始化head指向链表当前元素e, 初始化后hiHead代表下标更改的链表头元素
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 遍历结束, 将tail指向null,并把链表头放入新数组的相应下标,形成新的映射。
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    

    HashMap是怎么解决哈希冲突的?

    答:在解决这个问题之前,我们首先需要知道什么是哈希冲突,而在了解哈希冲突之前我们还要知道什么是哈希才行;

    什么是哈希?

    Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数

    所有散列函数都有如下一个基本特性**:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同**。

    什么是哈希冲突?

    当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)

    HashMap的数据结构

    在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做链地址法的方式可以解决哈希冲突:

    img

    这样我们就可以将拥有相同哈希值的对象组织成一个链表放在hash值所对应的bucket下,但相比于hashCode返回的int类型,我们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要远小于int类型的范围,所以我们如果只是单纯的用hashCode取余来获取对应的bucket这将会大大增加哈希碰撞的概率,并且最坏情况下还会将HashMap变成一个单链表,所以我们还需要对hashCode作一定的优化

    hash()函数

    上面提到的问题,主要是因为如果使用hashCode取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动,在JDK 1.8中的hash()函数如下:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己右移16位进行异或运算(高低位异或)
    }
    

    这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动)

    JDK1.8新增红黑树

    img

    通过上面的链地址法(使用散列表)扰动函数我们成功让我们的数据分布更平均,哈希碰撞减少,但是当我们的HashMap中存在大量数据时,加入我们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度降低至O(logn);

    总结

    简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:

    1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
    2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
    3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

    能否使用任何类作为 Map 的 key?

    可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点:

    • 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。

    • 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。

    • 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。

    • 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。

    为什么HashMap中String、Integer这样的包装类适合作为K?

    答:String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率

    1. 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
    2. 内部已重写了equals()hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;

    如果使用Object作为HashMap的Key,应该怎么办呢?

    答:重写hashCode()equals()方法

    1. 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
    2. 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性

    HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?

    答:hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;

    那怎么解决呢?

    1. HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
    2. 在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;

    HashMap 的长度为什么是2的幂次方

    为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。

    这个算法应该如何设计呢?

    我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

    那为什么是两次扰动呢?

    答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;

    HashMap 与 HashTable 有什么区别?

    1. 线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
    2. 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
    3. 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。
    4. **初始容量大小和每次扩充容量大小的不同 **: ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
    5. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
    6. 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

    如何决定使用 HashMap 还是 TreeMap?

    对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

    HashMap 和 ConcurrentHashMap 的区别

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

    ConcurrentHashMap 和 Hashtable 的区别?

    ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。

    • 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
    • 实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

    两者的对比图

    HashTable:

    img

    JDK1.7的ConcurrentHashMap:

    img

    JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):

    img

    答:ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。

    ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

    JDK1.7

    首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

    在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:

    一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

    img

    1. 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
    2. Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

    JDK1.8

    JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

    结构如下:

    img

    附加源码,有需要的可以看看

    插入元素过程(建议去看看源码):

    如果相应位置的Node还没有初始化,则调用CAS插入相应的数据;

    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
        if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
            break;                   // no lock when adding to empty bin
    }
    

    如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,如果该节点的hash不小于0,则遍历链表更新节点或插入新节点;

    if (fh >= 0) {
        binCount = 1;
        for (Node<K,V> e = f;; ++binCount) {
            K ek;
            if (e.hash == hash &&
                ((ek = e.key) == key ||
                 (ek != null && key.equals(ek)))) {
                oldVal = e.val;
                if (!onlyIfAbsent)
                    e.val = value;
                break;
            }
            Node<K,V> pred = e;
            if ((e = e.next) == null) {
                pred.next = new Node<K,V>(hash, key, value, null);
                break;
            }
        }
    }
    
    1. 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值;
    2. 如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount;

    辅助工具类

    Array 和 ArrayList 有何区别?

    • Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
    • Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
    • Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

    对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

    如何实现 Array 和 List 之间的转换?

    • Array 转 List: Arrays. asList(array) ;
    • List 转 Array:List 的 toArray() 方法。

    comparable 和 comparator的区别?

    • comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
    • comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序

    一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().

    Collection 和 Collections 有什么区别?

    • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
    • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

    TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?

    TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。

    Collections 工具类的 sort 方法有两种重载的形式,

    第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;

    第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。

    展开全文
  • Spring入门第一讲——Spring框架的快速入门

    万次阅读 多人点赞 2017-04-08 00:27:34
    Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。 Spring是一个开源框架,Spring是于2003年...
  • Android容器框架

    千次阅读 2017-06-18 11:34:29
    标题标题Alibaba Atlas Atlas是阿里移动基础团队的核心中间件之一,Atlas特别适用于大规模团队的协同开发。通过提供组件化、动态性、解耦化的支持,Atlas能够实现每个业务在...开源Android容器框架Atlas开发者指南
  • Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。 Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。 然而,Spring的用途不仅限于服务器端的开发。从简单性、可...
  • 【JDK】:Java容器框架

    千次阅读 2016-05-12 09:16:43
    容器框架概述在JDK8中rt.jar文件中,java.util.*;包中的容器主要包括List、Set、Queue和Map四个大类,其中List、Set、Queue是和Collection接口相关的容器,而Map是单独列出来的容器。使用astah community版绘制了...
  • 深入理解Spring框架IOC容器和AOP

    千次阅读 2019-05-13 16:44:03
    深入理解Spring框架IOC容器和AOP...Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 Spring的基本框架主要模块:DAO、ORM、AOP、Context、WEB、CORE。 Spring DAO:Spring提供了对JDBC的...
  • 容器,组件与框架是什么?

    千次阅读 2018-05-30 16:59:40
    框架是构成一类特定软件可复用设计的一组相互协作的类,框架规定了你的应用程序的体系结构。它定义了整体结构,类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程,实现者无须...
  • 简述SSM框架与SSH框架

    千次阅读 2019-06-27 17:21:43
    简述SSM框架与SSH框架 一. 简介SSM框架 SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。常作为数据源较简单的web项目的框架。 Spring  Spring就...
  • Spring框架:核心容器总结

    万次阅读 多人点赞 2017-10-22 19:14:19
    首先我们看到Spring框架被分成7部分:Core Container(核心容器),Data Access/Integration、WEB、AOP、Instrumentation、Messaging(消息)和Test(测试),这篇博客我们把注意点放到最核心的核心容器上来进行总结。...
  • 顾名思义,生活中我们有锅碗瓢盆等容器来容纳物体,而程序中的“容器”也有类似的功能,我们可以使用它来容纳和处理数据。 在之前第八篇中讲了数组,但是数组并不能满足人们对于“管理和组织数据的需求”。所以引入...
  • Spring框架:启动IOC容器的三种方式

    千次阅读 2017-11-15 20:06:31
    创建项目时需要用到的框架有Java和Web 创建项目完成之后的目录 通过BeanFactory启动IOC容器 bean文件入在类路径classpath下 bean文件放在文件系统FileSystem目录下 通过ApplicationContext类启动IOC容器 通过...
  • 初识Spring Boot框架

    万次阅读 多人点赞 2016-12-19 20:42:04
    前面的铺垫文章已经连着写了六篇了,主要是介绍了Spring和SpringMVC框架,小伙伴们在学习的过程中大概也发现了这两个框架需要我们手动配置的地方非常多,不过做JavaEE开发的小伙伴们肯定也听说过“约定大于配置”...
  • 学习过Spring的同学都知道,Spring框架的核心就是IoC和AOP。Spring可以理解为一个工厂,负责对象的创建和对象间关系的维护。IoC即控制反转,简单点说就是原来的对象是在要使用之前通过在代码里通过new 的方式创建...
  • Spring IoC容器

    万次阅读 多人点赞 2019-05-31 11:40:01
    核心技术 发行版5.0.8 这一部分的指导文档覆盖了所有的完全集成到Spring框架中的...Spring框架拥有他自己的AOP框架,该技术在概念上是很容器理解的,并且成功的解决了在Java企业编程中的AOP需求的80%的点。 该框...
  • Spring框架:Spring容器详解

    千次阅读 2014-09-18 21:40:16
    而在控制反转中,对象的生成全部都交给框架完成。这样的好处就是减少了程序的依赖性。 Bean在Spring中的生命周期如下: 实例化。Spring通过new关键字将一个Bean进行实例化,JavaBean都有默认的构造函数,...
  • 容器编排解决方案

    千次阅读 2018-06-20 11:25:26
    目前市场上有很多容器编排工具可用,它们之间的能力有许多重叠的地方。通常的情况下,需要使用这些工具中的一个或多个的组合来满足业务的需求。容器管理或编排工具采用容器(包含要执行的服务)和一组约束或规则作为...
  • Android 常用六大框架

    热门讨论 2015-06-01 10:50:56
    (9) 下载器模块:可以简单的实现多线程下载、后台下载、断点续传、对下载进行控制、如开始、暂停、删除等等。 (10) 网络状态检测模块:当网络状态改变时,对其进行检 6、LoonAndroid 项目地址:...
  • Spring框架中的IOC容器及bean管理

    千次阅读 2018-07-08 17:45:17
    这篇文章讲述的是Spring框架中的IOC容器及bean管理,如有错误或者不当之处,还望各位大神批评指正。 什么是IOC容器? IOC即反转控制,创建对象的权利交给容器来完成,而程序要做的仅仅是使用对象。 Spring 容器是...
  • spring面试题和答案

    千次阅读 多人点赞 2018-09-18 16:21:35
    69道Spring面试题和答案 原文地址 译者:深海... 目录 Spring 概述 依赖注入 Spring beans Spring注解 Spring数据访问 Spring面向切面编程(AOP) ...Spring 是个java企业级应用的开源开发框架。Sp...
  • 3月13日,手机淘宝安卓客户端容器框架 Atlas 正式宣布开源。Atlas 由阿里巴巴移动团队自研,以容器化思路解决大规模团队协作问题,实现并行开发、快速迭代和动态部署,适用于 Android 4.x 以上系统版本的大小型 ...
  • C#/.NET IOC框架——Unity容器基础入门

    千次阅读 2018-12-16 23:20:05
    C# IOC容器Unity 控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency ...
  • 在以前复杂的socket连接和http细节以及线程池和IO处理,都需要人为手动完成,而tomcat容器出现了,解决了这个问题,所以tomcat被称之为通讯容器,一种相对来讲比较底层的容器;数据处理好被封装成request和response...
  • 由本章节开始,我们将从支付宝客户端的架构设计方案入手,细分拆解客户端在“容器框架设计”、“网络优化”、“性能启动优化”、“自动化日志收集”、“RPC 组件设计”、“移动应用监控、诊断、定位”等具体实现,...
  • Spring框架主要可以分为3个核心内容: 1)容器 2)控制反转(IoC ,Inversion ofControl) 3)面向切面编程(AOP ,Aspect-OrientedProgramming) 下面使用一个银行实例简单讲解他们的实现原理
  • 超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享,代码下载地址:http://www.zuidaima.com/share/1759689106541568.htm

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 463,406
精华内容 185,362
关键字:

容器框架下载