面试题总结-Java部分

1 集合

1.1 hashmap原理

HashMap是基于哈希表实现的,每个元素是一个key-value对,实现了Serializable、Cloneable接口,容许使用null值和null键。不保证映射的顺序,内部经过单链表解决冲突问题,容量超过(容量*加载因子)时,会自动增加。(除了不一样步和容许使用null以外,HashMap类与Hashtable大体相同)。HashMap不是线程安全的。前端

1.2 ConcurrentHashMap实现原理

首先将数据分红一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段(Segment)数据的时候,其余段的数据也能被其余线程访问。 有些方法须要跨段,好比size()和containsValue(),它们可能须要锁定整个表而而不只仅是某个段,这须要按顺序锁定全部段,操做完毕后,又按顺序释放全部段的锁。 Segment继承了ReetrantLock,表示Segment是一个可重入锁,所以ConcurrentHashMap经过可重入锁对每一个分段进行加锁。java

1.3 Hashmap hashtable区别

  • 继承的父类不一样
    Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但两者都实现了Map接口。
  • 线程安全性不一样
    Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省状况下是非Synchronize的。在多线程并发的环境下,能够直接使用Hashtable,不须要本身为它的方法实现同步,但使用HashMap时就必需要本身增长同步处理。
  • 是否提供contains方法
    HashMap把Hashtable的contains方法去掉了,改为containsValue和containsKey,由于contains方法容易让人引发误解。Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
  • key和value是否容许null值
    Hashtable中,key和value都不容许出现null值。HashMap中,null能够做为键,这样的键只有一个;能够有一个或多个键所对应的值为null。
  • 两个遍历方式的内部实现上不一样
    Hashtable、HashMap都使用了 Iterator。而因为历史缘由,Hashtable还使用了Enumeration的方式 。
  • hash值不一样
    哈希值的使用不一样,HashTable直接使用对象的hashCode。而HashMap从新计算hash值。
  • 内部实现使用的数组初始化和扩容方式不一样
    HashTable在不指定容量的状况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量必定要为2的整数次幂,而HashMap则要求必定为2的整数次幂。Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

1.4 hashmap的resize

当hashmap中的元素愈来愈多的时候,碰撞的概率也就愈来愈高(由于数组的长度是固定的),因此为了提升查询的效率,就要对hashmap的数组进行扩容,数组扩容以后,最消耗性能的点就出现了:原数组中的数据必须从新计算其在新数组中的位置,并放进去,这就是resize。
那么hashmap何时进行扩容呢?当hashmap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认状况下,数组大小为16,那么当hashmap中元素个数超过160.75=12的时候,就把数组的大小扩展为216=32,即扩大一倍,而后从新计算每一个元素在数组中的位置,而这是一个很是消耗性能的操做,因此若是咱们已经预知hashmap中元素的个数,那么预设元素的个数可以有效的提升hashmap的性能。好比说,咱们有1000个元素new HashMap(1000), 可是理论上来说new HashMap(1024)更合适,不过上面annegu已经说过,即便是1000,hashmap也自动会将其设置为1024。 可是new HashMap(1024)还不是更合适的,由于0.751000 < 1000, 也就是说为了让0.75 * size > 1000, 咱们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。git

2 数据结构

2.1 链表和数组内存中区别

  • 1.占用的内存空间
    链表存放的内存空间能够是连续的,也能够是不连续的,数组则是连续的一段内存空间。通常状况下存放相同多的数据数组占用较小的内存,而链表还须要存放其前驱和后继的空间。
  • 2.长度的可变性
    链表的长度是按实际须要能够伸缩的,而数组的长度是在定义时要给定的,若是存放的数据个数超过了数组的初始大小,则会出现溢出现象。
  • 3.对数据的访问
    链表方便数据的移动而访问数据比较麻烦;数组访问数据很快捷而移动数据比较麻烦。 链表和数组的差别决定了它们的不一样使用场景,若是须要不少对数据的访问,则适合使用数组;若是须要对数据进行不少移位操做,则设和使用链表。

3 线程

3.1 synchronized使用

  • 修饰一个方法
  • 修饰一个代码块
  • 修改一个静态的方法
  • 修饰一个类

3.2 synchronized与Lock的区别

  • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  • synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;
  • Lock可让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不可以响应中断;
  • 经过Lock能够知道有没有成功获取锁,而synchronized却没法办到。
  • Lock能够提升多个线程进行读操做的效率。
  • 在性能上来讲,若是竞争资源不激烈,二者的性能是差很少的,而当竞争资源很是激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。因此说,在具体使用时要根据适当状况选择

3.3 volatile

volatile做为java中的关键词之一,用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值当即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具有这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并非是线程A更新后的值)。volatile会禁止指令重排。volatile具备可见性、有序性,不具有原子性。算法

3.4 线程等待

在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的做用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的做用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒全部的线程。小程序

3.5 ThreadPoolExecutor执行顺序

  • 当线程数小于核心线程数时,建立线程。
  • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  • 当线程数大于等于核心线程数,且任务队列已满
    • 若线程数小于最大线程数,建立线程
    • 若线程数等于最大线程数,抛出异常,拒绝任务

3.6 ThreadPoolExecutor参数默认值

  • corePoolSize=1
  • queueCapacity=Integer.MAX_VALUE
  • maxPoolSize=Integer.MAX_VALUE
  • keepAliveTime=60s
  • allowCoreThreadTimeout=false
  • rejectedExecutionHandler=AbortPolicy()

3.7 线程池类型:

  • newCachedThreadPool:数组

    • 底层:
      返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
    • 通俗:
      当有新任务到来,则插入到SynchronousQueue中,因为SynchronousQueue是同步队列,所以会在池中寻找可用线程来执行,如有能够线程则执行,若没有可用线程则建立一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
    • 适用:
      执行不少短时间异步的小程序或者负载较轻的服务器
  • newFixedThreadPool:缓存

    • 底层:
      返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无解阻塞队列
    • 通俗:
      建立可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;若是池中的全部线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
    • 适用:
      执行长期的任务,性能好不少
  • newSingleThreadExecutor:安全

    • 底层:
      FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
    • 通俗:
      建立只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
    • 适用:
      一个任务一个任务执行的场景
  • newScheduledThreadPool:服务器

    • 底层:
      建立ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
    • 通俗:
      建立一个固定大小的线程池,线程池内线程存活时间无限制,线程池能够支持定时及周期性任务执行,若是全部线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
    • 适用:
      周期性执行任务的场景

4 Java虚拟机

4.1 工做原理

从宏观上介绍一下Java虚拟机的工做原理。首先Java源文件通过前端编译器(javac或ECJ)将.java文件编译为Java字节码文件,而后JRE加载Java字节码文件,载入系统分配给JVM的内存区,而后执行引擎解释或编译类文件,再由即时编译器将字节码转化为机器码。网络

4.2 运行时数据区

程序计数器:      
线程私有,用来指示当前线程所执行的字节码的行号,就是用来标记线程如今执行的代码的位置;
对Java方法,它存储的是字节码指令的地址;对于Native方法,该计数器的值为空。
栈:      
线程私有,一个方法的执行和退出就是用一个栈帧的入栈和出栈表示的,一般咱们不容许你使用递归就是由于,方法就是一个栈,太多的方法只执行而没有退出就会致使栈溢出,不过能够经过尾递归优化。栈又分为虚拟机栈和本地方法栈,一个对应Java方法,一个对应Native方法。
堆:      
用来给对象分配内存的,几乎全部的对象实例(包括数组)都在上面分配。它是垃圾收集器的主要管理区域,所以也叫GC堆。它其实是一块内存区域,因为一些收集算法的缘由,又将其细化分为新生代和老年代等。
方法区:      
方法区由多线程共享,用来存储类信息、常量、静态变量、即便编译后的代码等数据。运行时常量池是方法区的一部分,它用于存放编译器生成的各类字面量和符号引用,好比字符串常量等。
复制代码

4.3 是否回收判断

  • 引用记数法
    给对象添加一个引用计数器,被引用时计数器加1,引用失效时减1。 这种方法不经常使用,由于它难以解决两个变量相互引用的问题。
  • 可达性分析
    经过一系列GC Roots的对象做为起始点,从节点向下搜索, 当一个对象没有任何一条可到GC Roots的引用链,则该对象可回收。

4.4 垃圾回收算法

  • 标记-清除算法,这种算法直接在内存中把须要回收的对象“抠”出来。 好好的内存被它搞成了马蜂窝,因此效率不高,清除以后会产生内容碎片,形成内存不连续,当分配较大内存对象时可能会因内存不足而触发垃圾收集动做。

  • 复制算法:将内存分红两块,一次只在一块内存中进行分配,垃圾回收一次以后, 就将该内存中的未被回收的对象移动到另外一块内存中,而后将该内存一次清理掉。 好比将内存分红A和B,先在A中分配,当垃圾回收的时候把A中须要回收的内存清理掉,而后把不须要清理的全部对象复制到B里面。 复制算法常被用来回收新生代,并且分配空间也不是1:1,而是较大的Eden空间和较小的Survivor空间。在HotSpot中,其比例是8:1。

    • 标记整理算法:相似于标记-清除算法,只是回收了以后,它要对内存空间进行整理,以使得剩余的对象占用连续的存储空间。

    • 分代收集算法:上面是三种基本的垃圾回收算法,但实际上,咱们一般根据对象存活周期的不一样将内存划分红几块,而后根据其特色采用不一样的回收算法。

4.5 内存分配与回收策略

对象内存分配,往大方向上讲,就是在堆上分配,对象主要分配在新生代的Eden区上,若是启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少数状况下可能直接分配在老年代。

  • 对象优先在Eden分配:
    大多数状况下,对象在新生代Eden区非中分配,当Eden区没有足够空间时,虚拟机发起一次Minor GC。

  • 大对象直接进入老年代:
    大对象指须要大量连续内存空间的Java对象,好比很大的数组或者字符串。常常出现大对象会致使内存还有很多空间时就提早触发垃圾收集来获取足够的连续空间来安置它们。 虚拟机提供了-XX:PretenureSizeThreshold参数,当对象的大小大于它的值的时候将直接分配在老年代。这样作是为了不在Eden区和Survivor区之间发生大量的内存复制。

  • 长期存活对象将进入老年代:
    若对象出生在Eden区并通过一次Minor GC后仍然存活,而且能被Survivor容纳,将被移动到Survivor空间中,而且对象年龄将加1。 在Survivor中,每熬过一次Minor GC,则年龄加1,当年龄达到必定程度时(默认15岁),就会被晋升到老年代。该年龄的阈值经过参数-XX:MaxTenuringThreshold设置。

5 HTTP

5.1 Http链接复用原理

当一个http请求完成后,tcp链接不会当即释放,若是有新的http请求,而且host和上次同样,那么能够复用tcp链接,省去从新链接的过程。

5.2 Http中keep alive与TCP区别

  • HTTP Keep-Alive
    在HTTP 1.0之前,每一个http请求都要求打开一个TCP socket链接,而且使用一次以后就断开这个TCP链接,这会致使频繁地建立和销毁TCP。HTTP 1.1经过使用keep-alive能够改善这种状态,即在一次TCP链接中能够持续发送多份数据而不会断开链接。
  • TCP KEEPALIVE
    这是TCP协议栈为了检测链接情况的保活机制,当TCP空闲必定时间后会发送心跳包给对方,若是对端回复ACK后,就认为对端是存活的,重置定时器;若是对端回复RST应答(对端崩溃或者其余缘由,致使的复位),那就关闭该链接;若是对端无任何回应,那就会出发超时重传,直到达到重传的次数,若是对端依然没有回复,那么就关闭该链接。

HTTP位于网络协议栈的应用层,而TCP位于网络协议栈的传输层,二者的KEEP-ALIVE虽然名称相同,可是做用不一样。HTTP是为了重用TCP,避免每次请求,都重复建立TCP;而TCP的KEEP-ALIVE是一种保活机制,检测对端是否依然存活。

6 git

  • git cherry-pick
    将某一段commit粘贴到另外一个分支上
  • git revert
    经过反作建立一个新的版本,这个版本的内容与咱们要回退到的目标版本同样,可是HEAD指针是指向这个新生成的版本,而不是目标版本。 若是咱们想恢复以前的某一版本(该版本不是merge类型),可是又想保留该目标版本后面的版本,记录下这整个版本变更流程,就能够用这种方法。
  • git rebase
    合并多个commit为一个完整commit
  • git reset
    修改HEAD的位置,即将HEAD指向的位置改变为以前存在的某个版本。若是想恢复到以前某个提交的版本,且那个版本以后提交的版本咱们都不要了,就能够用这种方法。

7 基本数据类型相关

7.1 replace() replaceAll() replaceFirst()

  • replace(CharSequence target, CharSequence replacement) 用replacement替换掉target。这两个参数都是字符串
  • replaceAll(String regex, String replacement) 用replacement全部regex匹配的字符串。很明显regex参数是个正则匹配式,replacement是个字符串。
  • replaceFirst(String regex, String replacement),基本和replaceAll相同,区别是只替换第一个匹配项

7.2 java中int与Integer用==比较详解

①、不管如何,Integer与new Integer不会相等。不会经历拆箱过程,由于它们存放内存的位置不同。(要看具体位置,能够看看这篇文章:点击打开连接)

②、两个都是非new出来的Integer,若是数在-128到127之间,则是true,不然为false。

③、两个都是new出来的,则为false。

④、int和integer(new或非new)比较,都为true,由于会把Integer自动拆箱为int,其实就是至关于两个int类型比较。

8 其余

8.1 xml解析都有哪些 区别

  • SAX
    sax是一个用于处理xml事件驱动的“推”模型;
    优势:解析速度快,占用内存少,它须要哪些数据再加载和解析哪些内容。
    缺点:它不会记录标签的关系,而是须要应用程序本身处理,这样就会增长程序的负担。
  • DOM
    dom是一种文档对象模型;
    优势:dom能够以一种独立于平台和语言的方式访问和修改一个文档的内容和结构,dom技术使得用户页面能够动态的变化,如动态显示隐藏一个元素,改变它的属性,增长一个元素等,dom可使页面的交互性大大加强。
    缺点:dom解析xml文件时会将xml文件的全部内容以文档树方式存放在内存中。
  • PULL pull和sax很类似,区别在于:pull读取xml文件后触发相应的事件调用方法返回的是数字,且pull能够在程序中控制,想解析到哪里就能够中止解析。 (SAX解析器的工做方式是自动将事件推入事件处理器进行处理,所以你不能控制事件的处理主动结束;而Pull解析器的工做方式为容许你的应用程序代码主动从解析器中获取事件,正由于是主动获取事件,所以能够在知足了须要的条件后再也不获取事件,结束解析。pull是一个while循环,随时能够跳出,而sax不是,sax是只要解析了,就必须解析完成。)
相关文章
相关标签/搜索