JAVA八股文面试题(卷一)

JAVA八股文面试题(卷一)

注:素材来自与https://www.bilibili.com/video/BV1LS4y1V7MX?p=1 的上课笔记。

java基础

解释一下面向对象?

面向对象编程与之相对应的是面向过程编程

面向过程(Procedure Oriented简称PO)
把事情拆分成一个个的方法和数据,然后按照一定的顺序,执行完这些方法,等方法执行完了,事情就搞定了。(因为每个方法都可以看作一个过程,所以叫面向过程)

面向对象(Object Oriented简称OO)
面向对象会把事物抽象成对象的概念,先抽象出对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。

面向过程:
优点:性能比面向对象高,因为不需要实例化对象。
缺点:可维护性差(例如:洗衣服我不喜欢甩干、我洗衣服更喜欢用洗衣液)。

面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:性能比面向过程低.

面向对象编程的特性:封装、继承、多态
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承:继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:一个类实例的相同方法在不同情形有不同表现形式。

Java 中 sleep 方法和 wait 方法的区别?

1、sleep是线程中的方法,但是wait是Object中的方法。

2、sleep方法不会释放锁,但是 wait会释放锁。

3、sleep方法不依赖于同步器synchronized,但是 wait需要依赖synchronized关键字。

4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。

final、finalize和 finally 的不同之处?

final是一个修饰符,可以修饰变量、方法和类。如果final修饰变量,意味着该变量的值在初始化后不能被改变。

finally是一个关键字,与 try和 catch 一起用于异常的处理。finallyr块一定会被执行,.无论在 try块中是否有发生异常。

finalize方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用finalize没有保证。

hashCode()有什么用?与equals()有什么关系?

简单的来讲HashCode 就像是一个签名,当两个对象的 Hashcode 一样的时候,两个对象就可能一样,但如果Hashcode不一样,那么肯定不是同一个对象。相当于先确定一个大的范围,再用equals 去比较。
hashcode可以减少equals 比较的次数,提高运算效率。(equals方法效率比较低).如果对象1和对象﹖相等,说明他们的散列码相等!反过来就不一样了! hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等。

"a==b”和"a.equals(b)"有什么区别?

”操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用“”操作符。.
如果a和 b都是对象,则a==b是比较两个对象的引用。equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。

Collection和Collections 的区别?

Collection是一个接口,它是Set、List等容器的父接口。

Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

Java中如何实现序列化,有什么意义?

1:为什么要序列化·
网络传输的数据都必须是二进制数据,但是在Java中都是对象,是没有办法在网络中进行传输的,所以就需要对Java对象进行序列化,而且这个要求这个转换算法是可逆的,不然要是不可逆那鬼知道你传过来的是个什么东西。
2:Java 原生序列化·
只要让类实现 Serializable接口就行,序列化具体的实现是由ObjectOutputStream和ObjectInputStream来实现的

Java序列化的缺点: 1:序列化码流太大2:序列化效率低。

一般建议使用第三方的JSON、Hessian、ProtoBuf等效率高的方式.

String s = new String(“abc”);创建了几个对象?

两个对象,一个是常量池的”abc”,一个是用new 创建在堆上的对象。
JAVA八股文面试题(卷一)_第1张图片

抽象类( abstract class)和接口( interface)有什么区别?

接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。
抽象类表示的是,这个对象是什么。(例如:男人,女人的抽象类是人).接口表示的是,这个对象能做什么。.
(例如:“吃东西”定义成一个接口,让人和动物去实现它,因为都可以吃东西)。

同时在Java中,一个类只能继承一个抽象类(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。.
当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

抽象类的功能要远超过接口(因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法),但是定义抽象类的代价高(每个类只能继承一个抽象类).

如何通过反射调用对象的方法?

比如通过反射调用String. toUpperCase方法.

public class MethodInvokeTest {
    public static void main(String[] args) throws Exception {
        String str = "hello";
        Method m = str. getClass().getMethod( name: "toUpperCase");
        System. out. println(m. invoke(str));//HELLO
    }	
}

有没有可能两个不相等的对象有相同的hashcode?

有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在.hashmap 中会有冲突。.

重载(Overload)和重写( Override)的区别?

Overload是重载的意思,Override是覆盖的意思,也就是重写。
重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。
重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。

HashMap

深入理解HashMap与Hash算法

1)HashMap的数据结构
JDK1.7及之前HashMap底层是数组和链表(Entry,next,头插法)
JAVA八股文面试题(卷一)_第2张图片
JDK1.8及以后HashMap底层是数组和链表以及红黑树.(Entry,next,TreeNode),链表如果过长的话,它会导致我们在使用哈希Map的上进行查询(get)的时候效率低下,所以当数组进行两次扩容,且链表的长度大于8的时候就会把链表转换为红黑树。
JAVA八股文面试题(卷一)_第3张图片

JDK1.8什么时候链表会转化成红黑树?

HashMap在元素比较少的时候,也只会有数组+链表的结构。
当链表的长度大于8,HashMap可能会做树化(链表转变成红黑树),不过就是需要再满足一个条件,就是数组长度必须大于等千64(默认是16经过两次扩容<*2>),链表才会转变为红黑树。否则,只会触发数组的扩容。

讲一讲HashMap中的hash算法?

下图是HashMap的put和get方法流程
JAVA八股文面试题(卷一)_第4张图片

//源码(JDK1.8)根据key值计算hash值的方法 
static final int hash(object key){
    int h;
    return (key == null)? 0 : (h = key.hashCode())^ (h >>>16);
}
//源码(JDK1.8)根据hash值计算出对应数组的下标的代码
final V putVal(int hash,K key,V value,boolean onlyIfAbsent,
				boolean evict){
Node<K,V>[] tab; Node<K, V> p; int n,i;
if ((tab = table) == null || (n = tab.length)== 0)
	n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash])== null)
	tab[i] = newNode(hash, key, value, next: null) ;

为什么n(n为数组的长度)是2的次方的?

(n-1)&hash效果和hash%n是一样,且效率更高。HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构!

为什么HashMap要把hashcode做一个运算改进?

让计算出来的数组下标更加分散

JAVA八股文面试题(卷一)_第5张图片

HashMap什么条件下扩容?

HashMap.中最主要的数据结构是数组,一般情况下HashMap 的元素是方数组上的。如果数组已经有元素,那么就发生了Hash 冲突,那么就会使用链表或者红黑树的结构进行存储。
当然HashMap中数组的默认大小是16,如果不断的插入元素,那么Hash 冲突就会很频繁,所以这个时候就需要扩容!
HashMap 中有一个loadFactor参数,这个是当插入数组的元素数量达到了数组的比例之后,HashMap就要开始把数组进行扩容,扩成原来的2倍。loadFactor默认值0.75,例如:数组长度是16,如果数组上已经有12个元素(16*0.75)时,这个时候就需要进行扩容。

扩容后会发生什么?

当然扩容后原有的元素也需要进行重新散列,因为数组长度变长了,元素在数组上的位置也可能发生变化。

JAVA八股文面试题(卷一)_第6张图片

ArrayList 与LinkedList的区别?

JAVA八股文面试题(卷一)_第7张图片

1、ArrayList查询速度比LinkedList要快。

2、相对于ArrayList , LinkedList插入和删除是更快的。

3、LinkedList需要更多的内存。

Hashtable 与HashMap有什么不同之处?

1.安全性
Hashtable是线程安全,HashMap是非线程安全。

HashMap的性能会高于Hashtable,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下可以使用Hashtable…

2.是否可以使用null 作为key.
而Hashtable则不允许null作为 key,而 HashMap可以使用null作为 key,不过建议还是尽量避免这样使用。HashMap 以 null作为key时,总是存储在table数组的第一个节点上。.

JVM

int和 Integer 哪个会占用更多的内存?

Integer对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是int是一个基本数据类型的数据,所以占用的空间更少。

Java 中++操作符是线程安全的吗?

不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差从而导致值的不正确。

JAVA八股文面试题(卷一)_第8张图片

什么是用户线程?什么是内核线程?

在Java中,基本我们说的线程(Thread)实际上应该叫作“用户线程”,而对应到操作系统,还有另外一种线程叫作“内核线程”。
用户线程和内核线程之间必然存在某种关系,多对一模型、一对一模型和多对多模型多对一线程模型

多对一模型

多个用户线程对应到同一个内核线程上,线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。

优点:
用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换,使线程的创建、调度、同步等非常快;

缺点:
由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行;
内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的调度、优先级等;

一对一模型
即一个用户线程对应一个内核线程,内核负责每个线程的调度

优点:
(比如JVM几乎把所有对线程的操作都交给了内核)实现线程模型的容器(jvm)简单,所以我们经常听到在java中使用线程一定要慎重就是这个原因;

缺点:
对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换;内核为每个线程都映射调度实体,如果系统出现大量线程,会对系统性能有影响;

什么是JVM,与JDK有什么区别?

JVM全称是Java VirtualMachine ,中文称为Java虚拟机
JVM是Java程序运行的底层平台,与Java支持库一起构成了Java程序的执行环境。

JAVA八股文面试题(卷一)_第9张图片

讲─讲JVM跨平台与跨语言

跨平台:我们写的一个类,在不同的操作系统上(Linux、Windows、MacOS等平台)执行,效果是一样,这个就是JVM的跨平台性。
JAVA八股文面试题(卷一)_第10张图片

跨语言(语言无关性)JVM只识别字节码,所以JVM没有直接关联,JVM运行不是翻译Java文件,而是识别class文件,这个一般称之为字节码。还有像Groovy 、Kotlin、Scala等等语言,它们其实也是编译成子节,Ps匕们也可以在JVM上面跑,这个就是JVM的跨语言特征。Java的跨语言性一定程度上奠定了非常强大的java语言生态圈。

JVM的运行时数据区有哪些?

运行时数据区的定义: Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
根据JVM规范,标准的JVM运行时数据区包括以下部分:

·程序计数器
.Java虚拟机栈
·本地方法栈
·堆内存
·方法区
·运行时常量池
JAVA八股文面试题(卷一)_第11张图片

什么是堆内存?堆内存包含哪些部分?

堆是JVM上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。以Hotspot为例,堆空间为了方便GC模块进行对象分配和回收,可以把堆空间进行以下划分:
·新生代(Eden+s0+s1)
·老年代
JAVA八股文面试题(卷一)_第12张图片

什么是内存溢出?

内存溢出((OOM- Out Of Memory)是指JVM可用内存不足。JVM运行需要使用的内存超出最大可用值,会导致JVM出现异常。常见的OOM有以下几种。
·栈溢出
·堆溢出
·方法区溢出
·本机直接内存溢出

什么是内存泄漏?与内存溢出有什么关系?

内存泄漏(Memory Leak)是指本来无用的对象却继续占用内存,没有再恰当的时机释放占用的内存。

不使用的内存,却没有被释放,这个就叫做内存泄漏。
比较典型的场景是:每一个请求进来,或者每一次操作处理,都分配了内存,却有一部分不能回收(或未释放),那么随着处理的请求越来越多,内存泄漏也就越来越严重。
与内存溢出的关系!
如果存在严重的内存泄漏问题,随着时间的推移,则必然会引起内存溢出。
内存泄漏一般是资源管理问题和程序BUG,内存溢出则是内存空间不足和内存泄漏的哥终结里

对象头包含哪些部分?

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header) 、实例数据(Instance Data)和对齐填充( Padding)
JAVA八股文面试题(卷一)_第13张图片

public class MyOrder {
    private long orderId;
    private long userId;
    private byte state;
    private long createTime;
}	

给定具体的类,分析对象的内存占用!
每个long类型的字段占用8字节,3个long字段占用24字节。byte 字段占用1个字节。
JAVA八股文面试题(卷一)_第14张图片
最终实际占用:40个字节

常用的JVM启动参数有哪些?

#JVM启动参数不换行
#设置堆内存
-Xmx4g -Xms4g
#指定GC算法
-XX:+UseG1GC -XX:MaxGCPauseMillis=50
#指定GC并行线程数
-XX:ParallelGCThreads=4
#打印GC日志
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
#指定GC日志文件
-Xloggc:gc.log
#指定Meta区.的最大值
-XX:MaxMetaspaceSize=2g
#设置单个线程栈的大小
-Xss1m
#指定堆内存溢出时自动进行Dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/

设置堆空间的最大值(-Xmx)应该要考虑哪些因素?

需要根据系统的配置来确定,要给操作系统和JVM其他内存区域(栈、方法区)留下一定的剩余空间。推荐配置系统或容器里可用内存的70%~80%最好。假设物理内存是8G。

设置多大堆内存比较合适?
系统有8G物理内存,系统自己可能会用掉一点,大概还有7.5G可以用。那么建议配置-Xmx6g (7.5g*0.8=6g)
JAVA八股文面试题(卷一)_第15张图片

Java8默认使用的垃圾收集器是什么?

Java8版本的HotspotJVM,默认情况下使用的是并行垃圾收集器(Parallel GC)。其他厂商提供的JDK8基本上也默认使用并行垃圾收集器。
JAVA八股文面试题(卷一)_第16张图片

1、什么是并行垃圾收集?

并行垃圾收集,是指使用多个GC worker线程并行地执行垃圾收集,能充分利用多核CPU的能力,缩短垃圾收集的暂停时间。除了单线程的GC,其他的垃圾收集器,比如PS,CMS,G1等新的垃圾收集器都使用了多个线程来并行执行GC工作
JAVA八股文面试题(卷一)_第17张图片

2、什么是STW?什么是安全点,什么是安全区域?

因为GC过程中,所有应用线程都需要暂停之后才能执行GC,这时候就称为STW,或者叫做GC暂停。

安全点
用户线程暂停,GC线程要开始工作,但是要确保用户线程暂停的这行字节码指令是不会导致引用关系的变化。所以JVM会在字节码指令中,选一些指令,作为“安全点”,比如方法调用、循环跳转、异常跳转等,一般是这些指令才会产生安全点。为什么它叫安全点,是这样的,GC时要暂停业务线程并不是抢占式中断(立马把业务线程中断)而是主动是中断。
主动式中断是设置一个标志,这个标志是中断标志,各业务线程在运行过程中会不停的主动去轮询这个标志,一旦发现中断标志为True,就会在自己最近的“安全点”上主动中断挂起。

安全区域

为什么需要安全区域?
要是业务线程都不执行(业务线程处于Sleep或者是Blocked状态),那么程序就没办法进入安全点,对于这种情况,就必须引入安全区域。安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区城看作被扩展拉伸了的安全点。

JAVA八股文面试题(卷一)_第18张图片
当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,这段时间里JVM要发起GC就不必去管这个线程了。
当线程要离开安全区域时,它要JVM是否已经完成了(根节点枚举,或者其他GC中需要暂停用户线程的阶段)
1、如果完成了,那线程就当作没事发生过,继续执行。
2、否则它就必须一直等待,直到收到可以离开安全区域的信号为止。

3、如果CPU使用率突然飙升,你会怎么排查?

1、先通过top命令找到消耗cpu很高的进程id(假设是2732)top命令是我们在Linux下最常用的命令之一,它可以实时显示正在执行进程的CPU使用率、内存使用率以及系统负载等信息。其中上半部分显示的是系统的统计信息,下半部分显示的是进程的使用率统计信息。

2、执行top -p 2732单独监控该进程
3、在第2步的监控界面输入H,获取当前进程下的所有线程信息

4、找到消耗cpu特别高的线程编号(假设是2734)

5、执行jstack 2732对当前的进程做dump,输出所有的线程信息同时将第4步得到的线程十进制编号2734转成16进制(AAE),在堆栈信息里面去找对应线程内容

6、最后解读线程信息,定位具体代码位置

4、CMS、G1垃圾回收器中的三色标记你了解吗?

三色标记法是一种垃圾回收法,它可以让JVM不发生或仅短时间发生STW(StopTheWorld),从而达到清除JVM内存垃圾的目的。
三色标记法将对象的颜色分为了黑、灰、白,三种颜色
**黑色:**该对象已经被标记过了,且该对象下的属性也全部都被标记过了;

**灰色:**对象已经被垃圾收集器扫描过了,但是对象中还存在没有扫描的引用﹔

**白色:**表示对象没有被垃圾收集器访问过,即表示不可达;
JAVA八股文面试题(卷一)_第19张图片
三色标记的漏标问题
JAVA八股文面试题(卷一)_第20张图片
C被漏标了(不是垃圾的当做垃圾)
CMS的解决方案:Incremental Update(重新扫描)
JAVA八股文面试题(卷一)_第21张图片

5、讲一讲类加载与类加载器?

一个类从被加载到Java虚拟机内存开始,到卸载出内存为止,它的整个生命周期将会经历加载 (Loading)、验证(Verification)、准备(Preparation)、解析
(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中验证、准备、解析三个部分统称为连接(Linking)JAVA八股文面试题(卷一)_第22张图片
加载阶段虚拟机需要完成以下3件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

6、什么是双亲委派机制?它有什么作用?

双亲委派机制的意思是除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。
·通过带有优先级的层级关可以避免类的重复加载;
·保证Java程序安全稳定运行,Java核心API定义类型不会被随意替换。
JAVA八股文面试题(卷一)_第23张图片

7、G1收集器有哪些特点?

G1 的全称是 Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它。

1)空间整合
JAVA八股文面试题(卷一)_第24张图片
2)多线程+并发+可预测停顿
JAVA八股文面试题(卷一)_第25张图片

8、你有哪些手段来排查ooM的问题?

OOM: OutOfMemory,当JVM中内存不够并且垃圾回收的速度跟不上内存的分配速度就会发生 OOM的现象!
JDK1.8推荐生产环境开启以下两个参数:
-XX:+HeapDumpOnOutOfMemoryError 当OOM发生时自动dump堆内存信息

-XX:HeapDumpPath=/tmp/heapdump.hprofdump 堆内存信息存放目录

使用过哪些JVM相关的命令工具命令行工具

jps
列出当前机器上正在运行的虚拟机进程,JPS从操作系统的临时目录上去找(所以有一些信息可能显示不全)。
-q :仅仅显示进程,
-m:输出主函数传入的参数.下的hello就是在执行程序时从命令行输入的参数-:输出应用程序主类完整package名称或jar完整名称.
-v:列出jvm参数

jstat
是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GU图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。

比如说:我们要统计GC,就是垃圾回收,那么只需要使用这样的命令。
jstat-gc 13616(这个13616是JVM的进程,通过JPS命令得到),这样统计出来是的实时值。所以很多情况下,我们为了看变化值的,可以这么玩。
假设需要每250毫秒查询一次进程13616垃圾收集状况,一共查询10次,那命令应当是: jstat-gc 13616 250 10

jinfo
查看和修改虚拟机的参数
jinfo -sysprops可以查看由System.getProperties()取得的参数

jinfo -flag未被显式指定的参数的系统默认值
jinfo -flags(注意s)显示虚拟机的参数

jmap
-v:列出jvm参数

jstat
是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有GU图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。

比如说:我们要统计GC,就是垃圾回收,那么只需要使用这样的命令。
jstat-gc 13616(这个13616是JVM的进程,通过JPS命令得到),这样统计出来是的实时值。所以很多情况下,我们为了看变化值的,可以这么玩。
假设需要每250毫秒查询一次进程13616垃圾收集状况,一共查询10次,那命令应当是: jstat-gc 13616 250 10

jinfo
查看和修改虚拟机的参数
jinfo -sysprops可以查看由System.getProperties()取得的参数

jinfo -flag 未被显式指定的参数的系统默认值
jinfo -flags(注意s)显示虚拟机的参数

jmap
用于生成堆转储快照(一般称为heapdump或dump文件)。jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。和jinfo命令一样,jmap有不少功能在Windows平台下都是受限的,除了生成dump文件的-dump选项和用于查看每个类的实例、空间占用统计的-histo选项在所有操作系统都提供之外,其余选项都只能在Linux/Solaris下使用。

你可能感兴趣的:(java八股文,java,面试,链表)