1、(Java)程序员面试-11 及答案解析(总分:100.00,做题时间:90 分钟)一、论述题(总题数:26,分数:100.00)1.Java IO 流的实现机制是什么 (分数:4.00)_2.管理文件和目录的类是什么 (分数:4.00)_3.Java Socket 是什么 (分数:4.00)_4.Java NIO 是什么 (分数:4.00)_5.什么是 Java 序列化 (分数:4.00)_6.System.out.println()方法使用需要注意哪些问题 (分数:4.00)_7.为什么说 Java 是平台独立性语言 (分数:4.00)_8.Java 平台与其他语言平台有哪些区别 (分数:
2、4.00)_9.JVM 加载 class 文件的原理机制是什么 (分数:4.00)_10.什么是 GC (分数:4.00)_11.Java 是否存在内存泄露问题 (分数:4.00)_12.Java 中的堆和栈有什么区别 (分数:4.00)_13.Java Collections 框架是什么 (分数:4.00)_14.什么是迭代器 (分数:4.00)_15.ArrayList、Vector 和 LinkedList 有什么区别 (分数:4.00)_16.HashMap、HashTable、TreeMap 和 WeakHashMap 有哪些区别 (分数:4.00)_17.用自定义类型作为 Hash
3、Map 或 HashTable 的 key 需要注意哪些问题 (分数:4.00)_18.Collection 和 Collections 有什么区别 (分数:4.00)_19.什么是线程?它与进程有什么区别?为什么要使用多线程 (分数:4.00)_20.同步和异步有什么区别 (分数:4.00)_21.如何实现 Java 多线程 (分数:4.00)_22.run()方法与 start()方法有什么区别 (分数:4.00)_23.多线程同步的实现方法有哪些 (分数:3.00)_24.sleep()方法与 wait()方法有什么区别 (分数:3.00)_25.终止线程的方法有哪些 (分数:3.00)
4、_26.synchronized 与 Lock 有什么异同 (分数:3.00)_(Java)程序员面试-11 答案解析(总分:100.00,做题时间:90 分钟)一、论述题(总题数:26,分数:100.00)1.Java IO 流的实现机制是什么 (分数:4.00)_正确答案:()解析:在 Java 语言中,输入和输出都被称为抽象的流,流可以被看作一组有序的字节集合,即数据在两设备之间的传输。 流的本质是数据传输,根据处理数据类型的不同,流可以分为两大类:字节流和字符流。字节流以字节(8 bit)为单位,包含两个抽象类:InputStream(输入流)和 OutputStream(输出流)。字
5、符流以字符(16 bit)为单位,根据码表映射字符,一次可以读多个字节,它包含两个抽象类:Reader(输入流)和 Writer(输出流)。字节流和字符流最主要的区别为:字节流在处理输入输出时不会用到缓存,而字符流用到了缓存。每个抽象类都有很多具体的实现类,在这里就不详细介绍了。下图主要介绍 Java 中 IO 的设计理念。Java IO 类在设计时采用了 Decorator(装饰者)设计模式,以 InputStream 为例,介绍 Decorator 设计模式在IO 类中的使用如下。 2.管理文件和目录的类是什么 (分数:4.00)_正确答案:()解析:对文件或目录进行管理与操作在编程中有着
6、非常重要的作用,Java 提供了一个非常重要的类(File)来管理文件和文件夹,通过类不仅能够查看文件或目录的属性,而且还可以实现对文件或目录的创建、删除与重命名等操作。下面主要介绍 File 类中常用的几个方法,见表。 File 类常用的方法 方 法 作 用 File(String patmame) 根据指定的路径创建一个 File 对象 createNewFile() 若目录或文件存在,则返回 false,否则创建文件或文件夹 delete() 删除文件或文件夹 isFile() 判断这个对象表示的是否是文件 isDirectory() 判断这个对象表示的是否是文件夹 listFiles(
7、) 若对象代表目录,则返回目录中所有文件的 File 对象 mkdir() 根据当前对象指定的路径创建目录 exists() 判断对象对应的文件是否存在 常见笔试题: 如何列出某个目录下的所有目录和文件? 答案:假设目录“C:/testDir1”下有两个文件夹(dir1 和 dir2)和一个文件 file1.txt,实现代码如下:import java.io.File; public class Test public static void main(Stringargs) File file=new File(“C:/testDir“); /判断目录是否存在 if(!file.exists
8、() System.out.println(“diretory is empty“); return; File fileList=file.listFiles(); for(int i=0; ifileList.length; i+) /判断是否为目录 if(fileListi.isDirectory() System.out.println(“dirctory is:“ +fileListi. getName(); else System. out. println(“file is:“ +fileListi.getName(); 程序运行结果为: dirctory is:dir1 dir
9、ctory is:dir2 file is:file1.txt3.Java Socket 是什么 (分数:4.00)_正确答案:()解析:网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket 也称为套接字,可以用来实现不同虚拟机或不同计算机之间的通信。在 Java 语言中,Socket 可以分为两种类型:面向连接的 Socket 通信协议(TCP,Transmission Control Protocol,传输控制协议)和面向无连接的 Socket 通信协议(UDP,User Datagram Protocol,用户数据报协议)。任何一个
10、Socket 都是由 IP 地址和端口号唯一确定的,如图所示。 4.Java NIO 是什么 (分数:4.00)_正确答案:()解析:在非阻塞 IO(Nonblocking IO,NIO)出现之前,Java 是通过传统的 Socket 来实现基本的网络通信功能的。以服务器端为例,其实现基本流程如图所示。 Socket 使用流程如果客户端还没有对服务器端发起连接请求,那么 accept 就会阻塞(阻塞指的是暂停一个线程的执行以等待某个条件发生,例如某资源就绪)。如果连接成功,当数据还没有准备好时,对 rcad 的调用同样会阻塞。当要处理多个连接时,就需要采用多线程的方式,由于每个线程都拥有自己的
11、栈空间,而且由于阻塞会导致大量线程进行上下文切换,使得程序的运行效率非常低下,因此在 J2SE 1.4 中引入了 NIO 来解决这个问题。 NIO 通过 Selector、Channel 和 Buffer 来实现非阻塞的 IO 操作,其实现原理如图所示。 5.什么是 Java 序列化 (分数:4.00)_正确答案:()解析:Java 提供了两种对象持久化的方式,分别为序列化和外部序列化。 (1)序列化(Serialization) 在分布式环境下,当进行远程通信时,无论是何种类型的数据,都会以二进制序列的形式在网络上传送。序列化是一种将对象以一连串的字节描述的过程,用于解决在对对象流进行读写操
12、作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要时把该流读取出来重新构造一个相同的对象。 如何实现序列化呢?其实,所有要实现序列化的类都必须实现 Serializable 接口,Serializable 接口位于 java.lang 包中,它里面没有包含任何方法。使用一个输出流(例如 FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,紧接着,使用该对象的 writeObject(Object obj)方法就可以将 obj对象写出(即保存其状态),要恢复时可以使用其对应的输入流。 序列化有以下两个
13、特点: 1)如果一个类能被序列化,那么它的子类也能够被序列化。 2)由于 static(静态)代表类的成员,transient(Java 语言关键字,如果用 transient 声明一个实例变量,当对象存储时,它的值不需要维持。)代表对象的临时数据,因此被声明为这两种类型的数据成员是不能够被序列化的。 Java 提供了多个对象序列化的接口,包括 ObjectOutput、ObjectInput,ObjectOutputStream 和ObjectInputStream。 下面给出一个序列化的具体实例: import java.io.FileInputStream; import java.io
14、.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class People implements Serializable private String name; private int age; public People() this.name=“lili“; this.age=20; public int getAge() return age; public void setAge(int
15、 age) this.age=age; public String getName() return this.name; public void setName(String name) this.name=name; public static void main(Stringargs) People p=new People(); ObjectOutputStream oos=null; ObjectInputStream ois=null; try FileOutputStream fos=new FileOutputStream(“perple.out“); oos=new Obje
16、ctOutputStream(fos); oos.writeObject(p); oos.close(); catch(Exception ex) People p1; try FileInputStream fis=new FileInputStream(“perple.out“); ois=new ObjectInputStream(fis); p1=(People)ois.readObject(); System.out.println(“name:“ +p1.getName(); System.out.println(“age:“ +p1.getAge(); ois.close();
17、catch(Exception ex) 程序运行结果为: name:lili age:20 由于序列化的使用会影响系统的性能,因此如果不是必须要使用序列化,应尽可能不要使用序列化。那么在什么情况下需要使用该序列化呢? 1)需要通过网络来发送对象,或对象的状态需要被持久化到数据库或文件中。 2)序列化能实现深复制,即可以复制引用的对象。 与序列化相对的是反序列化,它将流转换为对象。在序列化与反序列化的过程中,serialVersionUID 起着非常重要的作用,每个类都有一个特定的 serialVersionUID,在反序列化的过程中,通过serialVersionUID 来判定类的兼容性。如果
18、待序列化的对象与目标对象的 serialVersionUID 不同,那么在反序列化时就会抛出 InvalidClassException 异常。作为一个好的编程习惯,最好在被序列化的类中显式地声明 serialVersionUID(该字段必须定义为 static final)。自定义 serialVersionUID 主要有如下 3个优点。 1)提高程序的运行效率。如果在类中未显式声明 serialVersionUID,那么在序列化时会通过计算得到一个 serialVersionUID 值。通过显式声明 serialVersionUID 的方式省去了计算的过程,因此提高了程序的运行效率。 2)
19、提高程序不同平台上的兼容性。由于各个平台的编译器在计算 serialVersionUID 时完全有可能会采用不同的计算方式,这就会导致在一个平台上序列化的对象在另外一个平台上将无法实现反序列化的操作。通过显式声明 serialVersionUID 的方法完全可以避免该问题的发生。 3)增强程序各个版本的可兼容性。在默认情况下,每个类都有唯一的 serialVersionUID,因此,当后期对类进行修改时(例如加入新的属性),类的 serialVersionUID 值将会发生变化,这将会导致类在修改前对象序列化的文件在修改后将无法进行反序列化操作。同样,通过显式声明 serialVersionU
20、ID 也会解决这个问题。 (2)外部序列化 Java 语言还提供了另外一种方式来实现对象持久化,即外部序列化。其接口如下: public interface Externalizable extends Serializable void readExternal(ObjectInput in);? void writeExternal(ObjectOutput out);? 外部序列化与序列化主要的区别在于序列化是内置的 API,只需要实现 Serializable 接口,开发人员不需要编写任何代码就可以实现对象的序列化,而使用外部序列化时,Externalizable 接口中的读写方法必须
21、由开发人员来实现。因此与实现 Serializable 接口的方法相比,使用 Externalizable 编写程序的难度更大,但是由于把控制权交给了开发人员,在编程时有更多的灵活性,对需要持久化的那些属性可以进行控制,可能会提高性能。 引申:在用接口 Serializable 实现序列化时,这个类中的所有属性都会被序列化,那么怎样才能实现只序列化部分属性呢? 一种方法为实现 Externalizable 接口,开发人员可以根据实际需求来实现 readExternal 与writeExternal 方法来控制序列化与反序列化所使用的属性,这种方法的缺点为增加了编程的难度。另一种方法为使用关键字
22、 transient 来控制序列化的属性。被 transient 修饰的属性是临时的,不会被序列化。因此,可以通过把不需要被序列化的属性用 transient 来修饰。 常见笔试题: import java.io.Serializable; public class DataObject implements Serializable private static int i=0; private String word=“; public static void setI(int i) DataObject.i=i; public void setWord(String word) this
23、.word=word; 创建一个如下方式的 DataObject:DataObject object=new DataObject();object.setWord(“123”);object.setI(2);将此对象序列化文件,并在另一个 JVM 中读取文件,进行反序列化,请问此时读出的DataObject 对象中的 word 和 i 的值分别是_。 A“”,0 B“”,2 C“123”,2 D“123”,0 答案:D。Java 在序列化时不会实例化 static 变量,因此上述代码只实例化了 word,而没有实例化 i。在反序列化时只能读取到 word 的值,i 为默认值。6.System
24、.out.println()方法使用需要注意哪些问题 (分数:4.00)_正确答案:()解析:Java 中的 System.out.println()方法提供了一种非常有效简单的方法来实现控制台的输出,该方法默认接收一个字符串类型的变量作为参数。当然,在使用时可以传递任意能够转换为 String 类型的变量作为参数(例如基本类型 int,或者一个实现 toString 方法的自定义类等),示例如下: class Poeple private String name; private int age; public Poeple() this.name=“何昊“; this.age=26; pu
25、blic String toString() return “name:“ +this.name+“ age:“ +this age; class Test public static void main(stringargs) Systein.out.println(new Poeple(); System.out.println(1+2+“); System.out.println(“+1+2); 程序运行结果为: name:何昊 age:26 3 12 对于第一个输出语句来说,由于传入的参数是一个对象,因此会调用这个对象的 toString()方法,把返回的字符串打印出来。对于第二个输出
26、语句来说,参数中的+会由左到右顺序计算。首先计算 1+2,由于它们都是整型变量,因此计算结果为 3,接着计算 3+“,由于“是字符串,因此首先会把 3 转换为字符串,其次执行加操作,计算结果为“3”,因此输出结果为 3。对于最后一个输出语句来说,首先计算“+1,会把 1 转换为字符串,其次执行加操作,计算结果为“1”,同理,接着计算“1”+2 结果为“12”,因此输出结果为 12。7.为什么说 Java 是平台独立性语言 (分数:4.00)_正确答案:()解析:平台独立性是指可以在一个平台上编写和编译程序,而在其他平台上运行。保证。Java 具有平台独立性的机制为“中间码”和“Java 虚拟机
27、(Java Virtual Machine, JVM)”。Java 程序被编译后不是生成能在硬件平台上可执行的代码,而是生成了一个“中间码”。不同的硬件平台上会安装有不同的 JVM,由 JVM 来负责把“中间码”翻译成硬件平台能执行的代码。由此可以看出 JVM 不具有平台独立性,而是与硬件平台相关的。 解释执行过程分三步进行:代码的装入、代码的校验和代码的执行。装入代码的工作由“类装载器”完成。被装入的代码由字节码校验器进行检查。 Java 字节码的执行也分为两种方式:即时编译方式与解释执行方式,即时编译方式指的是解释器先将字节码编译成机器码,然后再执行该机器码。解释执行方式指的是解释器通过每
28、次解释并执行一小段代码来完成 Java 字节码程序的所有操作。通常采用的是解释执行方式。 而在 C/C+语言中,编译后的代码只能在特定的硬件上执行,换个硬件平台这些代码就无法执行了,从而也导致了 C/C+没有跨平台的特性。但 C/C+有更高的执行效率。 常见笔试题: 1一个 Java 程序运行从上到下的环境次序是_。 A操作系统、Java 程序、JRE/JVM、硬件 BJRE/JVM、Java 程序、硬件、操作系统 CJava 程序、JRE/JVM、操作系统、硬件 DJava 程序、操作系统、JRE/JVM、硬件 答案:C。见上面讲解。 2下列说法中,正确的是_。 AJava 程序经编译后会产
29、生机器码 BJava 程序经编译后会产生字节码 CJava 程序经编译后会产生 DLL D以上都不正确 答案:B。java 文件被 javac 指令编译为.class 后缀的字节码文件,再由 JVM 执行。8.Java 平台与其他语言平台有哪些区别 (分数:4.00)_正确答案:()解析:Java 平台是一个纯软件的平台,这个平台可以运行在一些基于硬件的平台(例如 Linux、Windows等)之上。Java 平台主要包含两个模块:JVM 与 Java API(Application Program Interface,应用程序接口)。 JVM 是一个虚构出来的计算机,用来把 Java 编译生
30、成的中间代码转换为机器可以识别的编码并运行。它有自己完善的硬件架构,例如处理器、堆栈、寄存器等,还具有相应的指令系统,它屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(即字节码),就可以在多种平台上不加修改地顺利运行。每当一个 Java 程序运行时,都会有一个对应的 JVM 实例,只有当程序运行结束后,这个 JVM 才会退出。JVM 实例通过调用类的 main()方法来启动一个 Java 程序,而这个 main()方法必须是公有的、静态的且返回值为 void 的方法,该方法接受一个字符串数组的参数,只有同时满足这些条件才可以作为程序的入口方法。 J
31、ava API 是 Java 为了方便开发人员进行开发而设计的,它提供了许多非常有用的接口,这些接口也是用Java 语言编写的,并且运行在 JVM 上。9.JVM 加载 class 文件的原理机制是什么 (分数:4.00)_正确答案:()解析:Java 语言是一种具有动态性的解释型语言,类(class)只有被加载到 JVM 中后才能运行。当运行指定程序时,JVM 会将编译生成的.class 文件按照需求和一定的规则加载到内存中,并组织成为一个完整的Java 应用程序。这个加载过程是由类加载器来完成的,具体来说,就是由 ClassLoader 和它的子类来实现的。类加载器本身也是一个类,其实质是
32、把类文件从硬盘读取到内存中。 类的加载方式分为隐式加载与显式加载两种。隐式加载指的是程序在使用 new 等方式创建对象时,会隐式地调用类的加载器把对应的类加载到 JVM 中。显式加载指的是通过直接调用 class.forName()方法来把所需的类加载到 JVM 中。 任何一个工程项目都是由许多个类组成的,当程序启动时,只把需要的类加载到 JVM 中,其他类只有被使用到的时候才会被加载,采用这种方法,一方面可以加快加载速度,另外一方面可以节约程序运行过程中对内存的开销。此外,在 Java 语言中,每个类或接口都对应一个.class 文件,这些文件可以被看成一个个可以被动态加载的单元,因此当只有
33、部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。 在 Java 语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到 JVM 中,至于其他类,则在需要时才加载。在 Java 语言中,可以把类分为3 类:系统类、扩展类和自定义类。Java 针对这 3 种不同的类提供了 3 种类型的加载器,这 3 种加载器的关系如下: Bootstrap Loader -负责加载系统类(jre/lib/rt.jar 的类) | -ExtClassLoder -负责加载扩展类(jar/lib/ext/*.jar 的
34、类) | -AppClassLoader -负责加载应用类 (classpath 指定的目录或 jar 中的类) 以上这 3 个类是如何协调工作来完成类的加载呢?其实,它们是通过委托的方式实现的。具体而言,就是当有类需要被加载时,类加载器会请求父类来完成这个载入工作,父类会使用其自己的搜索路径来搜索需要被载入的类,如果搜索不到,才会由子类按照其搜索路径来搜索待加载的类。下例可以充分说明类加载器的工作原理: public class TestLoader public static void main(stringargs) throws Exception /调用 class 加载器 Clas
35、sLoader clApp=TestLoader.class.getClassLoader(); System.out.printIn(clApp); /调用上一层 Class 加载器 ClassLoader clExt=clApp.getParent(); System.out.println(clExt); /调用根部 Class 加载器 ClassLoader clBoot=clExt.getParent(); Systerm.out.println(clBoot); 程序运行结果为: sun.misc.Launeher SAppClassLoader19821f sun.misc.La
36、uncher $ExtClassLoaderaddbfl null 从上例可以看出,TestLoader 类是由 AppClassLoader 来加载的。另外需要说明的一点是,由于Bootstrap Loader 是用 C+语言来实现的,因此,在 Java 语言中是看不到它的,所以此时程序会输出null。 类加载的主要步骤分为以下 3 步: 1)装载。根据查找路径找到相对应的 class 文件,然后导入。 2)链接。链接又可以分为 3 个小的步骤,具体如下。 检查。检查待加载的 class 文件的正确性。 准备。给类中的静态变量分配存储空间。 解析。将符号引用转换成直接引用(这一步是可选的)。
37、 3)初始化。对静态变量和静态代码块执行初始化工作。10.什么是 GC (分数:4.00)_正确答案:()解析:在 Java 语言中,垃圾回收(Garbage Collection,GC)是一个非常重要的概念,它的主要作用是回收程序中不再使用的内存。在使用 C/C+语言进行程序开发时,开发人员必须非常仔细地管理好内存的分配与释放,如果忘记或者错误地释放内存往往会导致程序运行不正常甚至是程序崩溃。为了减轻开发人员的工作,同时增加系统的安全性与稳定性,Java 语言提供了垃圾回收器来自动检测对象的作用域,可自动地把不再被使用的存储空间释放掉。具体而言,垃圾回收器要负责完成 3 项任务:分配内存、确
38、保被引用对象的内存不被错误地回收以及回收不再被引用的对象的内存空间。 垃圾回收器的存在一方面把开发人员从释放内存的复杂工作中解脱出来,提高了开发人员的生产效率;另一方面,对开发人员屏蔽了释放内存的方法,可以避免因开发人员错误地操作内存而导致应用程序的崩溃,保证了程序的稳定性。但是,垃圾回收也带来了问题,为了实现垃圾回收,垃圾回收器必须跟踪内存的使用情况,释放没用的对象,在完成内存的释放后还需要处理堆中的碎片,这些操作必定会增加 JVM 的负担,从而降低程序的执行效率。 对对象而言,如果没有任何变量去引用它,那么该对象将不可能被程序访问,因此可以认为它是垃圾信息,可以被回收。只要有一个以上的变量引用该对象,该对象就不会被垃圾回收。 对于垃圾回收器来说,它使用有向图来记录和管理堆内存中的所有对象,通过这个有向图就可以识别哪些对象是“可达的”(有引用变量引用它就是“可达的”),哪些对象是“不可达的”(没有引用变量引用它就是不可达的),所有“不可达”对象都是可被垃圾回收的,示例如下: public class Test public staric void main(Stringa) Integer i1=new Integer(1); Integer