//打印菱形,分为两部分:正等腰三角形,倒等腰三角形 public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入奇数:"); int num = sc.nextInt(); if (num % 2 == 0) { return; } int mid = num / 2; //第一部分:正等腰三角形--------------- //外层循环控制打印行数 for (int i = 0; i <= mid; i++) { //打印空格 for (int j = 0; j < mid - i; j++) { System.out.print(" "); } //打印*号,个数是2*i+1 for (int k = 0; k < 2 * i + 1; k++) { //换成这两行就是打印字母菱形图 //char ch = (char) ('A'+i); //System.out.print(ch); System.out.print("*"); } System.out.println(); } //第二部分:倒等腰三角形---------------- //只需将i倒序遍历,其他不变 for (int i = mid - 1; i >= 0; i--) { for (int j = 0; j < mid - i; j++) { System.out.print(" "); } for (int k = 0; k < 2 * i + 1; k++) { //换成这两行就是打印字母菱形图 //char ch = (char) ('A'+i); //System.out.print(ch); System.out.print("*"); } System.out.println(); } }一、Java 基础
1.==和equals()的区别
==比较基本类型时比值,比较引用类型时比内存地址。equals()是Object的方法,默认比较地址,但String、Integer等重写后比较内容。自定义类需要重写
equals()才能实现内容比较。
2.String、StringBuilder、StringBuffer的区别
String不可变,每次修改都会生成新对象,线程安全。StringBuilder可变,非线程安全,性能高。StringBuffer可变,方法用synchronized修饰,线程安全,性能稍低。
3. 接口和抽象类的区别
抽象类可以有构造方法、普通成员变量、具体方法;接口(Java 8 之前)只能有常量和抽象方法。
类只能单继承抽象类,但可以实现多个接口。
接口强调行为规范,抽象类强调代码复用和 is-a 关系。
4. 深拷贝与浅拷贝
浅拷贝:复制对象时,基本类型复制值,引用类型复制引用地址,原对象和副本共享内部对象。
深拷贝:复制对象时,引用类型也递归复制一份,两者完全独立。
实现方式:重写
clone()、序列化、手动复制等。
5. Java 异常体系
Throwable是所有异常和错误的父类。Error:系统级错误,程序无法处理(如OOM、StackOverflowError)。Exception:程序可处理的异常,分为运行时异常(RuntimeException)和受检异常(Checked Exception)。常见运行时异常:
NullPointerException、IndexOutOfBoundsException、ClassCastException、IllegalArgumentException。受检异常必须显式捕获或抛出,如
IOException、SQLException。
6. 泛型与类型擦除
泛型提供编译期类型检查,避免强制类型转换。
Java 泛型在编译后会被擦除,替换为边界类型或
Object。不能使用基本类型作为泛型参数,不能创建泛型数组。
7.final、finally的区别
final:修饰类不可继承,修饰方法不可重写,修饰变量不可变(引用不可变,对象内容可变)。finally:异常处理中一定会执行的代码块(除System.exit()或 JVM 崩溃)。
二、集合框架
1.ArrayList
ArrayList基于动态数组ArrayList扩容默认增加原容量的一半
2.HashMap的底层原理
JDK 8 之前:数组 + 链表;JDK 8 之后:数组 + 链表 + 红黑树。
通过
key.hashCode()计算哈希值,定位桶下标。链表长度超过 8 且数组长度 ≥ 64 时,链表转红黑树。
默认容量 16,负载因子 0.75,扩容为原来的 2 倍。
3.HashMap的扩容机制
当元素数量超过
capacity * loadFactor时触发扩容。扩容时重新计算每个元素在新数组中的位置,数据迁移。
JDK 8 中利用高位运算判断元素是留在原位置还是移动到“原位置 + 旧容量”。
4. 集合工具类Collections和Arrays
Collections:提供排序、反转、同步包装、不可变集合等方法。Arrays:提供数组转 List、排序、二分查找、填充等方法。
三、JVM
1. JVM 内存区域
程序计数器:线程私有,记录指令地址。
Java 虚拟机栈:线程私有,存储栈帧(局部变量表、操作数栈等)。
本地方法栈:执行 Native 方法。
堆:对象实例分配区域,垃圾回收主要区域。
方法区(元空间):类信息、常量、静态变量。
运行时常量池:存放编译期生成的字面量和符号引用。
2. 堆和栈的区别
堆:线程共享,存放对象,GC 管理,内存可动态扩展。
栈:线程私有,存放方法调用和局部变量,生命周期随线程结束。
3. 类加载过程
加载 → 验证 → 准备 → 解析 → 初始化。
加载:读取字节码,生成
Class对象。验证:检查字节码合法性。
准备:为静态变量分配内存并赋默认值。
解析:将符号引用替换为直接引用。
初始化:执行静态变量赋值和静态代码块。
4. 双亲委派模型
类加载器层级:启动类加载器 → 扩展类加载器 → 应用程序类加载器。
加载时先委派父加载器,父加载器无法加载时才由子加载器加载。
作用:避免类重复加载,保护核心类库安全。
5. 如何判断对象可回收
引用计数法:有循环引用问题,Java 不使用。
可达性分析:从 GC Roots 出发,不可达的对象可回收。
GC Roots 包括:栈中引用、静态变量、常量、JNI 引用。
6. 垃圾回收算法
标记-清除:产生内存碎片。
复制算法:空间利用率低,适合新生代。
标记-整理:移动对象,消除碎片,适合老年代。
分代收集:新生代用复制,老年代用标记-整理或标记-清除。
7. 常见垃圾收集器
Serial:单线程,适合客户端。
ParNew:Serial 的多线程版本。
Parallel Scavenge:关注吞吐量。
CMS:关注低延迟,有碎片问题。
G1:区域化分代,可预测停顿。
ZGC/Shenandoah:低延迟,大堆。
8. 对象创建过程
类加载检查 → 分配内存(指针碰撞或空闲列表)→ 初始化零值 → 设置对象头 → 执行构造方法。
9. 内存泄漏与 OOM
内存泄漏:对象不再使用但无法被 GC 回收,常见原因:静态集合、连接未关闭、监听器未移除、
ThreadLocal未清理。OOM:堆溢出、栈溢出、元空间溢出、直接内存溢出。
10. JVM 调优常用参数
-Xms初始堆,-Xmx最大堆,-Xmn新生代大小。-XX:+PrintGCDetails打印 GC 详情。-XX:+HeapDumpOnOutOfMemoryErrorOOM 时生成堆转储。
四、设计模式
1. 单例模式
饿汉式、懒汉式(双重检查锁)、静态内部类、枚举。
注意线程安全、反射破坏、序列化破坏。
2. 工厂模式
简单工厂、工厂方法、抽象工厂。
优点:解耦创建者和使用者,符合开闭原则。
3. 代理模式
静态代理、JDK 动态代理、CGLIB 代理。
应用:Spring AOP、RPC 框架。
4. 观察者模式
定义一对多依赖,状态变化时通知所有观察者。
Java 内置
Observer和Observable,Spring 事件机制。
5. 适配器模式、装饰器模式、模板方法模式、策略模式
适配器:接口转换。
装饰器:动态增强功能,如 Java IO。
模板方法:定义算法骨架,子类实现具体步骤。
策略:封装算法族,可互相替换。