当前位置: 首页 > article >正文

Java面试题-Java基础

文章目录

    • 1.源码
        • 1.ArrayList
          • 1.ArrayList的扩容机制
          • 2.ArrayList和LinkedList的区别是什么?
          • 3.如何实现数组和List之间的转换?
        • 2.HashMap
          • 1.HashMap的put方法流程
          • 2.HashMap的扩容机制
          • 3.HashMap在1.7的情况下多线程死循环的情况
          • 4.**jdk7的ConcurrentHashMap实现?**
          • 5.**说一下java8实现的concurrentHashMap?**
        • 3.ThreadLocal
          • 1.核心数据结构 ThreadLocalMap
          • 2.ThreadLocal的set方法流程
          • 3.ThreadLocalMap过期 key 的探测式清理流程
    • 2.泛型
        • 1.什么是类型擦除?
        • 2.泛型擦除会导致什么问题呢?
          • 1.运行时无法获取类型信息
          • 2.重载冲突
          • 3.不能使用基本类型
        • 3.那么怎么解决泛型擦除的局限?
          • 1.`Class<T>` 类型
          • 2.Guava 的 `TypeToken`
        • 4.有了解过泛型的通配符吗?
          • 1.代码实例
          • 2.小结
    • 3.反射
        • 1.什么是反射机制?
        • 2.获取反射对象的四种方式?
        • 3.反射的基本步骤是什么?
          • 1.基本步骤说明
          • 2.代码演示

1.源码

1.ArrayList
1.ArrayList的扩容机制
  • ArrayList的初始容量为0,第一次添加数据时,初始化容量为10
  • 在这里我说一下添加第11个元素的全流程
  • 首先会确保size + 1不超过目前的容量,此时是11,肯定超过了10,则需要扩容
  • 新容量计算
    • 首先新容量为原来的1.5倍
    • 如果新容量比最小容量要小,就让新容量为最小容量
    • 如果新容量比Integer.MAX_VALUE - 8要大,就调用hugeCapacity
    • 如果发现最小容量是负数,就说明超过了Integer的最大值,则直接抛出异常,如果是正数就返回Integer.MAX_VALUE
  • 计算完成后将新元素添加到size的位置上,返回成功的布尔值
2.ArrayList和LinkedList的区别是什么?

底层数据结构:ArrayList是基于数组的,LinkedList是基于链表的

操作数据的效率

  • 查询ArrayList是O1,LinkedList是On
  • 增删ArrayList是On,LinkedList是O1

线程安全:都不是线程安全的可以使用Collections.synchronizedList()来生成线程安全的List

3.如何实现数组和List之间的转换?

数组转List,使用Arrays.asList(),将Arrays的内部类ArrayList的一个属性,a数组,直接指向了原数组。

List转数组,使用list.toArray(),底层调用copyOf()方法,创建了新数组并进行了数组的拷贝。

2.HashMap
1.HashMap的put方法流程
  • put方法会先将key进行hash,hash的算法就是hashcode 跟 hashcode右移16位做亦或运算
  • 然后调用putVal方法
  • 如果table数组为空,就进行扩容(扩容机制)
  • 接下来使用(n - 1)& hash去计算桶的位置,如果桶的位置没有元素,就直接new一个node放进去
  • 如果有hash冲突
    • 对于桶内的第一个元素,如果key相同(这里的相同是要hash相同并且key也要相同),则替换value
    • 如果是红黑树,就走红黑树的添加逻辑
    • 如果不是红黑树,就开始遍历链表,期间如果发现key相同,则替换value,如果没有发现,则最终添加新节点到链表的末尾
    • 当然,插入节点之后,可能会有树化的操作,就是当桶的个数大于等于64,桶内元素的个数大于等于8,进行树化
    • 最后,当++size大于阈值(数组长度 * 0.75),就会进行扩容
2.HashMap的扩容机制
  • 在添加元素或者初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次扩容都是达到了阈值(数组长度 * 0.75)
  • 如果旧容量大于等于 1 << 30 则将阈值设置为Integer.MAX_VALUE表示不会再扩容,否则就是两倍扩容
  • 扩容之后,会新创建一个数组,把老数组的数据移动到新数组中
    • 对于没有hash冲突的节点,则直接使用e.hash & (newCap - 1)计算新数组的索引位置
    • 如果是红黑树,走红黑树的添加逻辑
    • 如果是链表则需要遍历链表,可能需要拆分链表,判断e.hash & oldCap是否为0,如果为0就停留在原始位置,否则就移动到原位置 + oldCap这个位置上
3.HashMap在1.7的情况下多线程死循环的情况

CleanShot 2024-08-16 at 09.11.07@2x

4.jdk7的ConcurrentHashMap实现?

CleanShot 2024-08-18 at 15.31.13@2x

5.说一下java8实现的concurrentHashMap?

CleanShot 2024-08-18 at 15.33.09@2x

CleanShot 2024-08-23 at 11.42.43@2x

3.ThreadLocal
1.核心数据结构 ThreadLocalMap

有个静态内部类Entry,内部的key是弱引用ThreadLocal类型的,value是Object类型的

真正存储数据的是一个属性Entry类型的table数组

2.ThreadLocal的set方法流程
  • 首先获取当前的线程,然后获取当前线程的ThreadLocalMap
  • 如果map不为空,就调用map.set方法将当前的ThreadLocal作为key,ThreadLocal存的值作为value设置到map中
  • 先使用(n - 1)& hash(nextHashCode()生成唯一hash值)来找到当前的ThreadLocal在Entry数组中的位置
  • 只要位置中的元素不为空就进行循环,期间如果遇到key相同的就替换value,最后找到一个空的位置将Entry对象设置进去
  • 最后如果清理过期槽位失败并且元素数量大于等于阈值(数组长度 * 2/3)就进行rehash逻辑
  • rehash会先进行进行探测式清理工作,如果清理后size >= 阈值 * 3/4就进行扩容
  • 扩容的话就是两倍扩容,创建一个新的Entry数组,遍历旧的数组,将旧数组中的元素重新hash后放到新数组中,如果哈希冲突就往后放
3.ThreadLocalMap过期 key 的探测式清理流程

遍历散列数组,从开始位置向后探测清理过期数据,将过期数据的Entry设置为null

沿途中碰到未过期的数据则将此数据rehash后重新在table数组中定位,如果定位的位置已经有了数据,则会将未过期的数据放到最靠近此位置的Entry=null的桶中,使rehash后的Entry数据距离正确的桶的位置更近一些。

2.泛型

1.什么是类型擦除?

就是Java为了向后兼容没有泛型的版本,所以在编译时会将泛型擦除,然后泛型擦除有两种情况,一种是有上界的,一种是没有上界的,有上界的就会将泛型转换为上界类型,没有上界的就会转换为Object类型

2.泛型擦除会导致什么问题呢?
1.运行时无法获取类型信息
List<String> list = new ArrayList<>();
System.out.println(list.getClass()); // class java.util.ArrayList
// 无法区分 list 是 List<String> 还是 List<Integer>
2.重载冲突

由于泛型擦除,以下代码会报错:

class Test {
    public void method(List<String> list) {}
    public void method(List<Integer> list) {} // 编译错误
}
3.不能使用基本类型

泛型不支持基本类型(如 int),因为擦除时只能用引用类型。需要使用包装类型(如 Integer):

List<int> list = new ArrayList<>(); // 编译错误
List<Integer> list = new ArrayList<>();
3.那么怎么解决泛型擦除的局限?
1.Class<T> 类型

通过将类型信息传递到运行时:

public <T> T createInstance(Class<T> clazz) throws Exception {
    return clazz.getDeclaredConstructor().newInstance();
}
2.Guava 的 TypeToken

Guava 提供了 TypeToken,可以保留泛型的实际类型参数信息:

import com.google.common.reflect.TypeToken;

public class Main {
    public static void main(String[] args) {
        TypeToken<List<String>> typeToken = new TypeToken<List<String>>() {};
        System.out.println(typeToken.getType()); // 输出:java.util.List<java.lang.String>
    }
}
4.有了解过泛型的通配符吗?
1.代码实例
package com.sunxiansheng;

import java.util.ArrayList;
import java.util.List;

/**
 * Description: 泛型通配符示例
 *
 * @Author sun
 * @Create 2024/7/22 20:07
 * @Version 1.1
 */
public class Test {

    static class Father {
    }

    static class Son extends Father {
    }

    public static void main(String[] args) {
        List<Son> sonList = new ArrayList<>();
        // 添加元素
        sonList.add(new Son());
        test(sonList);
        test1(sonList);
        test2(sonList);
        test3(sonList);
    }

    /**
     * 无界通配符,允许读取元素,但不能写入元素(除了null)。
     *
     * @param list
     */
    public static void test(List<?> list) {
        // 读取元素,只能读取为 Object 类型
        for (Object o : list) {
            System.out.println(o);
        }
        // 尝试写入元素
        // list.add("world"); // 编译错误:无法确定类型,所以不能写入任何元素
    }

    /**
     * 上界通配符,类型为 Father 或其子类。可以读取元素,但不能写入新元素(除了null)。
     *
     * @param list
     */
    public static void test1(List<? extends Father> list) {
        // 读取元素
        for (Father f : list) {
            System.out.println(f);
        }
        // 尝试写入元素
        // list.add(new Son()); // 编译错误:不能确定 list 的具体类型,因此无法安全地写入
    }

    /**
     * 下界通配符,类型为 Son 或其父类。可以写入 Son 类型或其子类的元素,但读取时只能读取为 Object。
     *
     * @param list
     */
    public static void test2(List<? super Son> list) {
        // 读取元素
        for (Object o : list) {
            System.out.println(o); // 只能读取为 Object 类型
        }
        // 写入元素
        list.add(new Son()); // 可以安全地写入 Son 或其子类的元素
        // list.add(new Father()); // 编译错误:无法确定 Father 是否是 Son 的超类型
    }

    /**
     * 非泛型的 List,可以读写任意类型的元素。
     *
     * @param list
     */
    public static void test3(List list) {
        // 读取元素
        for (Object o : list) {
            System.out.println(o);
        }
        // 写入元素
        list.add("world"); // 可以写入任意类型的元素
        // 再次读取
        for (Object o : list) {
            System.out.println(o);
        }
    }

}
2.小结

上界通配符:?extend Father 可读不可写,可读Father,不可写(写null就不算了)

下界通配符:? super Son 可读可写,可读Object,可写Son以及Son的子类

3.反射

1.什么是反射机制?

反射是一种通过 Class 对象在运行时动态获取类的详细信息并对其进行操作的机制。

2.获取反射对象的四种方式?
  • 类名.class
  • 对象.getClass()
  • Class.forName(“全类名”)
  • ClassLoader.loadClass(“全类名”)
3.反射的基本步骤是什么?
1.基本步骤说明
  1. 获取 Class 对象:首先,通过类的全限定名、对象的 getClass() 方法、或 ClassName.class 来获取该类的 Class 对象。
  2. 获取类信息:通过 Class 对象,使用 getMethods()、getFields()、getConstructors() 等方法来获取类的构造函数、方法、字段等信息。
  3. 反射爆破:如果需要访问私有成员(如私有字段或方法),可以使用 setAccessible(true) 来绕过 Java 的访问控制检查。
  4. 进行操作:在获取了所需的类信息并修改了访问权限后,通过 invoke 方法调用方法,或通过 get/set 方法读取或修改字段的值,或者通过 newInstance 创建类的实例。
2.代码演示
package com.sunxiansheng;

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) {
        // 1、获取Class对象
        Class<Person> personClass = Person.class;
        // 2、获取类信息
        Method[] methods = personClass.getDeclaredMethods();
        for (Method method : methods) {
            String name = method.getName();
            if ("say".equals(name)) {
                // 3、反射爆破
                method.setAccessible(true);
                // 4、调用方法
                try {
                    method.invoke(personClass.newInstance());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Person {
    private String name = "sunxiansheng";
    private int age = 1;

    /**
     * 私有方法
     */
    private void say() {
        System.out.println("My name is " + name + " and age is " + age);
    }
}

CleanShot 2025-01-15 at 14.47.43@2x


http://www.kler.cn/a/536023.html

相关文章:

  • 深度学习里面的而优化函数 Adam,SGD,动量法,AdaGrad 等 | PyTorch 深度学习实战
  • 基于ansible部署elk集群
  • openssl 中 EVP_aes_256_gcm() 函数展开
  • “AI隐患识别系统,安全多了道“智能护盾”
  • 8.攻防世界Web_php_wrong_nginx_config
  • 2023年java面试问题大全及答案大全
  • 基础入门-算法解密散列对称非对称字典碰撞前后端逆向MD5AESDESRSA
  • C++:代码常见规范1
  • 七。自定义数据集 使用tensorflow框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测
  • Mac: docker安装以后报错Command not found: docker
  • ctf网络安全大赛python ctf网络安全大赛
  • 本文主要详细讲解ArcGIS中的线、多线段和多边形的结构关系。
  • Kafka 可靠性探究—副本刨析
  • 关于maven的java面试题汇总
  • 1 Java 基础面试题(上)
  • 物联网实训室解决方案(2025年最新版)
  • BUU26 [极客大挑战 2019]HardSQL1
  • Electron学习笔记,用node程序备份数据库(2)
  • Github 2025-02-07Java开源项目日报 Top9
  • 二叉树实现(学习记录)
  • 神经辐射场(NeRF):从2D图像到3D场景的革命性重建
  • Java面试题——事务
  • 【论文翻译】DeepSeek-V3论文翻译——DeepSeek-V3 Technical Report——第一部分:引言与模型架构
  • windows10环境下的Deepseek本地部署及接口调用
  • 网络安全威胁框架与入侵分析模型概述
  • 【PostgreSQL内核学习 —— (WindowAgg(三))】