Java 基础
JDK 和 JRE 是入门第一问。JDK(Java Development Kit)是开发工具包,包含了 JRE 以及 javac 编译器和调试工具;JRE(Java Runtime Environment)就只是运行环境。装了 JDK 自然就有了 JRE,反过来不行。开发用 JDK,服务器上只跑应用的话装个 JRE 就够了。
== 和 equals 的区别在面试里出现频率极高。简单说,对于基本类型,== 比较值是否相等;对于引用类型,== 比较两个变量是否指向同一块内存。equals 方法在 Object 类里就是 ==,所以默认是比较引用。但 String、Integer 这些类重写了 equals,改成了比较内容。看代码:
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x == y); // true
System.out.println(x == z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
x 和 y 指向常量池同一个引用,== 就是 true;而 z 是 new 出来在堆上的新对象,== 自然是 false。equals 全 true 是因为 String 重写了它。这也解释了为什么两个 hashCode 相同的对象 equals 不一定为 true——'通话'和'重地'就是典型案例,hashCode 都是 1179395,但字符串内容不同。
final 关键字用法:修饰类则类不可继承,修饰方法不可重写,修饰变量变成常量,必须初始化且之后不能修改。
Math.round(-1.5) 返回 -1。取整规则是向数轴右侧取,正数 0.5 向上取整,负数直接舍去小数,记住就好。
String 不是基本类型,Java 的基本类型就八种:byte、boolean、char、short、int、float、long、double。操作字符串时,String 是不可变对象,每次拼接都会产生新对象,所以频繁改动时该用 StringBuffer 或 StringBuilder。二者区别在于 StringBuffer 线程安全但慢,StringBuilder 快但在多线程下不安全。单线程场景优先 StringBuilder。还有,String str="i" 这种方式从常量池取,new String("i") 则在堆上分配,两回事。反转字符串直接用 new StringBuilder(str).reverse().toString() 就行。常用方法就不一个个列了,indexOf、charAt、replace、trim、split、getBytes、length、toLowerCase、toUpperCase、substring、equals 这些查 API 都能看到。
抽象类不一定包含抽象方法,但包含抽象方法的类一定得声明为抽象类。抽象类不能实例化,也不能用 final 修饰——这两个限制互相矛盾。接口和抽象类:一个类只能继承一个抽象类,但可以实现多个接口;抽象类可以有构造方法,接口不行;接口方法默认 public,抽象类没有这个限制。
Java IO 流分字节流和字符流,字节流按 8 位传输,字符流按 16 位传输,处理文本时字符流更方便。BIO 是传统的同步阻塞 IO;NIO 同步非阻塞,基于 Channel 和多路复用;AIO 是异步非阻塞,基于事件回调。文件操作现在多用 Files 工具类,exists、createFile、createDirectory、delete、copy、move、size、read、write 这些静态方法基本覆盖了日常需求。
容器
Java 集合框架分 Collection 和 Map 两大体系。Collection 下面有 List(有序可重复)和 Set(无序不可重复),List 的实现主要是 ArrayList(动态数组)和 LinkedList(双向链表),还有线程安全的 Vector 和 Stack;Set 常见的有 HashSet、LinkedHashSet、TreeSet。Map 存键值对,键不能重复,常用 HashMap、LinkedHashMap、TreeMap、Hashtable 以及并发下的 ConcurrentHashMap。
Collection 是接口,Collections 是工具类,提供了 sort、synchronizedCollection 等静态方法。选择集合时注意:需要快速随机访问用 ArrayList,频繁插入删除用 LinkedList;需要键排序用 TreeMap,一般场景 HashMap 就够用。
HashMap 和 Hashtable 的区别:HashMap 允许 null 键值,非线程安全;Hashtable 已过时,不允许 null,线程安全但有性能瓶颈。现在多线程环境该用 ConcurrentHashMap。HashMap 底层是数组加链表/红黑树,根据 key 的 hashCode 定位桶,冲突时拉链,长度超过 8 转红黑树。HashSet 内部就是维护了一个 HashMap,值存在 key 上,value 是个固定的 Object 占位符。
数组和 List 互转:Arrays.asList(array) 得到固定大小的 List,不能增删;list.toArray() 可以转成 Object 数组。Vector 和 ArrayList 类似,但每个方法都加 synchronized,扩容是翻倍而不是 50%,性能差。Queue 里 poll() 和 remove() 都取队首并删除,空队列时 poll 返回 null,remove 抛 NoSuchElementException。
线程安全的集合除了早期遗留的,还有 ConcurrentHashMap、CopyOnWriteArrayList 等 JUC 工具类。迭代器 Iterator 遍历时调用自己的 remove 是安全的,直接用集合的 remove 会抛 ConcurrentModificationException。ListIterator 可以双向遍历,还能增删改元素。如果想让集合只读,用 Collections.unmodifiableCollection() 包一层,任何修改都会抛异常。
多线程
并发是多个任务在一个核上交替执行,并行是多个核同时执行。进程是资源分配单元,线程是执行单元,一个进程至少有一个线程。守护线程是后台线程,比如 GC,主线程结束它自动退出。创建线程有三种方式:extends Thread、implements Runnable、implements Callable,第三种可以有返回值。
线程状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。sleep 和 wait 的区别:sleep 是 Thread 的静态方法,不释放锁;wait 是 Object 的方法,会释放当前锁,等着被 notify 唤醒。notify 随机唤醒一个等待线程,notifyAll 唤醒全部。run 方法只是普通调用,start 才是启动新线程。
线程池创建有多个工厂方法:newSingleThreadExecutor 单线程,newCachedThreadPool 缓存线程池,newFixedThreadPool 定长线程池,newScheduledThreadPool 支持定时调度,还有 Java 8 引入的 newWorkStealingPool。究其根本,都是对 ThreadPoolExecutor 的封装。线程池状态流转:RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED。submit 可以提交 Callable 获取返回值,execute 只能提交 Runnable。
保证线程安全:多用 java.util.concurrent 包下的类,或 synchronized,或手动 ReentrantLock。synchronized 在 JDK 1.6 后做了锁升级优化,偏向锁 → 轻量级锁(自旋)→ 重量级锁,以降低锁开销。死锁就是互持对方需要的锁,避免方式:tryLock 设超时、减小锁粒度、尽量不用嵌套锁。
ThreadLocal 给每个线程一份独立副本,经典场景是数据库连接和 Session 管理。volatile 保证可见性,不保证原子性,synchronized 两者都保证但可能阻塞。ReentrantLock 比 synchronized 灵活,支持公平锁、可中断获取,但必须手动 unlock,不然容易死锁。原子类靠 CAS + volatile 实现无锁同步,性能高。
反射、代理与序列化
反射让程序在运行时动态获取类的信息并调用方法,很多框架依赖它。序列化是把对象状态保存下来用于传输或持久化,比如存文件、走网络、RMI 远程调用。动态代理可以运行时生成代理类,JDK 原生代理要求接口,cglib 是基于子类继承。
对象拷贝
克隆是为了得到一个状态独立的新对象,而不是引用的复制。浅拷贝只复制基本类型字段和引用地址,深拷贝会递归复制整个对象图。实现 Cloneable 接口重写 clone 方法,或者通过序列化反序列化实现深层克隆。
Java Web 基础
JSP 本质是 Servlet,JSP 侧重视图,Servlet 侧重控制。JSP 内置 9 个对象:request、response、pageContext、session、application、out、config、page、exception。作用域分 page、request、session、application。Session 存服务端,Cookie 存浏览器端,Cookie 有大小和个数限制,Session 可以放 Redis 或 DB,更安全。
这些知识点是面试高频区,搞懂概念再结合源码,应对基础问题基本没问题。

