欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > 面试题之java基础

面试题之java基础

2025/4/20 4:43:36 来源:https://blog.csdn.net/zbq1017/article/details/147293042  浏览:    关键词:面试题之java基础

最近开始面试了,410面试了一家公司 针对自己薄弱的面试题库,深入了解下,也应付下面试。在这里先祝愿大家在现有公司好好沉淀,定位好自己的目标,在自己的领域上发光发热,在自己想要的领域上(技术管理、项目管理、业务管理等)越走越远!希望各位面试都能稳过,待遇都是杠杠的!

基础概念

java的特点是?
  • 面向对象
  • 平台无关性(一次编译,到处运行)
  • 支持多线程
  • 自动内存管理(垃圾回收)
  • 安全性
  • 丰富的类库

Q:为何说JAVA是跨平台的?而他如何做到一次编译 到处运行的?

A:通过标准化的字节码和平台特定的JVM,将平台相关性从编译阶段转移到运行阶段,从而实现“一次编译,到处运行”。

  • 编译生成字节码,java源代(.java文件)通过javac编译器编译成平台无关的字节码(.class文件)
  • 字节码是JVM的指令集,不是针对特定硬件或者操作系统的机器码,而是一种中间代码。
  • java解释执行字节码,不同的JVM负责相关的字节码翻译成当前本地机器指令并执行;jvm是平台相关的(需要针对不同操作系统单独安装),但字节码和平台无关。
  • 关键技术支持:
    • 字节码规范:JVM定义统一的字节码格式(由oracle的jvm规范标准化)
    • JVM适配各平台:不同厂商为不同系统实现JVM,确保字节码能正确的翻译为本地指令。
    • 类加载机制:jvm动态加载.class文件,按需解析和执行
  • 实际限制:
    • JVM版本兼容性:字节码需与目标JVM版本兼容(高版本的字节码不能在低版本的JVM运行)
    • 平台相关代码:通过native方法(如JNI)调用本地库(.dll/.so)扔需针对不同平台编译
    • 性能开销:JVM解释执行或者JIT编译可能比直接运行机器码稍慢,但已经大幅度提升了。

2.JDK\JRE\JVM的区别是什么?
  • JDK(java development kit):开发工具包,包含JRE+编译器(javac)等开发工具
  • JRE(Java runtime environment):运行环境,包含JVM+核心类库
  • JVM(java virtual machine):执行java字节码的虚拟机
3.java是解释型语言还是编译型语言

        两者结合,java代码先编译为字节码(.class文件),然后由JVM解释执行或者JIT编译执行。

4.java的基础类型有哪些
  • 整形:byte(1),sort(2) ,int(4),long(8)
  • 浮点型:float(4),double(8)
  • 字符型:char(2)
  • 布尔型:boolean(1)

Q:使用BigDecimal对大数据处理留下的坑

  • 构架方法问题:double构造精度丢失
    • double本身精度问题,使用时先转成string
  • 等值比较问题
    • equals()与compareTo()差异
      • equals比较涉及到精度问题,比较尽量使用compareTo
  • 触发运算问题、精度问题
    • 未指定舍入模式,除法会出现ArithmeticExcetion
    • 舍入模式:RoundingMode.HALF_UP 四舍五入,RoundingMode.DOWN 直接截断,RoundingModeCEILING 向正无穷舍入
  • 大量运算性能开销大
    • BigDecimal的运算比基本类型慢很多
    • 对于简单的运算,考虑先用基本类型运算后,在转换成BigDecimal
    • 重用BigDecimal对象(不可变对象每次运算都创建新对象)
  • 不可变性带来的问题
    • 忽略返回值,计算时需要将返回值获取
  • 科学计数法问题
    • 意外科学计数法显示,可以用toPlainString处理

Q:为何BigDecimal更适合精确运算

  • 精确的数值表示
  • 完全控制运算精度
  • 可配置的舍入模式
  • 大数据处理能力好
  • 金融计算必须特性
  • 避免累积误差
  • 小结
    • 金融计算必须使用BigDecimal

    • 构造时使用String参数new BigDecimal("0.1")而非new BigDecimal(0.1)

    • 设置明确的精度和舍入模式:特别是除法运算

    • 考虑定义工具类封装常用操作

    • 对于简单计算:可先用基本类型计算,最后转为BigDecimal

    5.基础类型和包装类型的区别

    • 包装类型是类,可以为null,有方法和属性
    • 基本类型直接存储值,效率更高
    • 自动装箱/拆箱机制

    Q:自动装箱/拆箱机制是什么?

    A:装箱和拆箱主要是简化基本数据类型和其对应的包装类之间的转换

    • 自动装箱(AutoBoxing):将基本数据类型紫装转换成对应的包装类对象
    • 自动拆箱(Unboxing):将包装类对象自动转换为对应的基本数据类型
    • 实现原理:自动装箱和拆箱是编译器提供的语法糖,编译时会替换为对应的包装/拆箱方法调用
    • 装箱:调用Integer.valueOf()、Double.valueOf()等方法
    • 拆箱:调用intValue、doubleValue()等方法
    • 注意事项:
      • 性能考虑:自动装箱/拆箱会创建临时对象,在循环中大量使用可能会影响性能
      • 空指针异常:拆箱null值会抛出NullPointerException
      • 缓存机制:包装类对部分值有缓存(如integer缓存)
    6.==和equals()的区别
    • ==比较基本类型的值或对象的引用地址
    • equals()比较对象的内容(默认比较地址可重写)
    7.面向对象的三大特性是什么?
    • 封装:隐藏实现细节,提供公共访问方式
    • 继承:子类继承父类特征的行为
    • 多态:同一操作作用于不同对象产生的不同兴义
    8.重载(overload)和重写(override)的区别
    • 重载:同一类中,方法名相同但参数不同(类型、数量、顺序)
    • 重写:子类重新定义父类的方法,方法签名必须相同
    9.接口和抽象类的区别?
    • 接口:完全抽象,java8前只有抽象方法,java8后接口可以有默认方法和静态方法,支持多实现
    • 抽象类:可以有抽象方法和具体方法,单继承
    10.java集合框架主要接口有哪些?
    • Collection:List\Set\Queue
    • Map:Hashmap\Treemap
    11.ArrayList和LinkedList区别?
    • ArrayList:基于动态数组,随机访问快,插入删除慢
    • LinkedList:基于双向链表,插入删除快,随机访问慢

    Q:Arrays.asList数组转换列表的命案?

    • 固定大小列表,不可增加/删除元素
      • 因为返回的Arrays内部的ArrayList(非java.util.ArrayList)
      • 这个内部类没有实现add()和remove()方法
    解决方案
    // 方法1:使用 new ArrayList 包装
    List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));// 方法2:Java 8+ Stream
    List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
    • 数组与列表的共享引用,数组被修改后,list对应的元素值也会被修改
      • Arrays.asList()返回的列表直接“包装”原始数组
      • 两者共享同一数据引用
    解决方案
    // 创建新列表(深拷贝)
    List<String> list = new ArrayList<>(Arrays.asList(arr.clone()));
    • 基本类型数组问题,非包装类的基础类型,会被视为一个整体,即长度永远为1
      • 基本类型数组会被当做单个对象处理
      • Arrays.asList()不支持基本类型的自动装箱
    解决方案
    // 方法1:使用包装类型数组
    Integer[] integerArray = {1, 2, 3};
    List<Integer> list = Arrays.asList(integerArray);// 方法2:Java 8 Stream
    List<Integer> list = Arrays.stream(intArray).boxed().collect(Collectors.toList());
    • 不支持序列化
      • 尝试序列化可能失败,因为内部ArrayList未实现serializable

    解决方案
    // 使用可序列化的ArrayList
    List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    • 视图与原始数据同步
      • 当原始数据放入一个对象变量a,然后在通过asList转换后变量b,只要变量a中的元素变换了,b对应的位置也会改变
    解决方案
    // 如果需要独立副本
    List<String> list = new ArrayList<>(Arrays.asList(arr));
    • 结论:
      • 需要修改列表是:使用new ArrayList<>(Arrays.asList())
      • 处理基本类型数组时:先用Arrays.stream.boxed()转换
      • 需要独立副本时:确保穿件新列表而非直接使用asList结果
      • 需要序列化:避免直接使用asList返回的列表

    Q:ArrayList中的其他问题?

    • ArrayList的subList强转ArrayList导致异常calssCastException
      • subList返回的是ArrayList的内部类SubList并不是ArrayList,而是ArrayList的一个视图,对于Sublist子列表的所有操作最终会反映到原列表上。
      • 解决方案就是重新new一个新的arrayList出来装返回的list
    • ArrayList中的subList切片造成OOM
      • 使用subList切片,因为原List被强引用,得不到回收,造成OOM
      • 解决方案
        • 在subList方法返回SubList,重新使用new ArrayList,来构建一个独立的ArrayList
        • 利用Java8的Stream中的skip和limit来达到切片的目的
        • 切断与原始List的关系
    • ArrayList如果不正确增删操作会导致ConcurrentModificationException

    Q:copyOnWriteArrayList工作原理?

    A:当我们对某个类上面的全局变量List进行读写操作的时候,单线程不会有什么影响,但是多线程当A线程在迭代,B线程在写操作,会导致java.util.ConcurrentModificationException的异常,而CopyOnWriteArrayList就解决了这个问题。

    • 原理:在写操作(add、remove等)时,不直接对原数据进行修改,而是先将原数据复制一份,然后在心腹之的数据上执行写操作,最后将原数据的引用指向新数据。这样做的好处是读操作(get、iterator等)可以不枷锁,因为读取的数据始终是不变的。
    • 优点
      • 线程安全:copyOnWriteArrayList是线程安全的,由于写操作对原数据进行复制,因此写操作不会影响读操作,读操作不加锁,降低了并发冲突的概率。
      • 不会抛出ConcurrentModificationException异常。由于读操作遍历的是不变的数组副本,因此不会抛出ConcurrentModificationException异常
    • 缺点
      • 写操作性能较低:由于每次写操作都要复制一份数据,因此写操作性能较低
        • CopyOnWriteArrayList迭代器是只读的,不支持增删改,以为CopyOnWriteArrayList迭代器中没有remove()和add()方法,没有支持增删而是直接抛出了异常
      • 内存占用增加,由于每一次写操作都需要创建一个新的数据副本,因此内存占用会增加,特别是当集合有大量数据时,内存占用较高
        • CopyOnWriteArrayList每次修改都会重新创建一个大对象,并且原来的大对象也需要回收,这都是会触发GC,如果超过老年代的大小则容易触发FullGC 引发应用程序长时间停顿。
      • 数据一致性问题,由于读操作遍历的是不变的数组副本,因此在对数组执行写操作期间,读操作可能读取到旧的数组数据,这就涉及到数据一致性问题
    • 使用场景
      • 读多写少。
      • 集合不大。
      • 实时性要求不高
    12.HashMap的工作原理
    • 数组+链表/红黑树结果
    • 通过hashCode计算位置,解决冲突使用链表法
    • 当链表长度>8且数组长度>64时转为红黑树

    Q:如何优雅的删除HashMap元素

    • 已知key删除
    // 删除值为2的元素
    map.values().removeIf(value -> value == 2);// 删除key以"b"开头的元素
    map.keySet().removeIf(key -> key.startsWith("b"));
    • 迭代器删除
    //使用Iterator(传统方式
    Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
    while (it.hasNext()) {Map.Entry<String, Integer> entry = it.next();if (entry.getValue() == 1) {it.remove();  // 安全删除当前元素}
    }//使用removeIf(Java 8+更简洁)
    map.entrySet().removeIf(entry -> entry.getValue() == 1);
    • 函数式编程删除
    //使用Stream过滤(创建新Map)
    Map<String, Integer> filteredMap = map.entrySet().stream().filter(entry -> !entry.getKey().equals("a")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));//使用computeIfPresent(条件计算后删除)
    map.computeIfPresent("a", (k, v) -> v == 1 ? null : v);
    // 当返回null时,该键值对会被删除
    • 批量删除
    // 删除多个key
    Set<String> keysToRemove = Set.of("a", "b");
    keysToRemove.forEach(map::remove);
    // 保留特定元素(反向删除)
    Set<String> keysToKeep = Set.of("c", "d");
    map.keySet().retainAll(keysToKeep);  // 只保留指定key的元素
    • 并发安全删除
    // ConcurrentHashMap删除
    ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
    concurrentMap.remove("a");  // 线程安全删除// 原子条件删除
    concurrentMap.remove("a", 1);  // 只有当key"a"对应value为1时才删除
    // 同步块删除
    synchronized(map) {if (map.containsKey("a")) {map.remove("a");}
    }
    • 优雅的删除空值
    // Java 8+ 最简洁方式
    map.values().removeIf(Objects::isNull);// 传统方式
    Iterator<Integer> it = map.values().iterator();
    while (it.hasNext()) {if (it.next() == null) {it.remove();}
    }
    方法适用场景线程安全备注
    remove(key)简单单元素删除不安全最常用基础方法
    removeIf条件删除多个元素不安全Java8+ 简洁语法
    Iterator.remove()迭代时删除不安全传统安全删除方式
    Stream filter创建新Map不修改原Map不安全函数式风格,有性能开销
    ConcurrentHashMap多线程环境安全最佳并发性能

    Q:红黑树原理?

    A:jdk8中,HashMap为了解决哈希冲突导致的性能退化问题,当链表长度超过阈值(默认为8)且数组长度>=64,会将链表转换成红黑树。

    • 优化目的
      • 链表查找时间复杂度O(n)
      • 红黑树查找时间复杂度O(log n)
      • 显著提升高冲突情况下的查询效率
    • 红黑树基本特征
      • 红黑树是一种自平衡的二叉查找树,
      • 节点颜色:每个节点非红即黑
      • 根节点:根节点必须是黑色的
      • 叶子节点:所有叶子节点(NIL节点)都是黑色
      • 红色限制:红色节点的子节点必须是黑色(不能有连续红色节点)
      • 黑高平衡:从任意节点到器每个叶子节点所有的路径包含相同数量的黑色节点
    • 核心操作原理:
      • 插入操作:
        • 按照二叉查找树规则插入新节点(初始为红色)
        • 通过旋转和变色维持红黑树特性
        • 左旋:以某个节点为支点向右选择
        • 右旋:以某个节点为支点向左选择
        • 变色:改变节点颜色
      • 插入后的修复情况:
        • 叔节点为红色:重新着色
        • 叔节点为黑色且新节点是右孩子:左旋父节点
        • 叔节点为黑色且新节点是左孩子:右旋祖父节点并重新着色
      • 删除操作
        • 执行标准的BST删除
        • 如果删除的是黑色节点,需要进行平和修复
        • 兄弟节点为红色:转换情况
        • 兄弟节点为黑色且兄弟的子节点都是黑色:重新着色
        • 兄弟节点为黑色且至少一个红子节点:旋转并重新着色
      • 树化与反树化条件
        • 树化条件(链表->红黑树)
          • 链表长度>=TREEIFY_THRESHOLD(8)
          • 且数组长度>= MIN_TREEIFY_CAPACITY(64)
      • 反树化条件(红黑树->链表)
        • 树节点<=UNTREEIFY_THRESHOLD(6)
      • 红黑树的优势体现:同样是8节点
        • 查找操作时链表最多8次比较,插入O(1),删除O(n)
        • 查找操作时红黑树最多3次比较,插入O(log n),删除O(log n)
      • 红黑树树化是先扩容在树化
        • 设计考量
          • 为何阈值设为8
            • 根据泊松分布,哈希冲突达到8的概率极低(约0.000000006)
            • 正常使用情况下几乎不会转换为红黑树
          • 为什么会退化阈值为6
            • 避免频繁的树化和反树化
            • 为什么需要数组长度>=64才树化
            • 优先通过扩容减少冲突,而不是立刻转为树结构
      • 总结:
        • HashMap的红黑树机制是其高性能的重要保障
        • 在极端哈希冲突情况下自动优化数据结构
        • 保证O(log n)的查询效率
        • 平衡了空间和时间复杂度
        • 智能的在树和链表之间转换

    13.java异常解构体系是怎么样的
    • Throwable:
      • Error:系统错误,不应捕捉
    • Exception:
      • RuntimeException:非受检异常
      • 其他Exception:受检异常
    14.throw和throws的区别
    • throw:在方法内部抛出异常对象
    • throws:声明方法可能抛出的异常类型
    15.创建线程的几种方式
    • 集成Thread类
      • 由于java是单继承,这种方式会占用继承的位置
      • 耦合度高
    • 实现Runnale接口
      • 可以实现多接口,灵活度高
      • 适合多个线程共享相同代码的情况
    • 实现Callable接口(可返回结果)
      • 可以返回执行结果
      • 可以抛出异常
      • 通常配合ExecutorService使用
    • 实现线程池
      • 有效管理线程生命周期
      • 减少线程创建和销毁的开销
      • 提供多种线程池配置选项
    • completableFuture
      • 异步编程,使用并行流
    创建方式返回值异常处理适用场景推荐指数
    继承Thread简单测试★★☆☆☆
    实现Runnable通用场景★★★★★
    实现Callable支持需要返回结果的场景★★★★☆
    线程池可选支持生产环境、高并发场景★★★★★
    CompletableFuture支持异步编程、组合操作★★★★☆

    Q:为何不推荐用Executors创建线程池?

    A:虽然Executors工具类提供了快速创建线程吃的静态工厂方法,但在生产环境中直接使用这些方法可能存在风险。

    • 主要的问题与风险
      • FixedThreadPool和SingleThreadPool的队列风险
        • 问题:使用无界队列(LinkedBlockingQueue)
        • 风险:任务堆积可能导致OOM,eg:当任务提交速度 > 处理速度时,队列无限增长
      • CachedThreadPool的线程数风险
        • 问题:线程数无上限(Integer.MAX_VALUE)
        • 风险:
          • 大量创建线程可能导致系统资源耗尽
          • 极端情况下会引发OOM或者系统崩溃
      • SheduledThreadPool的类似问题
        • 问题:同样适用无界队列(DelayedWorkQueue)
        • 风险:与FixedThreadPool类似的内存问题
    • 推荐做法:
      • 手动创建ThreadPoolExector
        • 参数配置要点:
          • corePoolSize:根据CPU核数(通常Runtime.getRuntim().availableProcessors())
          • maximumPoolSize:根据任务特性设置(IO密集型设置高,CPU密集型不宜过高避免上下文切换)
          • workQueue:必须适用有界队列(ArrayBlockingQueue)
          • keepAliveTime:合理设置线程回收时间
        • 拒绝策略:
          • AbortPolicy(默认):抛出RejectedExecutionException
          • CallerRunsPolicy;由提交任务的线程直接执行
          • DiscardPolicy:静默丢弃任务
          • DiscardOldestPolicy:丢弃队列中最旧的任务
      • 使用第三方线程池工具
        • spring的ThreadPoolTaskExecutor
        • Guava的ThreadPoolBuilder
    16.sleep()和wait()的区别
    • sleep()是Thread方法,不释放锁
    • wait()是Object的方法,释放锁,需在同步块中使用。
    17.synchronized和ReentrantLock区别
    • synchronized是关键字,JVM实现;ReentrantLock是类
    • ReenTrantLock功能更丰富:可以中断,支持公平锁、多条件变量等
    18.BIO/NIO/AIO区别
    • BIO:同步阻塞IO,一个连接只能处理一个线程
    • NIO:同步非阻塞IO,多路复用
    • AIO:异步非阻塞IO,基于回调,IO处理完成后通知系统回调
    19.java NIO的核心组件有哪些
    • Channel数据通道
    • Buffer:数据缓冲区
    • Selector:多路复用器
    20.Java8的主要特性
    • Lambda表达式
    • Stream API
    • 方法引用
    • 默认方法
    • Optional类
    • 新的日期时间API
    21.Lambda表达式的使用场景
    • 函数式接口(只有一个抽象方法的接口)
    • 替代匿名内部类
    • 集合操作
    22.Stream API的特点是什么?
    • 函数式风格处理数据
    • 惰性求值
    • 可并行处理
    • 不修改原数据
    23.列举几种常见的设计模式以及应用场景
    • 单例模式:全局唯一实例(如配置类)
    • 工厂模式:对象创建解耦
    • 观察者模式:事件处理系统
    • 装饰器模式:IO流设计
    24.java如何实现线程安全的单例模式

    可以使用双重检锁定来确定,定义静态实例对象,获取实例对象时,判断实例对象是否为空,若为空通过synchronized锁住该对象,然后再次判断对象是否为空,若为空则创建,否则直接返回

    // 双重检查锁定
    public class Singleton {private volatile static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
    }
    25.java内存模型(JMM)是什么
    • 定义线程如何与内存交互
    • 主内存与工作内存的概念
    • happens-before原则保证可见性
    26.volatile关键字的作用
    • 保证可见性:修改立即对该线程可见
    • 禁止指令冲排序
    • 不保证原子性
    27.final关键字和finally的区别
    • final:
      • 修饰类:不可以集成
      • 修饰方法:不能重写
      • 修饰变量:基本类型值,引用类型引用不可变
    • finally:
      • 一般和try catch一起使用,保证try catch无论走哪个方法都可以进finally进行处理。
    28.如何优雅的避免空指针
    • 使用if else判断,避免出现空指针
    • 使用工具类美化一下if判断如StringUtils.isEmpty/OjbectUtils.isEmpty/Collections.emptyXxx
    • 使用optional解决层次多的问题,当配和orElse时,会优先执行orElse方法,然后执行逻辑代码
    • 使用断言处理接口入参,检查假设和前置条件是否满足,检查空值情况,提前捕获空指针
    • 使用@Nullable/@NotNull注解,标识变量或者方法参数和返回值是否为null,在编译器或者开发工具提示风险

    29.String能存储多少个字符

    • String的length方法返回的是int,理论上长度不会超过int的最大值
    • 编译器源码限制了字符串长度大于等于65535就编译不通过
    • JVM规范对常量池有所限制。量池中的每一种数据项都有自己的类型
    • 运行时限制,在运行时限制主要体现在构造函数上

    Q:JDK9为何要讲String的底层实现由char[]改成byte[]

    A:为了节省字符串暂用的内存,减少GC的次数

    • byte[]更节省空间,从char[]到byte[]中文是两个字节,纯应为是一个字节,在此之前中文是两个字节,英文也是两个字节
    29.如何在有限内存下读取超限的数据并统计数字重复此书,获取最大的重复数
    • 分块读取使用Map来进行统计
      • 这种适合处理数据均匀分布,不存在大量唯一值数据,如果文件高度稀疏,大量的唯一值,会导致Map爆内存出现OOM;
    • 外部排序法
      • 将文件分块读取,在将读取的数据hash到不同的文件中,然后在完成对应的统计。
      • MMAP映射不进行分片,逐个处理,但这种可能会出现统计Map爆内存OOM。
    • 概率统计法(近似算法)
      • 使用布隆过滤器等概率数据结构,适合允许近似结果的场景
    • 数据库辅助法
      • 将数据分批导入数据库,用SQL统计
        • 内存优化技巧
          • 使用原始类型集合:Trove库的TintIntHashMap
          • 压缩存储:对数子进行差值编码等压缩
          • 位图法:如果数据范围有限,可用BitSet统计

    版权声明:

    本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

    我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

    热搜词