高级Java开发面试常用题的答案1

一、数据结构与算法基础

· 说一下几种常见的排序算法和分别的复杂度。

· 用Java写一个冒泡排序算法

 
/** 现在有一个包含1000个数的数组,仅前面100个无序,后面900个都已排好序且都大于前面100个数字,那么在第一趟遍历后,最后发生交换的位置必定小于100,且这个位置之后的数据必定已经有序了,也就是这个位置以后的数据不需要再排序了,于是记录下这位置,第二次只要从数组头部遍历到这个位置就可以了。如果是对于上面的冒泡排序算法2来说,虽然也只排序100次,但是前面的100次排序每次都要对后面的900个数据进行比较,而对于现在的排序算法3,只需要有一次比较后面的900个数据,之后就会设置尾边界,保证后面的900个数据不再被排序。 */ public static void bubbleSort(int [] a, int n){ int j , k; int flag = n ;//flag来记录最后交换的位置,也就是排序的尾边界 while (flag > 0){//排序未结束标志 k = flag; //k 来记录遍历的尾边界 flag = 0; for(j=1; j a[j]){//前面的数字大于后面的数字就交换 //交换a[j-1]和a[j] int temp; temp = a[j-1]; a[j-1] = a[j]; a[j]=temp; //表示交换过数据; flag = j;//记录最新的尾边界. } } } } 

· 描述一下链式存储结构。

它不要求逻辑上相邻的元素在物理位置上也相邻。因此它没有顺序存储结构所具有的弱点,同时也失去了顺序表可随机存取的优点。

其特点主要表现为:

1、比顺序存储结构的存储密度小;

2、插入、删除灵活,结点可以被插入到链表的任何位置,首、中、末都可以,而且不必要移动结点中的指针;

3、链表的大小可以按需伸缩,是一种动态存储结构,其实现的集合在增、删方面性能更高;

4、查找结点时的效率就相对数组较低,只能从第一个结点开始顺着链表逐个查找(这是他的缺点)。

· 如何遍历一棵二叉树?

二叉树的遍历分为三种:

前序遍历:按照“根左右”,先遍历根节点,再遍历左子树 ,再遍历右子树

中序遍历:按照“左根右“,先遍历左子树,再遍历根节点,最后遍历右子树

后续遍历:按照“左右根”,先遍历左子树,再遍历右子树,最后遍历根节点

其中前,后,中指的是每次遍历时候的根节点被遍历的顺序

 
package com.tree; import java.util.ArrayList; import java.util.List; public class Tree { private Node root; private List list=new ArrayList(); public Tree(){ init(); } //树的初始化:先从叶节点开始,由叶到根 public void init(){ Node x=new Node("X",null,null); Node y=new Node("Y",null,null); Node d=new Node("d",x,y); Node e=new Node("e",null,null); Node f=new Node("f",null,null); Node c=new Node("c",e,f); Node b=new Node("b",d,null); Node a=new Node("a",b,c); root =a; } //定义节点类: private class Node{ private String data; private Node lchid;//定义指向左子树的指针 private Node rchild;//定义指向右子树的指针 public Node(String data,Node lchild,Node rchild){ this.data=data; this.lchid=lchild; this.rchild=rchild; } } /** * 对该二叉树进行前序遍历 结果存储到list中 前序遍历:ABDXYCEF */ public void preOrder(Node node) { list.add(node); //先将根节点存入list //如果左子树不为空继续往左找,在递归调用方法的时候一直会将子树的根存入list,这就做到了先遍历根节点 if(node.lchid != null) { preOrder(node.lchid); } //无论走到哪一层,只要当前节点左子树为空,那么就可以在右子树上遍历,保证了根左右的遍历顺序 if(node.rchild != null) { preOrder(node.rchild); } } /** * 对该二叉树进行中序遍历 结果存储到list中 */ public void inOrder(Node node) { if(node.lchid!=null){ inOrder(node.lchid); } list.add(node); if(node.rchild!=null){ inOrder(node.rchild); } } /** * 对该二叉树进行后序遍历 结果存储到list中 */ public void postOrder(Node node) { if(node.lchid!=null){ postOrder(node.lchid); } if(node.rchild!=null){ postOrder(node.rchild); } list.add(node); } /** * 返回当前数的深度 * 说明: * 1、如果一棵树只有一个结点,它的深度为1。 * 2、如果根结点只有左子树而没有右子树,那么树的深度是其左子树的深度加1; * 3、如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1; * 4、如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值再加1。 * * @return */ public int getTreeDepth(Node node) { if(node.lchid == null && node.rchild == null) { return 1; } int left=0,right = 0; if(node.lchid!=null) { left = getTreeDepth(node.lchid); } if(node.rchild!=null) { right = getTreeDepth(node.rchild); } return left>right?left+1:right+1; } //得到遍历结果 public List getResult() { return list; } public static void main(String[] args) { Tree tree=new Tree(); System.out.println("根节点是:"+tree.root); //tree.preOrder(tree.root); tree.postOrder(tree.root); for(Node node:tree.getResult()){ System.out.println(node.data); } System.out.println("树的深度是"+tree.getTreeDepth(tree.root)); } } 

二叉树与一般树的区别

  • 一般树的子树不分次序,而二叉树的子树有左右之分.
  • 由于二叉树也是树的一种,所以大部分的树的概念,对二叉树也适用.
  • 二叉树的存贮:每个节点只需要两个指针域(左节点,右节点),有的为了操作方便也会 增加指向父级节点的指针,除了指针域以外,还会有一个数据域用来保存当前节点的信息

二叉树的特点:

  • 性质1:在二叉树的第i层上至多有2^(i-1)个节点(i >= 1)
  • 性质2:深度为k的二叉树至多有2^k-1个节点(k >=1)
  • 性质3:对于任意一棵二叉树T而言,其叶子节点数目为N0,度为2的节点数目为N2,则有N0 = N2 + 1。
  • 性质4:具有n个节点的完全二叉树的深度。

· 倒排一个LinkedList。

根据LinkedList的实现,LinkedList的底层是双向链表,它在get任何一个位置的数据的时候,都会把前面的数据走一遍。用迭代器或者foreach循环(foreach循环的原理就是迭代器)去遍历LinkedList即可,这种方式是直接按照地址去找数据的,将会大大提升遍历LinkedList的效率。

 
public static  LinkedList reverse(LinkedList linkedList) { if (linkedList == null) return linkedList; LinkedList temp_linkedlist = new LinkedList(); for (T item: linkedList) { temp_linkedlist.addLast(item); } return temp_linkedlist; } 

· 用Java写一个遍历目录下面的所有文件。

 
public static void foreachFileList(String filePath) throws IOException { LinkedList linkedList = new LinkedList(); File file = new File(filePath); if (file.exists()) { linkedList.add(file); while (true) { file = linkedList.poll(); if (file == null) break; File[] fileList = file.listFiles(); for (File fileItem : fileList) { if (fileItem.isDirectory()) { linkedList.add(fileItem); continue;//for } if (fileItem.isFile()) System.out.println(fileItem.getCanonicalPath()); } } } } 

二、Java基础

· 接口与抽象类的区别?

  • 一个类可以实现多个接口,但只能继承最多一个抽象类
  • 抽象类可以包含具体的方法;接口所有的方法都是抽象的(不管是否对接口声明都是抽象的)(jdk1.7以前,jdk1.8开始新增功能接口中有default 方法,有兴趣自己研究)
  • 抽象类可以声明和使用字段;接口则不能,但是可以创建静态的final常量
  • 抽象类中的方法可以是public、protected、private或者默认的package;接口的方法都是public(不管是否声明,接口都是公开的)
  • 抽象类可以定义构造函数,接口不能。
  • 接口被声明为public,省略后,包外的类不能访问接口

· Java中的异常有哪几类?分别怎么使用?

  • Throwable包含了错误(Error)和异常(Excetion两类)
  • Exception又包含了 运行时异常(RuntimeException, 又叫非检查异常) 和 非运行时异常(又叫检查异常)(1) Error是程序无法处理了, 如果OutOfMemoryError、OutOfMemoryError等等, 这些异常发生时, java虚拟机一般会终止线程 .
  • -(2) 运行时异常都是RuntimeException类及其子类,如 NullPointerException、IndexOutOfBoundsException等, 这些异常是不检查的异常, 是在程序运行的时候可能会发生的, 所以程序可以捕捉, 也可以不捕捉. 这些错误一般是由程序的逻辑错误引起的, 程序应该从逻辑角度去尽量避免.
  • (3) 检查异常是运行时异常以外的异常, 也是Exception及其子类, 这些异常从程序的角度来说是必须经过捕捉检查处理的, 否则不能通过编译. 如IOException、SQLException等

· 常用的集合类有哪些?比如List如何排序?

  • 常用的集合分为List(有序排放)、Map(以名和值一一对应的存放)、Set(既无序也没名).在这三者之中其中List和Set是Collection接口的子接口,而Map不是Collection接口的子接口.
  • List常用有:ArrayList和LinkedList,Vecotr(线程安全)
  • Set常用有: TreeSet, HashSet 元素不可重复,内部结构用HashMap,Key为Set的item值,value为一个固定的常量。java.util.Collections.newHashSetFromMap(),内部其实质还是通过ConcurrentHashMap实现线程安全的。
  • Map: TreeMap和LinkedHashMap,HashMap,HashTable(线程安全)
  • sort()方法排序的本质其实也是借助Comparable接口和Comparator接口的实现,一般有2种用法:
  1. 直接将需要排序的list作为参数传入,此时list中的对象必须实现了Comparable接口,然后sort会按升序的形式对元素进行排序;
  2. 传入list作为第一个参数,同时追加一个Comparator的实现类作为第二个参数,然后sort方法会根据Comparator接口的实现类的逻辑,按升序进行排序;

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

  • Linkedlist集合的优势:添加元素时可以指定位置,比ArrayList集合添加元素要快很多。
  • Linkedlist在get很慢,LinkedList在get任何一个位置的数据的时候,都会把前面的数据走一遍。尽量不使用,而使用foreach LinkedList的方式来直接取得数据。
  • 这两种方式各有优缺,为更好的使用可以将这两者进行联合使用,使用Linkedlist集合进行存储和添加元素,使用Arraylist集合进行get获取元素。

· 内存溢出是怎么回事?请举一个例子?

  • 内存溢出(out of memory)通俗理解就是内存不够,在计算机程序中通俗的理解就是开辟的内存空间得不到释放。
  • OOM有堆溢出,栈溢出,方法区溢出(主要是动态生成class的处理过多)

· ==和equals的区别?

  • ==号在比较基本数据类型时比较的是值,而用==号比较两个对象时比较的是两个对象的地址值
  • Object类中equals()方法底层依赖的是==号,那么,在所有没有重写equals()方法的类中,调用equals()方法其实和使用==号的效果一样,然而,Java提供的所有类中,绝大多数类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值

· hashCode方法的作用?

  1. hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
  2. 如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
  3. 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
  4. 两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

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

  • JDK引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。
  • NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。

· HashMap实现原理,如何保证HashMap的线程安全?

HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap底层就是一个数组结构,数组中的每一项又是一个链表。

  • =使用 java.util.Hashtable 类,此类是线程安全的。Hashtable是通过每个方法用synchronized来处理,性能不及ConcurrentHashMap
  • 使用 java.util.concurrent.ConcurrentHashMap,此类是线程安全的。采用了分段锁实现同步。
  • 使用 java.util.Collections.synchronizedMap() 方法包装 HashMap object,得到线程安全的Map,并在此Map上进行操作。

· JVM内存结构,为什么需要GC?

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存,简化代码开发。

· NIO模型,select/epoll的区别,多路复用的原理

(过长,没有回答)

· Java中一个字符占多少个字节,扩展再问int, long, double占多少字节

Java char: utf-16:2个字节, int-4, long-8,double-9

· 创建一个类的实例都有哪些办法?

  1. 关键字 new。工厂模式是对这种方式的包装;
  2. 类实现克隆接口,克隆一个实例。原型模式是一个应用实例;
  3. 用该类的加载器,newinstance。java的反射,反射使用实例:Spring的依赖注入、切面编程中动态代理等取得
  4. 通过IO流反序列化读取一个类,获得实例。

· final/finally/finalize的区别?

  1. final:如果一个类被final修饰,意味着该类不能派生出新的子类,不能作为父类被继承。因此一个类不能被声明为abstract,又被声明为final。将变量或方法声明为final。可以保证他们在使用的时候不被改变。其初始化可以在两个地方:一是其定义的地方,也就是在final变量在定义的时候就对其赋值;二是在构造函数中。这两个地方只能选其中的一个。被声明为final的方法也只能使用,不能重写。
  2. finally:在异常处理的时候,提供finally块在成功或失败都可以执行任何的清除操作。
  3. finalize:finalize是方法名,java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的,可以从Object.finalize()继承下来。

· Session/Cookie的区别?

  • cookie是客户端的会话状态的一种储存机制。一般限制4k以内。session是一种服务器端的信息管理机制。
  • session产生的session_id放在cookie里面,如果用户把cookie禁止掉,可以通过在url中保留session_id

· String/StringBuffer/StringBuilder的区别,扩展再问他们的实现?

StringBuilder:线程非安全的,StringBuffer:线程安全的,三者在执行速度方面的比较:StringBuilder > StringBuffer > String

· Servlet的生命周期?

  1. Servlet 通过调用 init () 方法进行初始化。
  2. Servlet 调用 service() 方法来处理客户端的请求。
  3. Servlet 通过调用 destroy() 方法终止(结束)。
  4. 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

· 如何用Java分配一段连续的1G的内存空间?需要注意些什么?

使用ArrayList来分配,注意堆内存不足造面OOM

· Java有自己的内存回收机制,但为什么还存在内存泄漏的问题呢?

主要是没有释放对象引用造成的内存泄漏,比如下例:

 
class MyList{ /* * 此处只为掩饰效果,并没有进行封装之类的操作 * 将List集合用关键字 static 声明,这时这个集合将不属于任MyList 对象,而是一个类成员变量 */ public static List list = new ArrayList(); } class Demo{ public static void main(String[] args) { MyList list = new MyList(); list.list.add("123456"); // 此时即便我们将 list指向null,仍然存在内存泄漏,因为MyList中的list是静态的,它属于类所有而不属于任何特定的实例  list = null; } } 

· 什么是java序列化,如何实现java序列化?(写一个实例)?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。Java的序列化需要实现Serializable接口。

 
//序列化后生成指定文件路径  File file = new File("D:" + File.separator + "person.ser") ; ObjectOutputStream oos = null ; //装饰流(流) oos = new ObjectOutputStream(new FileOutputStream(file)) ; //实例化类  Person per = new Person("张三",30) ; oos.writeObject(per) ; //把类对象序列化 oos.close() ; 

· String s = new String("abc");创建了几个 String Object?

两个对象,一个是“abc”对象,在常量池中创建;一个是new关键字创建的s对象指向“abc”。

”我自己是一名从事了十余年的后端的老程序员,辞职后目前在做讲师,近期我花了一个月整理了一份最适合2018年学习的JAVA干货(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)从事后端的小伙伴们都可以来了解一下的,这里是程序员秘密聚集地,各位还在架构师的道路上挣扎的小伙伴们速来。“

加QQ群:585550789(名额有限哦!)

你可能感兴趣的:(activeMQ,rabbitMQ,maven,Tomcat,netty,dockerhub)