集合高级
理论篇
-
集合体系与学习路径
- 单列集合:实现 Collection
- List:有序、有索引、可重复(ArrayList、LinkedList)
- Set:无序(有规则)、无索引、不可重复(HashSet、LinkedHashSet、TreeSet)
- 双列集合:实现 Map
- HashMap(键无序)、LinkedHashMap(键有序)、TreeMap(键排序)
- 学习三步:创建与特点 → 常用 API → 遍历方式
- 单列集合:实现 Collection
-
Collection 接口与通用遍历
- 常用方法:add、remove、contains、clear、isEmpty、size(remove/contains 依赖 equals)
- 遍历三板斧:
- 迭代器 Iterator:hasNext/next
- 增强 for:for (T e : collection)
- forEach:c.forEach(Consumer) 支持 Lambda 与方法引用(::)
-
方法引用(JDK8)
- 作用:对 Lambda 的进一步简化,用“::”根据“可推导可省略”原则省略参数
- 形式:对象::实例方法、类名::静态方法、类名::实例方法(特殊用法)
-
List 专题
- List 特点:存取有序,有索引,可重复;提供大量基于索引的方法
- ListIterator:双向遍历(先正序移动到尾,再倒序)
- 并发修改异常 CME:遍历过程中用集合自身的 add/remove 会抛出;用迭代器 it.remove()/add() 解决
- ArrayList 原理:JDK8 空参初始容量0,首次添加扩容到10;满时按1.5倍扩容
- LinkedList:双向链表,首尾增删快;查找按就近端逐节点查(表面 get(index) 实际链式定位)
-
数据结构速览
- 栈:先进后出;队列:先进先出;数组:随机访问快、增删慢;链表:增删快、随机访问慢
-
泛型(JDK5)
- 目的:编译期约束与检查,统一类型,消除强转
- 泛型类/方法/接口:E、T 等类型形参;静态方法需自定义泛型;接口实现可指定或延后到类泛型
- 通配符:? 任意;? extends E(E 及子类);? super E(E 及父类)
-
Set 专题
- Set 特点:无序(有规则)、无索引、不可重复
- TreeSet:红黑树,排序且去重
- 自然排序:实现 Comparable,compareTo 返回负/零/正(左/不存/右)
- 比较器排序:构造传 Comparator,compare 规则同上;优先级高于自然排序
- 同值保留技巧:compareTo 计算为0时返回非0(一般真实项目用唯一字段继续比较,如学号)
- HashSet:哈希表(JDK7:数组+链表;JDK8+:数组+链表+红黑树),需重写 equals 和 hashCode(基于属性)保证唯一性与性能
- LinkedHashSet:哈希表 + 双向链表,保序 + 去重
-
可变参数
- 形式:类型... 参数名;可接收0个、多个或数组;方法中只能有一个且需放最后
-
Collections 工具类
- 常用:addAll、shuffle、max/min、sort(list)(自然排序)、sort(list,comp)(指定排序)
-
Map 专题
- Map 特点:键值对,key 不重复,value 不限
- 实现类:
- HashMap:键无序,哈希表
- LinkedHashMap:键有序(插入顺序),哈希表+链表
- TreeMap:键排序(红黑树)
- 常用 API:put/put 覆盖返回旧值、get、remove 返回旧值、containsKey/Value、isEmpty、size、clear
- 三种遍历:
- 键找值:keySet() → for key → get(key)
- entrySet:for (Map.Entry<K,V> e : map.entrySet()) { e.getKey()/e.getValue() }
- forEach(BiConsumer)
-
Stream 流(JDK8)
- 目的:配合 Lambda 简化集合/数组操作,流水线串联中间操作,终结操作落地
- 获取:
- 集合:collection.stream()
- 数组:Arrays.stream(arr)
- 零散:Stream.of(T...)
- Map:keySet/values/entrySet 各自 stream()
- 中间操作(返回 Stream):
- filter、limit、skip、distinct(依赖 equals/hashCode)、concat、map
- 终结操作(非 Stream):
- forEach、count、collect(Collectors.toList/toSet/toMap)、toList(JDK16+)
- 流一经消费不可重用;不修改源
代码篇
Collection 常用方法与遍历
作用/概念简述
- 增删查判空等基础;三种通用遍历(Iterator、增强 for、forEach)
通用模版
Collection<String> c = new ArrayList<>();
c.add("张三"); c.add("李四"); c.add("王五");
System.out.println(c.contains("李四")); // true
c.remove("李四");
System.out.println(c.isEmpty()); // false
System.out.println(c.size());
// 迭代器
Iterator<String> it = c.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
// 增强 for
for (String s : c) System.out.println(s);
// forEach + 方法引用
c.forEach(System.out::println);
属性知识卡片
方法 | 说明 |
---|---|
add/remove/contains | 依赖 equals |
iterator | 取迭代器 |
forEach | 默认方法,接收 Consumer |
方法引用(简化 Lambda)
作用/概念简述
- 以方法名替代 Lambda,减少冗余
通用模版
list.forEach(System.out::println); // 等价于 x -> System.out.println(x)
Arrays.stream(arr).map(String::valueOf).forEach(System.out::println);
属性知识卡片
形式 | 示例 |
---|---|
对象::实例方法 | System.out::println |
类名::静态方法 | Integer::parseInt |
类名::实例方法 | String::toUpperCase(某些场景) |
List 遍历与 ListIterator、CME 解决
作用/概念简述
- List 五种遍历、双向迭代、并发修改异常与规避
通用模版
List<String> list = new ArrayList<>(List.of("张三","李四","王五","赵六"));
// 1. 迭代器删除(安全)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("李四".equals(it.next())) it.remove();
}
// 2. 普通 for
for (int i=0;i<list.size();i++) System.out.println(list.get(i));
// 3. ListIterator 双向
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) System.out.println(lit.next());
while (lit.hasPrevious()) System.out.println(lit.previous());
// 4. 增强 for、5. forEach 同上
属性知识卡片
点 | 说明 |
---|---|
CME | 遍历时用集合增删会抛异常 |
解决 | 用迭代器 it.remove()/add() |
倒序 | ListIterator 需先跑完正序 |
ArrayList 与 LinkedList
作用/概念简述
- ArrayList 扩容机制;LinkedList 首尾快操作
通用模版
// ArrayList:新增少、随机访问多
List<Integer> arr = new ArrayList<>();
// LinkedList:首尾操作高效
LinkedList<String> l = new LinkedList<>();
l.addFirst("张三");
l.addLast("赵六");
System.out.println(l.getFirst());
l.removeLast();
属性知识卡片
类 | 适用 |
---|---|
ArrayList | 读多写少、随机访问 |
LinkedList | 频繁首尾增删 |
泛型:类/方法/接口/通配符
作用/概念简述
- 类型参数化;编译期检查
通用模版
// 泛型类
class Box<E>{ private E e; public E get(){return e;} public void set(E e){this.e=e;} }
// 泛型方法(静态需自定义)
public static <T> void printArray(T[] arr){ for(T t:arr) System.out.println(t); }
// 泛型接口实现
interface Inter<E>{ void show(E e); }
class InterAImpl implements Inter<String>{ public void show(String s){} }
class InterCImpl<E> implements Inter<E>{ public void show(E e){} }
// 通配符
public static void showAll(List<? extends Number> list){ for(Number n:list) System.out.println(n); }
属性知识卡片
关键点 | 说明 |
---|---|
静态泛型方法 | 必须自声明泛型 |
通配符 | extends 上界、super 下界 |
TreeSet 排序:Comparable 与 Comparator
作用/概念简述
- 自然排序 vs 比较器排序;红黑树结构
通用模版
// 自然排序
class Student implements Comparable<Student>{
String name; int age;
public int compareTo(Student o){
int r = this.age - o.age; // 正序
return r==0 ? this.name.compareTo(o.name) : r;
}
}
TreeSet<Student> ts = new TreeSet<>();
// 比较器排序(优先于自然)
TreeSet<Integer> set = new TreeSet<>((o1,o2)-> o2 - o1); // 倒序
属性知识卡片
返回值 | 位置 |
---|---|
负数 | 左 |
0 | 不存(根节点除外) |
正数 | 右 |
HashSet 唯一性:equals 与 hashCode
作用/概念简述
- 重写 equals/hashCode,基于属性计算,避免碰撞与重复
通用模版
class Person{
String name; int age;
@Override public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Person p=(Person)o;
return age==p.age && java.util.Objects.equals(name,p.name);
}
@Override public int hashCode(){
return java.util.Objects.hash(name, age);
}
}
Set<Person> hs = new HashSet<>();
属性知识卡片
点 | 说明 |
---|---|
hashCode | 参与属性,分布均匀 |
equals | 内容相等为真 |
可变参数
作用/概念简述
- 便捷接参;0/多/数组
通用模版
public static int sum(int... nums){
int s=0; for(int n:nums) s+=n; return s;
}
System.out.println(sum()); System.out.println(sum(1,2,3));
属性知识卡片
限制 | 说明 |
---|---|
位置 | 只能一个且放最后 |
Collections 工具
作用/概念简述
- 常用集合操作
通用模版
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5);
Collections.sort(list, (a,b)-> b - a);
Collections.shuffle(list);
System.out.println(Collections.max(list));
System.out.println(Collections.min(list));
属性知识卡片
方法 | 说明 |
---|---|
sort | 自然/比较器排序 |
shuffle | 打乱 |
Map 常用 API 与三种遍历
作用/概念简述
- put/覆盖、get/remove、contains、key/value/entry 遍历
通用模版
Map<String,String> map = new HashMap<>();
map.put("张三","北京"); map.put("李四","上海");
// 1. 键找值
for (String k : map.keySet()) {
System.out.println(k + "---" + map.get(k));
}
// 2. entrySet
for (Map.Entry<String,String> e : map.entrySet()) {
System.out.println(e.getKey() + "---" + e.getValue());
}
// 3. forEach
map.forEach((k,v)-> System.out.println(k + "---" + v));
属性知识卡片
实现 | 特点 |
---|---|
HashMap | 键无序,哈希表 |
LinkedHashMap | 键有序 |
TreeMap | 键排序,红黑树 |
练习:统计字符次数(a(5)b(4)...)
作用/概念简述
- Map 计数模式 + StringBuilder 输出
通用模版
String s = "aababcabcdabcde";
Map<Character,Integer> m = new HashMap<>();
for(char c: s.toCharArray()){
m.put(c, m.getOrDefault(c,0)+1);
}
StringBuilder sb = new StringBuilder();
m.forEach((k,v)-> sb.append(k).append("(").append(v).append(")"));
System.out.println(sb);
Stream:获取与常用操作
作用/概念简述
- 从集合/数组/零散数据取流;中间操作链;终结/收集
通用模版
// 获取
list.stream();
Arrays.stream(new int[]{1,2,3});
Stream.of("张三","李四");
// 中间
list.stream()
.filter(s-> s.startsWith("张"))
.filter(s-> s.length()==3)
.map(String::toUpperCase)
.distinct()
.limit(5)
.skip(1)
.forEach(System.out::println);
// 终结
long cnt = list.stream().count();
// 收集
List<String> l = list.stream().filter(...).collect(Collectors.toList());
Set<String> s = list.stream().filter(...).collect(Collectors.toSet());
Map<String,Integer> map = list.stream().collect(
Collectors.toMap(
x -> x.split(",")[0],
x -> Integer.parseInt(x.split(",")[1])
)
);
属性知识卡片
操作 | 说明 |
---|---|
filter | 过滤 |
map | 映射 |
distinct | 依赖 equals/hashCode |
concat | 合并流 |
forEach/count/collect | 终结操作 |
注意 | 流消费一次即失效 |
异常
理论篇
-
异常概念与体系
- 概念:程序在编译或运行期间出现的非正常情况(错误)。
- 继承体系:所有异常均继承自 Throwable
- Error:严重错误(如 StackOverflowError、OutOfMemoryError),程序一般无法处理。
- Exception:
- 运行时异常 RuntimeException 及其子类(如 NullPointerException、ArithmeticException、ArrayIndexOutOfBoundsException、NumberFormatException):编译期不强制处理,通常由代码不严谨引起。
- 编译时异常(除 RuntimeException 外的 Exception,如 FileNotFoundException、ClassNotFoundException):编译期强制处理(捕获或声明)。
-
Java 默认异常处理流程(不处理时)
- 异常位置创建异常对象并向上抛出 → 逐层调用者继续向上抛出 → 最终到达 JVM → 打印异常堆栈并终止程序。
-
异常处理的两种方式
- try...catch 捕获异常
- 将“可能异常”的代码放入 try,发生异常进入对应 catch,后续代码可继续执行。
- 多个 catch 从小到大(子类在前,父类在后)。
- throws 抛出异常(声明)
- 用在方法签名上,声明此方法将异常“交给调用方处理”。
- 覆写规则:子类重写父类方法时,不能抛出比父类更大的(更宽泛的)异常,也不能抛出父类方法声明中不存在的异常。
- try...catch 捕获异常
-
throw 抛出异常对象(真正抛)
- 用在方法体内,手动 new 异常对象并抛出。
- 抛出编译时异常:方法上必须 throws 或在内部 try...catch;抛出运行时异常:可不写 throws。
-
try...catch vs throws 使用选择
- 是否需要暴露问题给上层:
- 需要暴露:throws(如底层工具方法、框架回调等)
- 不需要暴露:try...catch(如用户交互输入校验、本地容错)
- 是否需要暴露问题给上层:
-
自定义异常
- 目的:Java 无法覆盖所有业务错误,自定义异常类表达特定业务问题(如学生年龄异常)。
- 继承选择:
- 继承 RuntimeException:非受检异常,调用者可选择性处理,常用于业务校验失败。
- 继承 Exception:受检异常,调用者必须处理(捕获或声明)。
- 常规实现:提供无参与带 message 的构造方法;在业务中 throw new 自定义异常(...);通过 e.getMessage() 获取错误信息。
代码篇
默认异常处理流程演示
作用/概念简述
- 未处理异常的向上抛流程,最终由 JVM 打印异常并终止程序。
通用模版
public class ExceptionDemo1 {
public static void main(String[] args) {
System.out.println("main开始");
method(); // 抛出至 main,最终到 JVM
System.out.println("main结束"); // 不会执行
}
private static void method() {
System.out.println("method开始");
int i = 1 / 0; // 这里抛出 ArithmeticException
System.out.println("method结束");
}
}
属性知识卡片
点 | 说明 |
---|---|
抛出链 | 异常点 → 调用者 → JVM |
结果 | 打印堆栈,程序终止 |
try...catch 捕获异常(多 catch)
作用/概念简述
- 捕获并处理异常,程序可继续向下执行;多 catch 从小到大。
通用模版
try {
int i = 1 / 1; // OK
int[] arr = new int[10];
System.out.println(arr[10]); // 触发 ArrayIndexOutOfBoundsException
} catch (ArithmeticException e) {
System.out.println("捕获运算异常");
} catch (NullPointerException e) {
System.out.println("捕获空指针异常");
} catch (Exception e) {
System.out.println("捕获其它异常");
}
System.out.println("后续代码继续执行...");
属性知识卡片
点 | 说明 |
---|---|
顺序 | 子类在前,父类在后 |
继续执行 | catch 后可继续运行 |
输入校验场景:NumberFormatException 捕获
作用/概念简述
- 键盘输入年龄为字符串,转换为 int,异常则提示重输。
通用模版
Scanner sc = new Scanner(System.in);
System.out.print("请输入学生姓名: ");
String name = sc.next();
Student stu = new Student();
stu.setName(name);
while (true) {
try {
System.out.print("请输入学生年龄: ");
int age = Integer.parseInt(sc.next()); // 可能抛 NumberFormatException
stu.setAge(age); // 可能抛业务异常(见自定义异常)
break;
} catch (NumberFormatException e) {
System.out.println("您输入的年龄有误, 请检查!");
} catch (StudentAgeException e) {
System.out.println(e.getMessage());
}
}
System.out.println(stu);
属性知识卡片
点 | 说明 |
---|---|
parseInt | "23" → 23,非法数字抛 NumberFormatException |
组合校验 | 转换异常 + 业务异常 |
throws 声明抛出(处理受检异常)
作用/概念简述
- 方法内存在受检异常(编译时异常),使用 throws 声明交给调用方处理。
通用模版
import java.io.FileNotFoundException;
import java.io.FileReader;
public class ExceptionDemo3 {
public static void main(String[] args) throws Exception {
method(); // main 此处继续声明或 try...catch
}
public static void method() throws FileNotFoundException, ClassNotFoundException {
System.out.println("开始");
FileReader fr = new FileReader("D:\\abc.txt"); // 可能 FileNotFoundException
Class.forName("com.itheima.pojo.Student"); // 可能 ClassNotFoundException
System.out.println("结束");
}
}
属性知识卡片
点 | 说明 |
---|---|
throws | 用在方法签名,声明交由调用者 |
受检异常 | 必须捕获或声明 |
throw 抛出异常对象(运行时/编译时)
作用/概念简述
- 在方法体内主动抛异常,运行时异常可不声明;编译时异常需声明。
通用模版
// 运行时异常:可不写 throws
public void setAge(int age) {
if (age < 0 || age > 100) {
throw new IllegalArgumentException("年龄必须在0~100之间");
}
this.age = age;
}
// 编译时异常:必须 throws 或 try...catch
public void read() throws java.io.IOException {
throw new java.io.IOException("IO错误");
}
属性知识卡片
关键字 | 作用 |
---|---|
throw | 真正抛出一个异常对象 |
throws | 声明此方法可能抛出的异常类型 |
方法重写与 throws 的约束
作用/概念简述
- 子类重写方法时,不能抛出比父类更大的受检异常,也不能新增父类没有的受检异常。
通用模版
class Fu {
public void show() throws Exception { }
}
class Zi extends Fu {
@Override
public void show() throws Exception { // OK:不大于父类
// 具体实现
}
}
属性知识卡片
点 | 说明 |
---|---|
受检异常 | 子类抛出的范围 ≤ 父类声明 |
运行时异常 | 不受此限制 |
自定义异常类与使用
作用/概念简述
- 使用自定义异常表达业务错误,抛出并在调用处提示。
通用模版
// 1) 自定义异常(运行时)
public class StudentAgeException extends RuntimeException {
public StudentAgeException() { }
public StudentAgeException(String message) { super(message); }
}
// 2) JavaBean 中使用
public class Student {
private String name;
private int age;
public void setName(String name) { this.name = name; }
public void setAge(int age) {
if (age >= 0 && age <= 100) {
this.age = age;
} else {
throw new StudentAgeException("年龄有误, 请检查是否是0~100之间的!");
}
}
// toString/getter 省略
}
属性知识卡片
点 | 说明 |
---|---|
继承 | RuntimeException(非受检)或 Exception(受检) |
构造 | 建议提供无参与 message 构造 |
获取信息 | e.getMessage() 获取错误提示 |
try...catch 与 throws 的选择
作用/概念简述
- 根据“是否需要暴露问题”选择处理方式。
通用模版
- 需要暴露给上层:throws(如工具方法、框架回调)
- 不需要暴露(就地消化):try...catch(如用户输入校验)
属性知识卡片
场景 | 推荐方式 |
---|---|
底层通用库 | throws |
交互/容错 | try...catch |
File类
理论篇
-
File 概念与对象创建
- File 用于封装“路径名”,本身不读写文件内容。封装的路径可以存在也可以不存在。
- 常用构造
- new File(String pathname)
- new File(String parent, String child)
- new File(File parent, String child)
-
路径类型
- 绝对路径:从盘符根开始(如 E:\data\log.txt)
- 相对路径:相对当前项目工作目录(如 day03\A.txt)
-
常用判断与获取信息
- 判断:exists、isFile、isDirectory
- 获取:length(字节数,仅文件有效)、getAbsolutePath、getPath、getName、lastModified(毫秒值)
- 注意:length 对文件夹返回的不是总大小;需要递归统计。
-
创建与删除
- 文件:createNewFile
- 目录:mkdir(一级)、mkdirs(多级)
- 删除:delete 仅能删除文件或“空目录”,且不进回收站
-
遍历与过滤
- listFiles 返回当前目录下“一级”File 对象数组
- 过滤 .java:判断 isFile 且 getName().endsWith(".java")
-
递归基础
- 定义:方法直接或间接调用自身
- 关键:必须有明确终止条件,否则可能栈溢出(StackOverflowError)
- 思路:将大问题转化为规模更小的同类问题(如阶乘、斐波那契)
-
递归在文件操作中的应用
- 遍历子目录查找指定类型文件
- 递归删除非空目录
- 递归统计目录大小
-
常见细节与陷阱
- listFiles 可能返回 null(权限不足、IO 错误或非目录),使用前判空
- delete 不可删除非空目录;删除目录需先递归删除其中的文件与子目录
- 路径分隔符跨平台建议使用 File.separator
- 相对路径基于当前工作目录(可通过 System.getProperty("user.dir") 查看)
代码篇
File 对象创建与路径
作用/概念简述
- 演示三种构造与路径存在性;File 仅封装路径。
通用模版
File f1 = new File("E:\\A.txt");
File f2 = new File("E:\\", "Develop");
File f3 = new File(new File("E:\\"), "Develop");
System.out.println(f1.exists()); // 路径是否存在
System.out.println(f2.exists());
System.out.println(f3.exists());
属性知识卡片
构造 | 说明 |
---|---|
File(String) | 直接路径 |
File(String,String) | 父路径+子路径 |
File(File,String) | 父File+子路径 |
相对路径与绝对路径
作用/概念简述
- 正确使用相对/绝对路径,获取真实绝对路径。
通用模版
File rel = new File("day03\\A.txt"); // 相对
File abs = new File("E:\\Develop\\A.txt"); // 绝对
System.out.println(rel.getAbsolutePath());
System.out.println(abs.getPath());
属性知识卡片
类型 | 示例 | 备注 |
---|---|---|
绝对 | E:\data\log.txt | 从盘符开始 |
相对 | day03\A.txt | 相对 user.dir |
判断方法
作用/概念简述
- 判断路径存在性与类型(文件/文件夹)。
通用模版
File f = new File("day03\\A.txt");
System.out.println(f.exists());
System.out.println(f.isFile());
System.out.println(f.isDirectory());
属性知识卡片
方法 | 说明 |
---|---|
exists | 路径是否存在 |
isFile | 是否为文件 |
isDirectory | 是否为目录 |
获取信息方法
作用/概念简述
- 文件信息获取与时间戳格式化。
通用模版
File f = new File("E:\\A.txt");
System.out.println(f.length()); // 文件字节数
System.out.println(f.getAbsolutePath()); // 绝对路径
System.out.println(f.getPath()); // 定义时的路径
System.out.println(f.getName()); // 文件名
long ts = f.lastModified(); // 毫秒值
System.out.println(new java.util.Date(ts));
属性知识卡片
方法 | 说明 |
---|---|
length | 文件大小;目录不可靠 |
lastModified | 上次修改时间戳 |
创建与删除
作用/概念简述
- 创建文件/目录,删除文件或空目录。
通用模版
File f1 = new File("day03\\B.txt");
boolean created = f1.createNewFile(); // 存在则返回 false
File d1 = new File("day03\\aaa");
boolean mk1 = d1.mkdir(); // 一级目录
File d2 = new File("day03\\a\\b\\c");
boolean mk2 = d2.mkdirs(); // 多级目录
boolean delFile = f1.delete(); // 删除文件
boolean delEmptyDir = d1.delete(); // 删除空目录
属性知识卡片
方法 | 注意 |
---|---|
createNewFile | 父目录需存在 |
mkdir/mkdirs | mkdir 仅一级;mkdirs 递归创建 |
delete | 仅文件/空目录;不进回收站 |
遍历一级文件对象并筛选 .java
作用/概念简述
- 使用 listFiles 遍历一级文件对象,筛选指定后缀。
通用模版
File dir = new File("E:\\project");
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".java")) {
System.out.println(file);
}
}
}
属性知识卡片
点 | 说明 |
---|---|
listFiles | 可能返回 null(判空) |
一级 | 仅当前目录,不含子目录 |
递归基础:阶乘与斐波那契
作用/概念简述
- 经典递归模板,强调终止条件。
通用模版
// 阶乘 n!
public static int fact(int n){
if (n <= 1) return 1;
return n * fact(n - 1);
}
// 斐波那契(第 month 月兔子对数)
public static int fib(int month){
if (month == 1 || month == 2) return 1;
return fib(month - 1) + fib(month - 2);
}
属性知识卡片
要点 | 说明 |
---|---|
终止条件 | 防止无限递归 |
复杂度 | 斐波那契朴素递归指数级,可加缓存优化 |
递归查找所有 .java 文件
作用/概念简述
- 深度遍历子目录,打印所有 .java 文件。
通用模版
public static void printJavaFiles(File dir){
File[] files = dir.listFiles();
if (files == null) return; // 判空
for (File f : files){
if (f.isFile() && f.getName().endsWith(".java")){
System.out.println(f);
} else if (f.isDirectory()){
printJavaFiles(f);
}
}
}
属性知识卡片
点 | 说明 |
---|---|
判空 | 无权限/IO 错误/非目录 |
递归 | 目录进入再查 |
递归删除非空目录
作用/概念简述
- 先删子文件与子目录,最后删除当前目录。
通用模版
public static void deleteDir(File dir){
File[] files = dir.listFiles();
if (files != null) {
for (File f : files){
if (f.isFile()){
f.delete();
} else {
deleteDir(f);
}
}
}
dir.delete(); // 删除空目录
}
属性知识卡片
点 | 说明 |
---|---|
顺序 | 先深后浅(后序遍历) |
返回值 | 可收集删除失败项做日志 |
递归统计目录大小(字节数)
作用/概念简述
- 统计所有文件 length 累加,目录递归进入。
通用模版
public static long dirSize(File dir){
long sum = 0;
File[] files = dir.listFiles();
if (files == null) return 0L;
for (File f : files){
if (f.isFile()){
sum += f.length();
} else {
sum += dirSize(f);
}
}
return sum;
}
属性知识卡片
点 | 说明 |
---|---|
length | 仅文件有效 |
溢出 | 极大目录注意 long 溢出风险极低但仍建议用 long |
实用:控制台校验目录输入
作用/概念简述
- 循环输入直到获得“存在且为目录”的 File 对象。
通用模版
public static File readDirFromConsole(){
java.util.Scanner sc = new java.util.Scanner(System.in);
while (true){
System.out.print("请输入一个文件夹路径: ");
File dir = new File(sc.next());
if (!dir.exists()){
System.out.println("路径不存在, 请检查!");
} else if (dir.isFile()){
System.out.println("输入的是文件路径, 请重新输入文件夹路径!");
} else {
return dir;
}
}
}
属性知识卡片
校验 | 说明 |
---|---|
exists | 存在性 |
isFile | 排除文件,要求目录 |
常用 API、IO 流
理论篇
-
Math 类(数值运算)
- abs 绝对值
- ceil 向上取整
- floor 向下取整
- round 四舍五入(float→int / double→long)
- max/min 最大/小值
- pow 幂
- random [0.0,1.0)
-
System 类(系统功能)
- exit(int) 终止 JVM(0 正常,非 0 异常)
- currentTimeMillis() 自 1970-01-01 00:00:00(北京时间为 08:00:00)至今的毫秒值
- arraycopy(src, srcPos, dest, destPos, length) 数组高效拷贝
-
包装类(基本类型 ↔ 引用类型)
- 对应关系:byte/Byte,short/Short,int/Integer,long/Long,char/Character,float/Float,double/Double,boolean/Boolean
- 常用方法
- valueOf 装箱(推荐,构造器已过时)
- xxxValue 拆箱(如 intValue、doubleValue)
- parseXxx 字符串转基本类型(如 parseInt、parseDouble)
- 自动装箱/拆箱:JDK5+ 支持 i = 10; int x = i; 表达式中自动转换
- 注意:parseXxx 解析失败抛 NumberFormatException
-
BigDecimal(高精度小数运算)
- 适用:解决小数计算不精确(如 0.1 + 0.2)
- 创建
- new BigDecimal(String)(推荐)
- BigDecimal.valueOf(double)(推荐,会做必要转换)
- new BigDecimal(double)(不推荐)
- 常用:add、subtract、multiply、divide(若除不尽需指定小数位与舍入模式)
- 舍入模式:RoundingMode.HALF_UP 四舍五入;UP 进一;DOWN 去尾
-
Arrays(数组工具)
- toString 数组转字符串
- equals 判断两个数组内容是否相同(元素、顺序、长度)
- sort 排序(可自定义 Comparator)
- binarySearch 二分查找元素索引(要求已排序,未找到返回负值)
-
传统日期时间(Date、SimpleDateFormat)
- Date:表示时间点(毫秒值封装)
- new Date() 当前时间;new Date(ms) 指定毫秒
- getTime()/setTime(ms)
- SimpleDateFormat:格式化/解析
- new SimpleDateFormat("yyyy年MM月dd日")
- format(Date) → String;parse(String) → Date
- 注意:格式必须与字符串匹配;parse 抛 ParseException
- Date:表示时间点(毫秒值封装)
-
JDK8 日期时间(推荐)
- LocalDate(日期)、LocalTime(时间)、LocalDateTime(日期+时间)
- 获取:now();of(…);由 LocalDateTime 拆分 toLocalDate()/toLocalTime()
- 读取:getYear/getMonthValue/getDayOfMonth/getDayOfWeek/getHour/…
- 运算:plusXxx/minusXxx;withXxx(设置某字段)
- 比较:isBefore/isAfter/equals
- DateTimeFormatter:ofPattern(…),format(temporal),parse(string, formatter)
- ChronoUnit:between(开始, 结束) 计算两时间的间隔(年/月/日/…)
- 注意:Local* 不含时区信息;格式化与解析由具体类型的 parse 调用
-
IO 流体系
- 输入(Input)读取;输出(Output)写出
- 字节流:FileInputStream / FileOutputStream(万能流,任何数据)
- 字符流:FileReader / FileWriter(文本文件,避免中文乱码)
- 异常处理
- 传统 try-catch-finally 手动 close
- JDK7 try-with-resources 自动 close(实现 AutoCloseable)
- 写出细节:FileOutputStream/FileWriter 默认覆盖,传 true 追加
- 字符输出需 flush 或 close 刷盘;字节输出 write 后一般直接落盘
-
Properties(配置键值对)
- 特点:双列集合(Hashtable 子类),键值均为字符串
- 作为集合
- setProperty、getProperty、stringPropertyNames
- 与 IO
- load(InputStream/Reader) 加载
- store(OutputStream/Writer, comments) 写出(不建议存中文,.properties 会转 Unicode)
-
Hutool(工具库)
- IoUtil:copy(InputStream, OutputStream, bufferSize),readLines(Reader, Collection),close(…)
- FileUtil:touch(创建文件并创建父目录)、mkdir(创建多级目录)、copy(文件/目录)、move(移动)
- 需引入依赖或 jar 包
-
Lombok(代码生成)
- 常用注解:@Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode @Data
- 需安装 IDE 插件并引入依赖(在 JavaBean 上标注,自动生成构造/get/set/toString)
代码篇
Math 与 System
作用/概念简述
- 常用数值方法与系统功能(时间、拷贝、退出)
通用模版
// Math
System.out.println(Math.abs(-10));
System.out.println(Math.ceil(12.3)); // 13.0
System.out.println(Math.floor(12.9)); // 12.0
System.out.println(Math.round(12.5)); // 13
System.out.println(Math.max(10, 20)); // 20
System.out.println(Math.pow(2, 3)); // 8.0
double r = Math.random(); // [0,1)
// System
long start = System.currentTimeMillis();
// ... 业务耗时
long end = System.currentTimeMillis();
System.out.println("耗时(ms): " + (end - start));
int[] a = {1,2,3,4,5}, b = new int[3];
System.arraycopy(a, 1, b, 0, 3); // b -> 2,3,4
// System.exit(0);
包装类与解析
作用/概念简述
- 基本类型与包装类转换;字符串解析
通用模版
// 装箱
Integer i = Integer.valueOf(10); // 推荐
// 拆箱
int x = i.intValue();
// 自动装拆箱
Integer j = 20; int y = j;
// 解析
int n = Integer.parseInt("123"); // NumberFormatException 风险
double d = Double.parseDouble("12.3");
练习:字符串数字求最大值
String s = "10,50,30,20,40";
String[] parts = s.split(",");
int max = Integer.MIN_VALUE;
for (String p : parts) {
int v = Integer.parseInt(p);
if (v > max) max = v;
}
System.out.println("最大值: " + max);
BigDecimal 精确运算
作用/概念简述
- 精确小数计算,避免二进制误差
通用模版
import java.math.BigDecimal;
import java.math.RoundingMode;
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = BigDecimal.valueOf(0.2);
BigDecimal sum = a.add(b);
BigDecimal diff = a.subtract(b);
BigDecimal prod = a.multiply(b);
// 除法:除不尽需指定小数位和舍入模式
BigDecimal div = BigDecimal.valueOf(10).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP);
System.out.println(sum); // 0.3
System.out.println(div); // 3.33
注意
- 不要使用 new BigDecimal(double)
- divide 遇到除不尽必须指定精度与舍入模式
Arrays 工具类
作用/概念简述
- 打印、比较、排序、二分查找
通用模版
import java.util.Arrays;
import java.util.Comparator;
int[] x = {11,22,33,44,55};
System.out.println(Arrays.toString(x));
System.out.println(Arrays.equals(x, new int[]{11,22,33,44,55}));
System.out.println(Arrays.binarySearch(x, 33)); // 保证已排序,否则结果不可靠
Integer[] arr = {22,11,66,77,44,55};
Arrays.sort(arr); // 升序
Arrays.sort(arr, (o1,o2) -> o2 - o1); // 降序
System.out.println(Arrays.toString(arr));
Date 与 SimpleDateFormat
作用/概念简述
- 格式化/解析传统 Date 对象
通用模版
import java.text.SimpleDateFormat;
import java.util.Date;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
String text = sdf.format(new Date());
System.out.println(text);
try {
Date birth = sdf.parse("2008年08月08日");
System.out.println(birth.getTime());
} catch (java.text.ParseException e) {
e.printStackTrace();
}
计算年龄(传统)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
Date birth = sdf.parse("2000年01月01日");
long years = (System.currentTimeMillis() - birth.getTime()) / 1000 / 60 / 60 / 24 / 365;
System.out.println(years);
JDK8 时间 API
作用/概念简述
- 获取/读取/运算/比较/格式化/解析
通用模版
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
// 获取
LocalDateTime now = LocalDateTime.now();
LocalDate date = LocalDate.of(2008,8,8);
LocalTime time = LocalTime.of(8,8,8);
// 读取
int year = now.getYear();
int month = now.getMonthValue();
int day = now.getDayOfMonth();
// 运算与设置(不可变返回新对象)
LocalDateTime later = now.plusDays(1).withHour(8);
// 比较
boolean before = date.isBefore(LocalDate.now());
// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年M月d日");
String text = fmt.format(now);
// 解析
LocalDate parsed = LocalDate.parse("2008年12月12日", fmt);
// 计算时间间隔
long age = ChronoUnit.YEARS.between(LocalDate.parse("2000年1月1日", fmt), LocalDate.now());
System.out.println(age);
IO:字节输出 FileOutputStream
作用/概念简述
- 程序数据写出到文件;支持追加
通用模版
import java.io.FileOutputStream;
import java.io.IOException;
try (FileOutputStream fos = new FileOutputStream("E:\\A.txt", true)) { // true 追加
fos.write(new byte[]{97,98,99}); // abc
fos.write("你好".getBytes()); // 默认平台编码
} catch (IOException e) {
e.printStackTrace();
}
注意
- 文件不存在会自动创建;存在且未追加时会清空
- try-with-resources 自动 close
IO:字节输入 FileInputStream
作用/概念简述
- 从文件读取字节到程序(通用数据)
通用模版
import java.io.FileInputStream;
import java.io.IOException;
try (FileInputStream fis = new FileInputStream("E:\\A.txt")) {
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) != -1) {
String s = new String(buf, 0, len);
System.out.print(s);
}
} catch (IOException e) {
e.printStackTrace();
}
文件拷贝
try (FileInputStream in = new FileInputStream("D:\\pic.jpg");
FileOutputStream out = new FileOutputStream("E:\\copy.jpg")) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
}
IO:字符流 FileReader / FileWriter
作用/概念简述
- 文本文件读写(中文不乱码),注意 flush/close
通用模版
// 读取
try (java.io.FileReader fr = new java.io.FileReader("E:\\A.txt")) {
char[] chs = new char[1024];
int len;
while ((len = fr.read(chs)) != -1) {
System.out.print(new String(chs, 0, len));
}
}
// 写出
try (java.io.FileWriter fw = new java.io.FileWriter("E:\\A.txt")) {
fw.write('你');
fw.write(new char[]{'a','b','c'});
fw.write("黑马程序员", 0, 2);
// fw.flush(); // try-with-resources 会在 close 前自动 flush
}
提示
- FileReader/FileWriter 使用平台默认编码。如需指定编码使用 InputStreamReader/OutputStreamWriter。
Properties:集合与 IO
作用/概念简述
- 字符串键值对配置;加载/保存
通用模版
import java.util.Properties;
// 作为集合
Properties prop = new Properties();
prop.setProperty("username", "admin");
prop.setProperty("password", "123456");
System.out.println(prop.getProperty("username"));
for (String key : prop.stringPropertyNames()) {
System.out.println(key + "=" + prop.getProperty(key));
}
// 与 IO
try (java.io.FileWriter fw = new java.io.FileWriter("config.properties")) {
prop.store(fw, "app config"); // 中文将被转为Unicode,影响可读性
}
Properties loaded = new Properties();
try (java.io.FileReader fr = new java.io.FileReader("config.properties")) {
loaded.load(fr);
}
System.out.println(loaded);
Hutool 快速 IO
作用/概念简述
- 复制/移动/创建 文件与目录;流拷贝/读取行;安全关闭
通用模版
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
// 文件/目录
FileUtil.touch("E:\\aaa\\bbb\\ccc\\A.txt"); // 自动创建父目录
FileUtil.mkdir("E:\\abc\\ddd");
FileUtil.copy("E:\\test", "D:\\", true); // 覆盖
FileUtil.move(new java.io.File("E:\\test"), new java.io.File("D:\\"), true);
// 流拷贝
try (java.io.FileInputStream in = new java.io.FileInputStream("D:\\1.jpg");
java.io.FileOutputStream out = new java.io.FileOutputStream("E:\\copy.jpg")) {
IoUtil.copy(in, out, 2048);
}
// 按行读取
java.util.ArrayList<String> lines = new java.util.ArrayList<>();
try (java.io.FileReader r = new java.io.FileReader("day04\\出师表.txt")) {
IoUtil.readLines(r, lines);
}
lines.forEach(System.out::println);
依赖(Maven 示例)
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
Lombok 快速 JavaBean
作用/概念简述
- 通过注解自动生成 getter/setter/toString/构造器
通用模版
// 需安装 IDE 插件,并添加依赖
// <dependency>
// <groupId>org.projectlombok</groupId>
// <artifactId>lombok</artifactId>
// <version>1.18.32</version>
// <scope>provided</scope>
// </dependency>
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private int age;
}
多线程
理论篇
-
进程与线程
- 进程:程序一次运行活动,是系统资源分配的基本单位。一个应用可包含多个进程。
- 线程:进程内的最小执行单位,一个进程可并发多个线程执行不同任务。
- 并行 vs 并发
- 并行:多个核上真正同时执行(多核多线程 CPU)。
- 并发:单核上在多个任务间快速切换,宏观“同时”,微观看是交替执行。
- Java 程序默认是多线程的:至少包含 main 线程与垃圾回收线程。
-
线程创建的三种方式
- 继承 Thread
- 步骤:继承 Thread → 重写 run → new 子类 → 调用 start 开启线程(注意:start 才能异步启动,直接调用 run 只是普通方法调用)。
- 实现 Runnable
- 步骤:实现 Runnable → 重写 run → new Thread(Runnable) → start。
- 特点:更灵活,便于资源共享(同一个 Runnable 实例可被多个线程共享)。
- 实现 Callable 配合 FutureTask
- 步骤:实现 Callable → 重写 call 返回结果 → 用 FutureTask 包装 → new Thread(FutureTask) → start → 通过 FutureTask.get() 获取结果(阻塞等待)。
- 特点:能有返回值、能抛出受检异常。
- 继承 Thread
-
Thread 常见方法
- 线程名:getName、setName;当前线程对象:Thread.currentThread()
- 休眠:Thread.sleep(ms)(让出 CPU 一段时间,可能抛 InterruptedException)
- 优先级:setPriority(1~10),优先级越高仅代表“更有可能”被调度,非绝对先后
- 守护线程:setDaemon(true) 将线程设置为守护线程(所有非守护线程结束后守护线程会随 JVM一并结束)
-
线程安全问题与出现条件
- 条件
- 存在多线程环境
- 存在共享数据
- 多条语句对共享数据进行读改写
- 现象:数据错乱(重复票、负数票等)
- 根因:线程切换发生在关键操作中间,导致“读-改-写”序列被打断
- 条件
-
同步与锁
- synchronized 同步代码块/同步方法
- 同步块:synchronized(锁对象){ 临界区 }
- 同步方法:在方法签名前加 synchronized
- 锁对象选择
- 实例方法:锁对象是 this
- 静态方法:锁对象是 当前类的字节码对象(Class对象)
- 多线程共享同一把锁(同一个对象/同一个 Class)才能互斥
- Lock 显式锁(以 ReentrantLock 为例)
- 优点:加锁/解锁范围可控、可尝试锁、可公平锁
- 使用:lock.lock() → try { 临界区 } finally { lock.unlock() }
- 注意
- 同步可以保证正确性,但会降低并发性能
- 锁的粒度越小越好;避免在锁内做耗时操作
- 小心死锁(多个锁交叉持有)
- synchronized 同步代码块/同步方法
-
线程池
- 目的:复用线程,避免频繁创建/销毁线程的成本,提升吞吐与稳定性
- Executors 工厂创建(了解)
- newCachedThreadPool、newFixedThreadPool 等,不便于精细化配置,实际生产不推荐
- ThreadPoolExecutor(推荐,7 个核心参数)
- corePoolSize:核心线程数(常驻)
- maximumPoolSize:最大线程数
- keepAliveTime + unit:非核心线程空闲存活时间
- workQueue:任务队列(如 ArrayBlockingQueue、LinkedBlockingQueue)
- threadFactory:线程工厂(可自定义命名)
- handler:拒绝策略(AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy)
- 生命周期与关闭
- 提交任务:submit/execute
- 关闭:shutdown(平滑)、shutdownNow(尝试中断),可配合 awaitTermination
-
建议与最佳实践
- 线程创建优先使用 Runnable/Callable,少用继承 Thread
- 共享资源访问加锁(synchronized/Lock),锁对象保持一致
- 有返回值、异常需传递使用 Callable + FutureTask 或线程池 submit 返回 Future
- 使用线程池统一管理线程,合理配置参数与拒绝策略,及时关闭
- 关键路径减少锁范围与竞争,避免在锁内进行 IO 或复杂计算
- 守护线程仅用于辅助服务(如监控),不要依靠守护线程处理必须完成的业务
代码篇
线程创建:Thread / Runnable / Callable
作用/概念简述
- 三种方式模板化使用;注意 start 与 run 的区别;Callable 可返回结果。
通用模版
// 1) 继承 Thread
class MyThread extends Thread {
public MyThread(String name){ super(name); }
@Override public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + " 执行 " + i);
}
}
}
new MyThread("线程A").start();
// 2) 实现 Runnable
class MyRunnable implements Runnable {
@Override public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " 执行 " + i);
}
}
}
Runnable task = new MyRunnable();
new Thread(task, "线程B").start();
new Thread(task, "线程C").start(); // 共享同一任务资源
// 3) 实现 Callable + FutureTask
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer> {
@Override public Integer call() {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
}
}
FutureTask<Integer> ft = new FutureTask<>(new MyCallable());
Thread t = new Thread(ft, "线程D");
t.start();
Integer result = ft.get(); // 阻塞等待结果
System.out.println("返回结果: " + result);
属性/知识点卡片
- start() 才会真正开启新线程并异步调用 run()
- Callable 适合有结果、有异常传递的任务
- FutureTask.get() 会阻塞,建议在任务启动后再获取
线程名、休眠、优先级、守护线程
作用/概念简述
- 线程调试与控制常用方法。
通用模版
// 名字
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}, "业务线程-1");
t1.start();
// 休眠
for (int i = 5; i >= 1; i--) {
System.out.println("倒计时:" + i + " 秒");
Thread.sleep(1000);
}
// 优先级(1~10)
Thread t2 = new Thread(() -> {/*...*/}, "高优先级");
t2.setPriority(10);
t2.start();
// 守护线程:主线程结束后,守护线程随 JVM 一并结束
Thread daemon = new Thread(() -> {
while (true) { /* 心跳/日志... */ }
}, "守护线程");
daemon.setDaemon(true);
daemon.start();
属性/知识点卡片
- 优先级仅影响调度概率,结果顺序不保证
- 守护线程不要承载必须完成的业务逻辑
线程安全:卖票示例与 synchronized
作用/概念简述
- 多线程共享资源,使用同步块/方法保护临界区。
通用模版
class TicketTask implements Runnable {
private int tickets = 100;
// 对于静态共享,考虑 static 成员并锁 Class 对象
@Override public void run() {
while (true) {
synchronized (this) { // 锁对象要一致(同一实例被多个线程共享)
if (tickets <= 0) break;
System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets + " 张票");
tickets--;
}
}
}
}
TicketTask task = new TicketTask();
new Thread(task, "窗口A").start();
new Thread(task, "窗口B").start();
new Thread(task, "窗口C").start();
// 同步方法(实例方法:锁 this;静态方法:锁 类对象)
class SafeCounter {
private int n = 0;
public synchronized void inc() { n++; }
public synchronized int get() { return n; }
}
属性/知识点卡片
- 共享资源 + 多语句更新是安全隐患,要在同一把锁保护
- 锁对象应为同一实例或同一 Class
Lock 显式锁
作用/概念简述
- ReentrantLock 提供更灵活的锁控制(加锁/解锁范围、可重入)。
通用模版
import java.util.concurrent.locks.ReentrantLock;
class TicketTask3 implements Runnable {
private int tickets = 100;
private final ReentrantLock lock = new ReentrantLock();
@Override public void run() {
while (true) {
lock.lock();
try {
if (tickets <= 0) break;
System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets + " 张票");
tickets--;
} finally {
lock.unlock();
}
}
}
}
TicketTask3 t = new TicketTask3();
new Thread(t, "窗口A").start();
new Thread(t, "窗口B").start();
属性/知识点卡片
- 必须在 finally 中 unlock,避免异常导致死锁
- 高级用法:tryLock、lockInterruptibly、公平锁等
线程池:JDK 线程池与自定义 ThreadPoolExecutor
作用/概念简述
- 统一管理线程,提高复用与吞吐
通用模版
// 不推荐:Executors 简单工厂(了解)
// ExecutorService pool = Executors.newFixedThreadPool(10);
// 推荐:自定义 ThreadPoolExecutor
import java.util.concurrent.*;
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // corePoolSize 核心线程数
5, // maximumPoolSize 最大线程数
60L, TimeUnit.SECONDS, // 非核心线程空闲存活时间
new ArrayBlockingQueue<>(10), // 有界队列
Executors.defaultThreadFactory(), // 线程工厂(可自定义命名)
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
for (int i = 1; i <= 16; i++) {
pool.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
});
}
// 关闭线程池
pool.shutdown(); // 平滑关闭
// 可选:pool.awaitTermination(30, TimeUnit.SECONDS);
属性/知识点卡片
- 任务>队列+最大线程时触发拒绝策略
- 常见拒绝策略
- AbortPolicy:抛出 RejectedExecutionException
- CallerRunsPolicy:由提交任务的线程执行
- DiscardPolicy:丢弃任务
- DiscardOldestPolicy:丢弃队列中最旧任务,再尝试提交
- 队列选择
- ArrayBlockingQueue(有界):可控内存,适合并发受限场景
- LinkedBlockingQueue(近似无界):容易 OOM,不建议无限制使用
综合:线程命名与自定义 ThreadFactory
作用/概念简述
- 方便定位线程来源与问题排查。
通用模版
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
class NamedThreadFactory implements ThreadFactory {
private final String prefix;
private final AtomicInteger idx = new AtomicInteger(1);
NamedThreadFactory(String prefix){ this.prefix = prefix; }
@Override public Thread newThread(Runnable r) {
Thread t = new Thread(r, prefix + "-" + idx.getAndIncrement());
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(50),
new NamedThreadFactory("biz-worker"),
new ThreadPoolExecutor.AbortPolicy()
);
属性/知识点卡片
- 线程名应包含业务前缀 + 自增编号,便于日志排查
- 关键线程不要设为守护线程
网络编程
理论篇
-
网络编程概念与架构
- 定义:让设备中的程序与网络上其他设备中的程序进行数据交互的技术(java.net 包提供支持)。
- 架构:
- CS(Client/Server):客户端与服务端直连,强交互、可定制。
- BS(Browser/Server):浏览器 + 服务端,通过 HTTP 通信,部署更新方便。
-
网络通信三要素
- IP
- 设备在网络中的唯一地址。IPv4(32 位,已枯竭但配合路由/NAT仍主流),IPv6(128 位,地址充足)。
- 127.0.0.1/localhost:本地回环地址,只指向本机,不经路由。
- 常用命令:ipconfig(看本机 IP)、ping(测试连通性)。
- DNS:域名解析服务,负责将域名解析为 IP。
- Java:InetAddress 类用于 IP/主机名解析和查询。
- 端口
- 应用在设备中的唯一标识,范围 0~65535。
- 0~1023 为知名端口,建议使用 >1024。
- 同一台机器上同一时间端口不可重复被占用。
- 协议
- UDP:面向无连接,发送快、最多单包 64KB、不可靠(可能丢包、乱序),适合实时性场景(语音、直播等)。
- TCP:面向连接(三次握手、四次挥手),可靠传输、无数据大小限制(流式),适合文件传输、聊天、邮件、下载等。
- IP
-
UDP 与 TCP 对比
- UDP:快、简单、无连接、不可靠,适合“速度优先、可容错”的场景。
- TCP:慢一些、可靠、有连接、流式传输,适合“准确性优先”的场景。
-
TCP 连接管理
- 三次握手:建立连接(SYN→SYN-ACK→ACK)。
- 四次挥手:断开连接(FIN/ACK 交互)。
-
常见注意事项与实践建议
- IP 与端口要匹配,接收方必须先启动并占用端口。
- UDP 收包缓冲区要足够大(否则截断);TCP 为流式没有报文边界,需要自定义“协议”(长度前缀、分隔符等)。
- 本地测试可用 127.0.0.1 或 localhost;跨机通信要确保在同一网络、端口未被防火墙拦截。
- 资源释放:Socket/ServerSocket/DatagramSocket/流对象使用完必须关闭(推荐 try-with-resources) 。
- TCP 文件传输:客户端写完要调用 socket.shutdownOutput() 告知服务端写入结束。
代码篇
IP 与主机名解析(InetAddress)
作用/概念简述
- 将主机名解析为 IP,查询主机名与文本 IP。
通用模版
import java.net.InetAddress;
InetAddress addr = InetAddress.getByName("www.baidu.com"); // 或主机名 / 文本IP
System.out.println(addr.getHostName()); // 主机名(可能为域名/计算机名)
System.out.println(addr.getHostAddress()); // 文本IP
属性知识卡片
- getByName 支持域名、主机名、字符串 IP
- getHostName 可能触发反向解析,受网络/DNS 影响
UDP 发送方
作用/概念简述
- 使用 DatagramSocket 发送数据报到指定 IP:端口。
通用模版
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
try (DatagramSocket socket = new DatagramSocket(8888)) { // 也可无参随机端口
byte[] data = "你好".getBytes();
DatagramPacket packet = new DatagramPacket(
data, data.length,
InetAddress.getByName("127.0.0.1"), 9999);
socket.send(packet);
} // 自动关闭
属性知识卡片
- 端口范围 1024+,避免与系统端口冲突
- 发送端可随机端口或指定固定端口
UDP 接收方
作用/概念简述
- 绑定端口,接收单个数据报并解析内容与来源 IP。
通用模版
import java.net.DatagramPacket;
import java.net.DatagramSocket;
byte[] buf = new byte[1024]; // 注意容量足够
try (DatagramSocket socket = new DatagramSocket(9999)) {
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet); // 阻塞等待
String msg = new String(packet.getData(), 0, packet.getLength());
String ip = packet.getAddress().getHostAddress();
System.out.println("来自 " + ip + " 的消息: " + msg);
}
属性知识卡片
- receive 阻塞直到有包到达
- getLength 为本次有效字节数
TCP 文件上传(客户端)
作用/概念简述
- 与服务端建立连接,流式发送文件,结束后读取服务端响应。
通用模版
import java.io.*;
import java.net.Socket;
File src = new File("D:\\2.jpg");
try (Socket socket = new Socket("localhost", 8899);
InputStream fileIn = new FileInputStream(src);
OutputStream netOut = socket.getOutputStream();
InputStream netIn = socket.getInputStream()) {
byte[] buf = new byte[8192];
int len;
while ((len = fileIn.read(buf)) != -1) {
netOut.write(buf, 0, len);
}
socket.shutdownOutput(); // 告知服务端:我写完了
byte[] resp = new byte[1024];
int n = netIn.read(resp); // 阻塞直到服务端返回
if (n != -1) {
System.out.println("服务端应答: " + new String(resp, 0, n));
}
}
属性知识卡片
- shutdownOutput 通知“写完”,便于服务端 read() 返回 -1
- 读写用缓冲(8KB+)更高效
TCP 文件接收(服务端)
作用/概念简述
- 监听端口,接受连接,读取客户端字节写入文件,回写应答。
通用模版
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
try (ServerSocket server = new ServerSocket(8899)) {
System.out.println("服务端启动,等待连接...");
try (Socket socket = server.accept();
InputStream netIn = socket.getInputStream();
OutputStream netOut = socket.getOutputStream();
OutputStream fileOut = new FileOutputStream("E:\\result.jpg")) {
byte[] buf = new byte[8192];
int len;
while ((len = netIn.read(buf)) != -1) {
fileOut.write(buf, 0, len);
}
netOut.write("上传成功".getBytes());
}
}
属性知识卡片
- 单线程服务端一次只服务一个客户端;多客户端需每个 accept() 交给新线程/线程池处理
- 文件路径需确保父目录存在并且有写权限
TCP 多客户端并发(服务端模板)
作用/概念简述
- 通过线程池处理每个客户端,提升吞吐。
通用模版
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class TcpServerPool {
public static void main(String[] args) throws IOException {
ExecutorService pool = new ThreadPoolExecutor(
2, 8, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try (ServerSocket server = new ServerSocket(8899)) {
while (true) {
Socket socket = server.accept();
pool.submit(() -> handle(socket));
}
} finally {
pool.shutdown();
}
}
private static void handle(Socket socket) {
try (Socket s = socket;
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream()) {
// TODO: 读取请求、处理业务、写回响应(示例:回显)
byte[] buf = new byte[1024];
int n = in.read(buf);
if (n > 0) out.write(("已收到: " + new String(buf, 0, n)).getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
属性知识卡片
- 线程池参数按实际负载调整
- 业务协议要明确消息边界(建议自定义协议:固定头、长度前缀、分隔符等)
自定义简单协议建议(TCP 流式边界)
作用/概念简述
- 解决 TCP 无边界:发送前写入长度,接收方先读长度再读内容。
通用模版
// 发送方
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
byte[] payload = "Hello".getBytes("UTF-8");
dos.writeInt(payload.length);
dos.write(payload);
// 接收方
DataInputStream dis = new DataInputStream(socket.getInputStream());
int len = dis.readInt();
byte[] body = new byte[len];
dis.readFully(body);
String msg = new String(body, "UTF-8");
属性知识卡片
- DataInputStream/DataOutputStream 便于读写基本类型(支持大端序)
- readFully 确保按指定长度完整读取
常见排障与技巧
作用/概念简述
- 网络编程常见问题定位与解决。
要点卡片
- 连不上:确认 IP、端口、进程是否监听、客户端/服务端是否同网段/公网可达、防火墙是否放行。
- 端口占用:Windows 用 netstat -ano | findstr 端口;Linux 用 lsof -i:端口。
- UDP 丢包:适当增大接收缓冲区、控制发送速率;必要时切 TCP 或做重传机制。
- 文件损坏:确保完整读取写入、及时 flush/close;TCP 端需要明确结束标识(如 shutdownOutput 或长度协议)。
- 本地/远端:127.0.0.1 仅本机可用,跨机需用真实局域网/公网 IP。
Java 高级技术
理论篇
-
枚举(enum)
- 定义:Java 的一种特殊类型,用于信息的标记与分类,比常量更严谨(类型安全、IDE 友好提示)。
- 特点:
- 每个枚举项本质上是枚举类的一个实例。
- 所有枚举类隐式继承自 java.lang.Enum。
- 可定义字段、构造器(默认 private)、方法,甚至抽象方法(各枚举项必须实现)。
- 第一行必须是枚举项;若后面还有其他成员,枚举项行末必须加分号。
- 常用方法:name、ordinal、values、valueOf。
- 适用:状态、类型、错误码、开关等“有限集合”。
-
类加载器与类加载过程
- 作用:将类的字节码加载到方法区(JDK8 及以下)/元空间(JDK8+)。
- 加载时机:用到时加载(首次主动使用)。
- 三个阶段:
- 加载:根据全限定名查找字节码,生成 Class 对象。
- 链接:
- 验证:字节码格式校验。
- 准备:为静态变量分配内存并赋默认值。
- 解析:常量池中的符号引用转直接引用(将“名字”换成“地址”)。
- 初始化:执行静态变量显式赋值与静态代码块。
- 类加载器类型:
- Bootstrap(引导/启动):加载 JDK 核心类(null 表示)。
- Platform(平台,JDK9+)/Extension(扩展,JDK8-):加载一些扩展模块。
- Application(应用):加载应用 classpath 下的类。
- 自定义类加载器:可定制加载来源和策略(不常用)。
- 双亲委派:类加载请求自下而上委托父加载器,父加载器无法完成时子加载器才尝试,避免重复加载与安全问题。
-
反射(Reflection)
- 定义:运行时动态获取类的结构信息并操作(字段、方法、构造等),框架的基础。
- 获取 Class 对象方式:
- Class.forName("全类名")
- 类名.class
- 对象.getClass()
- 常用反射对象:
- Constructor:获取/实例化对象(可暴力反射 setAccessible(true))。
- Field:读写成员变量(需指定目标对象)。
- Method:调用成员方法(invoke 指定目标对象与参数)。
- 注意:暴力反射破坏封装,生产慎用;反射有性能损耗。
-
注解(Annotation)
- 作用:为代码元素添加元数据(给编译器/JVM/框架看);如 @Override、@Deprecated、@SuppressWarnings。
- 自定义注解:
- 使用 @interface 声明;属性可指定默认值;单属性名为 value 时可省略 key。
- 元注解:
- @Target:限定使用位置(TYPE/METHOD/FIELD/…)
- @Retention:生命周期(SOURCE/CLASS/RUNTIME)
- @Documented、@Inherited(可选)
- 运行期配合反射使用(注解需标注 @Retention(RUNTIME))。
-
动态代理(Dynamic Proxy)
- 定义:在运行时生成代理对象,在不修改目标类源码的前提下增强方法(AOP 基础)。
- JDK 动态代理要求:目标对象必须实现接口(代理类实现同样的接口)。
- 关键 API:Proxy.newProxyInstance(ClassLoader, Class<?>[] interfaces, InvocationHandler)
- InvocationHandler.invoke(proxy, method, args) 中编写增强逻辑(如耗时统计、权限校验、事务等)。
- 备注:无接口的类可选 CGLIB(字节码增强)代理(框架中常见)。
代码篇
枚举:定义与使用
作用/概念简述
- 用类型安全的枚举替代常量,增强可读性与严谨性。
通用模板
enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}
public class EnumTest {
public static void main(String[] args) {
useSeason(Season.WINTER);
for (Season s : Season.values()) {
System.out.println(s.name() + " -> " + s.ordinal());
}
}
static void useSeason(Season season) {
switch (season) {
case SPRING -> System.out.println("春季");
case SUMMER -> System.out.println("夏季");
case AUTUMN -> System.out.println("秋季");
case WINTER -> System.out.println("冬季");
}
}
}
扩展:带字段/构造/抽象方法
enum OrderStatus {
NEW(0, "新建") { public boolean canCancel(){ return true; } },
PAID(1, "已支付") { public boolean canCancel(){ return false; } };
public final int code; public final String desc;
OrderStatus(int code, String desc){ this.code = code; this.desc = desc; }
public abstract boolean canCancel();
}
属性知识卡片
- 枚举项本质是实例;可定义字段/方法;可重写抽象方法。
类加载器:获取与层级
作用/概念简述
- 获取不同类由谁加载、查看父子关系与初始化时机。
通用模板
public class ClassLoaderDemo {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader()); // null (Bootstrap)
System.out.println(ClassLoaderDemo.class.getClassLoader()); // AppClassLoader
System.out.println(ClassLoaderDemo.class.getClassLoader().getParent()); // Platform
System.out.println(ClassLoaderDemo.class.getClassLoader().getParent().getParent()); // null
}
}
初始化演示(静态块)
class X {
static { System.out.println("X 初始化"); }
static int v = 42;
}
public class InitDemo {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("X"); // 触发初始化(主动使用)
System.out.println(X.v);
}
}
属性知识卡片
- 双亲委派:先委托父加载器,父无法加载才自己加载。
反射:获取 Class 与构造/字段/方法
作用/概念简述
- 运行期动态操作结构。
通用模板
// 1) 获取 Class
Class<?> c1 = Class.forName("com.itheima.pojo.Student");
Class<?> c2 = com.itheima.pojo.Student.class;
Class<?> c3 = new com.itheima.pojo.Student().getClass();
// 2) 构造:创建对象
var cons = c1.getDeclaredConstructor(String.class, int.class);
cons.setAccessible(true);
Object stu = cons.newInstance("张三", 23);
// 3) 字段:读写
var nameF = c1.getDeclaredField("name");
nameF.setAccessible(true);
nameF.set(stu, "李四");
System.out.println(nameF.get(stu));
// 4) 方法:调用
var m1 = c1.getMethod("eat");
var m2 = c1.getMethod("eat", int.class);
m1.invoke(stu);
m2.invoke(stu, 3);
练习:绕过泛型限制(仅演示)
var list = new java.util.ArrayList<Integer>();
list.add(1);
var add = list.getClass().getMethod("add", Object.class);
add.invoke(list, "张三");
System.out.println(list); // [1, 张三]
属性知识卡片
- 暴力反射需 setAccessible(true),生产慎用。
注解:定义、元注解与扫描执行
作用/概念简述
- 自定义 @Test 注解,运行期扫描并执行标注的方法。
通用模板
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test { }
public class MethodDemo {
@Test public void print(){ System.out.println("print"); }
public void show(){ System.out.println("show"); }
@Test public void method(){ System.out.println("method"); }
}
import java.lang.reflect.Method;
public class ScanTest {
public static void main(String[] args) throws Exception {
Class<MethodDemo> clz = MethodDemo.class;
MethodDemo obj = clz.getConstructor().newInstance();
for (Method m : clz.getMethods()) {
if (m.isAnnotationPresent(Test.class)) {
m.invoke(obj);
}
}
}
}
属性知识卡片
- @Retention(RUNTIME) 才能在运行期通过反射读取注解。
- 单一属性名为 value 时,使用时可省略键名。
动态代理:JDK Proxy 增强方法
作用/概念简述
- 运行期生成代理对象,在方法前后增强(如耗时统计)。
通用模板
import java.lang.reflect.*;
@SuppressWarnings("unchecked")
public class ProxyDemo {
public static void main(String[] args) {
java.util.ArrayList<String> target = new java.util.ArrayList<>();
java.util.List<String> proxy = (java.util.List<String>) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{java.util.List.class},
new TimeCostHandler(target)
);
proxy.add("张三"); // 增强 add 方法
proxy.add("李四");
System.out.println(target);
}
static class TimeCostHandler implements InvocationHandler {
private final Object target;
TimeCostHandler(Object target){ this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("add".equals(method.getName())) {
long start = System.currentTimeMillis();
Object res = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("add耗时: " + (end - start) + "ms");
return res;
}
return method.invoke(target, args);
}
}
}
通用代理工厂(任意接口对象的通用增强)
@SuppressWarnings("unchecked")
public static <T> T wrap(T target, java.util.function.BiFunction<Method, Object[], Object> around, Class<?>... ifaces) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
ifaces.length == 0 ? target.getClass().getInterfaces() : ifaces,
(proxy, method, args) -> around.apply(method, args) != null
? around.apply(method, args)
: method.invoke(target, args));
}
属性知识卡片
- JDK 动态代理要求目标有接口;无接口可用 CGLIB(框架层常见,如 Spring AOP)。
- InvocationHandler 中可实现统一日志、鉴权、事务、重试、熔断等横切逻辑。