java工程师面试题
来源:SegmentFault
时间:2023-01-19 21:27:47 496浏览 收藏
本篇文章给大家分享《java工程师面试题》,覆盖了数据库的常见基础知识,其实一个语言的全部知识点一篇文章是不可能说完的,但希望通过这些问题,让读者对自己的掌握程度有一定的认识(B 数),从而弥补自己的不足,更好的掌握它。
内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、 Redis、MySQL、Spring、Spring Boot、SpringCloud、RabbitMQ、Kafka、 Linux 等
MyBatis 面试题
1、什么是Mybatis?
1、Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时 只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性 能,灵活度高。
2、MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数 据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
3、通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最 后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。(从执行 sql 到返 回 result 的过程)。
2、Mybaits的优点:
第 34 页 共 485 页
1、基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任 何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
2、与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不 需要手动开关连接;
3、很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持)。
4、能够与 Spring 很好的集成;
5、提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射 标签,支持对象关系组件维护。
3、MyBatis框架的缺点:
1、SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求。
2、SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
4、MyBatis框架适用场合:
1、MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。
2、对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis 将是 不错的选择。
第 35 页 共 485 页
5、MyBatis与Hibernate有哪些不同?
1、Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要 程序员自己编写 Sql 语句。
2、Mybatis 直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高,非常 适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需 求变化要求迅速输出成果。但是灵活的前提是 mybatis 无法做到数据库无关性, 如果需要实现支持多种数据库的软件,则需要自定义多套 sql 映射文件,工作量大。
3、Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的 软件,如果用 hibernate 开发可以节省很多代码,提高效率。
6、#{}和${}的区别是什么?
{}是预编译处理,${}是字符串替换。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
Mybatis 在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止 SQL 注入,提高系统安全性。
7、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第 1 种: 通过在查询的 sql 语句中定义字段名的别名,让字段名的别名和实体类 的属性名一致。
第 36 页 共 485 页
第 2 种: 通过
8、 模糊查询like语句该怎么写?
第 1 种:在 Java 代码中添加 sql 通配符。
string wildcardname = “%smi%”; list
第 37 页 共 485 页
第 2 种:在 sql 语句中拼接通配符,会引起 sql 注入
string wildcardname = “smi”; list
9、通常一个Xml映射文件,都会写一个Dao接口与之对应,
请问,这个Dao接口的工作原理是什么?Dao接口里的方法,
参数不同时,方法能重载吗?
Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值; 接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的 参数,就是传递给 sql 的参数。
Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符 串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个
- waiting to lock (a java.lang.Object) - locked (a java.lang.Object)
"Thread-0"#11prio=5os_prio=0 tid=0x0000000018d44000nid=0x1ebc waiting for monitor entry [0x000000001907f000] java.lang.Thread.State: BLOCKED (on object monitor) at com.leo.interview.SimpleDeadLock$A.run(SimpleDeadLock.java:34) - waiting to lock (a java.lang.Object) - locked (a java.lang.Object)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000000018ca5000 nid=0x1264 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000000018c46000 nid=0xb8c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000018be4800 nid=0x1db4 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000018be3800 nid=0x810 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
第 169 页 共 485 页
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000018bcc800 nid=0x1c24 runnable [0x00000000193ce000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) - locked (a java.io.InputStreamReader) at java.io.InputStreamReader.read(InputStreamReader.java:184) at java.io.BufferedReader.fill(BufferedReader.java:161) at java.io.BufferedReader.readLine(BufferedReader.java:324) - locked (a java.io.InputStreamReader) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:6 4)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000017781800 nid=0x524 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001778f800 nid=0x1b08 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
第 170 页 共 485 页
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001776a800 nid=0xdac in Object.wait() [0x0000000018b6f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000017723800 nid=0x1670 in Object.wait() [0x00000000189ef000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000000001771b800 nid=0x604 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000001c9d800 nid=0x9f0 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000001c9f000 nid=0x154c runnable
第 171 页 共 485 页
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000001ca0800 nid=0xcd0 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000001ca2000 nid=0x1e58 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000018c5a000 nid=0x1b58 waiting on condition
JNI global references: 33
/ 此处可以看待死锁的相关信息! / Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x0000000017729fc8 (object 0x00000000d629b4d8, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x0000000017727738 (object 0x00000000d629b4e8, a java.lang.Object), which is held by "Thread-1"
Java stack information for the threads listed above: ============================================== ===== "Thread-1": at com.leo.interview.SimpleDeadLock$B.run(SimpleDeadLock.java:56)
第 172 页 共 485 页
- waiting to lock (a java.lang.Object) - locked (a java.lang.Object) "Thread-0": at com.leo.interview.SimpleDeadLock$A.run(SimpleDeadLock.java:34) - waiting to lock (a java.lang.Object) - locked (a java.lang.Object)
Found 1 deadlock.
/ 内存使用状况,详情得看JVM方面的书 / Heap PSYoungGen total 37888K, used 4590K [0x00000000d6100000, 0x00000000d8b00000, 0x0000000100000000) eden space 32768K, 14% used [0x00000000d6100000,0x00000000d657b968,0x00000000d8100000) from space 5120K, 0% used [0x00000000d8600000,0x00000000d8600000,0x00000000d8b00000) to space 5120K, 0% used [0x00000000d8100000,0x00000000d8100000,0x00000000d8600000) ParOldGen total 86016K, used 0K [0x0000000082200000, 0x0000000087600000, 0x00000000d6100000) object space 86016K, 0% used [0x0000000082200000,0x0000000082200000,0x0000000087600000) Metaspace used 3474K, capacity 4500K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576K
第 173 页 共 485 页
20、为什么我们调用start()方法时会执行run()方法,为什么
我们不能直接调用run()方法?
当你调用 start()方法时你将创建新的线程,并且执行在 run()方法里的代码。 但是如果你直接调用 run()方法,它不会创建新的线程也不会执行调用线程的代码, 只会把 run 方法当作普通方法去执行。
21、Java中你怎样唤醒一个阻塞的线程?
在 Java 发展史上曾经使用 suspend()、resume()方法对于线程进行阻塞唤醒,但 随之出现很多问题,比较典型的还是死锁问题。 解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait()和 notify()方 法实现线程阻塞。 首先,wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将导致线程 阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则 将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才 能往下执行;其次,wait、notify 方法必须在 synchronized 块或方法中被调用, 并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如 此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当 前线程就将之前获取的对象锁释放。
22、在Java中CycliBarriar和CountdownLatch有什么区
别?
CyclicBarrier 可以重复使用,而 CountdownLatch 不能重复使用。
第 174 页 共 485 页
Java 的 concurrent 包里面的 CountDownLatch 其实可以把它看作一个计数器, 只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器, 也就是同时只能有一个线程去减这个计数器里面的值。 你可以向 CountDownLatch 对象设置一个初始的数字作为计数值,任何调用这个 对象上的 await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为 0 为 止。 所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待 的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法 被重置。如果需要重置计数,请考虑使用 CyclicBarrier。 CountDownLatch 的一个非常典型的应用场景是:有一个任务想要往下执行,但 必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续 往下执行的任务调用一个 CountDownLatch 对象的 await()方法,其他的任务执 行完自己的任务后调用同一个 CountDownLatch 对象上的 countDown()方法, 这个调用 await()方法的任务将一直阻塞等待,直到这个 CountDownLatch 对象 的计数值减到 0 为止。
CyclicBarrier 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏 障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程 必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待 线程后可以重用,所以称它为循环 的 barrier。
23、什么是不可变对象,它对写并发应用有什么帮助?
不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即 对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。 不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可 变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。 不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的。既然 它们的状态无法修改,这些常量永远不会变。
第 175 页 共 485 页
不可变对象永远是线程安全的。 只有满足如下状态,一个对象才是不可变的; 它的状态不能在创建后再被修改; 所有域都是 final 类型;并且, 它被正确创建(创建期间没有发生 this 引用的逸出)。
24、什么是多线程中的上下文切换?
在上下文切换过程中,CPU 会停止处理当前运行的程序,并保存当前程序运行的 具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读 几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中, 上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB 还经 常被称作“切换桢”(switchframe)。“页码”信息会一直保存到 CPU 的内存 中,直到他们被再次使用。 上下文切换是存储和恢复 CPU 状态的过程,它使得线程执行能够从中断点恢复执 行。上下文切换是多任务操作系统和多线程环境的基本特征。
25、Java中用到的线程调度算法是什么?
计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得 CPU 的使用权才能执行指令.所谓多线程的并发运行,其实是指从宏观上看,各个线 程轮流获得 CPU 的使用权,分别执行各自的任务.在运行池中,会有多个处于就绪状 态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指 按照特定机制为多个线程分配 CPU 的使用权.
有两种调度模型:分时调度模型和抢占式调度模型。 分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占 用的 CPU 的时间片这个也比较好理解。
第 176 页 共 485 页
java 虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用 CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。
26、什么是线程组,为什么在Java中不推荐使用?
线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程 的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。
27、为什么使用Executor框架比使用应用创建和管理线程好?
为什么要使用 Executor 线程池框架 1、每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗 时、耗资源的。 2、调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的 创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程 之间的频繁交替也会消耗很多系统资源。 3、直接使用 new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、 定时定期执行、线程中断等都不便实现。
使用 Executor 线程池框架的优点
1、能复用已存在并空闲的线程从而减少线程对象的创建从而减少了消亡线程的开 销。 2、可有效控制最大并发线程数,提高系统资源使用率,同时避免过多资源竞争。 3、框架中已经有定时、定期、单线程、并发数控制等功能。 综上所述使用线程池框架 Executor 能更好的管理线程、提供系统资源使用率。
第 177 页 共 485 页
28、java中有几种方法可以实现一个线程?
继承 Thread 类 实现 Runnable 接口 实现 Callable 接口,需要实现的是 call() 方法
29、如何停止一个正在运行的线程?
使用共享变量的方式 在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的 线程用来作为是否中断的信号,通知中断线程的执行。
使用 interrupt 方法终止线程
如果一个线程由于等待某些事件的发生而被阻塞,又该怎样停止该线程呢?这种 情况经常会发生,比如当一个线程由于需要等候键盘输入而被阻塞,或者调用 Thread.join()方法,或者 Thread.sleep()方法,在网络中调用 ServerSocket.accept()方法,或者调用了 DatagramSocket.receive()方法时,都 有可能导致线程阻塞,使线程处于处于不可运行状态时,即使主程序中将该线程 的共享变量设置为 true,但该线程此时根本无法检查循环标志,当然也就无法立 即中断。这里我们给出的建议是,不要使用 stop()方法,而是使用 Thread 提供的 interrupt()方法,因为该方法虽然不会中断一个正在运行的线程,但是它可以使一 个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代 码。
30、notify()和notifyAll()有什么区别?
第 178 页 共 485 页
当一个线程进入 wait 之后,就必须等其他线程 notify/notifyall,使用 notifyall,可 以唤醒所有处于 wait 状态的线程,使其重新进入锁的争夺队列中,而 notify 只能 唤醒一个。
如果没把握,建议 notifyAll,防止 notigy 因为信号丢失而造成程序异常。
31、什么是Daemon线程?它有什么意义?
所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线 程,并且这个线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程 结束时,程序也就终止了,同时会杀死进程中的所有后台线程。反过来说, 只要有任何非后台线程还在运行,程序就不会终止。必须在线程启动之前调用 setDaemon()方法,才能把它设置为后台线程。注意:后台进程在不执行 finally 子句的情况下就会终止其 run()方法。
比如:JVM 的垃圾回收线程就是 Daemon 线程,Finalizer 也是守护线程。
32、java如何实现多线程之间的通讯和协作?
中断 和 共享变量
33、什么是可重入锁(ReentrantLock)?
举例来说明锁的可重入性
public class UnReentrant{ Lock lock = new Lock(); public void outer(){
第 179 页 共 485 页
lock.lock(); inner(); lock.unlock();
} public void inner(){ lock.lock(); //do something lock.unlock(); }
}
outer 中调用了 inner,outer 先锁住了 lock,这样 inner 就不能再获取 lock。其 实调用 outer 的线程已经获取了 lock 锁,但是不能在 inner 中重复利用已经获取 的锁资源,这种锁即称之为 不可重入可重入就意味着:线程可以进入任何一个它 已经拥有的锁所同步着的代码块。
synchronized、ReentrantLock 都是可重入的锁,可重入锁相对来说简化了并发 编程的开发。
34、当一个线程进入某个对象的一个synchronized的实例方
法后,其它线程是否可进入此对象的其它方法?
如果其他方法没有 synchronized 的话,其他线程是可以进入的。
所以要开放一个线程安全的对象时,得保证每个方法都是线程安全的。
35、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
第 180 页 共 485 页
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每 次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传 统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写 锁等,都是在做操作之前先上锁。再比如 Java 里面的同步原语 synchronized 关 键字的实现也是悲观锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所 以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据, 可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量, 像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实 现方式 CAS 实现的。
乐观锁的实现方式: 1、使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标 识,不一致时可以采取丢弃和再次尝试的策略。 2、java 中的 Compare and Swap 即 CAS ,当多个线程尝试使用 CAS 同时更新 同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的 线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作 中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A) 和拟写入的新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自 动将该位置值更新为新值 B。否则处理器不做任何操作。
CAS 缺点:
1、ABA 问题: 比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中 取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A, 这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线 程 one 的 CAS 操作成功,但可能存在潜藏的问题。从 Java1.5 开始 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
第 181 页 共 485 页
2、循环时间长开销大: 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪 费更多的 CPU 资源,效率低于 synchronized。 3、只能保证一个共享变量的原子操作: 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作, 但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可 以用锁。
36、SynchronizedMap和ConcurrentHashMap有什么区
别?
SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来 访为 map。
ConcurrentHashMap 使用分段锁来保证在多线程下的性能。 ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默认将 hash 表分为 16 个桶,诸如 get,put,remove 等常用操作只锁当前需要用到的桶。 这样,原来只能一个线程进入,现在却能同时有 16 个写线程执行,并发性能的提 升是显而易见的。 另外 ConcurrentHashMap 使用了一种不同的迭代方式。在这种迭代方式中,当 iterator 被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,取而代之的是在改变时 new 新的数据从而 不影响原有的数据 ,iterator 完成后再将头指针替换为新的数据 ,这样 iterator 线程可以使用原来老的数据,而写线程也可以并发的完成改变。
37、CopyOnWriteArrayList可以用于什么应用场景?
第 182 页 共 485 页
CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这 个列表时,不会抛出 ConcurrentModificationException。在 CopyOnWriteArrayList 中,写入将导致创建整个底层数组的副本,而源数组将保 留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。
1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的 情况下,可能导致 young gc 或者 full gc; 2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set 操作后,读取到数据可能还是旧的,虽然 CopyOnWriteArrayList 能做到最终一致 性,但是还是没法满足实时性要求;
CopyOnWriteArrayList 透露的思想 1、读写分离,读和写分开 2、最终一致性 3、使用另外开辟空间的思路,来解决并发冲突
38、什么叫线程安全?servlet是线程安全吗?
线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够 正确地处理多个线程之间的共享变量,使程序功能正确完成。
Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一个 方法,是不能保证共享变量的线程安全性的。 Struts2 的 action 是多实例多线程的,是线程安全的,每个请求过来都会 new 一 个新的 action 分配给这个请求,请求完成后销毁。 SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 类似的处理流程。
Struts2 好处是不用考虑线程安全问题;Servlet 和 SpringMVC 需要考虑线程安 全问题,但是性能可以提升不用处理太多的 gc,可以使用 ThreadLocal 来处理多 线程的问题。
第 183 页 共 485 页
39、volatile有什么用?能否用一句话说明下volatile的应用
场景?
volatile 保证内存可见性和禁止指令重排。
volatile 用于多线程环境下的单次操作(单次读或者单次写)。
40、为什么代码会重排序?
在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是 不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:
在单线程环境下不能改变程序运行的结果; 存在数据依赖关系的不允许重排序 需要注意的是:重排序不会影响单线程环境的执行结果,但是会破坏多线程的执 行语义。
41、在java中wait和sleep方法的不同?
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。Wait 通常被用于线 程间交互,sleep 通常被用于暂停执行。
直接了解的深入一点吧:
在 Java 中线程的状态一共被分成 6 种:
第 184 页 共 485 页
初始态:NEW
创建一个 Thread 对象,但还未调用 start()启动线程时,线程处于初始态。
运行态:RUNNABLE 在 Java 中,运行态包括就绪态 和 运行态。 就绪态 该状态下的线程已经获得执行所需的所有资源,只要 CPU 分配执行权就 能运行。所有就绪态的线程存放在就绪队列中。 运行态 获得 CPU 执行权,正在执行的线程。由于一个 CPU 同一时刻只能执行一 条线程,因此每个 CPU 每个时刻只有一条运行态的线程。
阻塞态
当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。而在 Java 中,阻 塞态专指请求锁失败时进入的状态。由一个阻塞队列存放所有阻塞态的线程。处 于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。 PS:锁、IO、Socket 等都资源。
等待态
当前线程中调用 wait、join、park 函数时,当前线程就会进入等待态。也有一个 等待队列存放所有等待态的线程。线程处于等待态表示它需要等待其他线程的指 示才能继续运行。进入等待态的线程会释放 CPU 执行权,并释放资源(如:锁)
超时等待态
当运行中的线程调用 sleep(time)、wait、join、parkNanos、parkUntil 时,就 会进入该状态;它和等待态一样,并不是因为请求不到资源,而是主动进入,并 且进入后需要其他线程唤醒;进入该状态后释放 CPU 执行权 和 占有的资源。与 等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
第 185 页 共 485 页
终止态
线程执行结束后的状态。
注意:
wait()方法会释放 CPU 执行权 和 占有的锁。 sleep(long)方法仅释放 CPU 使用权,锁仍然占用;线程被放入超时等待队列,与 yield 相比,它会使线程较长时间得不到运行。 yield()方法仅释放 CPU 执行权,锁仍然占用,线程会被放入就绪队列,会在短时 间内再次执行。 wait 和 notify 必须配套使用,即必须使用同一把锁调用; wait 和 notify 必须放在一个同步块中调用 wait 和 notify 的对象必须是他们所处 同步块的锁对象。
42、用Java实现阻塞队列
参考 java 中的阻塞队列的内容吧,直接实现有点烦: http://www.infoq.com/cn/artic...
43、一个线程运行时发生异常会怎样?
如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler 是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异 常将造成线程中断的时候 JVM 会使用 Thread.getUncaughtExceptionHandler() 来查询线程的 UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler 的 uncaughtException()方法进行处理。
第 186 页 共 485 页
44、如何在两个线程间共享数据?
在两个线程间共享变量即可实现共享。 一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如 果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。
45、Java中notify 和 notifyAll有什么区别?
notify() 方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有 用武之地。而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程 能继续运行。
46、为什么wait, notify 和 notifyAll这些方法不在thread
类里面?
一个很明显的原因是 JAVA 提供的锁是对象级的而不是线程级的,每个对象都有 锁,通过线程获得。由于 wait,notify 和 notifyAll 都是锁级别的操作,所以把他 们定义在 Object 类中因为锁属于对象。
47、什么是ThreadLocal变量?
ThreadLocal 是 Java 里一种特殊的变量。每个线程都有一个 ThreadLocal 就是每 个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。它是为创建代价 高昂的对象获取线程安全的好方法,比如你可以用 ThreadLocal 让 SimpleDateFormat 变成线程安全的,因为那个类创建代价高昂且每次调用都需 要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己
第 187 页 共 485 页
独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创 建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。
48、Java中interrupted 和 isInterrupted方法的区别?
interrupt
interrupt 方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。 注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监 视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出 interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状 态被置为“中断状态”,就会抛出中断异常。
interrupted
查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调 用 interrupted 则返回 true,第二次和后面的就返回 false 了。
isInterrupted
仅仅是查询当前线程的中断状态
49、为什么wait和notify方法要在同步块中调用?
Java API 强制要求这样做,如果你不这么做,你的代码会抛出 IllegalMonitorStateException 异常。还有一个原因是为了避免 wait 和 notify 之间产生竞态条件。
第 188 页 共 485 页
50、为什么你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条 件,程序就会在没有满足结束条件的情况下退出。
51、Java中的同步集合与并发集合有什么区别?
同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发 集合的可扩展性更高。在 Java1.5 之前程序员们只有同步集合来用且在多线程并发 的时候会导致争用,阻碍了系统的扩展性。Java5 介绍了并发集合像 ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高 了可扩展性。
52、什么是线程池? 为什么要使用它?
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变 长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候 就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供了 Executor 框架让你可以创建不同的线程池。
53、怎么检测一个线程是否拥有锁?
在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当 前线程拥有某个具体对象的锁。
54、你如何在Java中获取线程堆栈?
第 189 页 共 485 页
kill -3 [java pid] 不会在当前终端输出,它会输出到代码执行的或指定的地方去。比如,kill -3 tomcat pid, 输出堆栈到 log 目录下。 Jstack [java pid] 这个比较简单,在当前终端显示,也可以重定向到指定文件中。 -JvisualVM:Thread Dump 不做说明,打开 JvisualVM 后,都是界面操作,过程还是很简单的。 55、JVM 中哪个参数是用来控制线程的栈堆栈小的? -Xss 每个线程的栈大小
56、Thread类中的yield方法有什么作用?
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可 能是当前线程,也可能是其他线程,看系统的分配了。
57、Java中ConcurrentHashMap的并发度是什么?
ConcurrentHashMap 把实际 map 划分成若干部分来实现它的可扩展性和线程安 全。这种划分是使用并发度获得的,它是 ConcurrentHashMap 类构造函数的一 个可选参数,默认值为 16,这样在多线程情况下就能避免争用。
在 JDK8 后,它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实 现,利用 CAS 算法。同时加入了更多的辅助变量来提高并发度,具体内容还是查看 源码吧。
第 190 页 共 485 页
58、Java中Semaphore是什么?
Java 中的 Semaphore 是一种新的同步类,它是一个计数信号。从概念上讲,从 概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个 正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的 号码进行计数,并采取相应的行动。信号量常常用于多线程的代码中,比如数据 库连接池。
59、Java线程池中submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务,execute()方法的返回类型是 void,它定义在 Executor 接口中。
而 submit()方法可以返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。
60、什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的 accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前 线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在 任务完成前就返回。
61、Java中的ReadWriteLock是什么?
第 191 页 共 485 页
读写锁是用来提升并发程序性能的锁分离技术的成果。
62、volatile 变量和 atomic 变量有什么不同?
Volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不 能保证原子性。例如用 volatile 修饰 count 变量那么 count++ 操作就不是原子 性的。
而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如 getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型 和引用变量也可以进行相似操作。
63、可以直接调用Thread类的run ()方法么?
当然可以。但是如果我们调用了 Thread 的 run()方法,它的行为就会和普通的方 法一样,会在当前线程中执行。为了在新的线程中执行我们的代码,必须使用 Thread.start()方法。
64、如何让正在运行的线程暂停一段时间?
我们可以使用 Thread 类的 Sleep()方法让线程暂停一段时间。需要注意的是,这 并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为 Runnable, 并且根据线程调度,它将得到执行。
65、你对线程优先级的理解是什么?
第 192 页 共 485 页
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权, 但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我 们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线 程前执行。线程优先级是一个 int 变量(从 1-10),1 代表最低优先级,10 代表最 高优先级。
java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级 有关,如非特别需要,一般无需设置线程优先级。
66、什么是线程调度器(Thread Scheduler)和时间分片(Time
Slicing )?
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。 一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。 同上一个问题,线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是 更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
67、你如何确保main()方法所在的线程是Java 程序最后结束
的线程?
我们可以使用 Thread类的 join()方法来确保所有程序创建的线程在 main()方法退 出前结束。
第 193 页 共 485 页
68、线程之间是如何通信的?
当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。Object 类中 wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。
69、为什么线程通信的方法wait(),notify()和notifyAll()被定
义在Object 类里?
Java 的每个对象中都有一个锁(monitor,也可以成为监视器) 并且 wait(),notify() 等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在 Java 的线程中 并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是 Object 类的一 部分,这样 Java 的每一个类都有用于线程间通信的基本方法。
70、为什么wait(), notify()和notifyAll ()必须在同步方法或
者同步块中被调用?
当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接 着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify() 方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的 锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要 线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者 同步块中被调用。
71、为什么Thread类的sleep()和yield ()方法是静态的?
第 194 页 共 485 页
Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他 处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静 态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在 其他非运行线程调用这些方法。
72、如何确保线程安全?
在 Java 中可以有很多方法来保证线程安全——同步,使用原子类(atomic concurrent classes),实现并发锁,使用 volatile 关键字,使用不变类和线程安 全类。
73、同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对 象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通 常会导致他们停止执行并需要等待获得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样 从侧面来说也可以避免死锁。
74、如何创建守护线程?
使用 Thread 类的 setDaemon(true)方法可以将线程设置为守护线程,需要注意 的是,需要在调用 start()方法前调用这个方法,否则会抛出 IllegalThreadStateException 异常。
第 195 页 共 485 页
75、什么是Java Timer 类?如何创建一个有特定时间间隔的
任务?
java.util.Timer 是一个工具类,可以用于安排一个线程在未来的某个特定时间执 行。Timer 类可以用安排一次性任务或者周期任务。 java.util.TimerTask 是一个实现了 Runnable 接口的抽象类,我们需要去继承这 个类来创建我们自己的定时任务并使用 Timer 去安排它的执行。
Java 并发编程(二)
1、并发编程三要素?
1、原子性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操 作打断,要么就全部都不执行。 2、可见性 可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他 线程可以立即看到修改的结果。 3、有序性 有序性,即程序的执行顺序按照代码的先后顺序来执行。
2、实现可见性的方法有哪些?
synchronized 或者 Lock:保证同一个时刻只有一个线程获取锁执行代码,锁释放 之前把最新的值刷新到主内存,实现可见性。
第 196 页 共 485 页
3、多线程的价值?
1、发挥多核 CPU 的优势 多线程,可以真正发挥出多核 CPU 的优势来,达到充分利用 CPU 的目的,采用多 线程的方式去同时完成几件事情而不互相干扰。 2、防止阻塞 从程序运行效率的角度来看,单核 CPU 不但不会发挥出多线程的优势,反而会因 为在单核 CPU 上运行多线程导致线程上下文的切换,而降低程序整体的效率。但 是单核 CPU 我们还是要应用多线程,就是为了防止阻塞。试想,如果单核 CPU 使 用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未 返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。 多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数 据阻塞,也不会影响其它任务的执行。 3、便于建模 这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么 就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务 A 分解成 几个小任务,任务 B、任务 C、任务 D,分别建立程序模型,并通过多线程分别运 行这几个任务,那就简单很多了。
4、创建线程的有哪些方式?
1、继承 Thread 类创建线程类 2、通过 Runnable 接口创建线程类 3、通过 Callable 和 Future 创建线程 4、通过线程池创建
5、创建线程的三种方式的对比?
第 197 页 共 485 页
1、采用实现 Runnable、Callable 接口的方式创建多线程。 优势是: 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。 在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线 程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模 型,较好地体现了面向对象的思想。 劣势是: 编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread()方法。 2、使用继承 Thread 类的方式创建多线程 优势是: 编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法, 直接使用 this 即可获得当前线程。 劣势是: 线程类已经继承了 Thread 类,所以不能再继承其他父类。 3、Runnable 和 Callable 的区别 1、Callable 规定(重写)的方法是 call(),Runnable 规定(重写)的方法是 run()。 2、Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。 3、Call 方法可以抛出异常,run 方法不可以。 4、运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供 了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
6、线程的状态流转图
线程的生命周期及五种基本状态: img_2.png
7、Java线程具有五中基本状态
第 198 页 共 485 页
1、新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); 2、就绪状态(Runnable):当调用线程对象的 start()方法(t.start();),线程 即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时 等待 CPU 调度执行,并不是说执行了 t.start()此线程立即就会执行; 3、运行状态(Running):当 CPU 开始调度处于就绪状态的线程时,此时线程 才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入 口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中; 4、阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再 次被 CPU 调用以进入到运行状态。 根据阻塞产生的原因不同,阻塞状态又可以分为三种: 1、等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态; 2、同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用), 它会进入同步阻塞状态; 3、其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入 到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理 完毕时,线程重新转入就绪状态。 5、死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束 生命周期。
8、什么是线程池?有哪几种创建方式?
线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处 理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁 线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考 虑使用线程池来提升系统的性能。 java 提供了一个 java.util.concurrent.Executor 接口的实现用于创建线程池。
第 199 页 共 485 页
9、四种线程池的创建:
1、newCachedThreadPool 创建一个可缓存线程池 2、newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。 3、newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执 行。 4、newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工 作线程来执行任务。
10、线程池的优点?
1、重用存在的线程,减少对象创建销毁的开销。 2、可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞 争,避免堵塞。 3、提供定时执行、定期执行、单线程、并发数控制等功能。
11、常用的并发工具类有哪些?
1、CountDownLatch 2、CyclicBarrier 3、Semaphore 4、Exchanger
12、CyclicBarrier和CountDownLatch的区别
1、CountDownLatch 简单的说就是一个线程等待,直到他所等待的其他线程都执 行完成并且调用 countDown()方法发出通知后,当前线程才可以继续执行。
第 200 页 共 485 页
2、cyclicBarrier 是所有线程都进行等待,直到所有线程都准备好进入 await()方 法之后,所有线程同时开始执行! 3、CountDownLatch 的计数器只能使用一次。而 CyclicBarrier 的计数器可以使 用 reset() 方法重置。所以 CyclicBarrier 能处理更为复杂的业务场景,比如如果 计算发生错误,可以重置计数器,并让线程们重新执行一次。 4、CyclicBarrier 还提供其他有用的方法,比如 getNumberWaiting 方法可以获 得 CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。 如果被中断返回 true,否则返回 false。
13、synchronized的作用?
在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境 下,控制 synchronized 代码段不被多个线程同时执行。 synchronized 既可以加在一段代码上,也可以加在方法上。
14、volatile关键字的作用
对于可见性,Java 提供了 volatile 关键字来保证可见性。 当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当 有其他线程需要读取时,它会去内存中读取新值。 从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详 细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。
15、什么是CAS
CAS 是 compare and swap 的缩写,即我们所说的比较交换。 cas 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲 观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访
第 201 页 共 485 页
问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通 过给记录加 version 来获取数据,性能较悲观锁有很大的提高。 CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如 果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS 是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被 b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。 java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的 ( AtomicInteger,AtomicBoolean,AtomicLong)。
16、CAS的问题
1、CAS 容易造成 ABA 问题 一个线程 a 将数值改成了 b,接着又改成了 a,此时 CAS 认为是没有变化,其实 是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次 version 加 1。在 java5 中,已经提供了 AtomicStampedReference 来解决问题。 2、不能保证代码块的原子性 CAS 机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。 比如需要保证 3 个变量共同进行原子性的更新,就不得不使用 synchronized 了。 3、CAS 造成 CPU 利用率增加 之前说过了 CAS 里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu 资源会一直被占用。
17、什么是Future?
在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不 管是继承 thread 类还是实现 runnable 接口,都无法保证获取到之前的执行结果。 通过实现 Callback 接口,并用 Future 可以来接收多线程的执行结果。 Future 表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加 Callback 以便在任务执行成功或失败后作出相应的操作。
第 202 页 共 485 页
18、什么是AQS
AQS 是 AbustactQueuedSynchronizer 的简称,它是一个 Java 提高的底层同步 工具类,用一个 int 类型的变量表示同步状态,并提供了一系列的 CAS 操作来管 理这个同步状态。 AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广 泛的大量的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等皆是基于 AQS 的。
19、AQS支持两种同步方式:
1、独占式 2、共享式 这样方便使用者实现不同类型的同步组件,独占式如 ReentrantLock,共享式如 Semaphore,CountDownLatch,组合式的如 ReentrantReadWriteLock。总之, AQS 为使用提供了底层支撑,如何组装实现,使用者可以自由发挥。
20、ReadWriteLock是什么
首先明确一下,不是说 ReentrantLock 不好,只是 ReentrantLock 某些时候有局 限。如果使用 ReentrantLock,可能本身是为了防止线程 A 在写数据、线程 B 在 读数据造成的数据不一致,但这样,如果线程 C 在读数据、线程 D 也在读数据, 读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。 因为这个,才诞生了读写锁 ReadWriteLock。ReadWriteLock 是一个读写锁接口, ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体实现,实现了读写
第 203 页 共 485 页
的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、 写和写之间才会互斥,提升了读写的性能。
21、FutureTask是什么
这个其实前面有提到过,FutureTask 表示一个异步运算的任务。FutureTask 里面 可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等 待获取、判断是否已经完成、取消任务等操作。当然,由于 FutureTask 也是 Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。
22、synchronized和ReentrantLock的区别
synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类, 这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的 类变量,ReentrantLock 比 synchronized 的扩展性体现在几点上: 1、ReentrantLock 可以对获取锁的等待时间进行设置,这样就避免了死锁 2、ReentrantLock 可以获取各种锁的信息 3、ReentrantLock 可以灵活地实现多路通知 另外,二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的 park 方法加锁,synchronized 操作的应该是对象头中 mark word,这点我不能 确定。
23、什么是乐观锁和悲观锁
1、乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态, 乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作
第 204 页 共 485 页
为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应 该有相应的重试逻辑。 2、悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状 态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个 独占的锁,就像 synchronized,不管三七二十一,直接上了锁就操作资源了。
24、线程B怎么知道线程A修改了变量
1、volatile 修饰变量 2、synchronized 修饰修改变量的方法 3、wait/notify 4、while 轮询
25、synchronized、volatile、CAS比较
1、synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。 2、volatile 提供多线程共享变量可见性和禁止指令重排序优化。 3、CAS 是基于冲突检测的乐观锁(非阻塞)
26、sleep方法和wait方法有什么区别?
这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点 在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器, wait 方法会放弃这个对象的监视器
27、ThreadLocal是什么?有什么用?
第 205 页 共 485 页
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存 放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可 以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。 简单说 ThreadLocal 就是一种以空间换时间的做法,在每个 Thread 里面维护了 一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据 不共享,自然就没有线程安全方面的问题了。
28、为什么wait()方法和notify()/notifyAll()方法要在同步块
中被调用
这是 JDK 强制的,wait()方法和 notify()/notifyAll()方法在调用前都必须先获得对 象的锁
29、多线程同步有哪几种方法?
Synchronized 关键字,Lock 锁实现,分布式锁等。
30、线程的调度策略
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线 程的运行: 1、线程体中调用了 yield 方法让出了对 cpu 的占用权利 2、线程体中调用了 sleep 方法使线程进入睡眠状态 3、线程由于 IO 操作受到阻塞 4、另外一个更高优先级线程出现 5)在支持时间片的系统中,该线程的时间片用完
第 206 页 共 485 页
31、ConcurrentHashMap的并发度是什么
ConcurrentHashMap 的并发度就是 segment 的大小,默认为 16,这意味着最 多同时可以有 16 条线程操作 ConcurrentHashMap,这也是 ConcurrentHashMap 对 Hashtable 的最大优势,任何情况下,Hashtable 能同 时有两条线程获取 Hashtable 中的数据吗?
32、Linux环境下如何查找哪个线程使用CPU最长
1、获取项目的 pid,jps 或者 ps -ef | grep java,这个前面有讲过 2、top -H -p pid,顺序不能改变
33、Java死锁以及如何避免?
Java 中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java 死锁情况 出现至少两个线程和两个或更多资源。 Java 发生死锁的根本原因是:在申请锁时发生了交叉闭环申请。
34、死锁的原因
1、是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖 的闭环。 例如:线程在获得了锁 A 并且没有释放的情况下去申请锁 B,这时,另一个线程 已经获得了锁 B,在释放锁 B 之前又要先获得锁 A,因此闭环发生,陷入死锁循环。 2、默认的锁申请操作是阻塞的。
第 207 页 共 485 页
所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对 象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免 在一个同步方法中调用其它对象的延时方法和同步方法。
35、怎么唤醒一个阻塞的线程
如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线 程,并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞, 无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。
36、不可变对象对多线程有什么帮助
前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象 的读取不需要进行额外的同步手段,提升了代码执行效率。
37、什么是多线程的上下文切换
多线程的上下文切换是指 CPU 控制权由一个已经正在运行的线程切换到另外一个 就绪并等待获取 CPU 执行权的线程的过程。
38、如果你提交任务时,线程池队列已满,这时会发生什么
这里区分一下: 1、如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关 系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎 认为是一个无穷大的队列,可以无限存放任务
第 208 页 共 485 页
2、如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来, ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
39、Java中用到的线程调度算法是什么
抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等 数据算出一个总的优先级并分配下一个时间片给某个线程执行。
40、什么是线程调度器(Thread Scheduler)和时间分片(Time
Slicing)?
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。 一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分 片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可 以基于线程优先级或者线程等待的时间。线程调度并不受到 Java 虚拟机控制,所 以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优 先级)。
41、什么是自旋
很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等 待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核 态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线
第 209 页 共 485 页
程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多 次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
42、JavaConcurrencyAPI中的Lock接口(Lockinterface)
是什么?对比同步它有什么优势?
Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结 构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。 它的优势有: 1、可以使锁更公平 2、可以使线程在等待锁的时候响应中断 3、可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间 4、可以在不同的范围,以不同的顺序获取和释放锁
43、单例模式的线程安全性
老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在 多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下: 1、饿汉式单例模式的写法:线程安全 2、懒汉式单例模式的写法:非线程安全 3、双检锁单例模式的写法:线程安全
44、Semaphore有什么作用
Semaphore 就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore 有一个构造函数,可以传入一个 int 型整数 n,表示某段代码最多只有 n 个线程可 以访问,如果超出了 n,那么请等待,等到某个线程执行完毕这段代码块,下一个
第 210 页 共 485 页
线程再进入。由此可以看出如果 Semaphore 构造函数中传入的 int 型整数 n=1, 相当于变成了一个 synchronized 了。
45、Executors类是什么?
Executors 为 Executor,ExecutorService,ScheduledExecutorService, ThreadFactory 和 Callable 类提供了一些工具方法。 Executors 可以用于方便的创建线程池
46、线程类的构造方法、静态块是被哪个线程调用的
这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被 new 这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用 的。 如果说上面的说法让你感到困惑,那么我举个例子,假设 Thread2 中 new 了 Thread1,main 函数中 new 了 Thread2,那么: 1、Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方法是 Thread2 自己调用的 2、Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run()方法是 Thread1 自己调用的
47、同步方法和同步块,哪个是更好的选择?
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代 码的效率。请知道一条原则:同步的范围越小越好。
48、Java线程数过多会造成什么异常?
第 211 页 共 485 页
1、线程的生命周期开销非常高 2、消耗过多的 CPU 资源 如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空 闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争 CPU 资源时还将产生其他性能的开销。 3、降低稳定性 JVM 在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同, 并且承受着多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求栈的 大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出 OutOfMemoryError 异常。
Java 面试题(一)
1、面向对象的特征有哪些方面?
答:
面向对象的特征主要有以下几个方面:
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽 象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的 细节是什么。 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类 被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让 变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要 手段(如果不能理解请阅读阎宏博士的《Java与模式》或《设计模式精解》中 关于桥梁模式的部分)。
第 212 页 共 485 页
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问 只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自 治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写 一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西, 只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别, 明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是 封装得足够好的,因为几个按键就搞定了所有的事情)。 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。 简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分 为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的 服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B 系统有多种提供服务的方式,但一切对A系统来说都是透明的(就像电动剃须 刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电, 甚至还有可能是太阳能,A系统只会通过B类对象调用供电的方法,但并不知道 供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载 (overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override) 实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的 东西,要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已 有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样 的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
2、访问修饰符public,private,protected,以及不写(默认)
时的区别?
答:
修饰符 当前类 同 包 子 类 其他包
public √ √ √ √
第 213 页 共 485 页
protecte d
√ √ √ ×
default √ √ × ×
private √ × × ×
类的成员不写访问修饰时默认为 default。默认对于同一个包中的其他类相当于公 开(public),对于不是同一个包中的其他类相当于私有(private)。受保护 (protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私 有。Java 中,外部类的修饰符只能是 public 或默认,类的成员(包括内部类)的 修饰符可以是以上四种。
3、String 是最基本的数据类型吗?
答:
不是。Java 中的基本数据类型只有 8 个:byte、short、int、long、float、double、 char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
4、float f=3.4;是否正确?
答:不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于 下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f =(float)3.4; 或者写成 float f =3.4F;。
第 214 页 共 485 页
5、shorts1=1;s1=s1+1;有错吗?shorts1=1;s1+=1;
有错吗?
答:
对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。而 short s1 = 1; s1 += 1;可以正确 编译,因为 s1+= 1;相当于 s1 = (short)(s1 + 1);其中有隐含的强制类型转换。
6、Java有没有goto?
答:
goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。 (根据 James Gosling (Java 之父)编写的《The Java Programming Language》一书的附录中给出 了一个 Java 关键字列表,其中有 goto 和 const,但是这两个是目前无法使用的 关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意 义,因为熟悉 C 语言的程序员都知道,在系统类库中使用过的有特殊意义的单词 或单词的组合都被视为保留字)
7、int和Integer有什么区别?
答:
Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本 数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本
第 215 页 共 485 页
数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer, 从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。 Java 为每个原始类型提供了包装类型:
原始类型: boolean,char,byte,short,int,long,float,double 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float, Double
class AutoUnboxingTest {
public static void main(String[] args) { Integer a = new Integer(3); Integer b = 3; // 将3自动装箱成Integer类型 int c = 3; System.out.println(a == b); // false 两个引用没有引用同一对
象
System.out.println(a==c); //truea自动拆箱成int类型再和c
比较
}
}
最近还遇到一个面试题,也是和自动装箱和拆箱有点关系的,代码如下所示:
public class Test03 {
public static void main(String[] args) { Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2); System.out.println(f3 == f4);
第 216 页 共 485 页
}
}
如果不明就里很容易认为两个输出要么都是 true 要么都是 false。首先需要注意的 是 f1、f2、f3、f4 四个变量都是 Integer 对象引用,所以下面的==运算比较的不 是值而是引用。装箱的本质是什么呢?当我们给一个 Integer 对象赋一个 int 值的 时候,会调用 Integer 类的静态方法 valueOf,如果看看 valueOf 的源代码就知 道发生了什么。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i IntegerCache 是 Integer 的内部类,其代码如下所示:
/**
- Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS. The cache is initialized on first usage. The size of the cache may be controlled by the {@code -XX:AutoBoxCacheMax=
} option. During VM initialization, java.lang.Integer.IntegerCache.high property may be set and saved in the private system properties in the sun.misc.VM class. /
第 217 页 共 485 页
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[];
static { // high value may be configured by property int h = 127; String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h;
cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127;
第 218 页 共 485 页
}
private IntegerCache() {}
}
简单的说,如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的 Integer 对象,所以上面的面试题中 f1f4 的结果 是 false。
提醒:越是貌似简单的面试题其中的玄机就越多,需要面试者有相当深厚的功力。
8、&和&&的区别?
答:
&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与 跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。&&之所以称为短路运算是因为,如果&&左边的 表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。很多时候我 们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是 null 而且不 是空字符串,应当写为:username != null &&!username.equals(“”),二者 的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行 字符串的 equals 比较,否则会产生 NullPointerException 异常。注意:逻辑或 运算符(|)和短路或运算符(||)的差别也是如此。
补充:如果你熟悉 JavaScript,那你可能更能感受到短路运算的强大,想成为 JavaScript 的高手就先从玩转短路运算开始吧。
第 219 页 共 485 页
9、解释内存中的栈(stack)、堆(heap)和方法区(methodarea)
的用法。
答:
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的 现场保存都使用 JVM 中的栈空间;而通过 new 关键字和构造器创建的对象则放在 堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收 集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为 Eden、 Survivor(又可分为 From Survivor 和 To Survivor)、Tenured;方法区和堆都 是各个线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变 量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的 100、” hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来 最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM 的启动参数来进行调整,栈空间用光了会引发 StackOverflowError,而堆和常量 池空间不足则会引发 OutOfMemoryError。
String str = new String("hello");
上面的语句中变量 str 放在栈上,用 new 创建出来的字符串对象放在堆上,而” hello”这个字面量是放在方法区的。
补充 1:较新版本的 Java(从 Java 6 的某个更新开始)中,由于 JIT 编译器的发 展和”逃逸分析”技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一 定分配在堆上这件事情已经变得不那么绝对了。 补充 2:运行时常量池相当于 Class 文件常量池具有动态性,Java 语言并不要求 常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String 类的 intern()方法就是这样的。
第 220 页 共 485 页
看看下面代码的执行结果是什么并且比较一下 Java 7 以前和以后的运行结果是否 一致。
String s1 = new StringBuilder("go") .append("od").toString(); System.out.println(s1.intern() == s1); String s2 = new StringBuilder("ja") .append("va").toString(); System.out.println(s2.intern() == s2);
10、Math.round(11.5) 等于多少?Math.round(-11.5)等于
多少?
答:
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五 入的原理是在参数上加 0.5 然后进行下取整。
11、switch 是否能作用在byte 上,是否能作用在long 上,
是否能作用在String上?
答:
在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始, expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是 不可以的。
第 221 页 共 485 页
12、用最有效率的方法计算2乘以8?
答:
2 补充:我们为编写的类重写 hashCode 方法时,可能会看到如下所示的代码,其 实我们不太理解为什么要使用这样的乘法运算来产生哈希码(散列码),而且为 什么这个数是个素数,为什么通常选择 31 这个数?前两个问题的答案你可以自己 百度一下,选择 31 是因为可以用移位和减法运算来代替乘法,从而得到更好的性 能。说到这里你可能已经想到了:31 * num 等价于(num public class PhoneNumber { private int areaCode; private String prefix; private String lineNumber;
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime result + areaCode; result = prime result + ((lineNumber == null) ? 0 : lineNumber.hashCode()); result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); return result; }
第 222 页 共 485 页
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PhoneNumber other = (PhoneNumber) obj; if (areaCode != other.areaCode) return false; if (lineNumber == null) { if (other.lineNumber != null) return false; } else if (!lineNumber.equals(other.lineNumber)) return false; if (prefix == null) { if (other.prefix != null) return false; } else if (!prefix.equals(other.prefix)) return false; return true; }
}
13、数组有没有length()方法?String有没有length()方法?
答:
第 223 页 共 485 页
数组没有 length()方法,有 length 的属性。String 有 length()方法。JavaScript 中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。
14、在Java中,如何跳出当前的多重嵌套循环?
答:
在最外层循环前加一个标记如 A,然后用 break A;可以跳出多重循环。(Java 中 支持带标签的 break 和 continue 语句,作用有点类似于 C 和 C++中的 goto 语 句,但是就像要避免使用 goto 一样,应该避免使用带标签的 break 和 continue, 因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法 其实不知道更好)
15、构造器(constructor)是否可被重写(override)?
答:
构造器不能被继承,因此不能被重写,但可以被重载。
16、两个对象值相同(x.equals(y) == true),但却可有不同的
hash code,这句话对不对?
答:
不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hash code) 应当相同。Java 对于 eqauls 方法和 hashCode 方法是这样规定的:(1)如果两个
第 224 页 共 485 页
对象相同(equals 方法返回 true),那么它们的 hashCode 值一定要相同;(2) 如果两个对象的 hashCode 相同,它们并不一定相同。当然,你未必要按照要求 去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现 在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统, 如果哈希码频繁的冲突将会造成存取性能急剧下降)。
补充:关于 equals 和 hashCode 方法,很多 Java 程序都知道,但很多人也就是 仅仅知道而已,在 Joshua Bloch 的大作《Effective Java》(很多软件公司, 《Effective Java》、《Java 编程思想》以及《重构:改善既有代码质量》是 Java 程序员必看书籍,如果你还没看过,那就赶紧去亚马逊买一本吧)中是这样介绍 equals 方法的:首先 equals 方法必须满足自反性(x.equals(x)必须返回 true)、 对称性(x.equals(y)返回 true 时,y.equals(x)也必须返回 true)、传递性 (x.equals(y)和 y.equals(z)都返回 true 时,x.equals(z)也必须返回 true)和一 致性(当 x 和 y 引用的对象信息没有被修改时,多次调用 x.equals(y)应该得到同 样的返回值),而且对于任何非 null 值的引用 x,x.equals(null)必须返回 false。 实现高质量的 equals 方法的诀窍包括:1. 使用==操作符检查”参数是否为这个 对象的引用”;2. 使用 instanceof 操作符检查”参数是否为正确的类型”;3. 对 于类中的关键属性,检查参数传入对象的属性是否与之相匹配;4. 编写完 equals 方法后,问自己它是否满足对称性、传递性、一致性;5. 重写 equals 时总是要 重写 hashCode; 6. 不要将 equals 方法参数中的 Object 对象替换为其他的类型, 在重写时不要忘掉@Override 注解。
17、是否可以继承String类?
答:
String 类是 final 类,不可以被继承。
补充:继承 String 本身就是一个错误的行为,对 String 类型最好的重用方式是关 联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。
第 225 页 共 485 页
18、当一个对象被当作参数传递到一个方法后,此方法可改变
这个对象的属性,并可返回变化后的结果,那么这里到底是值传
递还是引用传递?
答:
是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个 参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调 用过程中被改变,但对对象引用的改变是不会影响到调用者的。C++和 C#中可以 通过传引用或传输出参数来改变传入的参数的值。在 C#中可以编写如下所示的代 码,但是在 Java 中却做不到。
using System;
namespace CS01 {
class Program { public static void swap(ref int x, ref int y) { int temp = x; x = y; y = temp; }
public static void Main (string[] args) { int a = 5, b = 10; swap (ref a, ref b); // a = 10, b = 5;
第 226 页 共 485 页
Console.WriteLine ("a = {0}, b = {1}", a, b);
}
}
}
说明:Java 中没有传引用实在是非常的不方便,这一点在 Java 8 中仍然没有得到 改进,正是如此在 Java 编写的代码中才会出现大量的 Wrapper 类(将需要通过 方法调用修改的引用置于一个 Wrapper 类中,再将 Wrapper 对象传入方法), 这样的做法只会让代码变得臃肿,尤其是让从 C 和 C++转型为 Java 程序员的开 发者无法容忍。
19、String和StringBuilder、StringBuffer的区别?
答:
Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它 们可以储存和操作字符串。其中 String 是只读字符串,也就意味着 String 引用的 字符串内容是不能被改变的。而 StringBuffer/StringBuilder 类表示的字符串对象 可以直接进行修改。StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方 法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 要高。
面试题 1 - 什么情况下用+运算符进行字符串连接比调用 StringBuffer/StringBuilder 对象的 append 方法连接字符串性能更好? 面试题 2 - 请说出下面程序的输出。
class StringEqualTest {
public static void main(String[] args) { String s1 = "Programming";
第 227 页 共 485 页
String s2 = new String("Programming"); String s3 = "Program"; String s4 = "ming"; String s5 = "Program" + "ming"; String s6 = s3 + s4; System.out.println(s1 == s2); System.out.println(s1 == s5); System.out.println(s1 == s6); System.out.println(s1 == s6.intern()); System.out.println(s2 == s2.intern());
}
}
补充:解答上面的面试题需要清除两点:1. String 对象的 intern 方法会得到字符 串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与 String 对象 的 equals 结果是 true),如果常量池中没有对应的字符串,则该字符串将被添加 到常量池中,然后返回常量池中字符串的引用;2. 字符串的+操作其本质是创建 了 StringBuilder 对象进行 append 操作,然后将拼接后的 StringBuilder 对象用 toString 方法处理成 String 对象,这一点可以用 javap -c StringEqualTest.class 命令获得 class 文件对应的 JVM 字节码指令就可以看出来。
20、重载(Overload)和重写(Override)的区别。重载的
方法能否根据返回类型进行区分?
答:
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性, 而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同 的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写
第 228 页 共 485 页
发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返 回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里 氏代换原则)。重载对返回类型没有特殊的要求。
面试题:华为的面试题中曾经问过这样一个问题 - “为什么不能根据返回类型来 区分重载”,快说出你的答案吧!
21、描述一下JVM加载class文件的原理机制?
答:
JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的 类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件 中的类。 由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一 个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、 连接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读 入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应 的 Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类 被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设 置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对 类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么 就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加 载器(Extension)、系统加载器(System)和用户自定义类加载器 (java.lang.ClassLoader 的子类)。从 Java 2(JDK 1.2)开始,类加载过程采 取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制 中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载 器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载
第 229 页 共 485 页
器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类 加载器的说明:
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar); Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父 加载器是Bootstrap; System:又叫应用类加载器,其父类是Extension。它是应用最广泛的 类加载器。它从环境变量classpath或者系统属性java.class.path 所指定的目 录中记载类,是用户自定义加载器的默认父加载器。
22、char 型变量中能不能存贮一个中文汉字,为什么?
答:
char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择 任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一 个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的。
补充:使用 Unicode 意味着字符在 JVM 内部和外部有不同的表现形式,在 JVM 内部都是 Unicode,当这个字符被从 JVM 内部转移到外部时(例如存入文件系统 中),需要进行编码转换。所以 Java 中有字节流和字符流,以及在字符流和字节 流之间进行转换的转换流,如 InputStreamReader 和 OutputStreamReader, 这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于 C 程 序员来说,要完成这样的编码转换恐怕要依赖于 union(联合体/共用体)共享内 存的特征来实现了。
第 230 页 共 485 页
23、抽象类(abstract class)和接口(interface)有什么异
同?
答:
抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如 果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实 现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中 可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其 中的方法全部都是抽象方法。抽象类中的成员可以是 private、默认、protected、 public 的,而接口中的成员全都是 public 的。抽象类中可以定义成员变量,而接 口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而 抽象类未必要有抽象方法。
24、静态嵌套类(StaticNestedClass)和内部类(InnerClass)
的不同?
答:
Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖于外部类 实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,其语法看起 来挺诡异的,如下所示。
/* 扑克类(一副扑克) @author 骆昊
第 231 页 共 485 页
*/ public class Poker { private static String[] suites = {"黑桃", "红桃", "草花", "方块"}; private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
private Card[] cards;
/* 构造器 / public Poker() { cards = new Card[52]; for(int i = 0; i /* 洗牌 (随机乱序) / public void shuffle() { for(int i = 0, len = cards.length; i 第 232 页 共 485 页
}
/* 发牌 @param index 发牌的位置 */ public Card deal(int index) { return cards[index]; }
/* 卡片类(一张扑克) [内部类] @author 骆昊 / public class Card { private String suite; // 花色 private int face; // 点数
public Card(String suite, int face) { this.suite = suite; this.face = face; }
@Override public String toString() { String faceStr = ""; switch(face) { case 1: faceStr = "A"; break;
第 233 页 共 485 页
case 11: faceStr = "J"; break; case 12: faceStr = "Q"; break; case 13: faceStr = "K"; break; default: faceStr = String.valueOf(face); } return suite + faceStr;
}
}
}
测试代码:
class PokerTest {
public static void main(String[] args) { Poker poker = new Poker(); poker.shuffle(); // 洗牌 Poker.Card c1 = poker.deal(0); // 发第一张牌 // 对于非静态内部类Card // 只有通过其外部类Poker对象才能创建Card 对象 Poker.Card c2 = poker.new Card("红心", 1); // 自己创建一张牌
System.out.println(c1); // 洗牌后的第一张 System.out.println(c2); // 打印: 红心A
}
}
面试题 - 下面的代码哪些地方会产生编译错误?
class Outer {
第 234 页 共 485 页
class Inner {}
public static void foo() { new Inner(); }
public void bar() { new Inner(); }
public static void main(String[] args) { new Inner(); }
}
注意:Java 中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中 foo 和 main 方法都是静态方法,静态方法中没有 this,也就是说没有所谓的外部类对 象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样 做:
new Outer().new Inner();
25、Java 中会存在内存泄漏吗,请简单描述。
答:
理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被 广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无 用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。例如 Hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收 这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close) 或清空(flush)一级缓存就可能导致内存泄露。下面例子中的代码也会导致内存 泄露。
第 235 页 共 485 页
import java.util.Arrays; import java.util.EmptyStackException;
public class MyStack
private static final int INIT_CAPACITY = 16;
public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; }
public void push(T elem) { ensureCapacity(); elements[size++] = elem; }
public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; }
private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } }
}
第 236 页 共 485 页
上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明 显的问题,它甚至可以通过你编写的各种单元测试。然而其中的 pop 方法却存在 内存泄露的问题,当我们用 pop 方法弹出栈中的对象时,该对象不会被当作垃圾 回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过 期引用(obsolete reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的, 这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起 来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象, 即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外, 从而对性能造成重大影响,极端情况下会引发 Disk Paging(物理内存与硬盘的虚 拟内存交换数据),甚至造成 OutOfMemoryError。
26、抽象的(abstract)方法是否可同时是静态的(static),
是否可同时是本地方法(native),是否可同时被synchronized
修饰?
答:
都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛 盾的。本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现 的,也是矛盾的。synchronized 和方法的实现细节有关,抽象方法不涉及实现细 节,因此也是相互矛盾的。
27、阐述静态变量和实例变量的区别。
答:
第 237 页 共 485 页
静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的 任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷 贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。 静态变量可以实现让多个对象共享内存。
补充:在 Java 开发中,上下文类和工具类中通常会有大量的静态成员。
28、是否可以从一个静态(static)方法内部发出对非静态
(non-static)方法的调用?
答:
不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在 调用静态方法时可能对象并没有被初始化。
29、如何实现对象克隆?
答:
有两种方式: 1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法; 2). 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真 正的深度克隆,代码如下。
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream;
第 238 页 共 485 页
import java.io.Serializable;
public class MyUtil {
private MyUtil() { throw new AssertionError(); }
@SuppressWarnings("unchecked") public static
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); return (T) ois.readObject();
// 说明:调用ByteArrayInputStream或ByteArrayOutputStream 对象的close方法没有任何意义 // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这 一点不同于对外部资源(如文件流)的释放 } }
下面是测试代码:
import java.io.Serializable;
第 239 页 共 485 页
/* 人类 @author 骆昊 */ class Person implements Serializable { private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名 private int age; // 年龄 private Car car; // 座驾
public Person(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) {
第 240 页 共 485 页
this.age = age;
}
public Car getCar() { return car; }
public void setCar(Car car) { this.car = car; }
@Override public String toString() { return "Person [name=" + name + ", age=" + age + ", car=" + car + "]"; }
}
/* 小汽车类 @author 骆昊 */ class Car implements Serializable { private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌 private int maxSpeed; // 最高时速
public Car(String brand, int maxSpeed) {
第 241 页 共 485 页
this.brand = brand; this.maxSpeed = maxSpeed;
}
public String getBrand() { return brand; }
public void setBrand(String brand) { this.brand = brand; }
public int getMaxSpeed() { return maxSpeed; }
public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; }
@Override public String toString() { return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed +
"]";
}
}
class CloneTest {
public static void main(String[] args) {
第 242 页 共 485 页
try {
Person p1 = new Person("Hao LUO", 33, new Car("Benz",
300));
Person p2 = MyUtil.clone(p1); // 深度克隆 p2.getCar().setBrand("BYD"); // 修改克隆的Person对象p2关联的汽车对象的品牌属性 // 原来的Person对象p1关联的汽车不会受到任何影响 // 因为在克隆Person对象时其关联的汽车对象也被克隆了 System.out.println(p1); } catch (Exception e) { e.printStackTrace(); }
}
}
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛 型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的, 不是在运行时抛出异常,这种是方案明显优于使用 Object 类的 clone 方法克隆对 象。让问题在编译的时候暴露出来总是好过把问题留到运行时。
30、GC是什么?为什么要有GC?
答:
GC 是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误 的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动 监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放 已分配内存的显示操作方法。Java 程序员不用担心内存管理,因为垃圾收集器会 自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或 Runtime.getRuntime().gc() ,但 JVM 可以屏蔽掉显示的垃圾回收调用。
第 243 页 共 485 页
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通 常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死 亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回 收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java 最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过 境迁,如今 Java 的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常 觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在 于 Android 系统中垃圾回收的不可预知性。
补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量 垃圾回收等方式。标准的 Java 进程既有栈又有堆。栈保存了原始型局部变量,堆 保存了要创建的对象。Java 平台对堆内存回收和再利用的基本算法被称为标记和 清除,但是 Java 对其进行了改进,采用“分代式垃圾收集”。这种方法会跟 Java 对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象 移动到不同区域:
伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说, 这里是它们唯一存在过的区域。 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。 终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集 (Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身 颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩, 以便为大对象腾出足够的空间。
与垃圾回收相关的 JVM 参数:
-Xms / -Xmx — 堆的初始大小 / 堆的最大大小 -Xmn — 堆中年轻代的大小 -XX:-DisableExplicitGC — 让System.gc()不产生任何作用 -XX:+PrintGCDetails — 打印GC 的细节
第 244 页 共 485 页
-XX:+PrintGCDateStamps — 打印GC操作的时间戳 -XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小 -XX:NewRatio — 可以设置老生代和新生代的比例 -XX:PrintTenuringDistribution — 设置每次新生代GC 后输出幸存者 乐园中对象年龄的分布 -XX:InitialTenuringThreshold/-XX:MaxTenuringThreshold:设置老 年代阀值的初始值和最大值 -XX:TargetSurvivorRatio:设置幸存区的目标使用率
31、Strings=newString(“xyz”);创建了几个字符串对象?
答:
两个对象,一个是静态区的”xyz”,一个是用 new 创建在堆上的对象。
32、接口是否可继承(extends)接口?抽象类是否可实现
(implements)接口?抽象类是否可继承具体类(concrete
class)?
答:
接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽 象类可继承具体类也可以继承抽象类。
第 245 页 共 485 页
33、一个”.java”源文件中是否可以包含多个类(不是内部类) ?
有什么限制?
答:
可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和 公开类的类名完全保持一致。
34、AnonymousInnerClass(匿名内部类)是否可以继承其它
类?是否可以实现接口?
答:
可以继承其他类或实现其他接口,在 Swing 编程和 Android 开发中常用此方式来 实现事件监听和回调。
35、内部类可以引用它的包含类(外部类)的成员吗?有没有
什么限制?
答:
一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
36、Java 中的final关键字有哪些用法?
第 246 页 共 485 页
答:
(1)修饰类:表示该类不能被继承;(2)修饰方法:表示方法不能被重写;(3)修饰变 量:表示变量只能一次赋值以后值不能被修改(常量)。
37、指出下面程序的运行结果
class A {
static { System.out.print("1"); }
public A() { System.out.print("2"); }
}
class B extends A{
static { System.out.print("a"); }
public B() { System.out.print("b"); }
}
第 247 页 共 485 页
public class Hello {
public static void main(String[] args) { A ab = new B(); ab = new B(); }
}
答:
执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后 调用父类构造器,再初始化非静态成员,最后调用自身构造器。
提示:如果不能给出此题的正确答案,说明之前第 21 题 Java 类加载机制还没有 完全理解,赶紧再看看吧。
38、数据类型之间的转换:
如何将字符串转换为基本数据类型? 如何将基本数据类型转换为字符串?
答:
调用基本数据类型对应的包装类中的方法parseXXX(String)或 valueOf(String)即可返回相应基本类型;
第 248 页 共 485 页
一种方法是将基本数据类型与空字符串(”“)连接(+)即可获得其所 对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符 串
39、如何实现字符串的反转及替换?
答:
方法很多,可以自己写实现也可以使用 String 或 StringBuffer/StringBuilder 中 的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示:
public static String reverse(String originStr) { if(originStr == null || originStr.length() 40、怎样将GB2312编码的字符串转换为ISO-8859-1编码的
字符串?
答:
代码如下所示:
String s1 = "你好"; String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");
第 249 页 共 485 页
41、日期和时间:
如何取得年月日、小时分钟秒? 如何取得从1970年1月1日0时0分0秒到现在的毫秒数? 如何取得某月的最后一天? 如何格式化日期?
答:
问题 1:创建 java.util.Calendar 实例,调用其 get()方法传入不同的参数即可获 得参数所对应的值。Java 8 中可以使用 java.time.LocalDateTimel 来获取,代码 如下所示。
public class DateTimeTest { public static void main(String[] args) { Calendar cal = Calendar.getInstance(); System.out.println(cal.get(Calendar.YEAR)); System.out.println(cal.get(Calendar.MONTH)); // 0 - 11 System.out.println(cal.get(Calendar.DATE)); System.out.println(cal.get(Calendar.HOUR_OF_DAY)); System.out.println(cal.get(Calendar.MINUTE)); System.out.println(cal.get(Calendar.SECOND));
// Java 8 LocalDateTime dt = LocalDateTime.now(); System.out.println(dt.getYear()); System.out.println(dt.getMonthValue()); // 1 - 12 System.out.println(dt.getDayOfMonth()); System.out.println(dt.getHour());
第 250 页 共 485 页
System.out.println(dt.getMinute()); System.out.println(dt.getSecond());
}
}
问题 2:以下方法均可获得该毫秒数。
Calendar.getInstance().getTimeInMillis(); System.currentTimeMillis(); Clock.systemDefaultZone().millis(); // Java 8
问题 3:代码如下所示。
Calendar time = Calendar.getInstance(); time.getActualMaximum(Calendar.DAY_OF_MONTH);
问题 4:利用 java.text.DataFormat 的子类(如 SimpleDateFormat 类)中的 format(Date)方法可将日期格式化。Java 8 中可以用 java.time.format.DateTimeFormatter 来格式化时间日期,代码如下所示。
import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Date;
class DateFormatTest {
public static void main(String[] args) { SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd"); Date date1 = new Date();
第 251 页 共 485 页
System.out.println(oldFormatter.format(date1));
// Java 8 DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); LocalDate date2 = LocalDate.now(); System.out.println(date2.format(newFormatter)); } }
补充:Java 的时间日期 API 一直以来都是被诟病的东西,为了解决这一问题,Java 8 中引入了新的时间日期 API,其中包括 LocalDate、LocalTime、LocalDateTime、 Clock、Instant 等类,这些的类的设计都使用了不变模式,因此是线程安全的设 计。如果不理解这些内容,可以参考我的另一篇文章《关于 Java 并发编程的总结 和思考》。
42、打印昨天的当前时刻。
答:
import java.util.Calendar;
class YesterdayCurrent { public static void main(String[] args){ Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -1); System.out.println(cal.getTime()); } }
第 252 页 共 485 页
在 Java 8 中,可以用下面的代码实现相同的功能。
import java.time.LocalDateTime;
class YesterdayCurrent {
public static void main(String[] args) { LocalDateTime today = LocalDateTime.now(); LocalDateTime yesterday = today.minusDays(1);
System.out.println(yesterday);
}
}
43、比较一下Java和JavaSciprt。
答:
JavaScript 与 Java 是两个公司开发的不同的两个产品。Java 是原 Sun Microsystems 公司推出的面向对象的程序设计语言,特别适合于互联网应用程序 开发;而 JavaScript 是 Netscape 公司的产品,为了扩展 Netscape 浏览器的功 能而开发的一种可以嵌入 Web 页面中运行的基于对象和事件驱动的解释性语言。 JavaScript 的前身是 LiveScript;而 Java 的前身是 Oak 语言。 下面对两种语言间的异同作如下比较:
基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发 简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络 无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和
第 253 页 共 485 页
事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对 象供设计人员使用。 解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是 一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏 览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率) 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编 译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作 声明,JavaScript的解释器在运行时检查推断其数据类型。 代码格式不一样。
补充:上面列出的四点是网上流传的所谓的标准答案。其实 Java 和 JavaScript 最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势 是函数式语言和动态语言。在 Java 中类(class)是一等公民,而 JavaScript 中 函数(function)是一等公民,因此 JavaScript 支持函数式编程,可以使用 Lambda 函数和闭包(closure),当然 Java 8 也开始支持函数式编程,提供了对 Lambda 表达式以及函数式接口的支持。对于这类问题,在面试的时候最好还是用自己的 语言回答会更加靠谱,不要背网上所谓的标准答案。
44、什么时候用断言(assert)?
答:
断言在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一 般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试 时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。断言 是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true;如果表 达式的值为 false,那么系统会报告一个 AssertionError。断言的使用如下面的代 码所示:
第 254 页 共 485 页
assert(a > 0); // throws an AssertionError if a 断言可以有两种形式: assert Expression1; assert Expression1 : Expression2 ; Expression1 应该总是产生一个布尔值。 Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信 息的字符串消息。
要在运行时启用断言,可以在启动 JVM 时使用-enableassertions 或者-ea 标记。 要在运行时选择禁用断言,可以在启动 JVM 时使用-da 或者-disableassertions 标记。要在系统类中启用或禁用断言,可使用-esa 或-dsa 标记。还可以在包的基 础上启用或者禁用断言。
注意:断言不应该以任何方式改变程序的状态。简单的说,如果希望在不满足某 些条件时阻止代码的执行,就可以考虑用断言来阻止它。
45、Error和Exception有什么区别?
答:
Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情 况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况; Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题; 也就是说,它表示如果程序运行正常,从不会发生的情况。
面试题:2005 年摩托罗拉的面试中曾经问过这么一个问题“If a process reports a stack overflow run-time error, what’s the most possible cause?”,给了 四个选项 a. lack of memory; b. write on an invalid memory space; c. recursive function calling; d. array index out of boundary. Java 程序在运行
第 255 页 共 485 页
时也可能会遭遇 StackOverflowError,这是一个无法恢复的错误,只能重新修改 代码了,这个面试题的答案是 c。如果写了不能迅速收敛的递归,则很有可能引发 栈溢出的错误,如下所示:
class StackOverflowErrorTest {
public static void main(String[] args) { main(null); }
}
提示:用递归编写程序时一定要牢记两点:1. 递归公式;2. 收敛条件(什么时候 就不再继续递归)。
46、try{}里有一个return语句,那么紧跟在这个try后的
finally{}里的代码会不会被执行,什么时候被执行,在return
前还是后?
答:
会执行,在方法返回调用者前执行。
注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完 毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改 后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中 直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升
第 256 页 共 485 页
编译器的语法检查级别来产生警告或错误,Eclipse 中可以在如图所示的地方进行 设置,强烈建议将此项设置为编译错误。
47、Java语言如何进行异常处理,关键字:throws、throw、
try、catch、finally分别如何使用?
答:
Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了 良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类 的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,
第 257 页 共 485 页
调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理 是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。一般情况 下是用 try 来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过 它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try 用 来指定一块预防所有异常的程序;catch 子句紧跟在 try 块后面,用来指定你想要 捕获的异常的类型;throw 语句用来明确地抛出一个异常;throws 用来声明一个 方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally 为确保一段 代码不管发生什么异常状况都要被执行;try 语句可以嵌套,每当遇到一个 try 语 句,异常的结构就会被放入异常栈中,直到所有的 try 语句都完成。如果下一级的 try 语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这 种异常的 try 语句或者最终将异常抛给 JVM。
48、运行时异常与受检异常有何异同?
答:
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常 操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就 不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可 能因使用的问题而引发。Java 编译器要求方法必须声明抛出可能发生的受检异常, 但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对 象程序设计中经常被滥用的东西,在 Effective Java 中对异常的使用给出了以下指 导原则:
不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调 用者为了正常的控制流而使用异常) 对可以恢复的情况使用受检异常,对编程错误使用运行时异常 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发 生)
第 258 页 共 485 页
优先使用标准的异常 每个方法抛出的异常都要有文档 保持异常的原子性 不要在catch中忽略掉捕获到的异常
49、列出一些你常见的运行时异常?
答:
ArithmeticException(算术异常) ClassCastException (类转换异常) IllegalArgumentException (非法参数异常) IndexOutOfBoundsException (下标越界异常) NullPointerException (空指针异常) SecurityException (安全异常)
50、阐述final、finally、finalize的区别。
答:
final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味 着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将 变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须 在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方 法也同样只能使用,不能在子类中被重写。
第 259 页 共 485 页
finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着 程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以 将释放外部资源的代码写在finally块中。 finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃 圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收 集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行 其他清理工作。
51、类ExampleA继承Exception,类ExampleB继承
ExampleA。
有如下代码片断:
try {
throw new ExampleB("b") } catch(ExampleA e){ System.out.println("ExampleA"); } catch(Exception e){ System.out.println("Exception"); }
**请问执行此段代码的输出是什么?
答:
输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型], 抓取 ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的 异常)
第 260 页 共 485 页
面试题 - 说出下面代码的运行结果。(此题的出处是《Java 编程思想》一书)
class Annoyance extends Exception {} class Sneeze extends Annoyance {}
class Human {
public static void main(String[] args) throws Exception { try { try { throw new Sneeze(); } catch ( Annoyance a ) { System.out.println("Caught Annoyance"); throw a; } } catch ( Sneeze s ) { System.out.println("Caught Sneeze"); return ; } finally { System.out.println("Hello World!"); } }
}
52、List、Set、Map是否继承自Collection接口?
第 261 页 共 485 页
答:
List、Set 是,Map 不是。Map 是键值对映射容器,与 List 和 Set 有明显的区别, 而 Set 存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List 是线性结构的容器,适用于按数值索引访问元素的情形。
53、阐述ArrayList、Vector、LinkedList的存储性能和特性。
答:
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的 数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉 及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 中的方法由 于添加了 synchronized 修饰,因此 Vector 是线程安全的容器,但性能上较 ArrayList 差,因此已经是 Java 中的遗留容器。LinkedList 使用双向链表实现存 储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索 引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更 高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本 项的前后项即可,所以插入速度较快。Vector 属于遗留容器(Java 早期的版本中 提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties 都是遗留容器),已经不推荐使用,但是由于 ArrayList 和 LinkedListed 都是非 线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类 Collections 中的 synchronizedList 方法将其转换成线程安全的容器后再使用(这 是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强 实现)。
补充:遗留容器中的 Properties 类和 Stack 类在设计上有严重的问题,Properties 是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个 Hashtable 并将其两个泛型参数设置为 String 类型,但是 Java API 中的 Properties 直接继承了 Hashtable,这很明显是对继承的滥用。这里复用代码的
第 262 页 共 485 页
方式应该是 Has-A 关系而不是 Is-A 关系,另一方面容器都属于工具类,继承工具 类本身就是一个错误的做法,使用工具类最好的方式是 Has-A 关系(关联)或 Use-A 关系(依赖)。同理,Stack 类继承 Vector 也是不正确的。Sun 公司的工 程师们也会犯这种低级错误,让人唏嘘不已。
54、Collection和Collections的区别?
答:
Collection 是一个接口,它是 Set、List 等容器的父接口;Collections 是个一个 工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、 排序、线程安全化等等。
55、List、Map、Set三个接口存取元素时,各有什么特点?
答:
List 以特定索引来存取元素,可以有重复元素。Set 不能存放重复元素(用对象的 equals()方法来区分元素是否重复)。Map 保存键值对(key-value pair)映射, 映射关系可以是一对一或多对一。Set 和 Map 容器都有基于哈希存储和排序树的 两种实现版本,基于哈希存储的版本理论存取时间复杂度为 O(1),而基于排序树 版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达 到排序和去重的效果。
56、TreeMap和TreeSet在排序时如何比较元素?
Collections工具类中的sort()方法如何比较元素?
第 263 页 共 485 页
答:
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比 较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。 TreeMap要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元 素进行排序。Collections 工具类的 sort 方法有两种重载的形式,第一种要求传入 的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;第二 种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是 Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于 一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对 回调模式的应用(Java 中对函数式编程的支持)。 例子 1:
public class Student implements Comparable
public Student(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; }
@Override public int compareTo(Student o) { return this.age - o.age; // 比较年龄(年龄的升序) }
第 264 页 共 485 页
}
import java.util.Set; import java.util.TreeSet;
class Test01 {
public static void main(String[] args) { Set
for(Student stu : set) { System.out.println(stu); } // 输出结果: // Student [name=Bob YANG, age=22] // Student [name=XJ WANG, age=32] // Student [name=Hao LUO, age=33] // Student [name=Bruce LEE, age=60] } }
例子 2:
public class Student { private String name; // 姓名
第 265 页 共 485 页
private int age; // 年龄
public Student(String name, int age) { this.name = name; this.age = age; }
/* 获取学生姓名 */ public String getName() { return name; }
/* 获取学生年龄 */ public int getAge() { return age; }
@Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; }
}
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator;
第 266 页 共 485 页
import java.util.List;
class Test02 {
public static void main(String[] args) { List
// 通过sort方法的第二个参数传入一个Comparator接口对象 // 相当于是传入一个比较对象大小的算法到sort方法中 // 由于Java中没有函数指针、仿函数、委托这样的概念 // 因此要将一个算法传入一个方法中唯一的选择就是通过接口回调 Collections.sort(list, new Comparator
@Override public int compare(Student o1, Student o2) { return o1.getName().compareTo(o2.getName()); //
比较学生姓名
}
});
for(Student stu : list) { System.out.println(stu); } // 输出结果: // Student [name=Bob YANG, age=22] // Student [name=Bruce LEE, age=60]
第 267 页 共 485 页
// Student [name=Hao LUO, age=33] // Student [name=XJ WANG, age=32] } }
57、Thread类的sleep()方法和对象的wait()方法都可以让线
程暂停执行,它们有什么区别?
答:
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程 暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保 持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第 66 题中的线 程状态转换图)。wait()是 Object 类的方法,调用对象的 wait()方法导致当前线 程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用 对象的 notify()方法(或 notifyAll()方法)时才能唤醒等待池中的线程进入等锁池 (lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
补充:可能不少人对什么是进程,什么是线程还比较模糊,对于为什么需要多线 程编程也不是特别理解。简单的说:进程是具有一定独立功能的程序关于某个数 据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线 程是进程的一个实体,是 CPU 调度和分派的基本单位,是比进程更小的能独立运 行的基本单位。线程的划分尺度小于进程,这使得多线程程序的并发性高;进程 在执行时通常拥有独立的内存单元,而线程之间可以共享内存。使用多线程的编 程通常能够带来更好的性能和用户体验,但是多线程的程序对于其他程序是不友 好的,因为它可能占用了更多的 CPU 资源。当然,也不是线程越多,程序的性能 就越好,因为线程之间的调度和切换也会浪费 CPU 时间。时下很时髦的 Node.js 就采用了单线程异步 I/O 的工作模式。
第 268 页 共 485 页
58、线程的sleep()方法和yield()方法有什么区别?
答:
① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的 线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的 机会; ② 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转 入就绪(ready)状态; ③ sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异 常; ④ sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性。
59、当一个线程进入一个对象的synchronized方法A之后,
其它线程是否可进入此对象的synchronized方法B?
答:
不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静 态方法上的 synchronized 修饰符要求执行方法时要获得对象的锁,如果已经进入 A 方法说明对象锁已经被取走,那么试图进入 B 方法的线程就只能在等锁池(注 意不是等待池哦)中等待对象的锁。
60、请说出与线程同步以及线程调度相关的方法。
答:
第 269 页 共 485 页
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用 此方法要处理InterruptedException异常; notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并 不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且 与优先级无关; notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给 所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
提示:关于 Java 多线程和并发编程的问题,建议大家看我的另一篇文章《关于 Java 并发编程的总结和思考》。 补充:Java 5 通过 Lock 接口提供了显式的锁机制(explicit lock),增强了灵活 性以及对线程的协调。Lock 接口中定义了加锁(lock())和解锁(unlock())的方 法,同时还提供了 newCondition()方法来产生用于线程之间通信的 Condition 对 象;此外,Java 5 还提供了信号量机制(semaphore),信号量可以用来限制对 某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信 号量的许可(调用 Semaphore 对象的 acquire()方法);在完成对资源的访问后, 线程必须向信号量归还许可(调用 Semaphore 对象的 release()方法)。
下面的例子演示了 100 个线程同时向一个银行账户中存入 1 元钱,在没有使用同 步机制和使用同步机制情况下的执行情况。
银行账户类:
/* 银行账户 @author 骆昊 */
第 270 页 共 485 页
public class Account { private double balance; // 账户余额
/* 存款 @param money 存入金额 / public void deposit(double money) { double newBalance = balance + money; try { Thread.sleep(10); // 模拟此业务需要一段处理时间 } catch(InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; }
/* 获得账户余额 */ public double getBalance() { return balance; }
}
存钱线程类:
/* 存钱线程
第 271 页 共 485 页
- @author 骆昊 / public class AddMoneyThread implements Runnable { private Account account; // 存入账户 private double money; // 存入金额
public AddMoneyThread(Account account, double money) { this.account = account; this.money = money; }
@Override public void run() { account.deposit(money); }
}
测试类:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class Test01 {
public static void main(String[] args) { Account account = new Account(); ExecutorService service = Executors.newFixedThreadPool(100);
第 272 页 共 485 页
for(int i = 1; i service.shutdown();
while(!service.isTerminated()) {}
System.out.println("账户余额: " + account.getBalance());
}
}
在没有同步的情况下,执行结果通常是显示账户余额在 10 元以下,出现这种状况 的原因是,当一个线程 A 试图存入 1 元的时候,另外一个线程 B 也能够进入存款 的方法中,线程 B 读取到的账户余额仍然是线程 A 存入 1 元钱之前的账户余额, 因此也是在原来的余额 0 上面做了加 1 元的操作,同理线程 C 也会做类似的事情, 所以最后 100 个线程执行结束时,本来期望账户余额为 100 元,但实际得到的通 常在 10 元以下(很可能是 1 元哦)。解决这个问题的办法就是同步,当一个线程 对银行账户存钱时,需要将此账户锁定,待其操作完成后才允许其他的线程进行 操作,代码有如下几种调整方案:
在银行账户的存款(deposit)方法上同步(synchronized)关键字
/* 银行账户 @author 骆昊 */ public class Account { private double balance; // 账户余额
第 273 页 共 485 页
/* 存款 @param money 存入金额 / public synchronized void deposit(double money) { double newBalance = balance + money; try { Thread.sleep(10); // 模拟此业务需要一段处理时间 } catch(InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; }
/* 获得账户余额 */ public double getBalance() { return balance; }
}
在线程调用存款方法时对银行账户进行同步
/* 存钱线程 @author 骆昊
第 274 页 共 485 页
*/ public class AddMoneyThread implements Runnable { private Account account; // 存入账户 private double money; // 存入金额
public AddMoneyThread(Account account, double money) { this.account = account; this.money = money; }
@Override public void run() { synchronized (account) { account.deposit(money); } }
}
通过Java 5显示的锁机制,为每个银行账户创建一个锁对象,在存款操 作进行加锁和解锁的操作
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
/* 银行账户 @author 骆昊 *
第 275 页 共 485 页
*/ public class Account { private Lock accountLock = new ReentrantLock(); private double balance; // 账户余额
/* 存款 @param money 存入金额 / public void deposit(double money) { accountLock.lock(); try { double newBalance = balance + money; try { Thread.sleep(10); // 模拟此业务需要一段处理时间 } catch (InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } finally { accountLock.unlock(); } }
/* 获得账户余额 */
第 276 页 共 485 页
public double getBalance() { return balance; }
}
按照上述三种方式对代码进行修改后,重写执行测试代码 Test01,将看到最终的 账户余额为 100 元。当然也可以使用 Semaphore 或 CountdownLatch 来实现同 步。
61、编写多线程程序有几种实现方式?
答:
Java 5 以前实现多线程有两种实现方法:一种是继承 Thread 类;另一种是实现 Runnable 接口。两种方式都要通过重写 run()方法来定义线程的行为,推荐使用 后者,因为 Java 中的继承是单继承,一个类有一个父类,如果继承了 Thread 类 就无法再继承其他类了,显然使用 Runnable 接口更为灵活。
补充:Java 5 以后创建线程还有第三种方式:实现 Callable 接口,该接口中的 call 方法可以在线程执行结束时产生一个返回值,代码如下所示:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future;
class MyTask implements Callable
第 277 页 共 485 页
private int upperBounds;
public MyTask(int upperBounds) { this.upperBounds = upperBounds; }
@Override public Integer call() throws Exception { int sum = 0; for(int i = 1; i }
class Test {
public static void main(String[] args) throws Exception { List
第 278 页 共 485 页
}
System.out.println(sum);
}
}
62、synchronized关键字的用法?
答:
synchronized 关键字可以将对象或者方法标记为同步,以实现对对象和方法的互 斥访问,可以用 synchronized(对象) { … }定义同步代码块,或者在声明方法时 将 synchronized 作为方法的修饰符。在第 60 题的例子中已经展示了 synchronized 关键字的用法。
63、举例说明同步和异步。
答:
如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正 在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线 程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好 的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并 且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异 步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻 塞式操作。
64、启动一个线程是调用run()还是start()方法?
第 279 页 共 485 页
答:
启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态, 这意味着它可以由 JVM 调度并执行,这并不意味着线程就会立即运行。run()方 法是线程启动后要进行回调(callback)的方法。
65、什么是线程池(thread pool)?
答:
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内 存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象, 以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽 可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就 是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的 线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完 毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 Java 5+中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口 是 ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理 不是很清楚的情况下,因此在工具类 Executors 面提供了一些静态工厂方法,生 成一些常用的线程池,如下所示:
newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只 有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线 程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执 行顺序按照任务的提交顺序执行。
第 280 页 共 485 页
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创 建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就 会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小 超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的 线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程 池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM) 能够创建的最大线程大小。 newScheduledThreadPool:创建一个大小无限的线程池。此线程池支 持定时以及周期性执行任务的需求。 newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持 定时以及周期性执行任务的需求。
第 60 题的例子中演示了通过 Executors 工具类创建线程池并使用线程池执行线程 的代码。如果希望在服务器上使用线程池,强烈建议使用 newFixedThreadPool 方法来创建线程池,这样能获得更好的性能。
66、线程的基本状态以及状态之间的关系?
答:
第 281 页 共 485 页
说明:其中 Running 表示运行状态,Runnable 表示就绪状态(万事俱备,只欠 CPU),Blocked 表示阻塞状态,阻塞状态又有多种情况,可能是因为调用 wait() 方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用 了 sleep()方法或 join()方法等待休眠或其他线程结束,或是因为发生了 I/O 中断。
67、简述synchronized 和java.util.concurrent.locks.Lock
的异同?
答:
Lock 是 Java 5 以后引入的新的 API,和关键字 synchronized 相比主要相同点: Lock 能完成 synchronized 所实现的所有功能;主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得 锁。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且最好 在 finally 块中释放(这是释放外部资源的最好的地方)。
第 282 页 共 485 页
68、Java中如何实现序列化,有什么意义?
答:
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流 化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。 序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会 存在数据乱序的问题)。 要实现序列化,需要让一个类实现 Serializable 接口,该接口是一个标识性接口, 标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通 过 writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要 反序列化则可以用一个输入流建立对象输入流,然后通过 readObject 方法从流中 读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆 (可以参考第 29 题)。
69、Java中有几种类型的流?
答:
字节流和字符流。字节流继承于 InputStream、OutputStream,字符流继承于 Reader、Writer。在 java.io 包中还有许多其他的流,主要是为了提高性能和使 用方便。关于 Java 的 I/O 需要注意的有两点:一是两种对称性(输入和输出的对 称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。 另外 Java 中的流不同于 C#的是它只有一个维度一个方向。
面试题 - 编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给 出了两种实现方案)
import java.io.FileInputStream;
第 283 页 共 485 页
import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel;
public final class MyUtil {
private MyUtil() { throw new AssertionError(); }
public static void fileCopy(String source, String target) throws IOException { try (InputStream in = new FileInputStream(source)) { try (OutputStream out = new FileOutputStream(target)) { byte[] buffer = new byte[4096]; int bytesToRead; while((bytesToRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } } } }
public static void fileCopyNIO(String source, String target) throws IOException { try (FileInputStream in = new FileInputStream(source)) { try(FileOutputStreamout=newFileOutputStream(target)){ FileChannel inChannel = in.getChannel();
第 284 页 共 485 页
FileChannel outChannel = out.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(4096); while(inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); }
}
}
}
}
注意:上面用到 Java 7 的 TWR,使用 TWR 后可以不用在 finally 中释放外部资源 , 从而让代码更加优雅。
70、写一个方法,输入一个文件名和一个字符串,统计这个字
符串在这个文件中出现的次数。
答:
代码如下:
import java.io.BufferedReader; import java.io.FileReader;
public final class MyUtil {
// 工具类中的方法都是静态方式访问的因此将构造器私有不允许创建对象 (绝对好习惯)
第 285 页 共 485 页
private MyUtil() { throw new AssertionError(); }
/* 统计给定文件中给定字符串的出现次数 @param filename 文件名 @param word 字符串 @return 字符串在文件中出现的次数 */ public static int countWordInFile(String filename, String word) { int counter = 0; try (FileReader fr = new FileReader(filename)) { try (BufferedReader br = new BufferedReader(fr)) { String line = null; while ((line = br.readLine()) != null) { int index = -1; while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) { counter++; line = line.substring(index + word.length()); } } } } catch (Exception ex) { ex.printStackTrace(); } return counter; }
第 286 页 共 485 页
}
71、如何用Java代码列出一个目录下所有的文件?
答:
如果只要求列出当前文件夹下的文件,代码如下所示:
import java.io.File;
class Test12 {
public static void main(String[] args) { File f = new File("/Users/Hao/Downloads"); for(File temp : f.listFiles()) { if(temp.isFile()) { System.out.println(temp.getName()); } } }
}
如果需要对文件夹继续展开,代码如下所示:
import java.io.File;
class Test12 {
public static void main(String[] args) { showDirectory(new File("/Users/Hao/Downloads"));
第 287 页 共 485 页
}
public static void showDirectory(File f) { _walkDirectory(f, 0); }
private static void _walkDirectory(File f, int level) { if(f.isDirectory()) { for(File temp : f.listFiles()) { _walkDirectory(temp, level + 1); } } else { for(int i = 0; i }
在 Java 7 中可以使用 NIO.2 的 API 来做同样的事情,代码如下所示:
class ShowFileTest {
public static void main(String[] args) throws IOException { Path initPath = Paths.get("/Users/Hao/Downloads"); Files.walkFileTree(initPath, new SimpleFileVisitor
@Override
第 288 页 共 485 页
public FileVisitResult visitFile(Path file, BasicFileAttributes
attrs)
throws IOException { System.out.println(file.getFileName().toString()); return FileVisitResult.CONTINUE;
}
});
}
}
72、用Java的套接字编程实现一个多线程的回显(echo)服
务器。
答:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket;
public class EchoServer {
private static final int ECHO_SERVER_PORT = 6789;
public static void main(String[] args) {
第 289 页 共 485 页
try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) { System.out.println("服务器已经启动..."); while(true) { Socket client = server.accept(); new Thread(new ClientHandler(client)).start(); } } catch (IOException e) { e.printStackTrace(); } }
private static class ClientHandler implements Runnable { private Socket client;
public ClientHandler(Socket client) { this.client = client; }
@Override public void run() { try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter pw = new PrintWriter(client.getOutputStream())) { String msg = br.readLine(); System.out.println("收到" + client.getInetAddress() + " 发送的: " + msg); pw.println(msg); pw.flush(); } catch(Exception ex) {
第 290 页 共 485 页
ex.printStackTrace(); } finally { try { client.close(); } catch (IOException e) { e.printStackTrace(); } }
}
}
}
注意:上面的代码使用了 Java 7 的 TWR 语法,由于很多外部资源类都间接的实 现了 AutoCloseable 接口(单方法回调接口),因此可以利用 TWR 语法在 try 结束的时候通过回调的方式自动调用外部资源类的 close()方法,避免书写冗长的 finally 代码块。此外,上面的代码用一个静态内部类实现线程的功能,使用多线 程可以避免一个用户 I/O 操作所产生的中断影响其他用户对服务器的访问,简单 的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用 线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可 忽视的。
下面是一段回显客户端测试代码:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner;
public class EchoClient {
第 291 页 共 485 页
public static void main(String[] args) throws Exception { Socket client = new Socket("localhost", 6789); Scanner sc = new Scanner(System.in); System.out.print("请输入内容: "); String msg = sc.nextLine(); sc.close(); PrintWriter pw = new PrintWriter(client.getOutputStream()); pw.println(msg); pw.flush(); BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); System.out.println(br.readLine()); client.close(); } }
如果希望用 NIO 的多路复用套接字实现服务器,代码如下所示。NIO 的操作虽然 带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于 理解。
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator;
第 292 页 共 485 页
public class EchoServerNIO {
private static final int ECHO_SERVER_PORT = 6789; private static final int ECHO_SERVER_TIMEOUT = 5000; private static final int BUFFER_SIZE = 1024;
private static ServerSocketChannel serverChannel = null; private static Selector selector = null; // 多路复用选择器 private static ByteBuffer buffer = null; // 缓冲区
public static void main(String[] args) { init(); listen(); }
private static void init() { try { serverChannel = ServerSocketChannel.open(); buffer = ByteBuffer.allocate(BUFFER_SIZE); serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT)); serverChannel.configureBlocking(false); selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (Exception e) { throw new RuntimeException(e); } }
private static void listen() { while (true) {
第 293 页 共 485 页
try {
if (selector.select(ECHO_SERVER_TIMEOUT) != 0) { Iterator
private static void handleKey(SelectionKey key) throws IOException { SocketChannel channel = null;
try {
if (key.isAcceptable()) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); channel = serverChannel.accept(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { channel = (SocketChannel) key.channel(); buffer.clear(); if (channel.read(buffer) > 0) { buffer.flip();
第 294 页 共 485 页
CharBuffer charBuffer = CharsetHelper.decode(buffer); String msg = charBuffer.toString(); System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg);
channel.write(CharsetHelper.encode(CharBuffer.wrap(msg))); } else { channel.close(); } } } catch (Exception e) { e.printStackTrace(); if (channel != null) { channel.close(); } } }
}
import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder;
public final class CharsetHelper { private static final String UTF_8 = "UTF-8";
第 295 页 共 485 页
private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder(); private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
private CharsetHelper() { }
public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{ return encoder.encode(in); }
public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{ return decoder.decode(in); } }
73、XML文档定义有几种形式?它们之间有何本质区别?解析
XML文档有哪几种方式?
答:
XML 文档定义分为 DTD 和 Schema 两种形式,二者都是对 XML 语法的约束,其 本质区别在于 Schema 本身也是一个 XML 文件,可以被 XML 解析器解析,而且 可以为 XML 承载的数据定义类型,约束能力较之 DTD 更强大。对 XML 的解析主 要有 DOM(文档对象模型,Document Object Model)、SAX(Simple API for XML)和 StAX(Java 6 中引入的新的解析 XML 的方式,Streaming API for XML),
第 296 页 共 485 页
其中 DOM 处理大型文件时其性能下降的非常厉害,这个问题是由 DOM 树结构占 用的内存较多造成的,而且 DOM 解析方式必须在解析文件之前把整个文档装入内 存,适合对 XML 的随机访问(典型的用空间换取时间的策略);SAX 是事件驱动 型的 XML 解析方式,它顺序读取 XML 文件,不需要一次全部装载整个文件。当 遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件, 用户通过事件回调代码来处理 XML 文件,适合对 XML 的顺序访问;顾名思义, StAX 把重点放在流上,实际上 StAX 与其他解析方式的本质区别就在于应用程序 能够把 XML 作为一个事件流来处理。将 XML 作为一组事件来处理的想法并不新 颖(SAX 就是这样做的),但不同之处在于 StAX 允许应用程序代码把这些事件逐 个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。
74、你在项目中哪些地方用到了XML?
答:
XML 的主要作用有两个方面:数据交换和信息配置。在做数据交换时,XML 将数 据用标签组装成起来,然后压缩打包加密后通过网络传送给接收者,接收解密与 解压缩后再从 XML 文件中还原相关信息进行处理,XML 曾经是异构系统间交换数 据的事实标准,但此项功能几乎已经被 JSON(JavaScript Object Notation)取 而代之。当然,目前很多软件仍然使用 XML 来存储配置信息,我们在很多项目中 通常也会将作为配置信息的硬代码写在 XML 文件中,Java 的很多框架也是这么做 的,而且这些框架都选择了 dom4j 作为处理 XML 的工具,因为 Sun 公司的官方 API 实在不怎么好用。
补充:现在有很多时髦的软件(如 Sublime)已经开始将配置文件书写成 JSON 格式,我们已经强烈的感受到 XML 的另一项功能也将逐渐被业界抛弃。
75、阐述JDBC操作数据库的步骤。
第 297 页 共 485 页
答:
下面的代码以连接本机的 Oracle 数据库为例,演示 JDBC 操作数据库的步骤。
加载驱动。
Class.forName("oracle.jdbc.driver.OracleDriver");
创建连接。
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");
创建语句。
PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?"); ps.setInt(1, 1000); ps.setInt(2, 3000);
执行语句。
ResultSet rs = ps.executeQuery();
第 298 页 共 485 页
处理结果。
while(rs.next()) { System.out.println(rs.getInt("empno") + " - " + rs.getString("ename")); }
关闭资源。
finally { if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } }
提示:关闭外部资源的顺序应该和打开的顺序相反,也就是说先关闭 ResultSet、 再关闭 Statement、在关闭 Connection。上面的代码只关闭了 Connection(连 接),虽然通常情况下在关闭连接时,连接上创建的语句和打开的游标也会关闭, 但不能保证总是如此,因此应该按照刚才说的顺序分别关闭。此外,第一步加载 驱动在 JDBC 4.0 中是可以省略的(自动从类路径中加载驱动),但是我们建议保 留。
76、Statement和PreparedStatement有什么区别?哪个性
能更好?
第 299 页 共 485 页
答:
与 Statement 相比,①PreparedStatement 接口代表预编译的语句,它主要的优 势在于可以减少 SQL 的编译错误并增加 SQL 的安全性(减少 SQL 注射攻击的可 能性);②PreparedStatement 中的 SQL 语句是可以带参数的,避免了用字符串 连接拼接 SQL 语句的麻烦和不安全;③当批量处理 SQL 或频繁执行相同的查询时, PreparedStatement 有明显的性能上的优势,由于数据库可以将编译优化后的 SQL 语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成 执行计划)。
补充:为了提供对存储过程的调用,JDBC API 中还提供了 CallableStatement 接 口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的 SQL 语 句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数 (如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全 性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦, 因为每种数据库的存储过程在书写上存在不少的差别。
77、使用JDBC操作数据库时,如何提升读取数据的性能?如
何提升更新数据的性能?
答:
要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的 setFetchSize() 方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能 可以使用 PreparedStatement 语句构建批处理,将若干 SQL 语句置于一个批处 理中执行。
第 300 页 共 485 页
78、在进行数据库编程时,连接池有什么作用?
答:
由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每 次建立连接都需要进行 TCP 的三次握手,释放连接需要进行 TCP 四次握手,造成 的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连 接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭 连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间 的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在 Java 开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于 Java 的 开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid 等。
补充:在计算机系统中时间和空间是不可调和的矛盾,理解这一点对设计满足性 能要求的算法是至关重要的。大型网站性能优化的一个关键就是使用缓存,而缓 存跟上面讲的连接池道理非常类似,也是使用空间换时间的策略。可以将热点数 据置于缓存中,当用户查询这些数据时可以直接从缓存中得到,这无论如何也快 过去数据库中查询。当然,缓存的置换策略等也会对系统性能产生重要影响,对 于这个问题的讨论已经超出了这里要阐述的范围。
79、什么是DAO模式?
答:
DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了 抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访 问操作。在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在 一个公共 API 中。用程序设计语言来说,就是建立一个接口,接口中定义了此应 用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交
第 301 页 共 485 页
互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该 类对应一个特定的数据存储。DAO 模式实际上包含了两个模式,一是 Data Accessor(数据访问器),二是 Data Object(数据对象),前者要解决如何访 问数据的问题,而后者要解决的是如何用对象封装数据。
80、事务的ACID是指什么?
答:
原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作 的失败都会导致整个事务的失败; 一致性(Consistent):事务结束后系统状态是一致的; 隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态; 持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难 性的失败。通过日志和同步备份可以在故障发生后重建数据。
补充:关于事务,在面试中被问到的概率是很高的,可以问的问题也是很多的。 首先需要知道的是,只有存在并发数据访问时才需要事务。当多个事务访问同一 数据时,可能会存在 5 类问题,包括 3 类数据读取问题(脏读、不可重复读和幻 读)和 2 类数据更新问题(第 1 类丢失更新和第 2 类丢失更新)。
脏读(Dirty Read):A 事务读取 B 事务尚未提交的数据并在此基础上操作,而 B 事务执行回滚,那么 A 读取到的数据就是脏数据。
时间 转账事务 A 取款事务 B
T1 开始事务
T2 开始事务
T3 查询账户余额为1000 元
第 302 页 共 485 页
T4 取出 500元余额修改为 500 元
T5 查询账户余额为500 元(脏读)
T6 撤销事务余额恢复为1000 元
T7 汇入 100元把余额修改为 600 元
T8 提交事务
不可重复读(Unrepeatable Read):事务 A 重新读取前面读取过的数据,发现 该数据已经被另一个已提交的事务 B 修改过了。
时间 转账事务 A 取款事务 B
T1 开始事务
T2 开始事务
T3 查询账户余额为 1000 元
T4 查询账户余额为1000 元
T5 取出 100 元修改余额为 900 元
T6 提交事务
T7 查询账户余额为900 元(不可重复读)
幻读(Phantom Read):事务 A 重新执行一个查询,返回一系列符合查询条件 的行,发现其中插入了被事务 B 提交的行。
第 303 页 共 485 页
时间 统计金额事务 A 转账事务 B
T1 开始事务
T2 开始事务
T3 统计总存款为 10000 元
T4 新增一个存款账户存入 100 元
T5 提交事务
T6 再次统计总存款为 10100 元(幻读)
第 1 类丢失更新:事务 A 撤销时,把已经提交的事务 B 的更新数据覆盖了。
时间 取款事务 A 转账事务B
T1 开始事务
T2 开始事务
T3 查询账户余额为1000 元
T4 查询账户余额为1000 元
T5 汇入100 元修改余额为 1100 元
T6 提交事务
T7 取出100元将余额修改为900元
T8 撤销事务
T9 余额恢复为1000 元(丢失更新)
第 304 页 共 485 页
第 2 类丢失更新:事务 A 覆盖事务 B 已经提交的数据,造成事务 B 所做的操作丢 失。
时间 转账事务 A 取款事务B
T1 开始事务
T2 开始事务
T3 查询账户余额为1000 元
T4 查询账户余额为1000 元
T5 取出100 元将余额修改为900 元
T6 提交事务
T7 汇入 100元将余额修改为 1100 元
T8 提交事务
T9 查询账户余额为1100 元(丢失更新)
数据并发访问所产生的问题,在有些场景下可能是允许的,但是有些场景下可能 就是致命的,数据库通常会通过锁机制来解决数据并发访问问题,按锁定对象不 同可以分为表级锁和行级锁;按并发事务锁定关系可以分为共享锁和独占锁,具 体的内容大家可以自行查阅资料进行了解。 直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定 会话的事务隔离级别,数据库就会通过分析 SQL 语句然后为事务访问的资源加上 合适的锁,此外,数据库还会维护这些锁通过各种手段提高系统的性能,这些对 用户来说都是透明的(就是说你不用理解,事实上我确实也不知道)。ANSI/ISO SQL 92 标准定义了 4 个等级的事务隔离级别,如下表所示:
隔离级别 脏读 不可重复读 幻读 第一类丢失更新 第二类丢失更新
第 305 页 共 485 页
READ UNCOMMITED
允许 允许 允许 不允许 允许
READ COMMITTED
不允许 允许 允许 不允许 允许
REPEATABLE READ
不允许 不允许 允许 不允许 不允许
SERIALIZABLE 不允许 不允许 不允许 不允许 不允许
需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高 并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没 有万能的原则。
**81、JDBC 中如何进行事务处理?
答:
Connection 提供了事务处理的方法,通过调用 setAutoCommit(false)可以设置 手动提交事务;当事务完成后用 commit()显式提交事务;如果在事务处理过程中 发生异常则通过 rollback()进行事务回滚。除此之外,从 JDBC 3.0 中还引入了 Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保 存点。
第 306 页 共 485 页
82、JDBC能否处理Blob和Clob?
答:
Blob 是指二进制大对象(Binary Large Object),而 Clob 是指大字符对象 (Character Large Objec),因此其中 Blob 是为存储大的二进制数据而设计的, 而 Clob 是为存储大的文本数据而设计的。JDBC 的 PreparedStatement 和 ResultSet 都提供了相应的方法来支持 Blob 和 Clob 操作。下面的代码展示了如 何使用 JDBC 操作 LOB: 下面以 MySQL 数据库为例,创建一个张有三个字段的用户表,包括编号(id)、 姓名(name)和照片(photo),建表语句如下:
create table tb_user ( id int primary key auto_increment, name varchar(20) unique not null, photo longblob );
下面的 Java 代码向数据库中插入一条记录:
import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException;
class JdbcLobTest {
第 307 页 共 485 页
public static void main(String[] args) { Connection con = null; try { // 1. 加载驱动(Java6 以上版本可以省略) Class.forName("com.mysql.jdbc.Driver"); // 2. 建立连接 con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456"); // 3. 创建语句对象 PreparedStatement ps = con.prepareStatement("insert into tb_user values (default, ?, ?)"); ps.setString(1, "骆昊"); // 将SQL语句中第一个 占位符换成字符串 try (InputStream in = new FileInputStream("test.jpg")) { // Java 7的TWR ps.setBinaryStream(2,in); // 将SQL语句中第二个占 位符换成二进制流 // 4. 发出SQL语句获得受影响行数 System.out.println(ps.executeUpdate() == 1 ? "插入成功 " : "插入失败"); } catch(IOException e) { System.out.println("读取照片失败!"); } } catch (ClassNotFoundException | SQLException e) { // Java 7的多异常捕获 e.printStackTrace(); } finally { // 释放外部资源的代码都应当放在finally中保证其能够得 到执行 try {
第 308 页 共 485 页
if(con != null && !con.isClosed()) { con.close(); // 5. 释放数据库连接 con = null; // 指示垃圾回收器可以回收该对象 } } catch (SQLException e) { e.printStackTrace(); }
}
}
}
83、简述正则表达式及其用途。
答:
在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。 正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本 规则的代码。
说明:计算机诞生初期处理的信息几乎都是数值,但是时过境迁,今天我们使用 计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符 串匹配和处理的时候最为强大的工具,绝大多数语言都提供了对正则表达式的支 持。
84、Java中是如何支持正则表达式操作的?
答:
第 309 页 共 485 页
Java 中的 String 类提供了支持正则表达式操作的方法,包括:matches()、 replaceAll()、replaceFirst()、split()。此外,Java 中可以用 Pattern 类表示正则 表达式对象,它提供了丰富的 API 进行各种正则表达式操作,请参考下面面试题 的代码。
面试题: - 如果要从字符串中截取第一个英文左括号之前的字符串,例如:北京 市(朝阳区)(西城区)(海淀区),截取结果为:北京市,那么正则表达式怎么写?
import java.util.regex.Matcher; import java.util.regex.Pattern;
class RegExpTest {
public static void main(String[] args) { String str = "北京市(朝阳区)(西城区)(海淀区)"; Pattern p = Pattern.compile(".*?(?=\()"); Matcher m = p.matcher(str); if(m.find()) { System.out.println(m.group()); } }
}
说明:上面的正则表达式中使用了懒惰匹配和前瞻,如果不清楚这些内容,推荐 读一下网上很有名的《正则表达式 30 分钟入门教程》。
85、获得一个类的类对象有哪些方式?
答:
第 310 页 共 485 页
方法1:类型.class,例如:String.class 方法2:对象.getClass(),例如:”hello”.getClass() 方法3:Class.forName(),例如:Class.forName(“java.lang.String”)
**86、如何通过反射创建对象?
答:
方法1:通过类对象调用newInstance()方法,例如: String.class.newInstance() 方法2:通过类对象的getConstructor()或getDeclaredConstructor() 方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象, 例如:String.class.getConstructor(String.class).newInstance(“Hello”);
**87、如何通过反射获取和设置对象私有字段的值?
答:
可以通过类对象的 getDeclaredField()方法字段(Field)对象,然后再通过字段 对象的 setAccessible(true)将其设置为可以访问,接下来就可以通过 get/set 方 法来获取/设置字段的值了。下面的代码实现了一个反射的工具类,其中的两个静 态方法分别用于获取和设置私有字段的值,字段可以是基本类型也可以是对象类 型且支持多级对象操作,例如 ReflectionUtil.get(dog, “owner.car.engine.id” ); 可以获得 dog 对象的主人的汽车的引擎的 ID 号。
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList;
第 311 页 共 485 页
import java.util.List;
/* 反射工具类 @author 骆昊 */ public class ReflectionUtil {
private ReflectionUtil() { throw new AssertionError(); }
/* 通过反射取对象指定字段(属性)的值 @param target 目标对象 @param fieldName 字段的名字 @throws 如果取不到对象指定字段的值则抛出异常 @return 字段的值 */ public static Object getValue(Object target, String fieldName) { Class> clazz = target.getClass(); String[] fs = fieldName.split("\.");
try {
for(int i = 0; i 第 312 页 共 485 页
Field f = clazz.getDeclaredField(fs[fs.length - 1]); f.setAccessible(true); return f.get(target);
} catch (Exception e) { throw new RuntimeException(e); }
}
/* 通过反射给对象的指定字段赋值 @param target 目标对象 @param fieldName 字段的名称 @param value 值 / public static void setValue(Object target, String fieldName, Object value) { Class> clazz = target.getClass(); String[] fs = fieldName.split("\."); try { for(int i = 0; i c = f.getType().getDeclaredConstructor(); c.setAccessible(true); val = c.newInstance(); f.set(target, val);
第 313 页 共 485 页
} target = val; clazz = target.getClass();
}
Field f = clazz.getDeclaredField(fs[fs.length - 1]); f.setAccessible(true); f.set(target, value);
} catch (Exception e) { throw new RuntimeException(e); }
}
}
88、如何通过反射调用对象的方法?
答:
请看下面的代码:
import java.lang.reflect.Method;
class MethodInvokeTest {
public static void main(String[] args) throws Exception { String str = "hello"; Method m = str.getClass().getMethod("toUpperCase"); System.out.println(m.invoke(str)); // HELLO
第 314 页 共 485 页
}
}
**89、简述一下面向对象的”六原则一法则”。
答:
单一职责原则:一个类只做它该做的事情。 (单一职责原则想表达的就是” 高内聚”,写代码最终极的原则只有六个字”高内聚、低耦合”,就如同葵花宝 典或辟邪剑谱的中心思想就八个字”欲练此功必先自宫”,所谓的高内聚就是一 个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事, 而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。我 们都知道一句话叫”因为专注,所以专业”,一个对象如果承担太多的职责,那 么注定它什么都做不好。这个世界上任何好的东西都有两个特征,一个是功能单 一,好的相机绝对不是电视购物里面卖的那种一个机器有一百多种功能的,它基 本上只能照相;另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速 器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定 是底板和胶皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模 块也应该是可以轻易的拿到其他系统中使用的,这样才能实现软件复用的目标。) 开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下, 当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类 就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关 键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系 统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系 统将变得复杂而换乱,如果不清楚如何封装可变性,可以参考《设计模式精解》 一书中对桥梁模式的讲解的章节。) 依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方 法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不 用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里 氏替换原则。)
第 315 页 共 485 页
里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的 描述,Barbara Liskov女士的描述比这个要复杂得多,但简单的说就是能用父 类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如 果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对 代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都 是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是: 子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更 多,把能力多的对象当成能力少的对象来用当然没有任何问题。) 接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的 污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高 度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口 中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用, 毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现 几个接口,这样的话每个接口被复用的可能性是很高的。Java中的接口代表能 力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。) 合成聚合复用原则:优先使用聚合或合成关系复用代码。(通过继承来复 用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例 外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系, Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关 联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是 Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关 系复用代码,原因嘛可以自己从百度上找到一万个理由,需要说明的是,即使在 Java的API中也有不少滥用继承的例子,例如Properties类继承了Hashtable 类,Stack类继承了Vector类,这些继承明显就是错误的,更好的做法是在 Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符 串来存储数据,而Stack类的设计也应该是在Stack类中放一个Vector对象来 存储数据。记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的, 而不是拿来继承的。) 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有 尽可能少的了解。(迪米特法则简单的说就是如何做到”低耦合”,门面模式和
第 316 页 共 485 页
调停者模式就是对迪米特法则的践行。对于门面模式可以举一个简单的例子,你 去一家公司洽谈业务,你不需要了解这个公司内部是如何运作的,你甚至可以对 这个公司一无所知,去的时候只需要找到公司入口处的前台美女,告诉她们你要 做什么,她们会找到合适的人跟你接洽,前台的美女就是公司这个系统的门面。 再复杂的系统都可以为用户提供一个简单的门面,JavaWeb开发中作为前端控 制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所 知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可 以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡 各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起, 计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它 将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统 的耦合度和复杂度,如下图所示。迪米特法则用通俗的话来将就是不要和陌生人 打交道,如果真的需要,找一个自己的朋友,让他替你和陌生人打交道。)
第 317 页 共 485 页
90、简述一下你了解的设计模式。
答:
所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经 过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他 人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计 和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解 其设计思路。 在 GoF 的《Design Patterns: Elements of Reusable Object-Oriented Software》中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如 何将类或对象结合在一起形成更大的结构]、行为型[对在不同的对象之间划分责任 和算法的抽象化])共 23 种设计模式,包括:Abstract Factory(抽象工厂模式), Builder(建造者模式),Factory Method(工厂方法模式),Prototype(原始 模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器 模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模 式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),
第 318 页 共 485 页
Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式), Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式), State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibility(责任链模式)。 面试被问到关于设计模式的知识时,可以拣最常用的作答,例如:
工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公 共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同 的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而 不必考虑到底返回的是哪一个子类的实例。 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引 用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、 保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理。 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使 原本因接口不匹配而无法在一起使用的类能够一起工作。 模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式 实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不 同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。
除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式 (Collections工具类和I/O系统中都使用装潢模式)等,反正基本原则就是拣 自己最熟悉的、用得最多的作答,以免言多必失。
91、用Java写一个单例类。
答:
饿汉式单例
第 319 页 共 485 页
public class Singleton { private Singleton(){} private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
懒汉式单例
public class Singleton { private static Singleton instance = null; private Singleton() {} public static synchronized Singleton getInstance(){ if (instance == null) instance = new Singleton(); return instance; } }
注意:实现一个单例有两点注意事项,①将构造器私有,不允许外界通过构造器 创建对象;②通过公开的静态方法向外界返回类的唯一实例。这里有一个问题可 以思考:Spring 的 IoC 容器可以为普通的类创建单例,它是怎么做到的呢?
92、什么是UML?
答:
第 320 页 共 485 页
UML 是统一建模语言(Unified Modeling Language)的缩写,它发表于 1997 年,综合了当时已经存在的面向对象的建模语言、方法和过程,是一个支持模型 化和软件系统开发的图形化语言,为软件开发的所有阶段提供模型化和可视化支 持。使用 UML 可以帮助沟通与交流,辅助应用设计和文档的生成,还能够阐释系 统的结构和行为。
93、UML中有哪些常用的图?
答:
UML 定义了多种图形化的符号来描述软件系统部分或全部的静态结构和动态结 构,包括:用例图(use case diagram)、类图(class diagram)、时序图(sequence diagram)、协作图(collaboration diagram)、状态图(statechart diagram)、 活动图(activity diagram)、构件图(component diagram)、部署图(deployment diagram)等。在这些图形化符号中,有三种图最为重要,分别是:用例图(用来 捕获需求,描述系统的功能,通过该图可以迅速的了解系统的功能模块及其关系)、 类图(描述类以及类与类之间的关系,通过该图可以快速了解系统)、时序图(描 述执行特定任务时对象之间的交互关系以及执行顺序,通过该图可以了解对象能 接收的消息也就是说对象能够向外界提供的服务)。 用例图:
第 321 页 共 485 页
类图:
时序图:
第 322 页 共 485 页
**94、用 Java 写一个冒泡排序。
答:
冒泡排序几乎是个程序员都写得出来,但是面试的时候如何写一个逼格高的冒泡 排序却不是每个人都能做到,下面提供一个参考代码:
import java.util.Comparator;
/* 排序器接口(策略模式: 将算法封装到具有共同接口的独立的类中使得它们可 以相互替换) @author骆昊 */ public interface Sorter {
/* 排序
第 323 页 共 485 页
- @param list 待排序的数组 */ public
> void sort(T[] list);
/* 排序 @param list 待排序的数组 @param comp 比较两个对象的比较器 */ public
}
import java.util.Comparator;
/* 冒泡排序 @author骆昊 / public class BubbleSorter implements Sorter {
@Override public
第 324 页 共 485 页
list[j + 1] = temp; swapped = true;
}
}
}
}
@Override public
}
95、用Java写一个折半查找。
答:
折半查找,也称二分查找、二分搜索,是一种在有序数组中查找某一特定元素的 搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,
第 325 页 共 485 页
则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小 于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一 步骤数组已经为空,则表示找不到指定的元素。这种搜索算法每一次比较都使搜 索范围缩小一半,其时间复杂度是 O(logN)。
import java.util.Comparator;
public class MyUtil {
public static
// 使用循环实现的二分查找 public static
{
int low = 0; int high = x.length - 1; while (low >> 1; int cmp = comp.compare(x[mid], key); if (cmp 0) { high= mid - 1; } else { return mid; }
第 326 页 共 485 页
} return -1;
}
// 使用递归实现的二分查找 private static
Java 面试题(二)
第 327 页 共 485 页
下面列出这份 Java 面试问题列表包含的主题
多线程,并发及线程基础 数据类型转换的基本原则 垃圾回收(GC) Java 集合框架 数组 字符串 GOF 设计模式 SOLID 抽象类与接口 Java 基础,如 equals 和 hashcode 泛型与枚举 Java IO 与 NIO 常用网络协议 Java 中的数据结构和算法 正则表达式 JVM 底层 Java 最佳实践 JDBC Date, Time 与 Calendar Java 处理 XML JUnit 编程
现在是时候给你展示我近 5 年从各种面试中收集来的 133 个问题了。我确定你 在自己的面试中见过很多这些问题,很多问题你也能正确回答。
第 328 页 共 485 页
多线程、并发及线程的基础问题
1、Java 中能创建 volatile 数组吗?
能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不 是整个数组。我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护, 但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护 作用了。
2、volatile 能使得一个非原子操作变成原子操作吗?
一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量 会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为什么? 因为 Java 中读取 long 类型变量不是原子的,需要分成两步,如果一个线程正 在修改该 long 变量的值,另一个线程可能只能看到该值的一半(前 32 位)。 但是对一个 volatile 型的 long 或 double 变量的读写是原子。
3、volatile 修饰符的有过什么实践?
一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。 double 和 long 都是 64 位宽,因此对这两种类型的读是分为两部分的,第一次 读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个 作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的 说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意 思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,
第 329 页 共 485 页
在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其 他所有写的值更新到缓存。
4、volatile 类型变量提供什么保证?
volatile 变量提供顺序和可见性保证,例如,JVM 或者 JIT 为了获得更好的性能 会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会 与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的 修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位 数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。
5、10 个线程和 2 个线程的同步代码,哪个更容易写?
从写代码的角度来说,两者的复杂度是相同的,因为同步代码与线程数量是相互 独立的。但是同步策略的选择依赖于线程的数量,因为越多的线程意味着更大的 竞争,所以你需要利用同步技术,如锁分离,这要求更复杂的代码和专业知识。
6、你是如何调用 wait()方法的?使用 if 块还是循环?为什
么?
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条 件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段 标准的使用 wait 和 notify 方法的代码:
// The standard idiom for using the wait method synchronized (obj) {
第 330 页 共 485 页
while (condition does not hold) obj.wait(); // (Releases lock, and reacquires on wakeup) ... // Perform action appropriate to condition }
参见 [Effective Java]第 69 条,获取更多关于为什么应该在循环中来调用 wait 方法的内容。
7、什么是多线程环境下的伪共享(false sharing)? 伪共享是多线程系统(每个处理器有自己的局部缓存)中一个众所周知的性能问 题。伪共享发生在不同处理器的上的线程对变量的修改依赖于相同的缓存行,如 下图所示:
有经验程序员的 Java 面试题
伪共享问题很难被发现,因为线程可能访问完全不同的全局变量,内存中却碰巧 在很相近的位置上。如其他诸多的并发问题,避免伪共享的最基本方式是仔细审 查代码,根据缓存行来调整你的数据结构。
8、什么是 Busy spin?我们为什么要使用它?
Busy spin 是一种在不释放 CPU 的基础上等待事件的技术。它经常用于避免丢 失 CPU 缓存中的数据(如果线程先暂停,之后在其他 CPU 上运行就会丢失)。 所以,如果你的工作要求低延迟,并且你的线程目前没有任何顺序,这样你就可 以通过循环检测队列中的新消息来代替调用 sleep() 或 wait() 方法。它唯一的 好处就是你只需等待很短的时间,如几微秒或几纳秒。LMAX 分布式框架是一个 高性能线程间通信的库,该库有一个 BusySpinWaitStrategy 类就是基于这个概 念实现的,使用 busy spin 循环 EventProcessors 等待屏障。
第 331 页 共 485 页
9、Java 中怎么获取一份线程 dump 文件?
在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应用的 dump 文件。在 Windows 下,你可以按下 Ctrl + Break 来获取。这 样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在 控制台或者日志文件中,具体位置依赖应用的配置。如果你使用 Tomcat。
10、Swing 是线程安全的?
不是,Swing 不是线程安全的。你不能通过任何线程来更新 Swing 组件,如 JTable、JList 或 JPanel,事实上,它们只能通过 GUI 或 AWT 线程来更新。 这就是为什么 Swing 提供 invokeAndWait() 和 invokeLater() 方法来获取其 他线程的 GUI 更新请求。这些方法将更新请求放入 AWT 的线程队列中,可以 一直等待,也可以通过异步更新直接返回结果。你也可以在参考答案中查看和学 习到更详细的内容。
11、什么是线程局部变量?
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共 享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方 式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心, 在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线 程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
12、用 wait-notify 写一段代码来解决生产者-消费者问题?
答案
第 332 页 共 485 页
http://java67.blogspot.sg/201... t-and-notify-example.html
请参考答案中的示例代码。只要记住在同步块中调用 wait() 和 notify()方法,如 果阻塞,通过循环来测试等待条件。
13、用 Java 写一个线程安全的单例模式(Singleton)?
答案
http://javarevisited.blogspot... eton-in-java-example.html
请参考答案中的示例代码,这里面一步一步教你创建一个线程安全的 Java 单例 类。当我们说线程安全时,意思是即使初始化是在多线程环境中,仍然能保证单 个实例。Java 中,使用枚举作为单例类是最简单的方式来创建线程安全单例模式 的方式。
14、Java 中 sleep 方法和 wait 方法的区别?
虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因 为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因 为只有这样,其他等待的线程才能在满足条件时获取到该锁。
15、什么是不可变对象(immutableobject)?Java 中怎么
创建一个不可变对象?
第 333 页 共 485 页
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的 对象,如 String、Integer 及其它包装类。详情参见答案,一步一步指导你在 Java 中创建一个不可变的类。
16、我们能创建一个包含可变对象的不可变对象吗?
是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点, 不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。 最常见的例子就是对象中包含一个日期对象的引用。
数据类型和 Java 基础面试问题
17、Java 中应该使用什么数据类型来代表价格?
如果不是特别关心内存和性能的话,使用 BigDecimal,否则使用预定义精度的 double 类型。
18、怎么将 byte 转换为 String?
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用 的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也 可能不同。
19、Java 中怎样将 bytes 转换为 long 类型?
第 334 页 共 485 页
这个问题你来回答 :-)
20、我们能将 int 强制转换为 byte 类型的变量吗?如果该值
大于 byte 类型的范围,将会出现什么现象?
是的,我们可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位 的,所以,如果强制转化是,int 类型的高 24 位将会被丢弃,byte 类型的范围 是从 -128 到 128。
21、存在两个类,B 继承 A,C 继承 B,我们能将 B 转换为
C 么?如 C = (C) B;
答案
http://javarevisited.blogspot... ss-interface-example.html
22、哪个类包含 clone 方法?是 Cloneable 还是 Object?
java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。
23、Java 中 ++ 操作符是线程安全的吗?
第 335 页 共 485 页
答案:不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存 储回内存,这个过程可能会出现多个线程交差。
23、不是线程安全的操作。它涉及到多个指令,如读取变量值,
增加,然后存储回内存,这个过程可能会出现多个线程交差。
24、a = a + b 与 a += b 的区别
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相 加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法 操作。如果加法操作的结果比 a 的最大值要大,则 a+b 会出现编译错误,但是 a += b 没问题,如下:
byte a = 127; byte b = 127; b = a + b; // error : cannot convert from int to byte b += a; // ok
(译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会 报错,因为 a+b 操作会将 a、 b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
25、我能在不进行强制转换的情况下将一个 double 值赋值给
long 类型的变量吗?
第 336 页 共 485 页
不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型 的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。
26、3*0.1 == 0.3 将会返回什么?true 还是 false?
false,因为有些浮点数不能完全精确的表示出来。
27、int 和 Integer 哪个会占用更多的内存?
Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。 但是 int 是一个原始类型的数据,所以占用的空间更少。
28、为什么 Java 中的 String 是不可变的(Immutable)?
Java 中的 String 不可变是因为 Java 的设计者认为字符串使用非常频繁,将字 符串设置为不可变可以允许多个客户端之间共享相同的字符串。
29、我们能在 Switch 中使用 String 吗? 从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法 糖。内部实现在 switch 中使用字符串的 hash code。
30、Java 中的构造器链是什么?
当你从一个构造器中调用另一个构造器,就是 Java 中的构造器链。这种情况只在 重载了类的构造器的时候才会出现。
JVM 底层 与 GC(Garbage Collection) 的面试问题
第 337 页 共 485 页
31、64 位 JVM 中,int 的长度是多数?
Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就 是说,在 32 位 和 64 位 的 Java 虚拟机中,int 类型的长度是相同的。
32、Serial 与 Parallel GC之间的不同之处?
Serial 与 Parallel 在 GC 执行的时候都会引起 stop-the-world。它们之间主要 不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而 parallel 收集器使用多个 GC 线程来执行。
33、32 位和 64 位的 JVM,int 类型变量的长度是多数?
32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4 个字节。
34、Java 中 WeakReference 与 SoftReference的区别?
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率, 但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用 虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。
35、WeakHashMap 是怎么工作的?
第 338 页 共 485 页
WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key, 意思就是当 key 对象没有任何引用时,key/value 将会被回收。
36、JVM 选项 -XX:+UseCompressedOops 有什么作用?
为什么要使用?
当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从 32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到 64 位的 JVM 主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位 的 OOP。
37、怎样通过 Java 程序来判断 JVM 是 32 位 还是 64
位?
你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。
38、32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?
理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个 小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM 允许指定最大的堆内存,理论上可以达到 2^64,这是一个非 常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul, 堆内存到 1000G 都是可能的。
第 339 页 共 485 页
39、JRE、JDK、JVM 及 JIT 之间有什么不同?
JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。JDK 代 表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java 编译器,它也包含 JRE。JVM 代表 Java 虚拟机(Java virtual machine),它 的责任是运行 Java 应用。JIT 代表即时编译(Just In Time compilation),当 代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主 要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。
3 年工作经验的 Java 面试题
40、解释 Java 堆空间及 GC?
当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于 创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内 部的一个进程,回收无效对象的内存用于将来的分配。
JVM 底层面试题及答案
41、你能保证 GC 执行吗?
不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。
42、怎么获取 Java 程序使用的内存?堆使用的百分比?
第 340 页 共 485 页
可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及 最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。 Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。
43、Java 中堆和栈有什么区别?
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局 部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享, 而堆被整个 JVM 的所有线程共享。
关于内存的的面试问题和答案
Java 基本概念面试题
44、“a==b”和”a.equals(b)”有什么区别?
如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指 向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以 通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方 法,所以可以用于两个不同对象,但是包含的字母相同的比较。
45、a.hashCode() 有什么用?与 a.equals(b) 有什么关系?
第 341 页 共 485 页
hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类, 如 Hashtable、HashMap、LinkedHashMap 等等。它与 equals() 方法关系特 别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有 相同的 hash code。
46、final、finalize 和 finally 的不同之处?
final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该 变量的值在初始化后不能被改变。finalize 方法是在对象被回收之前调用的方法, 给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行, 无论在 try 块中是否有发生异常。
47、Java 中的编译期常量是什么?使用它又什么风险?
公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里 的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些 变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你 使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改 变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的 jar。为了 避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。
Java 集合框架的面试题
这部分也包含数据结构、算法及数组的面试问题
48、List、Set、Map 和 Queue 之间的区别(答案)
第 342 页 共 485 页
List 是一个有序集合,允许元素重复。它的某些实现可以提供基于下标值的常量 访问时间,但是这不是 List 接口保证的。Set 是一个无序集合。
49、poll() 方法和 remove() 方法的区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败 的时候会返回空,但是 remove() 失败的时候会抛出异常。
50、Java 中 LinkedHashMap 和 PriorityQueue 的区别是
什么?
PriorityQueue 保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺 序。
51、ArrayList 与 LinkedList 的不区别?
最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构书链表,不支持随机访问。使用下标访问一个元素, ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。更多细节的讨论参见 答案。
52、用哪两种方式来实现集合的排序?
第 343 页 共 485 页
你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合, 如 list,然后通过 Collections.sort() 来排序。
53、Java 中怎么打印数组?
你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。由 于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。
54、Java 中的 LinkedList 是单向链表还是双向链表?
是双向链表,你可以检查 JDK 的源码。在 Eclipse,你可以使用快捷键 Ctrl + T, 直接在编辑器中打开该类。
55、Java 中的 TreeMap 是采用什么树实现的?(答案)
Java 中的 TreeMap 是使用红黑树实现的。
56、Hashtable 与 HashMap 有什么不同之处?
这两个类有许多不同的地方,下面列出了一部分: a) Hashtable 是 JDK 1 遗留下来的类,而 HashMap 是后来增加的。 b)Hashtable 是同步的,比较慢,但 HashMap 没有同步策略,所以会更快。 c)Hashtable 不允许有个空的 key,但是 HashMap 允许出现一个 null key。 更多的不同之处参见答案。
第 344 页 共 485 页
57、Java 中的 HashSet,内部是如何工作的?
HashSet 的内部采用 HashMap 来实现。由于 Map 需要 key 和 value,所以 所有 key 的都有一个默认 value。类似于 HashMap,HashSet 不允许重复的 key,只允许有一个 null key,意思就是 HashSet 中只允许存储一个 null 对象。
58、写一段代码在遍历 ArrayList 时移除一个元素?
该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法。这有一段示例代码,是使用正确的方式来实现在遍历的过程中移 除元素,而不会出现 ConcurrentModificationException 异常的示例代码。
59、我们能自己写一个容器类,然后使用 for-each 循环码?
可以,你可以写一个自己的容器类。如果你想使用 Java 中增强的循环来遍历, 你只需要实现 Iterable 接口。如果你实现 Collection 接口,默认就具有该属性。
60、ArrayList 和 HashMap 的默认大小是多数?
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是 16 个元素(必须是 2 的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的 代码片段:
// from ArrayList.java JDK 1.7 private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
第 345 页 共 485 页
static final int DEFAULT_INITIAL_CAPACITY = 1 61、有没有可能两个不相等的对象有有相同的 hashcode?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必 须有相同的 hashcode 值,但是没有关于不相等对象的任何规定。
62、两个相同的对象会有不同的的 hash code 吗?
不能,根据 hash code 的规定,这是不可能的。
63、我们可以在 hashcode() 中使用随机数字吗?
答案
http://javarevisited.blogspot... ple.html
不行,因为对象的 hashcode 值必须是相同的。参见答案获取更多关于 Java 中 重写 hashCode() 方法的知识。
64、Java 中,Comparator 与 Comparable 有什么不同?
Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户 定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义 对象的顺序。
第 346 页 共 485 页
65)为什么在重写 equals 方法的时候需要重写 hashCode 方法?(答案) 因为有强制的规范指定需要同时重写 hashcode 与 equal 是方法,许多容器类, 如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。
Java IO 和 NIO 的面试题
IO 是 Java 面试中一个非常重要的点。你应该很好掌握 Java IO,NIO,NIO2 以 及与操作系统,磁盘 IO 相关的基础知识。下面是 Java IO 中经常问的问题。
66、在我 Java 程序中,我有三个 socket,我需要多少个线
程来处理?
67、Java 中怎么创建 ByteBuffer?
byte[] bytes = new byte[10]; ByteBuffer buf = ByteBuffer.wrap(bytes);
68、Java 中,怎么读写 ByteBuffer ?
69、Java 采用的是大端还是小端?
70、ByteBuffer 中的字节序是什么?
第 347 页 共 485 页
71、Java 中,直接缓冲区与非直接缓冲器有什么区别?
答案 http://javarevisited.blogspot...
72、Java 中的内存映射缓存区是什么?
答案
http://javarevisited.blogspot... ava.html
73、socket 选项 TCP NO DELAY 是指什么?
74、TCP 协议与 UDP 协议有什么区别?
答案
http://javarevisited.blogspot... -udp-protocol.html
75、Java 中,ByteBuffer 与 StringBuffer有什么区别?(答
案)
第 348 页 共 485 页
Java 最佳实践的面试问题
包含 Java 中各个部分的最佳实践,如集合,字符串,IO,多线程,错误和异常 处理,设计模式等等。
76、Java 中,编写多线程程序的时候你会遵循哪些最佳实践?
这是我在写 Java 并发程序的时候遵循的一些最佳实践: a)给线程命名,这样可以帮助调试。 b)最小化同步的范围,而不是将整个方法同步,只对关键部分做同步。 c)如果可以,更偏向于使用 volatile 而不是 synchronized。 d)使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通 信,如 BlockingQueue,CountDownLatch 及 Semeaphore。 e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
77、说出几点 Java 中使用 Collections 的最佳实践
这是我在使用 Java 中 Collectionc 类的一些最佳实践: a)使用正确的集合类,例如,如果不需要同步列表,使用 ArrayList 而不是 Vector。 b)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。 c)使用接口代表和访问集合,如使用 List 存储 ArrayList,使用 Map 存储 HashMap 等等。 d)使用迭代器来循环集合。 e)使用集合的时候使用泛型。
78、说出至少 5 点在 Java 中使用线程的最佳实践。
第 349 页 共 485 页
答案
http://java67.blogspot.com/20... gthread-in-java.html
这个问题与之前的问题类似,你可以使用上面的答案。对线程来说,你应该: a)对线程命名 b)将线程和任务分离,使用线程池执行器来执行 Runnable 或 Callable。 c)使用线程池
79、说出 5 条 IO 的最佳实践(答案)
IO 对 Java 应用的性能非常重要。理想情况下,你不应该在你应用的关键路径上 避免 IO 操作。下面是一些你应该遵循的 Java IO 最佳实践: a)使用有缓冲区的 IO 类,而不要单独读取字节或字符。 b)使用 NIO 和 NIO2 c)在 finally 块中关闭流,或者使用 try-with-resource 语句。 d)使用内存映射文件获取更快的 IO。
80、列出 5 个应该遵循的 JDBC 最佳实践
答案
http://javarevisited.blogspot... ava.html))
有很多的最佳实践,你可以根据你的喜好来例举。下面是一些更通用的原则: a)使用批量的操作来插入和更新数据
第 350 页 共 485 页
b)使用 PreparedStatement 来避免 SQL 异常,并提高性能。 c)使用数据库连接池 d)通过列名来获取结果集,不要使用列的下标来获取。
81、说出几条 Java 中方法重载的最佳实践?
下面有几条可以遵循的方法重载的最佳实践来避免造成自动装箱的混乱。 a)不要重载这样的方法:一个方法接收 int 参数,而另个方法接收 Integer 参 数。 b)不要重载参数数量一致,而只是参数顺序不同的方法。 c)如果重载的方法参数个数多于 5 个,采用可变参数。
Date、Time 及 Calendar 的面试题
82、在多线程环境下,SimpleDateFormat 是线程安全的吗?
不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是 线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中 使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做, 在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、 时间处理的所有实践来说,我强力推荐 joda-time 库。
83、Java 中如何格式化一个日期?如格式化为 ddMMyyyy
的形式?
答案
第 351 页 共 485 页
http://javarevisited.blogspot... dateformat.html Java 中,可以使用 SimpleDateFormat 类或者 joda-time 库来格式日期。 DateFormat 类允许你使用多种流行的格式来格式化日期。参见答案中的示例代 码,代码中演示了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。
84、Java 中,怎么在格式化的日期中显示时区?
答案
http://java67.blogspot.sg/201... eformat-example.html
85、Java 中 java.util.Date 与 java.sql.Date 有什么区别?
答案
http://java67.blogspot.sg/201... ldate-example.html
86、Java 中,如何计算两个日期之间的差距?
程序
http://javarevisited.blogspot... tween-two-dates-in-java.html
第 352 页 共 485 页
87、Java 中,如何将字符串 YYYYMMDD 转换为日期?
答案
http://java67.blogspot.sg/201... hreading.html
单元测试 JUnit 面试题
89、如何测试静态方法?(答案)
可以使用 PowerMock 库来测试静态方法。
90、怎么利用 JUnit 来测试一个方法的异常?
答案
http://javarevisited.blogspot... ption-thrown-by-java-method.html
91、你使用过哪个单元测试库来测试你的 Java 程序?
92、@Before 和 @BeforeClass 有什么区别?
答案
第 353 页 共 485 页
http://javarevisited.blogspot... ption-thrown-by-java-method.html
编程和代码相关的面试题
93、怎么检查一个字符串只包含数字?解决方案
http://java67.blogspot.com/20... mbers-in-String.html
94、Java 中如何利用泛型写一个 LRU 缓存?
95、写一段 Java 程序将 byte 转换为 long?
95、在不使用 StringBuffer 的前提下,怎么反转一个字符串?
解决方案
http://java67.blogspot.com/20... buffer-stringbuilder.htm
97、Java 中,怎么获取一个文件中单词出现的最高频率?
解决方案
第 354 页 共 485 页
http://java67.blogspot.com/20... ds-and-count.html
98、如何检查出两个给定的字符串是反序的?
解决方案
http://javarevisited.blogspot... tring-are-anagrams-example-tutorial.html
99、Java 中,怎么打印出一个字符串的所有排列?
解决方案
http://javarevisited.blogspot...
100、Java 中,怎样才能打印出数组中的重复元素?
解决方案
http://javarevisited.blogspot... ents-in-array-java.html
101、Java 中如何将字符串转换为整数?
第 355 页 共 485 页
String s="123"; int i; 第一种方法:i=Integer.parseInt(s); 第二种方法:i=Integer.valueOf(s).intValue();
102、在没有使用临时变量的情况如何交换两个整数变量的值?
解决方案
https://blog.csdn.net/zidane_...
关于 OOP 和设计模式的面试题
这部分包含 Java 面试过程中关于 SOLID 的设计原则,OOP 基础,如类,对象, 接口,继承,多态,封装,抽象以及更高级的一些概念,如组合、聚合及关联。 也包含了 GOF 设计模式的问题。
103、接口是什么?为什么要使用接口而不是直接使用具体类?
接口用于定义 API。它定义了类必须得遵循的规则。同时,它提供了一种抽象, 因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使用可随机 访问的 ArrayList,也可以使用方便插入和删除的 LinkedList。接口中不允许写 代码,以此来保证抽象,但是 Java 8 中你可以在接口声明静态的默认方法,这 种方法是具体的。
104、Java 中,抽象类与接口之间有什么不同?
第 356 页 共 485 页
Java 中,抽象类和接口有很多不同之处,但是最重要的一个是 Java 中限制一个 类只能继承一个类,但是可以实现多个接口。抽象类可以很好的定义一个家族类 的默认行为,而接口能更好的定义类型,有助于后面实现多态机制。
105、除了单例模式,你在生产环境中还用过什么设计模式?
这需要根据你的经验来回答。一般情况下,你可以说依赖注入,工厂模式,装饰 模式或者观察者模式,随意选择你使用过的一种即可。不过你要准备回答接下的 基于你选择的模式的问题。
106、你能解释一下里氏替换原则吗?
答案
https://blog.csdn.net/pu_xubo...
107) 什么情况下会违反迪米特法则?为什么会有这个问题?
迪米特法则建议“只和朋友说话,不要陌生人说话”,以此来减少类之间的耦合。
108、适配器模式是什么?什么时候使用?
适配器模式提供对接口的转换。如果你的客户端使用某些接口,但是你有另外一 些接口,你就可以写一个适配去来连接这些接口。
109、什么是“依赖注入”和“控制反转”?为什么有人使用?
第 357 页 共 485 页
控制反转(IOC)是 Spring 框架的核心思想,用我自己的话说,就是你要做一件 事,别自己可劲 new 了,你就说你要干啥,然后外包出去就好~ 依赖注入(DI) 在我浅薄的想法中,就是通过接口的引用和构造方法的表达,将 一些事情整好了反过来传给需要用到的地方~
110、抽象类是什么?它与接口有什么区别?你为什么要使用过
抽象类?
接口用于规范,抽象类用于共性. 声明方法的存在而不去实现它的类被叫做抽象类 接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。
111、构造器注入和 setter 依赖注入,那种方式更好?
每种方式都有它的缺点和优点。构造器注入保证所有的注入都被初始化,但是 setter 注入提供更好的灵活性来设置可选依赖。如果使用 XML 来描述依赖, Setter 注入的可读写会更强。经验法则是强制依赖使用构造器注入,可选依赖使 用 setter 注入。
112、依赖注入和工程模式之间有什么不同?
虽然两种模式都是将对象的创建从应用的逻辑中分离,但是依赖注入比工程模式 更清晰。通过依赖注入,你的类就是 POJO,它只知道依赖而不关心它们怎么获 取。使用工厂模式,你的类需要通过工厂来获取依赖。因此,使用 DI 会比使用 工厂模式更容易测试。
第 358 页 共 485 页
113、适配器模式和装饰器模式有什么区别?
虽然适配器模式和装饰器模式的结构类似,但是每种模式的出现意图不同。适配 器模式被用于桥接两个接口,而装饰模式的目的是在不修改类的情况下给类增加 新的功能。
114、适配器模式和代理模式之前有什么不同?
这个问题与前面的类似,适配器模式和代理模式的区别在于他们的意图不同。由 于适配器模式和代理模式都是封装真正执行动作的类,因此结构是一致的,但是 适配器模式用于接口之间的转换,而代理模式则是增加一个额外的中间层,以便 支持分配、控制或智能访问。
115、什么是模板方法模式?
模板方法提供算法的框架,你可以自己去配置或定义步骤。例如,你可以将排序 算法看做是一个模板。它定义了排序的步骤,但是具体的比较,可以使用 Comparable 或者其语言中类似东西,具体策略由你去配置。列出算法概要的方 法就是众所周知的模板方法。
116、什么时候使用访问者模式?
访问者模式用于解决在类的继承层次上增加操作,但是不直接与之关联。这种模 式采用双派发的形式来增加中间层。
117、什么时候使用组合模式?
第 359 页 共 485 页
组合模式使用树结构来展示部分与整体继承关系。它允许客户端采用统一的形式 来对待单个对象和对象容器。当你想要展示对象这种部分与整体的继承关系时采 用组合模式。
118、继承和组合之间有什么不同?
虽然两种都可以实现代码复用,但是组合比继承共灵活,因为组合允许你在运行 时选择不同的实现。用组合实现的代码也比继承测试起来更加简单。
119、描述 Java 中的重载和重写?
重载和重写都允许你用相同的名称来实现不同的功能,但是重载是编译时活动, 而重写是运行时活动。你可以在同一个类中重载方法,但是只能在子类中重写方 法。重写必须要有继承。
120、Java 中,嵌套公共静态类与顶级类有什么不同?
类的内部可以有多个嵌套公共静态类,但是一个 Java 源文件只能有一个顶级公 共类,并且顶级公共类的名称与源文件名称必须一致。
121、 OOP 中的 组合、聚合和关联有什么区别?
如果两个对象彼此有关系,就说他们是彼此相关联的。组合和聚合是面向对象中 的两种形式的关联。组合是一种比聚合更强力的关联。组合中,一个对象是另一 个的拥有者,而聚合则是指一个对象使用另一个对象。如果对象 A 是由对象 B
第 360 页 共 485 页
组合的,则 A 不存在的话,B 一定不存在,但是如果 A 对象聚合了一个对象 B, 则即使 A 不存在了,B 也可以单独存在。
122、给我一个符合开闭原则的设计模式的例子?
开闭原则要求你的代码对扩展开放,对修改关闭。这个意思就是说,如果你想增 加一个新的功能,你可以很容易的在不改变已测试过的代码的前提下增加新的代 码。有好几个设计模式是基于开闭原则的,如策略模式,如果你需要一个新的策 略,只需要实现接口,增加配置,不需要改变核心逻辑。一个正在工作的例子是 Collections.sort() 方法,这就是基于策略模式,遵循开闭原则的,你不需为新的 对象修改 sort() 方法,你需要做的仅仅是实现你自己的 Comparator 接口。
123、抽象工厂模式和原型模式之间的区别?
抽象工厂模式:通常由工厂方法模式来实现。但一个工厂中往往含有多个工厂方 法生成一系列的产品。这个模式强调的是客户代码一次保证只使用一个系列的产 品。当要切换为另一个系列的产品,换一个工厂类即可。
原型模式:工厂方法的最大缺点就是,对应一个继承体系的产品类,要有一个同 样复杂的工厂类的继承体系。我们可以把工厂类中的工厂方法放到产品类自身之 中吗?如果这样的话,就可以将两个继承体系为一个。这也就是原型模式的思想, 原型模式中的工厂方法为 clone,它会返回一个拷贝(可以是浅拷贝,也可以是深 拷贝,由设计者决定)。为了保证用户代码中到时可以通过指针调用 clone 来动 态绑定地生成所需的具体的类。这些原型对象必须事先构造好。
原型模式想对工厂方法模式的另一个好处是,拷贝的效率一般对构造的效率要高。
124、什么时候使用享元模式?
第 361 页 共 485 页
享元模式通过共享对象来避免创建太多的对象。为了使用享元模式,你需要确保 你的对象是不可变的,这样你才能安全的共享。JDK 中 String 池、Integer 池 以及 Long 池都是很好的使用了享元模式的例子。
Java 面试中其他各式各样的问题
这部分包含 Java 中关于 XML 的面试题,正则表达式面试题,Java 错误和异常 及序列化面试题
125、嵌套静态类与顶级类有什么区别?
一个公共的顶级类的源文件名称与类名相同,而嵌套静态类没有这个要求。一个 嵌套类位于顶级类内部,需要使用顶级类的名称来引用嵌套静态类,如 HashMap.Entry 是一个嵌套静态类,HashMap 是一个顶级类,Entry 是一个嵌 套静态类。
126、你能写出一个正则表达式来判断一个字符串是否是一个数
字吗?
一个数字字符串,只能包含数字,如 0 到 9 以及 +、- 开头,通过这个信息, 你可以下一个如下的正则表达式来判断给定的字符串是不是数字。
首先要import java.util.regex.Pattern 和 java.util.regex.Matcher public boolean isNumeric(String str){ Pattern pattern = Pattern.compile("[0-9]*"); Matcher isNum = pattern.matcher(str); if( !isNum.matches() ){
第 362 页 共 485 页
return false;
} return true;
}
127、Java 中,受检查异常 和 不受检查异常的区别?
受检查异常编译器在编译期间检查。对于这种异常,方法强制处理或者通过 throws 子句声明。其中一种情况是 Exception 的子类但不是 RuntimeException 的子类。非受检查是 RuntimeException 的子类,在编译阶 段不受编译器的检查。
128、Java 中,throw 和 throws 有什么区别
throw 用于抛出 java.lang.Throwable 类的一个实例化对象,意思是说你可以通 过关键字 throw 抛出一个 Error 或者 一个 Exception,如: throw new IllegalArgumentException(“size must be multiple of 2″)
而 throws 的作用是作为方法声明和签名的一部分,方法被抛出相应的异常以便 调用者能处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。
129、Java 中,Serializable 与 Externalizable 的区别?
Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输 或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、 脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进 制格式,增加安全机制。
第 363 页 共 485 页
130、Java 中,DOM 和 SAX 解析器有什么不同?
DOM 解析器将整个 XML 文档加载到内存来创建一棵 DOM 模型树,这样可以 更快的查找节点和修改 XML 结构,而 SAX 解析器是一个基于事件的解析器, 不会将整个 XML 文档加载到内存。由于这个原因,DOM 比 SAX 更快,也要 求更多的内存,不适合于解析大 XML 文件。
131、说出 JDK 1.7 中的三个新特性?
虽然 JDK 1.7 不像 JDK 5 和 8 一样的大版本,但是,还是有很多新的特性, 如 try-with-resource 语句,这样你在使用流或者资源的时候,就不需要手动关 闭,Java 会自动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。 允许 Switch 中有 String 变量和文本。菱形操作符()用于类型推断,不再需 要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。另一 个值得一提的特性是改善异常处理,如允许在同一个 catch 块中捕获多个异常。
132、说出 5 个 JDK 1.8 引入的新特性?
Java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性: Lambda 表达式,允许像对象一样传递匿名函数 Stream API,充分利用现代多核 CPU,可以写出很简洁的代码 Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用 扩展方法,现在,接口中可以有静态、默认方法。 重复注解,现在你可以将相同的注解在同一类型上使用多次。
133、Java 中,Maven 和 ANT 有什么区别?
第 364 页 共 485 页
虽然两者都是构建工具,都用于创建 Java 应用,但是 Maven 做的事情更多, 在基于“约定优于配置”的概念下,提供标准的 Java 项目结构,同时能为应用自 动管理依赖(应用中所依赖的 JAR 文件),Maven 与 ANT 工具更多的不同之 处请参见答案。
这就是所有的面试题,如此之多,是不是?我可以保证,如果你能回答列表中的 所有问题,你就可以很轻松的应付任何核心 Java 或者高级 Java 面试。虽然, 这里没有涵盖 Servlet、JSP、JSF、JPA,JMS,EJB 及其它 Java EE 技术,也 没有包含主流的框架如 Spring MVC,Struts 2.0,Hibernate,也没有包含 SOAP 和 RESTful web service,但是这份列表对做 Java 开发的、准备应聘 Java web 开发职位的人还是同样有用的,因为所有的 Java 面试,开始的问题都是 Java 基 础和 JDK API 相关的。如果你认为我这里有任何应该在这份列表中而被我遗漏了 的 Java 流行的问题,你可以自由的给我建议。我的目的是从最近的面试中创建 一份最新的、最优的 Java 面试问题列表。
Spring 面试题(一)
1、一般问题
1.1、不同版本的 Spring Framework 有哪些主要功能?
Version Feature
Spring 2.5 发布于 2007 年。这是第一个支持注解的版本。
Spring 3.0 发布于 2009 年。它完全利用了 Java5 中的改进,并为 JEE6 提供了支 持。
Spring 4.0 发布于 2013 年。这是第一个完全支持 JAVA8 的版本。
第 365 页 共 485 页
1.2、什么是 Spring Framework?
Spring 是一个开源应用框架,旨在降低应用程序开发的复杂度。它是轻量级、松 散耦合的。它具有分层体系结构,允许用户选择组件,同时还为 J2EE 应用程序 开发提供了一个有凝聚力的框架。它可以集成其他框架,如 Structs、Hibernate、 EJB 等,所以又称为框架的框架。
1.3、列举 Spring Framework 的优点。
由于 Spring Frameworks 的分层架构,用户可以自由选择自己需要的组件。 Spring Framework 支持 POJO(Plain Old Java Object) 编程,从而具备持续集 成和可测试性。由于依赖注入和控制反转,JDBC 得以简化。它是开源免费的。
1.4、Spring Framework 有哪些不同的功能?
轻量级 - Spring 在代码量和透明度方面都很轻便。IOC - 控制反转 AOP - 面向 切面编程可以将应用业务逻辑和系统服务分离,以实现高内聚。容器 - Spring 负 责创建和管理对象(Bean)的生命周期和配置。MVC - 对 web 应用提供了高 度可配置性,其他框架的集成也十分方便。事务管理 - 提供了用于事务管理的通 用抽象层。Spring 的事务支持也可用于容器较少的环境。JDBC 异常 - Spring 的 JDBC 抽象层提供了一个异常层次结构,简化了错误处理策略。
1.5、SpringFramework 中有多少个模块,它们分别是什么?
第 366 页 共 485 页
Spring 核心容器 – 该层基本上是 Spring Framework 的核心。它包含以下模 块:
Spring Core Spring Bean SpEL (Spring Expression Language) Spring Context
数据访问/集成 – 该层提供与数据库交互的支持。它包含以下模块:
JDBC (Java DataBase Connectivity) ORM (Object Relational Mapping) OXM (Object XML Mappers)
第 367 页 共 485 页
JMS (Java Messaging Service) Transaction
Web – 该层提供了创建 Web 应用程序的支持。它包含以下模块:
Web Web – Servlet Web – Socket Web – Portlet
AOP
该层支持面向切面编程
Instrumentation
该层为类检测和类加载器实现提供支持。
Test
该层为使用 JUnit 和 TestNG 进行测试提供支持。
几个杂项模块:
第 368 页 共 485 页
Messaging – 该模块为 STOMP 提供支持。它还支持注解编程模型,该模型用 于从 WebSocket 客户端路由和处理 STOMP 消息。
Aspects – 该模块为与 AspectJ 的集成提供支持。
1.6、什么是 Spring 配置文件?
Spring 配置文件是 XML 文件。该文件主要包含类信息。它描述了这些类是如何 配置以及相互引入的。但是,XML 配置文件冗长且更加干净。如果没有正确规划 和编写,那么在大项目中管理变得非常困难。
1.7、Spring 应用程序有哪些不同组件?
Spring 应用一般有以下组件:
接口 - 定义功能。 Bean 类 - 它包含属性,setter 和 getter 方法,函数等。 Spring 面向切面编程(AOP) - 提供面向切面编程的功能。 Bean 配置文件 - 包含类的信息以及如何配置它们。 用户程序 - 它使用接口。
1.8、使用 Spring 有哪些方式?
使用 Spring 有以下方式:
作为一个成熟的 Spring Web 应用程序。
第 369 页 共 485 页
作为第三方 Web 框架,使用 Spring Frameworks 中间层。 用于远程使用。 作为企业级 Java Bean,它可以包装现有的 POJO(Plain Old Java Objects)。
2、依赖注入(Ioc)
2.1、什么是 Spring IOC 容器?
Spring 框架的核心是 Spring 容器。容器创建对象,将它们装配在一起,配置它 们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的 组件。容器通过读取提供的配置元数据来接收对象进行实例化,配置和组装的指 令。该元数据可以通过 XML,Java 注解或 Java 代码提供。
2.2、什么是依赖注入?
第 370 页 共 485 页
在依赖注入中,您不必创建对象,但必须描述如何创建它们。您不是直接在代码 中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务。由 IoC 容器将它们装配在一起。
2.3、可以通过多少种方式完成依赖注入?
通常,依赖注入可以通过三种方式完成,即:
构造函数注入 setter 注入 接口注入
在 Spring Framework 中,仅使用构造函数和 setter 注入。
2.4、区分构造函数注入和 setter 注入。
构造函数注入 setter 注入
没有部分注入 有部分注入
不会覆盖 setter 属性 会覆盖 setter 属性
任意修改都会创建一个新实例 任意修改不会创建一个新实例
适用于设置很多属性 适用于设置少量属性
2.5、spring 中有多少种 IOC 容器?
第 371 页 共 485 页
BeanFactory - BeanFactory 就像一个包含 bean 集合的工厂类。它会在客户端 要求时实例化 bean。
ApplicationContext - ApplicationContext 接口扩展了 BeanFactory 接口。它 在 BeanFactory 基础上提供了一些额外的功能。
2.6、区分 BeanFactory 和 ApplicationContext。
BeanFactory ApplicationContext
它使用懒加载 它使用即时加载
它使用语法显式提供资源对象 它自己创建和管理资源对象
不支持国际化 支持国际化
不支持基于依赖的注解 支持基于依赖的注解
2.7、列举 IoC 的一些好处。
IoC 的一些好处是:
它将最小化应用程序中的代码量。 它将使您的应用程序易于测试,因为它不需要单元测试用例中的任何单例 或 JNDI 查找机制。 它以最小的影响和最少的侵入机制促进松耦合。 它支持即时的实例化和延迟加载服务。
2.8、Spring IoC 的实现机制。
第 372 页 共 485 页
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
示例:
interface Fruit { public abstract void eat(); } class Apple implements Fruit { public void eat(){ System.out.println("Apple"); } } class Orange implements Fruit { public void eat(){ System.out.println("Orange"); } } class Factory { public static Fruit getInstance(String ClassName) { Fruit f=null; try { f=(Fruit)Class.forName(ClassName).newInstance(); } catch (Exception e) { e.printStackTrace(); } return f; } } class Client { public static void main(String[] a) {
第 373 页 共 485 页
Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple"); if(f!=null){ f.eat(); }
}
}
3、Beans
3.1、什么是 spring bean?
它们是构成用户应用程序主干的对象。 Bean 由 Spring IoC 容器管理。 它们由 Spring IoC 容器实例化,配置,装配和管理。 Bean 是基于用户提供给容器的配置元数据创建。
3.2、spring 提供了哪些配置方式?
基于 xml 配置
bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常 包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开 头。例如:
第 374 页 共 485 页
基于注解配置
您可以通过在相关的类,方法或字段声明上使用注解,将 bean 配置为组件类本 身,而不是使用 XML 来描述 bean 装配。默认情况下,Spring 容器中未打开 注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。例如:
第 392 页 共 485 页
Spring 面试题(二)
1、什么是spring?
Spring 是个 java 企业级应用的开源开发框架。Spring 主要用来开发 Java 应用, 但是有些扩展是针对构建 J2EE 平台的 web 应用。Spring 框架目标是简化 Java 企业级应用开发,并通过 POJO 为基础的编程模型促进良好的编程习惯。
2、使用Spring框架的好处是什么?
轻量:Spring 是轻量的,基本的版本大约2MB。 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依 赖,而不是创建或查找依赖的对象们。 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务 逻辑和系统服务分开。 容器:Spring 包含并管理应用中对象的生命周期和配置。 MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的 一个很好的替代品。 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地 事务下至全局事务(JTA)。 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC, Hibernate or JDO抛出的)转化为一致的unchecked 异常。
3、Spring由哪些模块组成?
以下是 Spring 框架的基本模块:
第 393 页 共 485 页
Core module Bean module Context module Expression Language module JDBC module ORM module OXM module Java Messaging Service(JMS) module Transaction module Web module Web-Servlet module Web-Struts module Web-Portlet module
4、核心容器(应用上下文) 模块。
这是基本的 Spring 模块,提供 spring 框架的基础功能,BeanFactory 是 任何 以 spring 为基础的应用的核心。Spring 框架建立在此模块之上,它使 Spring 成 为一个容器。
5、BeanFactory – BeanFactory 实现举例。
Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依 赖从正真的应用代码中分离。
最常用的 BeanFactory 实现是 XmlBeanFactory 类。
第 394 页 共 485 页
6、XMLBeanFactory
最常用的就是 org.springframework.beans.factory.xml.XmlBeanFactory ,它 根据 XML 文件中的定义加载 beans。该容器从 XML 文件读取配置元数据并用它 去创建一个完全配置的系统或应用。
7、解释AOP模块
AOP 模块用于发给我们的 Spring 应用做面向切面的开发, 很多支持由 AOP 联 盟提供,这样就确保了 Spring 和其他 AOP 框架的共通性。这个模块将元数据编 程引入 Spring。
8、解释JDBC抽象和DAO模块。
通过使用 JDBC 抽象和 DAO 模块,保证数据库代码的简洁,并能避免数据库资源 错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一 的异常访问层。它还利用 Spring 的 AOP 模块给 Spring 应用中的对象提供事务 管理服务。
9、解释对象/关系映射集成模块。
Spring 通过提供 ORM 模块,支持我们在直接 JDBC 之上使用一个对象/关系映射 映射(ORM)工具,Spring 支持集成主流的 ORM框架,如 Hiberate,JDO和 iBATIS SQL Maps。Spring 的事务管理同样支持以上所有 ORM 框架及 JDBC。
10、解释WEB 模块。
第 395 页 共 485 页
Spring 的 WEB 模块是构建在 application context 模块基础之上,提供一个适 合 web 应用的上下文。这个模块也包括支持多种面向 web 的任务,如透明地处理 多个文件上传请求和程序级请求参数的绑定到你的业务对象。它也有对 Jakarta Struts 的支持。
12、Spring配置文件
Spring 配置文件是个 XML 文件,这个文件包含了类信息,描述了如何配置它们, 以及如何相互调用。
13、什么是Spring IOC 容器?
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对 象,并且管理这些对象的整个生命周期。
14、IOC的优点是什么?
IOC 或 依赖注入把应用的代码量降到最低。它使应用容易测试,单元测试不再需 要单例和 JNDI 查找机制。最小的代价和最小的侵入性使松散耦合得以实现。IOC 容器支持加载服务时的饿汉式初始化和懒加载。
15、ApplicationContext通常的实现是什么?
FileSystemXmlApplicationContext :此容器从一个XML文件中加 载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
第 396 页 共 485 页
ClassPathXmlApplicationContext:此容器也从一个XML文件中加 载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath 里找bean配置。 WebXmlApplicationContext:此容器加载一个XML文件,此文件定 义了一个WEB应用的所有bean。
16、Bean 工厂和 Application contexts 有什么区别?
Application contexts 提供一种方法处理文本消息,一个通常的做法是加载文件 资源(比如镜像),它们可以向注册为监听器的 bean 发布事件。另外,在容器或 容器内的对象上执行的那些不得不由 bean 工厂以程序化方式处理的操作,可以在 Application contexts 中以声明的方式处理。Application contexts 实现了 MessageSource 接口,该接口的实现以可插拔的方式提供获取本地化消息的方 法。
17、一个Spring的应用看起来象什么?
一个定义了一些功能的接口。 这实现包括属性,它的Setter , getter 方法和函数等。 Spring AOP。 Spring 的XML 配置文件。 使用以上功能的客户端程序。
依赖注入
18、什么是Spring的依赖注入?
第 397 页 共 485 页
依赖注入,是 IOC 的一个方面,是个通常的概念,它有多种解释。这概念是说你 不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和 服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC 容 器)负责把他们组装起来。
19、有哪些不同类型的IOC(依赖注入)方式?
构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现 的,该类有一系列参数,每个参数代表一个对其他类的依赖。 Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参 static工厂 方法实例化bean之后,调用该bean的setter 方法,即实现了基 于setter的依赖注入。
20、哪种依赖注入方式你建议使用,构造器注入,还是 Setter
方法注入?
你两种依赖方式都可以使用,构造器注入和 Setter 方法注入。最好的解决方案是 用构造器参数实现强制依赖,setter 方法实现可选依赖。
Spring Beans
21.什么是Spring beans?
第 398 页 共 485 页
Spring beans 是那些形成 Spring 应用的主干的 java 对象。它们被 Spring IOC 容器初始化,装配,和管理。这些 beans 通过容器中配置的元数据创建。比如, 以 XML 文件中 的形式定义。
Spring 框架定义的 beans 都是单件 beans。在 bean tag 中有个属性” singleton”,如果它被赋为 TRUE,bean 就是单件,否则就是一个 prototype bean。默认是 TRUE,所以所有在 Spring 框架中的 beans 缺省都是单件。
22、一个 Spring Bean 定义 包含什么?
一个 Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个 bean,它的生命周期详情及它的依赖。
23、如何给Spring 容器提供配置元数据?
这里有三种重要的方法给 Spring 容器提供配置元数据。
XML 配置文件。
基于注解的配置。
基于 java 的配置。
24、你怎样定义类的作用域?
当定义一个 在 Spring 里,我们还能给这个 bean 声明一个作用域。它可以通过 bean 定义中的 scope 属性来定义。如,当 Spring 要在需要的时候每次生产一个 新的 bean 实例,bean 的 scope 属性被指定为 prototype。另一方面,一个 bean
第 399 页 共 485 页
每次使用的时候必须返回同一个实例,这个 bean 的 scope 属性 必须设为 singleton。
25、解释Spring支持的几种bean的作用域。
Spring 框架支持以下五种 bean 的作用域:
singleton : bean在每个Spring ioc 容器中只有一个实例。 prototype:一个bean的定义可以有多个实例。 request:每次http请求都会创建一个bean,该作用域仅在基于web 的Spring ApplicationContext情形下有效。 session:在一个HTTP Session中,一个bean定义对应一个实例。该 作用域仅在基于web的Spring ApplicationContext情形下有效。 global-session:在一个全局的HTTP Session中,一个bean定义对应 一个实例。该作用域仅在基于web的SpringApplicationContext情形下有效。
缺省的 Spring bean 的作用域是 Singleton.
26、Spring框架中的单例bean是线程安全的吗?
不,Spring 框架中的单例 bean 不是线程安全的。
27、解释Spring框架中bean的生命周期。
Spring容器 从XML 文件中读取bean的定义,并实例化bean。 Spring根据bean的定义填充所有的属性。
第 400 页 共 485 页
如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。 如果Bean 实现了 BeanFactoryAware 接口, Spring 传递 beanfactory 给setBeanFactory 方法。 如果有任何与bean相关联的BeanPostProcessors,Spring 会在 postProcesserBeforeInitialization()方法内调用它们。 如果bean实现IntializingBean了,调用它的afterPropertySet方法, 如果bean声明了初始化方法,调用此初始化方法。 如果有BeanPostProcessors 和bean 关联,这些bean的 postProcessAfterInitialization() 方法将被调用。 如果bean实现了 DisposableBean,它将调用destroy()方法。
28、哪些是重要的bean生命周期方法?你能重载它们吗?
有两个重要的 bean 生命周期方法,第一个是 setup , 它是在容器加载 bean 的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。
The bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们 你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和 @PreDestroy)。
29、什么是Spring的内部bean?
当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部 bean,为 了定义 inner bean,在 Spring 的 基于 XML 的 配置元数据中,可以在 或 元 素内使用 元素,内部 bean 通常是匿名的,它们的 Scope 一般是 prototype。
30、在 Spring中如何注入一个java集合?
第 401 页 共 485 页
Spring 提供以下几种集合的配置元素:
类型用于注入一列值,允许有相同的值。 类型用于注入一组值,不允许有相同的值。 类型用于注入一组键值对,键和值都可以为任意类型。 类型用于注入一组键值对,键和值都只能为String类型。
31、什么是bean装配?
装配,或 bean 装配是指在 Spring 容器中把 bean 组装到一起,前提是容器需要 知道 bean 的依赖关系,如何通过依赖注入来把它们装配到一起。
32、什么是bean的自动装配?
Spring 容器能够自动装配相互合作的 bean,这意味着容器不需要和配置,能通 过 Bean 工厂自动处理 bean 之间的协作。
33、解释不同方式的自动装配 。
有五种自动装配的方式,可以用来指导 Spring 容器用自动装配方式来进行依赖注 入。
no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
第 402 页 共 485 页
byName:通过参数名 自动装配,Spring容器在配置文件中发现bean 的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属 性具有相同名字的bean。 byType::通过参数类型自动装配,Spring容器在配置文件中发现bean 的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属 性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。 constructor:这个方式类似于byType, 但是要提供给构造器参数,如 果没有确定的带参数的构造器参数类型,将会抛出异常。 autodetect:首先尝试使用constructor来自动装配,如果无法工作, 则使用byType方式。
34.自动装配有哪些局限性 ?
自动装配的局限性是:
重写:你仍需用 和 配置来定义依赖,意味着总要重写自动装配。 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String 字符串,和类。 模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。
35、你可以在Spring中注入一个null 和一个空字符串吗?
可以。
Spring注解
第 403 页 共 485 页
36、什么是基于Java的Spring注解配置? 给一些注解的例子.
基于 Java 的配置,允许你在少量的 Java 注解的帮助下,进行你的大部分 Spring 配置而非通过 XML 文件。
以@Configuration 注解为例,它用来标记类可以当做一个 bean 的定义,被 Spring IOC 容器使用。另一个例子是@Bean 注解,它表示此方法将要返回一个 对象,作为一个 bean 注册进 Spring 应用上下文。
37、什么是基于注解的容器配置?
相对于 XML 文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号 的声明。
开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置, 而不是使用 xml 表述 bean 的装配关系。
38、怎样开启注解装配?
注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在 Spring 配置 文件中配置 context:annotation-config/元素。
39、@Required 注解
这个注解表明 bean 的属性必须在配置的时候设置,通过一个 bean 定义的显式的 属性值或通过自动装配,若@Required 注解的 bean 属性未被设置,容器将抛出 BeanInitializationException。
第 404 页 共 485 页
40、@Autowired 注解
@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。 它的用法和@Required 一样,修饰 setter 方法、构造器、属性或者具有任意名称 和/或多个参数的 PN 方法。
41、@Qualifier 注解
当有多个相同类型的 bean 却只有一个需要自动装配时,将@Qualifier 注解和 @Autowire 注解结合使用以消除这种混淆,指定需要装配的确切的 bean。
Spring数据访问
42.在Spring框架中如何更有效地使用JDBC?
使用 SpringJDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只 需写 statements 和 queries 从数据存取数据,JDBC 也可以在 Spring 框架提供 的模板类的帮助下更有效地被使用,这个模板叫 JdbcTemplate (例子见这里 here)
43、JdbcTemplate
JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据 类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处 理。
第 405 页 共 485 页
44、Spring对DAO的支持
Spring 对数据访问对象(DAO)的支持旨在简化它和数据访问技术如 JDBC, Hibernate or JDO 结合使用。这使我们可以方便切换持久层。编码时也不用担心 会捕获每种技术特有的异常。
45、使用Spring通过什么方式访问Hibernate?
在 Spring 中有两种方式访问 Hibernate:
控制反转 Hibernate Template和 Callback。 继承 HibernateDAOSupport提供一个AOP 拦截器。
46、Spring支持的ORM
Spring 支持以下 ORM:
Hibernate iBatis JPA (Java Persistence API) TopLink JDO (Java Data Objects) OJB
第 406 页 共 485 页
47.如何通过HibernateDaoSupport将Spring和Hibernate
结合起来?
用 Spring 的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:
配置the Hibernate SessionFactory。 继承HibernateDaoSupport实现一个DAO。 在AOP 支持的事务中装配。
48、Spring支持的事务管理类型
Spring 支持两种类型的事务管理:
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵 活性,但是难维护。 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用 注解和XML配置来管理事务。
49、Spring框架的事务管理有哪些优点?
它为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供 一个不变的编程模式。 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API 如
第 407 页 共 485 页
它支持声明式事务管理。 它和Spring各种数据访问抽象层很好得集成。
50、你更倾向用那种事务管理类型?
大多数 Spring 框架的用户选择声明式事务管理,因为它对应用代码的影响最小, 因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务 管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵 活性。
Spring面向切面编程(AOP)
51、解释AOP
面向切面的编程,或 AOP, 是一种编程技术,允许程序模块化横向切割关注点, 或横切典型的责任划分,如日志和事务管理。
52、Aspect 切面
AOP 核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一 组 API 提供横切功能。比如,一个日志模块可以被称作日志的 AOP 切面。根据需 求的不同,一个应用程序可以有若干切面。在 Spring AOP 中,切面通过带有 @Aspect 注解的类实现。
52、在Spring AOP 中,关注点和横切关注的区别是什么?
第 408 页 共 485 页
关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的 一个功能。
横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应 用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些 都属于横切关注点。
54、连接点
连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个 AOP 切面, 它实际上是个应用程序执行 Spring AOP 的位置。
55、通知
通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过 SpringAOP 框架触发的代码段。
Spring 切面可以应用五种类型的通知:
before:前置通知,在一个方法执行前被调用。 after: 在方法执行之后调用的通知,无论方法执行是否成功。 after-returning: 仅当方法成功完成后执行的通知。 after-throwing: 在方法抛出异常退出时执行的通知。 around: 在方法执行之前和之后调用的通知。
56、切点
第 409 页 共 485 页
切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的 方式指明切入点。
57、什么是引入?
引入允许我们在已存在的类中增加新的方法和属性。
58、什么是目标对象?
被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知 (advised)对象。
59、什么是代理?
代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是 一样的。
60、有几种不同类型的自动代理?
BeanNameAutoProxyCreator
DefaultAdvisorAutoProxyCreator
Metadata autoproxying
61、什么是织入。什么是织入应用的不同点?
第 410 页 共 485 页
织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。
织入可以在编译时,加载时,或运行时完成。
62、解释基于XML Schema方式的切面实现。
在这种情况下,切面由常规类以及基于 XML 的配置实现。
63、解释基于注解的切面实现
在这种情况下(基于@AspectJ 的实现),涉及到的切面声明的风格与带有 java5 标 注的普通 java 类一致。
Spring 的MVC
64、什么是Spring的MVC框架?
Spring 配备构建 Web 应用的全功能 MVC 框架。Spring 可以很便捷地和其他 MVC 框架集成,如 Struts,Spring 的 MVC 框架用控制反转把业务对象和控制逻 辑清晰地隔离。它也允许以声明的方式把请求参数和业务对象绑定。
65、DispatcherServlet
Spring的 MVC框架是围绕 DispatcherServlet来设计的,它用来处理所有的 HTTP 请求和响应。
第 411 页 共 485 页
66、WebApplicationContext
WebApplicationContext 继承了 ApplicationContext 并增加了一些 WEB 应用 必备的特有功能,它不同于一般的 ApplicationContext ,因为它能处理主题, 并找到被关联的 servlet。
67、什么是Spring MVC框架的控制器?
控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解 析用户输入并将其转换为一个由视图呈现给用户的模型。Spring 用一个非常抽象 的方式实现了一个控制层,允许用户创建多种用途的控制器。
68、@Controller 注解
该注解表明该类扮演控制器的角色,Spring 不需要你继承任何其他控制器基类或 引用 Servlet API。
69、@RequestMapping 注解
该注解是用来映射一个 URL 到一个类或一个特定的方处理法上。
微服务 面试题
1、您对微服务有何了解?
第 412 页 共 485 页
微服务,又称微服务
架构 ,是一种架构风格,它将应用程序构建为以业务领域为 模型的小型自治服务集合 。
通俗地说,你必须看到蜜蜂如何通过对齐六角形蜡细胞来构建它们的蜂窝状物。 他们最初从使用各种材料的小部分开始,并继续从中构建一个大型蜂箱。这些细 胞形成图案,产生坚固的结构,将蜂窝的特定部分固定在一起。这里,每个细胞 独立于另一个细胞,但它也与其他细胞相关。这意味着对一个细胞的损害不会损 害其他细胞,因此,蜜蜂可以在不影响完整蜂箱的情况下重建这些细胞。
第 413 页 共 485 页
图 1:微服务的蜂窝表示 – 微服务访谈问题
请参考上图。这里,每个六边形形状代表单独的服务组件。与蜜蜂的工作类似, 每个敏捷团队都使用可用的框架和所选的技术堆栈构建单独的服务组件。就像在 蜂箱中一样,每个服务组件形成一个强大的微服务架构,以提供更好的可扩展性。 此外,敏捷团队可以单独处理每个服务组件的问题,而对整个应用程序没有影响 或影响最小。
2、微服务架构有哪些优势?
图 2:微服务的 优点 – 微服务访谈问题
独立开发 – 所有微服务都可以根据各自的功能轻松开发 独立部署 – 基于其服务,可以在任何应用程序中单独部署它们 故障隔离 – 即使应用程序的一项服务不起作用,系统仍可继续运行 混合技术堆栈 – 可以使用不同的语言和技术来构建同一应用程序的不同 服务 粒度缩放 – 单个组件可根据需要进行缩放,无需将所有组件缩放在一起
第 414 页 共 485 页
3。微服务有哪些特点?
图 3:微服务的 特点 – 微服务访谈问题
解耦 – 系统内的服务很大程度上是分离的。因此,整个应用程序可以轻 松构建,更改和扩展 组件化 – 微服务被视为可以轻松更换和升级的独立组件 业务能力 – 微服务非常简单,专注于单一功能 自治 – 开发人员和团队可以彼此独立工作,从而提高速度 持续交付 – 通过软件创建,测试和批准的系统自动化,允许频繁发布软 件 责任 – 微服务不关注应用程序作为项目。相反,他们将应用程序视为他 们负责的产品 分散治理 – 重点是使用正确的工具来做正确的工作。这意味着没有标准 化模式或任何技术模式。开发人员可以自由选择最有用的工具来解决他们的问题 敏捷 – 微服务支持敏捷开发。任何新功能都可以快速开发并再次丢弃
第 415 页 共 485 页
4、设计微服务的最佳实践是什么?
以下是设计微服务的最佳实践:
图 4:设计微服务的最佳实践 – 微服务访谈问题
5、微服务架构如何运作?
微服务架构具有以下组件:
第 416 页 共 485 页
图 5:微服务 架构 – 微服务面试问题
客户端 – 来自不同设备的不同用户发送请求。 身份提供商 – 验证用户或客户身份并颁发安全令牌。 API网关 – 处理客户端请求。 静态内容 – 容纳系统的所有内容。 管理 – 在节点上平衡服务并识别故障。 服务发现 – 查找微服务之间通信路径的指南。 内容交付网络 – 代理服务器及其数据中心的分布式网络。 远程服务 – 启用驻留在IT设备网络上的远程访问信息。
6、微服务架构的优缺点是什么?
第 417 页 共 485 页
7、单片,SOA和微服务架构有什么区别?
图 6: 单片 SOA 和微服务之间的比较 – 微服务访谈问题
单片架构类似于大容器,其中应用程序的所有软件组件组装在一起并紧密 封装。
第 418 页 共 485 页
一个面向服务的架构是一种相互通信服务的集合。通信可以涉及简单的数 据传递,也可以涉及两个或多个协调某些活动的服务。 微服务架构是一种架构风格,它将应用程序构建为以业务域为模型的小型 自治服务集合。
8、在使用微服务架构时,您面临哪些挑战?
开发一些较小的微服务听起来很容易,但开发它们时经常遇到的挑战如下。
自动化组件:难以自动化,因为有许多较小的组件。因此,对于每个组件, 我们必须遵循Build,Deploy和Monitor的各个阶段。 易感性:将大量组件维护在一起变得难以部署,维护,监控和识别问题。 它需要在所有组件周围具有很好的感知能力。 配置管理:有时在各种环境中维护组件的配置变得困难。 调试:很难找到错误的每一项服务。维护集中式日志记录和仪表板以调试 问题至关重要。
9、SOA和微服务架构之间的主要区别是什么?
SOA 和微服务之间的主要区别如下:
第 419 页 共 485 页
10、微服务有什么特点?
您可以列出微服务的特征,如下所示:
图 7:微服务的特征 – 微服务访谈问题
11、什么是领域驱动设计?
第 420 页 共 485 页
图 8: DDD 原理 – 微服务面试问题
12、为什么需要域驱动设计(DDD)?
图 9:我们需要 DDD 的因素 – 微服务面试问题
13、什么是无所不在的语言?
第 421 页 共 485 页
如果您必须定义泛在语言(UL),那么它是特定域的开发人员和用户使用的通用 语言,通过该语言可以轻松解释域。
无处不在的语言必须非常清晰,以便它将所有团队成员放在同一页面上,并以机 器可以理解的方式进行翻译。
14、什么是凝聚力?
模块内部元素所属的程度被认为是凝聚力。
15、什么是耦合?
组件之间依赖关系强度的度量被认为是耦合。一个好的设计总是被认为具有高内 聚力和低耦合性。
16、什么是REST / RESTful以及它的用途是什么?
Representational State Transfer(REST)/ RESTful Web 服务是一种帮助计 算机系统通过 Internet 进行通信的架构风格。这使得微服务更容易理解和实现。
微服务可以使用或不使用 RESTful API 实现,但使用 RESTful API 构建松散耦合 的微服务总是更容易。
17、你对Spring Boot有什么了解?
第 422 页 共 485 页
事实上,随着新功能的增加,弹簧变得越来越复杂。如果必须启动新的 spring 项 目,则必须添加构建路径或添加 maven 依赖项,配置应用程序服务器,添加 spring 配置。所以一切都必须从头开始。
Spring Boot 是解决这个问题的方法。使用 spring boot 可以避免所有样板代码 和配置。因此,基本上认为自己就好像你正在烘烤蛋糕一样,春天就像制作蛋糕 所需的成分一样,弹簧靴就是你手中的完整蛋糕。
图 10: Spring Boot 的因素 – 微服务面试问题
18、什么是Spring引导的执行器?
Spring Boot 执行程序提供了 restful Web 服务,以访问生产环境中运行应用程序 的当前状态。在执行器的帮助下,您可以检查各种指标并监控您的应用程序。
19、什么是Spring Cloud?
第 423 页 共 485 页
根据 Spring Cloud 的官方网站,Spring Cloud 为开发人员提供了快速构建分布 式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由, 领导选举,分布式会话,集群状态)。
20、Spring Cloud解决了哪些问题?
在使用 Spring Boot 开发分布式微服务时,我们面临的问题很少由 Spring Cloud 解决。
与分布式系统相关的复杂性 – 包括网络问题,延迟开销,带宽问题,安 全问题。 处理服务发现的能力 – 服务发现允许集群中的进程和服务找到彼此并进 行通信。 解决冗余问题 – 冗余问题经常发生在分布式系统中。 负载平衡 – 改进跨多个计算资源(例如计算机集群,网络链接,中央处 理单元)的工作负载分布。 减少性能问题 – 减少因各种操作开销导致的性能问题。
21、在SpringMVC应用程序中使用WebMvcTest注释有什
么用处?
在测试目标只关注 Spring MVC 组件的情况下,WebMvcTest 注释用于单元测试 Spring MVC 应用程序。在上面显示的快照中,我们只想启动 ToTestController。 执行此单元测试时,不会启动所有其他控制器和映射。
第 424 页 共 485 页
22。你能否给出关于休息和微服务的要点?
虽然您可以通过多种方式实现微服务,但 REST over HTTP 是实现微服务的一种 方式。REST 还可用于其他应用程序,如 Web 应用程序,API 设计和 MVC 应用程 序,以提供业务数据。
微服务是一种体系结构,其中系统的所有组件都被放入单独的组件中,这些组件 可以单独构建,部署和扩展。微服务的某些原则和最佳实践有助于构建弹性应用 程序。
简而言之,您可以说 REST 是构建微服务的媒介。
23、什么是不同类型的微服务测试?
在使用微服务时,由于有多个微服务协同工作,测试变得非常复杂。因此,测试 分为不同的级别。
在底层,我们有面向技术的测试,如单元测试和性能测试。这些是完全自 动化的。 在中间层面,我们进行了诸如压力测试和可用性测试之类的探索性测试。 在顶层, 我们的 验收测试数量很少。这些验收测试有助于利益相关者理 解和验证软件功能。
24、您对Distributed Transaction有何了解?
第 425 页 共 485 页
分布式事务是指单个事件导致两个或多个不能以原子方式提交的单独数据源的突 变的任何情况。在微服务的世界中,它变得更加复杂,因为每个服务都是一个工 作单元,并且大多数时候多个服务必须协同工作才能使业务成功。
25、什么是Idempotence以及它在哪里使用?
幂等性是能够以这样的方式做两次事情的特性,即最终结果将保持不变,即好像 它只做了一次。
用法:在远程服务或数据源中使用 Idempotence,这样当它多次接收指令时,它 只处理指令一次。
26、什么是有界上下文?
有界上下文是域驱动设计的核心模式。DDD 战略设计部门的重点是处理大型模型 和团队。DDD 通过将大型模型划分为不同的有界上下文并明确其相互关系来处理 大型模型。
27、什么是双因素身份验证?
双因素身份验证为帐户登录过程启用第二级身份验证。
第 426 页 共 485 页
图 11: 双因素认证的表示 – 微服务访谈问题
因此,假设用户必须只输入用户名和密码,那么这被认为是单因素身份验证。
28、双因素身份验证的凭据类型有哪些?
这三种凭证是:
第 427 页 共 485 页
图 12: 双因素认证的证书类型 – 微服务面试问题
29、什么是客户证书?
客户端系统用于向远程服务器发出经过身份验证的请求的一种数字证书称为客户 端证书。客户端证书在许多相互认证设计中起着非常重要的作用,为请求者的身 份提供了强有力的保证。
30、PACT在微服务架构中的用途是什么?
PACT 是一个开源工具,允许测试服务提供者和消费者之间的交互,与合同隔离, 从而提高微服务集成的可靠性。
微服务中的用法
用于在微服务中实现消费者驱动的合同。 测试微服务的消费者和提供者之间的消费者驱动的合同。
查看即将到来的批次
31、什么是OAuth?
OAuth 代表开放授权协议。这允许通过在 HTTP 服务上启用客户端应用程序(例 如第三方提供商 Facebook,GitHub 等)来访问资源所有者的资源。因此,您可 以在不使用其凭据的情况下与另一个站点共享存储在一个站点上的资源。
第 428 页 共 485 页
32、康威定律是什么?
“ 任何设计系统的组织(广泛定义)都将产生一种设计,其结构是组织通信结构 的副本。 ” – Mel Conway
图 13: Conway 定律的表示 – 微服务访谈问题
该法律基本上试图传达这样一个事实:为了使软件模块起作用,整个团队应该进 行良好的沟通。因此,系统的结构反映了产生它的组织的社会边界。
33、合同测试你懂什么?
根据 Martin Flower 的说法,合同测试是在外部服务边界进行的测试,用于验证 其是否符合消费服务预期的合同。
此外,合同测试不会深入测试服务的行为。更确切地说,它测试该服务调用的输 入&输出包含所需的属性和所述响应延迟,吞吐量是允许的限度内。
第 429 页 共 485 页
34、什么是端到端微服务测试?
端到端测试验证了工作流中的每个流程都正常运行。这可确保系统作为一个整体 协同工作并满足所有要求。
通俗地说,你可以说端到端测试是一种测试,在特定时期后测试所有东西。
图 14:测试层次 – 微服务面试问题
35、Container在微服务中的用途是什么?
第 430 页 共 485 页
容器是管理基于微服务的应用程序以便单独开发和部署它们的好方法
。 您可以将 微服务封装在容器映像及其依赖项中,然后可以使用它来滚动按需实例的微服务, 而无需任何额外的工作。
图 15: 容器的表示及其在微服务中的使用方式 – 微服务访谈问题
36、什么是微服务架构中的DRY?
DRY 代表不要重复自己。它基本上促进了重用代码的概念。这导致开发和共享库, 这反过来导致紧密耦合。
37、什么是消费者驱动的合同(CDC)?
这基本上是用于开发微服务的模式,以便它们可以被外部系统使用。当我们处理 微服务时,有一个特定的提供者构建它,并且有一个或多个使用微服务的消费者。
通常,提供程序在 XML 文档中指定接口。但在消费者驱动的合同中,每个服务消 费者都传达了提供商期望的接口。
第 431 页 共 485 页
38、Web,RESTful API在微服务中的作用是什么?
微服务架构基于一个概念,其中所有服务应该能够彼此交互以构建业务功能。因 此,要实现这一点,每个微服务必须具有接口。这使得 Web API 成为微服务的一 个非常重要的推动者。RESTful API 基于 Web 的开放网络原则,为构建微服务架 构的各个组件之间的接口提供了最合理的模型。
39、您对微服务架构中的语义监控有何了解?
语义监控,也称为 综合监控, 将自动化测试与监控应用程序相结合,以检测业 务失败因素。
40、我们如何进行跨功能测试?
跨功能测试是对非功能性需求的验证,即那些无法像普通功能那样实现的需求。
41、我们如何在测试中消除非决定论?
非确定性测试(NDT)基本上是不可靠的测试。所以,有时可能会发生它们通过, 显然有时它们也可能会失败。当它们失败时,它们会重新运行通过。
从测试中删除非确定性的一些方法如下:
1、 隔离 2、 异步 3、 远程服务 4、 隔离
第 432 页 共 485 页
5、 时间 6、 资源泄漏
42、Mock或Stub有什么区别?
存根
一个有助于运行测试的虚拟对象。 在某些可以硬编码的条件下提供固定行为。 永远不会测试存根的任何其他行为。
例如,对于空堆栈,您可以创建一个只为 empty()方法返回 true 的存根。因此, 这并不关心堆栈中是否存在元素。
嘲笑
一个虚拟对象,其中最初设置了某些属性。 此对象的行为取决于set属性。 也可以测试对象的行为。
例如,对于 Customer 对象,您可以通过设置名称和年龄来模拟它。您可以将 age 设置为 12,然后测试 isAdult()方法,该方法将在年龄大于 18 时返回 true。因 此,您的 Mock Customer 对象适用于指定的条件。
43、您对Mike Cohn的测试金字塔了解多少?
第 433 页 共 485 页
Mike Cohn 提供了一个名为 Test Pyramid 的模型。这描述了软件开发所需的自 动化测试类型。
图 16: Mike Cohn 的测试金字塔 – 微服务面试问题
根据金字塔,第一层的测试数量应该最高。在服务层,测试次数应小于单元测试 级别,但应大于端到端级别。
44、Docker的目的是什么?
Docker 提供了一个可用于托管任何应用程序的容器环境。在此,软件应用程序和 支持它的依赖项紧密打包在一起。
因此,这个打包的产品被称为 Container,因为它是由 Docker 完成的,所以它 被称为 Docker 容器!
第 434 页 共 485 页
45、什么是金丝雀释放?
Canary Releasing 是一种降低在生产中引入新软件版本的风险的技术。这是通过 将变更缓慢地推广到一小部分用户,然后将其发布到整个基础架构,即将其提供 给每个人来完成的。
46、什么是持续集成(CI)?
持续集成(CI)是每次团队成员提交版本控制更改时自动构建和测试代码的过程。 这鼓励开发人员通过在每个小任务完成后将更改合并到共享版本控制存储库来共 享代码和单元测试。
47、什么是持续监测?
持续监控深入监控覆盖范围,从浏览器内前端性能指标,到应用程序性能,再到 主机虚拟化基础架构指标。
48、架构师在微服务架构中的角色是什么?
微服务架构中的架构师扮演以下角色:
决定整个软件系统的布局。 帮助确定组件的分区。因此,他们确保组件相互粘合,但不紧密耦合。 与开发人员共同编写代码,了解日常生活中面临的挑战。 为开发微服务的团队提供某些工具和技术的建议。
第 435 页 共 485 页
提供技术治理,以便技术开发团队遵循微服务原则。
49、我们可以用微服务创建状态机吗?
我们知道拥有自己的数据库的每个微服务都是一个可独立部署的程序单元,这反 过来又让我们可以创建一个状态机。因此,我们可以为特定的微服务指定不同的 状态和事件。
例如,我们可以定义 Order 微服务。订单可以具有不同的状态。Order 状态的转 换可以是 Order 微服务中的独立事件。
50、什么是微服务中的反应性扩展?
Reactive Extensions 也称为 Rx。这是一种设计方法,我们通过调用多个服务来 收集结果,然后编译组合响应。这些调用可以是同步或异步,阻塞或非阻塞。Rx 是分布式系统中非常流行的工具,与传统流程相反。
希望这些微服务面试问题可以帮助您进行微服务架构师访谈。
翻译来源: https://www.edureka.co/blog/i... w-questions/
Linux 面试题
第 436 页 共 485 页
1、绝对路径用什么符号表示?当前目录、上层目录用什么表示?
主目录用什么表示? 切换目录用什么命令?
答案:
绝对路径: 如/etc/init.d 当前目录和上层目录: ./ ../ 主目录: ~/ 切换目录: cd
2、怎么查看当前进程?怎么执行退出?怎么查看当前路径?
答案:
查看当前进程: ps 执行退出: exit 查看当前路径: pwd
3、怎么清屏?怎么退出当前命令?怎么执行睡眠?怎么查看当
前用户 id?查看指定帮助用什么命令?
答案:
清屏: clear 退出当前命令: ctrl+c 彻底退出 执行睡眠 : ctrl+z 挂起当前进程 fg 恢复后台
第 437 页 共 485 页
查看当前用户 id: ”id“:查看显示目前登陆账户的 uid 和 gid 及所属分组 及用户名 查看指定帮助: 如 man adduser 这个很全 而且有例子; adduser --help 这 个告诉你一些常用参数; info adduesr;
4、Ls 命令执行什么功能? 可以带哪些参数,有什么区别?
答案:
ls 执行的功能: 列出指定目录中的目录,以及文件 哪些参数以及区别: a 所有文件 l 详细信息,包括大小字节数,可读可写可执行 的权限等
5、建立软链接(快捷方式),以及硬链接的命令。
答案:
软链接: ln -s slink source 硬链接: ln link source
6、目录创建用什么命令?创建文件用什么命令?复制文件用什
么命令?
答案:
创建目录: mkdir
第 438 页 共 485 页
创建文件:典型的如 touch,vi 也可以创建文件,其实只要向一个不存在的文件 输出,都会创建文件 复制文件: cp 7. 文件权限修改用什么命令?格式是怎么样的? 文件权限修改: chmod
格式如下:
chmodu+xfile 给 file 的属主增加执行权限 chmod 751 file 给 file 的属主分配 读、写、执行(7)的权限,给 file 的所在组分配读、执行(5)的权限,给其他用户 分配执行(1)的权限 chmodu=rwx,g=rx,o=xfile 上例的另一种形式 chmod =r file 为所有用户分配 读权限 chmod444file 同上例 chmod a-wx,a+r file 同上例 $ chmod -R u+r directory 递归地给 directory 目录下所有文件和子目录的属 主分配读的权限
7、查看文件内容有哪些命令可以使用?
答案:
vi 文件名 #编辑方式查看,可修改 cat 文件名 #显示全部文件内容 more 文件名 #分页显示文件内容 less 文件名 #与 more 相似,更好的是可以往前翻页 tail 文件名 #仅查看尾部,还可以指定行数 head 文件名 #仅查看头部,还可以指定行数
第 439 页 共 485 页
8、随意写文件命令?怎么向屏幕输出带空格的字符串,比如”
hello world”?
答案:
写文件命令:vi
向屏幕输出带空格的字符串:echo hello world
9、终端是哪个文件夹下的哪个文件?黑洞文件是哪个文件夹下
的哪个命令?
答案:
终端 /dev/tty
黑洞文件 /dev/null
10、移动文件用哪个命令?改名用哪个命令?
答案:
mv mv
第 440 页 共 485 页
11、复制文件用哪个命令?如果需要连同文件夹一块复制呢?
如果需要有提示功能呢?
答案:
cp cp -r ????
12、删除文件用哪个命令?如果需要连目录及目录下文件一块
删除呢?删除空文件夹用什么命令?
答案:
rm rm -r rmdir
13、Linux 下命令有哪几种可使用的通配符?分别代表什么含
义?
答案:
“?”可替代单个字符。
“*”可替代任意多个字符。
方括号“[charset]”可替代 charset 集中的任何单个字符,如[a-z],[abABC]
第 441 页 共 485 页
14、用什么命令对一个文件的内容进行统计?(行号、单词数、
字节数)
答案:
wc 命令 - c 统计字节数 - l 统计行数 - w 统计字数。
15、Grep 命令有什么用? 如何忽略大小写? 如何查找不含
该串的行?
答案:
是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印 出来。 grep [stringSTRING] filename grep 1 filename
16、Linux 中进程有哪几种状态?在 ps 显示出来的信息中,
分别用什么符号表示的?
答案:
1、不可中断状态:进程处于睡眠状态,但是此刻进程是不可中断的。不可中断, 指进程不响应异步信号。
第 442 页 共 485 页
2、暂停状态/跟踪状态:向进程发送一个 SIGSTOP 信号,它就会因响应该信号 而 进入 TASK_STOPPED 状态;当进程正在被跟踪时,它处于 TASK_TRACED 这个 特殊的状态。 正被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。
3、就绪状态:在 run_queue 队列里的状态
4、运行状态:在 run_queue 队列里的状态
5、可中断睡眠状态:处于这个状态的进程因为等待某某事件的发生(比如等待 socket 连接、等待信号量),而被挂起
6、zombie 状态(僵尸):父亲没有通过 wait 系列的系统调用会顺便将子进程 的尸体(task_struct)也释放掉
7、退出状态
D 不可中断 Uninterruptible(usually IO) R 正在运行,或在队列中的进程 S 处于休眠状态 T 停止或被追踪 Z 僵尸进程 W 进入内存交换(从内核 2.6 开始无效) X 死掉的进程
17、怎么使一个命令在后台运行?
答案:
一般都是使用 & 在命令结尾来让程序自动运行。(命令后可以不追加空格)
第 443 页 共 485 页
18、利用 ps 怎么显示所有的进程? 怎么利用 ps 查看指定进
程的信息?
答案:
ps -ef (system v 输出)
ps -aux bsd 格式输出
ps -ef | grep pid
19、哪个命令专门用来查看后台任务?
答案:
job -l
20、把后台任务调到前台执行使用什么命令?把停下的后台任务
在后台执行起来用什么命令?
答案:
把后台任务调到前台执行 fg
把停下的后台任务在后台执行起来 bg
第 444 页 共 485 页
21、终止进程用什么命令? 带什么参数?
答案:
kill -s 或 kill [-l ]
kill-9 pid
22、怎么查看系统支持的所有信号?
答案:
kill -l
23、搜索文件用什么命令? 格式是怎么样的?
答案:
find
whereis 加参数与文件名
locate 只加文件名
find 直接搜索磁盘,较慢。
find / -name "string*"
第 445 页 共 485 页
24、查看当前谁在使用该主机用什么命令? 查找自己所在的终
端信息用什么命令?
答案:
查找自己所在的终端信息:who am i
查看当前谁在使用该主机:who
25、使用什么命令查看用过的命令列表?
答案:
history
26、使用什么命令查看磁盘使用空间? 空闲空间呢?
答案:
df -hl
文件系统 容量 已用 可用 已用% 挂载点
Filesystem Size Used Avail Use% Mounted on /dev/hda2 45G 19G 24G 44% / /dev/hda1 494M 19M 450M 4% /boot
第 446 页 共 485 页
27、使用什么命令查看网络是否连通?
答案:
netstat
28、使用什么命令查看 ip 地址及接口信息?
答案:
ifconfig
29、查看各类环境变量用什么命令?
答案:
查看所有 env 查看某个,如 home: env $HOME
30、通过什么命令指定命令提示符?
答案:
\u:显示当前用户账号 \h:显示当前主机名
第 447 页 共 485 页
\W:只显示当前路径最后一个目录 \w:显示当前绝对路径(当前用户目录会以~代替) $PWD:显示当前全路径 $:显示命令行’$'或者’#'符号 #:下达的第几个命令 \\d:代表日期,格式为week day month date,例如:"MonAug1" \\t:显示时间为24小时格式,如:HH:MM:SS \\T:显示时间为12小时格式 \\A:显示时间为24小时格式:HH:MM \\v:BASH的版本信息 如export PS1=’[\\u@\\h\\w#]$‘
31、查找命令的可执行文件是去哪查找的? 怎么对其进行设置
及添加?
答案:
whereis -bfmsu-M ...[文件...]
补充说明:whereis 指令会在特定目录中查找符合条件的文件。这些文件的烈性 应属于原始代码,二进制文件,或是帮助文件。
-b 只查找二进制文件。 -B 只在设置的目录下查找二进制文件。 -f 不显示文件名前的 路径名称。 -m 只查找说明文件。 -M 只在设置的目录下查找说明文件。-s 只查找原始代码文件。 -S 只在设置的目录下查找原始代码文件。 -u 查找不包含指定 类型的文件。
第 448 页 共 485 页
w-hich 指令会在 PATH 变量指定的路径中,搜索某个系统命令的位置,并且 返回第一个搜索结果。 -n 指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件 名。 -p 与-n 参数相同,但此处的包括了文件的路径。 -w 指定输出时栏位 的宽度。 -V 显示版本信息
32、通过什么命令查找执行命令?
答案:
which 只能查可执行文件
whereis 只能查二进制文件、说明文档,源文件等
33、怎么对命令进行取别名?
答案:
alias la='ls -a'
34、du 和 df 的定义,以及区别?
答案:
du 显示目录或文件的大小
第 449 页 共 485 页
df 显示每个所在的文件系统的信息,默认是显示所有文件系统。 (文件系统分配其中的一些磁盘块用来记录它自身的一些数据,如 i 节点,磁盘 分布图,间接块,超级块等。这些数据对大多数用户级的程序来说是不可见的, 通常称为 Meta Data。) du 命令是用户级的程序,它不考虑 Meta Data,而 df 命令则查看文件系统的磁盘分配图并考虑 Meta Data。 df 命令获得真正的文件系统数据,而 du 命令只查看文件系统的部分情况。
35、awk 详解。
答案:
awk '{pattern + action}' {filenames} #cat /etc/passwd |awk -F ':' '{print 1"\t"7}' //-F 的意思是以':'分隔 root /bin/bash daemon /bin/sh 搜索/etc/passwd 有 root 关键字的所有行
awk -F: '/root/' /etc/passwd root0:0:root:/root:/bin/bash
36、当你需要给命令绑定一个宏或者按键的时候,应该怎么做
呢?
答案:
可以使用 bind 命令,bind 可以很方便地在 shell 中实现宏或按键的绑定。
在进行按键绑定的时候,我们需要先获取到绑定按键对应的字符序列。
第 450 页 共 485 页
比如获取 F12 的字符序列获取方法如下:先按下 Ctrl+V,然后按下 F12 .我们就可 以得到 F12 的字符序列 ^[[24~。
接着使用 bind 进行绑定。
[root@localhost ~]# bind ‘”\e[24~":"date"'
注意:相同的按键在不同的终端或终端模拟器下可能会产生不同的字符序列。
【附】也可以使用 showkey -a 命令查看按键对应的字符序列。
37、如果一个linux新手想要知道当前系统支持的所有命令的
列表,他需要怎么做?
答案:
使用命令 compgen -c,可以打印出所有支持的命令列表。
[root@localhost ~]$ compgen -c l. ll ls which if then else elif fi case
第 451 页 共 485 页
esac for select while until do done …
38、如果你的助手想要打印出当前的目录栈,你会建议他怎么
做?
答案:
使用 Linux 命令 dirs 可以将当前的目录栈打印出来。
[root@localhost ~]# dirs /usr/share/X11
【附】:目录栈通过 pushd popd 来操作。
39、你的系统目前有许多正在运行的任务,在不重启机器的条
件下,有什么方法可以把所有正在运行的进程移除呢?
答案:
使用 linux 命令 ’disown -r ’可以将所有正在运行的进程移除。
第 452 页 共 485 页
40、bash shell 中的hash 命令有什么作用?
答案:
linux 命令’hash’管理着一个内置的哈希表,记录了已执行过的命令的完整路径, 用该命令可以打印出你所使用过的命令以及执行的次数。
[root@localhost ~]# hash
hits command
2 /bin/ls
2 /bin/su
41、哪一个bash内置命令能够进行数学运算。
答案:
bash shell 的内置命令 let 可以进行整型数的数学运算。
! /bin/bash … … let c=a+b … …
第 453 页 共 485 页
42、怎样一页一页地查看一个大文件的内容呢?
答案:
通过管道将命令”cat file_name.txt” 和 ’more’ 连接在一起可以实现这个 需要.
[root@localhost ~]# cat file_name.txt | more
43、数据字典属于哪一个用户的?
答案:
数据字典是属于’SYS’用户的,用户‘SYS’ 和 ’SYSEM’是由系统默认自动 创建的
44、怎样查看一个linux命令的概要与用法?假设你在/bin目
录中偶然看到一个你从没见过的的命令,怎样才能知道它的作用
和用法呢?
答案:
使用命令 whatis 可以先出显示出这个命令的用法简要,比如,你可以使用 whatis zcat 去查看‘zcat’的介绍以及使用简要。
[root@localhost ~]# whatis zcat
第 454 页 共 485 页
zcat [gzip] (1) – compress or expand files
45、使用哪一个命令可以查看自己文件系统的磁盘空间配额
呢?
答案:
使用命令 repquota 能够显示出一个文件系统的配额信息
【附】只有 root 用户才能够查看其它用户的配额。
Spring Boot 面试题
1、什么是Spring Boot?
多年来,随着新功能的增加,spring 变得越来越复杂。只需访问 https://spring.io/projects 页面,我们就会看到可以在我们的应用程序中使用的 所有 Spring 项目的不同功能。如果必须启动一个新的 Spring 项目,我们必须添 加构建路径或添加 Maven 依赖关系,配置应用程序服务器,添加 spring 配置。 因此,开始一个新的 spring 项目需要很多努力,因为我们现在必须从头开始做所 有事情。
Spring Boot 是解决这个问题的方法。Spring Boot 已经建立在现有 spring 框架 之上。使用 spring 启动,我们避免了之前我们必须做的所有样板代码和配置。因 此,Spring Boot 可以帮助我们以最少的工作量,更加健壮地使用现有的 Spring 功能。
第 455 页 共 485 页
2、Spring Boot有哪些优点?
Spring Boot 的优点有:
1、减少开发,测试时间和努力。
2、使用 JavaConfig 有助于避免使用 XML。
3、避免大量的 Maven 导入和各种版本冲突。
4、提供意见发展方法。
5、通过提供默认值快速开始开发。
6、没有单独的 Web 服务器需要。这意味着你不再需要启动 Tomcat,Glassfish 或其他任何东西。
7、需要更少的配置 因为没有 web.xml 文件。只需添加用@ Configuration 注释 的类,然后添加用@Bean 注释的方法,Spring 将自动加载对象并像以前一样对其 进行管理。您甚至可以将@Autowired 添加到 bean 方法中,以使 Spring 自动装 入需要的依赖关系中。
8、基于环境的配置 使用这些属性,您可以将您正在使用的环境传递到应用程序: -Dspring.profiles.active = {enviornment}。在加载主应用程序属性文件后, Spring 将在(application{environment} .properties)中加载后续的应用程序属 性文件。
3、什么是JavaConfig?
第 456 页 共 485 页
Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯 Java 方法。因此它有助于避免使用 XML 配置。使用 JavaConfig 的优点在于:
1、面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分 利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方 法等。
2、减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。但是, 许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供 了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲, 只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将 JavaConfig 与 XML 混合匹配是理想的。
3、类型安全和重构友好。JavaConfig 提供了一种类型安全的方法来配置 Spring 容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不 需要任何强制转换或基于字符串的查找。
4、如何重新加载SpringBoot上的更改,而无需重新启动服务
器?
这可以使用 DEV 工具来实现。通过这种依赖关系,您可以节省任何更改,嵌入式 tomcat 将重新启动。Spring Boot 有一个开发工具(DevTools)模块,它有助于 提高开发人员的生产力。Java 开发人员面临的一个主要挑战是将文件更改自动部 署到服务器并自动重启服务器。开发人员可以重新加载 Spring Boot 上的更改, 而无需重新启动服务器。这将消除每次手动部署更改的需要。Spring Boot 在发布 它的第一个版本时没有这个功能。这是开发人员最需要的功能。DevTools 模块完 全满足开发人员的需求。该模块将在生产环境中被禁用。它还提供 H2 数据库控制 台以更好地测试应用程序。
第 457 页 共 485 页
6、为什么需要消息系统,mysql不能满足需求吗?
1.解耦:
允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
2.冗余:
消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据 丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队 列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保 你的数据被安全的保存直到你使用完毕。
3.扩展性:
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的, 只要另外增加处理过程即可。
4.灵活性 & 峰值处理能力:
第 479 页 共 485 页
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不 常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪 费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负 荷的请求而完全崩溃。
5.可恢复性:
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合 度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复 后被处理。
6.顺序保证:
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的, 并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消 息的有序性)
7.缓冲:
有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度 不一致的情况。
8.异步通信:
很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允 许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放 多少,然后在需要的时候再去处理它们。
7、Zookeeper对于Kafka的作用是什么?
第 480 页 共 485 页
Zookeeper 是一个开放源码的、高性能的协调服务,它用于 Kafka 的分布式应用。
Zookeeper 主要用于在集群中不同节点之间进行通信
在 Kafka 中,它被用于提交偏移量,因此如果节点在任何情况下都失败了,它都 可以从之前提交的偏移量中获取
除此之外,它还执行其他活动,如: leader 检测、分布式同步、配置管理、识别新 节点何时离开或连接、集群、节点实时状态等等。
8、数据传输的事务定义有哪三种?
和 MQTT 的事务定义一样都是 3 种。
(1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输
(2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输.
(3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输 被一次而且仅仅被传输一次,这是大家所期望的
9、Kafka判断一个节点是否还活着有那两个条件?
(1)节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每 个节点的连接 (2)如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太 久
第 481 页 共 485 页
10、Kafka 与传统MQ消息系统之间有三个关键区别
(1).Kafka 持久化日志,这些日志可以被重复读取和无限期保留 (2).Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过 复制数据提升容错能力和高可用性 (3).Kafka 支持实时的流式处理
11、讲一讲kafka的ack的三种机制
request.required.acks 有三个值 0 1 -1(all)
0:生产者不会等待 broker 的 ack,这个延迟最低但是存储的保证最弱当 server 挂 掉的时候就会丢数据。
1:服务端会等待 ack 值 leader 副本确认接收到消息后发送 ack 但是如果 leader 挂掉后他不确保是否复制完成新 leader 也会导致数据丢失。
-1(all):服务端会等所有的 follower 的副本受到数据后才会受到 leader 发出的 ack,这样数据不会丢失
12、消费者如何不自动提交偏移量,由应用提交? 将 auto.commit.offset 设为 false,然后在处理一批消息后 commitSync() 或者 异步提交 commitAsync() 即:
ConsumerRecords records = consumer.poll(); for (ConsumerRecord record : records){ 。。。 tyr{
第 482 页 共 485 页
consumer.commitSync() } 。。。 }
13、消费者故障,出现活锁问题如何解决?
出现“活锁”的情况,是它持续的发送心跳,但是没有处理。为了预防消费者在 这种情况下一直持有分区,我们使用 max.poll.interval.ms 活跃检测机制。 在此 基础上,如果你调用的 poll 的频率大于最大间隔,则客户端将主动地离开组,以 便其他消费者接管该分区。 发生这种情况时,你会看到 offset 提交失败(调用 commitSync()引发的 CommitFailedException)。这是一种安全机制,保障 只有活动成员能够提交 offset。所以要留在组中,你必须持续调用 poll。
消费者提供两个配置设置来控制 poll 循环:
max.poll.interval.ms:增大 poll 的间隔,可以为消费者提供更多的时间去处理返 回的消息(调用 poll(long)返回的消息,通常返回的消息都是一批)。缺点是此值 越大将会延迟组重新平衡。
max.poll.records:此设置限制每次调用 poll 返回的消息数,这样可以更容易的 预测每次 poll 间隔要处理的最大值。通过调整此值,可以减少 poll 间隔,减少重 新平衡分组的
对于消息处理时间不可预测地的情况,这些选项是不够的。 处理这种情况的推荐 方法是将消息处理移到另一个线程中,让消费者继续调用 poll。 但是必须注意确 保已提交的 offset 不超过实际位置。另外,你必须禁用自动提交,并只有在线程 完成处理后才为记录手动提交偏移量(取决于你)。 还要注意,你需要 pause 暂 停分区,不会从 poll 接收到新消息,让线程处理完之前返回的消息(如果你的处 理能力比拉取消息的慢,那创建新线程将导致你机器内存溢出)。
第 483 页 共 485 页
14、如何控制消费的位置
kafka 使用 seek(TopicPartition, long)指定新的消费位置。用于查找服务器保留 的最早和最新的 offset 的特殊的方法也可用(seekToBeginning(Collection) 和 seekToEnd(Collection))
15、kafka分布式(不是单机)的情况下,如何保证消息的顺
序消费?
Kafka 分布式的单位是 partition,同一个 partition 用一个 write ahead log 组织, 所以可以保证 FIFO 的顺序。不同 partition 之间不能保证顺序。但是绝大多数用 户都可以通过 message key 来定义,因为同一个 key 的 message 可以保证只发 送到同一个 partition。
Kafka 中发送 1 条消息的时候,可以指定(topic, partition, key) 3 个参数。 partiton 和 key 是可选的。如果你指定了 partition,那就是所有消息发往同 1 个 partition,就是有序的。并且在消费端,Kafka 保证,1 个 partition 只能被 1 个 consumer 消费。或者你指定 key(比如 order id),具有同 1 个 key 的 所有消息,会发往同 1 个 partition。
16、kafka的高可用机制是什么?
这个问题比较系统,回答出 kafka 的系统特点,leader 和 follower 的关系,消息 读写的顺序即可。
https://www.cnblogs.com/qingy...
第 484 页 共 485 页
https://www.tuicool.com/artic...
https://yq.aliyun.com/article...
17、kafka如何减少数据丢失
https://www.cnblogs.com/huxi2...
18、kafka如何不消费重复数据?比如扣款,我们不能重复的
扣。
其实还是得结合业务来思考,我这里给几个思路:
比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入 了,update 一下好吧。 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据 的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费 到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消 费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保 证别重复处理相同的消息即可。 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束 了,重复数据插入只会报错,不会导致数据库中出现脏数据。
- string ↩
以上就是《java工程师面试题》的详细内容,更多关于mysql的资料请关注golang学习网公众号!
-
499 收藏
-
286 收藏
-
244 收藏
-
235 收藏
-
157 收藏
-
475 收藏
-
266 收藏
-
273 收藏
-
283 收藏
-
210 收藏
-
371 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习