01 接口和抽象类的区别

  • ⼀个类声明可否既是abstract的,⼜是final的? 不能,这两个修式符⽭盾(abstract就是要被继承)
  • 抽象类不⼀定包含抽象⽅法
  • 有抽象⽅法,则⼀定是抽象类
  • 抽象类不能被实例化,⼀般⽤作基类使⽤;
  • 类可以实现多个接⼝但只能继承⼀个抽象类
  • 接⼝⾥⾯所有的⽅法都是Public的,抽象类允许private、protected⽅法
  • JDK8前接⼝⾥⾯所有的⽅法都是抽象的且不允许有静态⽅法,抽象类可以有普通、静态⽅法,JDK8 接⼝可以实现默认⽅法和静态⽅法,前⾯加default、static关键字。

02 java中的异常有哪⼏类,分别怎么使⽤?

Java面试题 - 基础题_数组
分为错误和异常,异常⼜包括运⾏时异常、⾮运⾏时异常
错误:如StackOverflowError、OutOfMemoryError
异常

  • 运⾏时异常,如NullPointerException、IndexOutOfBoundsException,都是RuntimeException及其⼦类。
  • ⾮运⾏时异常,如IOException、SQLException,都是Exception及其⼦类,这些异常是⼀定需要try catch捕获的。

03 常⽤的集合类有哪些?⽐如list如何排序?

主要分为三类,Map、Set、List:

  • Map: HashMap、LinkedHashMap、TreeMap
  • Set:HashSet、LinkedHashSet、TreeSet
  • List: ArrayList、LinkedList
Collections.sort(list);

04 ArrayList和LinkedList内部实现⼤致是怎样的?他们之间的区别和优缺点?

  • ArrayList:内部使⽤数组的形式实现了存储,利⽤数组的下标进⾏元素的访问,因此对元素的随机访问速度⾮常快。因为是数组,所以ArrayList在初始化的时候,有初始⼤⼩10,插⼊新元素的时候,会判断是否需要扩容,扩容的步⻓是0.5倍原容量,扩容⽅式是利⽤数组的复制,因此有⼀定的开销。
  • LinkedList:内部使⽤双向链表的结构实现存储,LinkedList有⼀个内部类作为存放元素的单元,⾥⾯有三个属性,⽤来存放元素本身以及前后2个单元的引⽤,另外LinkedList内部还有⼀个header属性,⽤来标识起始位置,LinkedList的第⼀个单元和最后⼀个单元都会指向header,因此形成了⼀个双向的链表结构。
  • ArrayList查找较快,插⼊、删除较慢,LinkedList查找较慢,插⼊、删除较快。

05 内存溢出是怎么回事?举个例⼦?

内存溢出 out of memory,是指程序在申请内存时,没有⾜够的内存空间供其使⽤,出现out of memory,例如:

List<Object> list = new ArrayList<>(); 2 while (true) { 3 list.add(new Object()); 4 }

内存溢出可能的原因:

  • 程序中存在死循环
  • 静态变量和静态⽅法太多了
  • 内存泄漏:⽐如说⼀个静态的list,⼀直往⾥放值,⼜因为静态变量不会被释放,所以迟早是要内存溢出的。
  • ⼤对象过多:java中的⼤对象是直接进⼊⽼年代的,然后当多个⼤对象同时⼯作时造成程序的可⽤内存⾮常⼩,⽐如list中原本最多可以放1000个对象,因为可⽤内存太⼩,放了500个就放不下了。
  • 程序分配内存过⼩:还有⼀种很常⻅的情况,在把⼀个很⼤的程序直接导⼊,直接就内存溢出了,原因就是内存相对这个程序就是太⼩了,需要⼿动增加内存。
  • 内存泄漏 memory leak,是指程序在申请内存后,⽆法释放已申请的内存空间,⼀次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,⽆论多少内存,迟早会被占光。

06 ==和equals的区别

  • == 是运算符,⽽equals是Object的基本⽅法
  • == ⽤于基本类型的数据的⽐较,或者是⽐较两个对象的引⽤是否相同,equals⽤于⽐较两个对象的值是否相等,例如字符串的⽐较。

07 hashCode方法的作用

  • hashCode的存在主要是⽤于查找的快捷性,为了配合基于散列的集合正常运⾏,如Hashtable,HashMap等,hashCode是⽤来在散列存储结构中确定对象的存储地址的;
  • 如果两个对象相同,就是适⽤于equals(java.lang.Object) ⽅法,那么这两个对象的hashCode⼀定要相同;
  • 如果对象的equals⽅法被重写,那么对象的hashCode也尽量重写,并且产⽣hashCode使⽤的对象,⼀定要和equals⽅法中使⽤的⼀致,否则就会违反上⾯提到的第2点;
  • 两个对象的hashCode相同,并不⼀定表示两个对象就相同,也就是不⼀定适⽤于equals(java.lang.Object) ⽅法,只能够说明这两个对象在散列存储结构中,它们存放在同⼀个桶⾥⾯。

08 NIO是什么?适用于何种场景?

  1. NIO是为了弥补IO操作的不⾜⽽诞⽣的,NIO的⼀些新特性有:⾮阻塞I/O,选择器,缓冲以及管道。
  2. 如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,这时候⽤NIO处理数据可能是个很好的选择。(适⽤于⼩数据多连接)
  3. ⽽如果只有少量的连接,⽽这些连接每次要发送⼤量的数据,这时候传统的IO更合适。使⽤哪种处理数据,需要在数据的响应等待时间和检查缓冲区数据的时间上作⽐较来权衡选择。
  • NIO概念:NIO(new IO),是⼀种⾮阻塞式I/O;java NIO采⽤了双向通道进⾏数据传输,在通道上我们可以注册我们感兴趣的事件:连接事件、读写事件;NIO主要有三⼤核⼼部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统IO基于字节流和字符流进⾏操作,⽽NIO基于Channel和Buffer(缓冲区)进⾏操作,数据总是从通道读取到缓冲区中,或者从缓冲区写⼊到通道中。Selector(选择区)⽤于监听多个通道的事件(⽐如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
  • NIO原理:
  1. 由⼀个专⻔的线程来处理所有的 IO 事件,并负责分发。
  2. 事件驱动机制:事件到的时候触发,⽽不是同步的去监视事件。
  3. 线程通讯:线程之间通过 wait,notify 等⽅式通讯, 保证每次上下⽂切换都是有意义的。减少⽆谓的线程切换。
    Java面试题 - 基础题_C_02

09 HashMap实现原理?如何保证HashMap线程安全?

  1. HashMap简单说就是它根据键的hashCode值存储数据,⼤多数情况下可以直接定位到它的值,因⽽具有很快的访问速度,但遍历顺序却是不确定的。
  2. HashMap基于哈希表,底层结构由数组来实现,添加到集合中的元素以“key–value”形式保存到数组中,在数组中key–value被包装成⼀个实体来处理—也就是上⾯Map接⼝中的Entry。
  3. 在HashMap中,Entry[]保存了集合中所有的键值对,当我们需要快速存储、获取、删除集合中的元素时,HashMap会根据hash算法来获得“键值对”在数组中存在的位置,以来实现对应的操作⽅法。Java面试题 - 基础题_C_03
  4. HashMap底层是采⽤数组来维护的.Entry静态内部类的数组Java面试题 - 基础题_数组_04
  5. HashMap添加元素:将准备增加到map中的对象与该位置上的对象进⾏⽐较(equals⽅法),如果相同,那么就将该位置上的那个对象(Entry类型)的value值替换掉,否则沿着该Entry的链继续重复上述过程,如果到链的最后任然没有找到与此对象相同的对象,那么这个时候就会被增加到数组中,将数组中该位置上的那个Entry对象链到该对象的后⾯(先hashcode计算位置,如果找到相同位置便替换值,找不到则重复hashcode计算,直到最后在添加到hashmap最后⾯;)Java面试题 - 基础题_C_05
  6. HashMap是基于哈希表的Map接⼝的⾮同步实现,允许null键值,但不保证映射的顺序;底层使⽤数组实现,数组中的每项是⼀个链表;存储时根据key的hash算法来决定其存储位置;数组扩容需要重新计算扩容后每个元素在数组中的位置很耗性能;
  7. ConcurrentHashMap是HashMap的线程安全实现,允许多个修改操作同时进⾏(使⽤了锁分离技术),它使⽤了多个锁来控制对hash表的不同段进⾏的修改,每个段其实就是⼀个⼩的hashtable,它们有⾃⼰的锁。使⽤了多个⼦hash表(段Segment),允许多个读操作并发进⾏,读操作并不需要锁,因为它的HashEntry⼏乎是不可变的:

10 jvm内存结构?为什么需要GC?

  • 内存结构:Java面试题 - 基础题_java_06
  • 垃圾回收:垃圾回收可以有效的防⽌内存泄漏,有效的使⽤可以使⽤的内存。垃圾回收器通常是作为⼀个单独的低优先级的线程运⾏,不可预知的情况下对内存堆中已经死亡的或者⻓时间没有使⽤的对象进⾏清除和回收,程序员不能实时的调⽤垃圾回收器对某个对象或所有对象进⾏垃圾回收。回收机制有分代复制垃圾回收、标记垃圾回收、增量垃圾回收等⽅式。

11 NIO模型,select/epoll的区别,多路复⽤的原理?

io多路复⽤:

  • 概念:IO多路复⽤是指内核⼀旦发现进程指定的⼀个或者多个IO条件准备读取,它就通知该进程。
  • 优势:与多进程和多线程技术相⽐,I/O多路复⽤技术的最⼤优势是系统开销⼩,系统不必创建进程/线程,也不必维护这些进程/线程,从⽽⼤⼤减⼩了系统的开销。
  • 系统:⽬前⽀持I/O多路复⽤的系统调⽤有 select,pselect,poll,epoll。

select:

  • select⽬前⼏乎在所有的平台上⽀持,其良好跨平台⽀持也是它的⼀个优点。select的⼀个缺点在于单个进程能够监视的⽂件描述符的数量存在最⼤限制,在Linux上⼀般为1024,可以通过修改宏定义甚⾄重新编译内核的⽅式提升这⼀限制,但是这样也会造成效率的降低。

poll:

它没有最⼤连接数的限制,原因是它是基于链表来存储的,但是同样有⼀个缺点:

  • ⼤量的fd的数组被整体复制于⽤户态和内核地址空间之间,⽽不管这样的复制是不是有意义。
  • poll还有⼀个特点是“⽔平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

epoll跟select都能提供多路I/O复⽤的解决⽅案。在现在的Linux内核⾥有都能够⽀持,其中epoll是Linux所特有,⽽select则应该是POSIX所规定,⼀般操作系统均有实现。

12 ava中⼀个字符占多少个字节?int,long,double占多少个字节?

  • 1字节: byte , boolean
  • 2字节: short , char
  • 4字节: int , float
  • 8字节: long , double

13 创建⼀个类的实例都有哪些⽅法?

Java面试题 - 基础题_java_07

14 final/finaly/finalize区别?

  • final是定义类、⽅法、字段的修饰符,表示类不可被继承,⽅法不能被重写,字段值不能被修改
  • finally是异常处理机制的关键字,表示最后执⾏
  • finalize是Object的⼀个⽅法,在对象被虚拟机回收时会判断是否执⾏该⽅法,当对象没有覆盖finalize⽅法,或者finalize⽅法已经被虚拟机调⽤过,虚拟机将这两种情况都视为“没有必要执⾏”。

15 Session/Cookie区别?

  • Session存储在服务器端,类型可以是任意的Java对象,Cookie存储在客户端,类型只能为字符串。

16 String/StringBuffer/StringBuilder的区别以及实现?

  • String、StringBuffer是线程安全的,StringBuilder不是线程安全。
  • String不继承任何类,StringBuffer、StringBuilder继承⾃AbstractStringBuilder。
  • StringBuffer线程安全但效率低,应该使⽤在多线程情况下。
  • StringBuilder线程不安全,在单线程情况下效率⾼。

17 Servlet⽣命周期

Servlet⽣命周期分为三个阶段:

  1. 初始化阶段 调⽤init()⽅法
  2. 响应客户请求阶段  调⽤service()⽅法
  3. 终⽌阶段  调⽤destroy()⽅法

18 如何⽤java分配⼀段连续的1G的内存空间?需要注意些什么?

ByteBuffer.allocateDirect(1024*1024*1024);

要注意内存溢出问题。

19 Java有⾃⼰的内存回收机制,但为什么还存在内存泄漏的问题呢?

⾸先内存泄漏 memory leak,是指程序在申请内存后,⽆法释放已申请的内存空间,⼀次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,⽆论多少内存,迟早会被占光。

⽐如下⾯这段代码,list持有o的引⽤,o暂时是⽆法被JVM垃圾回收的,只有当list被垃圾回收或者o从对象list删除掉后,o才能被JVM垃圾回收:
Java面试题 - 基础题_C_08

20 什么是java序列化,如何实现java序列化(写⼀个例⼦)

  • 原理:将对象写⼊流中,再从流中还原.ObjectOutPutStream
  • 对象中的成员对象也是可序列化的,但可通过transient关键字标示不序列化
  • 通过序列化进⾏深拷⻉,从流中还原对象
  • 序列化作⽤:对象保存(保存为⼀组字节,只保存对象的成员变量);当使⽤RMI(远程⽅法调⽤),或者在⽹络中传递对象时,都会使⽤对象序列化;
  • serialVersionUID 主要是解决序列化后与再反序列化时有修改的不兼容问题.

序列化⼆叉树:

private int index = -1; 
String Serialize(TreeNode root) { 
	 StringBuilder sb = new StringBuilder(); 
	 if (root == null) { 
	  sb.append("#,"); 
	   return sb.toString(); 
	  } 
	  sb.append(root.val + ","); 
	  sb.append(Serialize(root.left));
      sb.append(Serialize(root.right));
      return sb.toString();
}

 TreeNode Deserialize(String str) {
    index++;
    String[] strs = str.split(",");
    if (index > strs.length || strs[index].equals("#")) {
        return null;
     }
    TreeNode root = new TreeNode(Integer.parseInt(strs[index])); 
    root.left = Deserialize(str);
    root.right = Deserialize(str);
    return root;
}

21 String s = new String(“abc”)创建了⼏个String Object?

  • 2个,会创建String对象在常量池和堆中。
  • String中的intern(),⾸先检查String pool是否有对应的字符串对象,如果有则返回,如果没有则在String pool中⽣成字符串,并返回地址;
  • String中字⾯值”ab"之间拼接是在String pool中产⽣,⽽字⾯值与变量拼接字符串或者new String(“")则是在堆中产⽣对象;

22 静态对象

  1. 在main⽅法开始运⾏时需要注意static的先后顺序:静态变量和静态代码块(按代码先后顺序)—-匿名块和成员变量(按代码先后顺序)—-构造函数—静态⽅法(调⽤时加载)
  2. 先执⾏⽗类的静态块,再执⾏⼦类的静态块,再执⾏⽗类的构造⽅法,再执⾏⼦类的构造⽅法
  • ------静态块>构造⽅法
  • ------⽗>⼦

23 final关键字

  1. ⽤在类上不能被继承,abstract(需要被继承)和final不能共存;
  2. ⽤在⽅法上不能被重写
  3. ⽤在变量上表示⽅变量不能被改变

对于final类的成员变量的初使化⽅式(基本数据类型):

  1. 申明变量时直接赋值
  2. 在构造⽅法中完成赋值,如果有多个构造⽅法,则每个都要完成final类变量的赋值
  3. 如果⼀个变量为static final则只能在申明时赋值

对于final类型的引⽤变量来说,所谓的不能改变指的是该引⽤不能改变,值是可以改变的(如StringBuffer)

为什么⼀般在public类final终态成员变量申明时要加static?

  • static对象存放在静态空间,不会在运⾏时被释放,可以节省内存,类的多个对象同时引⽤只有⼀份,没有多份拷⻉。

24 HashMap与HashTable的区别

  1. 线程安全上,hashtable是同步的线程安全;hashmap是⾮同步的线程不安全,可接受null的值和value(hashtable不允许);
  2. 对单线程来说,hashTable效率低
  3. 线程安全的类:vector(⽐arrayList多了同步机制,效率低不建议使⽤)、stack(堆栈类,先进后出)、hashtable(⽐hashmap多了同步机制)、enumeration(枚举类)。

25 多态

  • 多态条件:1.有继承 2. 有重写 3. 要有⽗类引⽤指向⼦类对象;如Animal a = new Tiger();
  • ⽗类或者接⼝引⽤指向⼦类或者实现该接⼝的类的对象;
  • 多态是运⾏时⾏为,不是编译时⾏为;
  • 多态要有动态绑定,通过⽅法重写与⽅法重载来实现多态?? 这种说法是错误,因为⽅法重载是编译期决定好的,没有后期也就是运⾏期的动态绑定;

26 集合删除

注意: List底层为数组,删除时数组元素下标会被改变

  1. 迭代器调⽤.next()⽅法时,会检测是否有被修改过
  2. 如果要删除集合中的元素⼀定要⽤跌代器的remove()⽅法.

27 参数传递与引⽤传递

  1. 基本数据类型传参,是数据值的拷⻉互不影响.
  2. 引⽤对象传参,是传地址,两个引⽤指向同⼀个对象,则对象改变两个引⽤也都改变(java编程只有值传递参数)

28 hash冲突

描述:当关键字值域远⼤于哈希表的⻓度,⽽且事先并不知道关键字的具体取值时,冲突就难免会发⽣(两个或两个以上的值hash计算的结果有相同的,造成冲突) 。

解决⽅法:

  1. 开放地址法:插⼊元素时,如果发⽣冲突,算法会简单的从该槽位置向后循环遍历hash表,直到找到表中的下⼀个空槽,并将该元素放⼊该槽中(会导致相同hash值的元素挨在⼀起和其他hash值对应的槽被占⽤)。查找元素时,⾸先散列值所指向的槽,如果没有找到匹配,则继续从该槽遍历hash表,直到:(1)找到相应的元素;(2)找到⼀个空槽,指示查找的元素不存在,(所以不能随便删除元素);(3)整个hash表遍历完毕(指示该元素不存在并且hash表是满的)
  2. 链地址法:现⾏探测法的基本思想是将所有哈希地址为i的元素构成⼀个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因⽽查找、插⼊和删除主要在同义词链中进⾏。链地址法适⽤于经常进⾏插⼊和删除的情况。
  3. 在散列(双/多重散列):当发⽣冲突时,使⽤第⼆个、第三个、哈希函数计算地址,直到⽆冲突时。缺点:计算时间增加。
  4. 建⽴⼀个公共溢出区:假设哈希函数的值域为[0,m-1],则设向量HashTable[0…m-1]为基本表,另外设⽴存储空间向量OverTable[0…v]⽤以存储发⽣冲突的记录。

29 在java中⼀个字符能否表示⼀个汉字

在java中,⼀个字符表示16位,相当于2个字节,⼀个汉字正好是2个字节。

30 ⼀致性hash

⼀致性hash算法:我们的memcached客户端(这⾥我看的spymemcache的源码),使⽤了⼀致性hash算法ketama进⾏数据存储节点的选择。与常规的hash算法思路不同,只是对我们要存储数据的key进⾏hash计算,分配到不同节点存储。⼀致性hash算法是对我们要存储数据的服务器进⾏hash计算,进⽽确认每个key的存储位置。这⾥提到的⼀致性hash算法ketama的做法是:选择具体的机器节点不在只依赖需要缓存数据的key的hash本身了,⽽是机器节点本身也进⾏了hash运算。

  1. ⼀致性hash算法是分布式系统中常⽤算法,设计⽬的是为了解决因特⽹中的热点(hot spot)问题。解决了P2P环境最为关键问题—如何在动态⽹络拓扑中分布存储和路由;
  2. ⼀致性hash算法引⼊虚拟节点机制,解决服务节点少时数据倾斜问题(即对每⼀个服务节点计算多个哈希,每个计算结果位置都放置⼀个此服务节点,称为虚拟节点。);

**具体做法:**如果有⼀个写⼊缓存的请求,其中Key值为K,计算器hash值Hash(K), Hash(K) 对应于图 – 1环中的某⼀个点,如果该点对应没有映射到具体的某⼀个机器节点,那么顺时针查找,直到第⼀次找到有映射机器的节点,该节点就是确定的⽬标节点,如果超过了2^32仍然找不到节点,则命中第⼀个机器节点。⽐如 Hash(K) 的值介于A~B之间,那么命中的机器节点应该是B节点。

数据保存流程:

  1. ⾸先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
  2. 然后采⽤同样的⽅法求出存储数据的键的哈希值,并映射到相同的圆上。
  3. 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第⼀个服务器上。如果超过232仍然找不到服务器,就会保存到第⼀台memcached服务器上。

Java面试题 - 基础题_C_09

31 Java反射机制

可以在运⾏时判断⼀个对象所属的类,构造⼀个类的对象,判断类具有的成员变量和⽅法,调⽤1个对象的⽅法。

4个关键的类:Class,Constructor,Field,Method。 getConstructor获得构造函数/getDeclardConstructor;getField/getFields/getDeclardFields获得类所⽣命的所有字段;getMethod/getMethods/getDeclardMethod获得类声明的所有⽅法,正常⽅法是⼀个类创建对象,⽽反射是1个对象找到1个类。

32 幂等的处理⽅式

描述:接⼝可重复调⽤,在调⽤⽅多次调⽤的情况下,接⼝最终得到的结果是⼀致的。

  1. 查询操作是天然幂等
  2. 唯⼀索引,防⽌新增脏数据
  3. token机制,防⽌⻚⾯重复提交
  4. 悲观锁 for update
  5. 乐观锁(通过版本号/时间戳实现, 通过条件限制where avai_amount-#subAmount# >= 0)
  6. 分布式锁
  7. 状态机幂等(如果状态机已经处于下⼀个状态,这时候来了⼀个上⼀个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。)

33 hashmap在jdk1.8中的改动?

  1. Jdk1.8以前是进⾏四次扰动计算,可能从速度功效各⽅⾯考虑,jdk1.8变成扰动⼀次,低16位和⾼16位进⾏异或计算。取模的时候考虑取模运算的速度⽐较慢,改⽤与操作优化效率,很巧妙,hash table就没设计的这么好。
  2. JDK1.8⾥对hashmap最⼤的改变是引⼊了红⿊树,这⼀点在hash不均匀并且元素个数很多的情况时,对hashmap的性能提升⾮常⼤。Hashmap的底层实现是使⽤⼀个entry数组存储,默认初始⼤⼩16,不过jdk8换了名字叫node,可能是因为引⼊了树,叫node更合适吧,另外我也不喜欢entry这个名字,不能望⽂⽣义,我在刚学的时候还以为是什么神秘的东⻄呢,其实就是个键值对对象⽽已。Node⾥有next引⽤指向下⼀个节点,因为hashmap解决冲突的思路是拉链法。
  3. 另外变化⽐较⼤的还有扩容机制,也就是resize⽅法。

34 java 8 流式使⽤

List<Integer> evens = nums.stream().filter(num -> num % 2 == 0).collect(Collectors.toList()); 
//1、stream()操作将集合转换成⼀个流,
//2、filter()执⾏我们⾃定义的筛选处理,这⾥是通过lambda表达式筛选出所有偶数,
//3、最后我们通过collect()对结果进⾏封装处理,并通过Collectors.toList()指定其封装成为⼀个List集合返回。

35 java 域的概念

field,域是⼀种属性,可以是⼀个类变量,⼀个对象变量,⼀个对象⽅法变量或者是⼀个函数的参数。

36 jdk1.8中ConcurrentHashMap size⼤于8时会转化成红⿊树,请问有什么作⽤,如果通过remove操作,size⼩于8了,会发⽣什么?

put时如果链表size>=8并且table.length>=64,这时链表会转变成⼀个红⿊树(红⿊树是⼀个⾃平衡的⼆叉查找树,查找效率会从链表的o(n)降低为o(logn),效率是⾮常⼤的提⾼),但是remove不会逆转。