I'm spiderman I'm spiderman
首页
  • 中间件
  • 基础架构
  • 微服务
  • 云原生
  • Java
  • Go
  • PHP
  • Python
  • 计算机网络
  • 操作系统
  • 数据结构
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档

spiderman

快乐学习,快乐编程
首页
  • 中间件
  • 基础架构
  • 微服务
  • 云原生
  • Java
  • Go
  • PHP
  • Python
  • 计算机网络
  • 操作系统
  • 数据结构
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
  • Java

    • Java基本原理
      • JVM原理
      • 线程
      • Spring原理
      • Mybatis和JPA
      • JVM调优
    • Golang

    • PHP

    • Python

    • 计算机语言
    • Java
    spiderman
    2022-07-21
    目录

    Java基本原理

    # Java基本理论

    # hashCode()与equals()之间的关系

    在Java中,每个对象都可以调⽤⾃⼰的hashCode()⽅法得到⾃⼰的哈希值(hashCode),相当于对象的 指纹信息,通常来说世界上没有完全相同的两个指纹,但是在Java中做不到这么绝对,但是我们仍然可 以利⽤hashCode来做⼀些提前的判断,⽐如:

    • 如果两个对象的hashCode不相同,那么这两个对象肯定不同的两个对象
    • 如果两个对象的hashCode相同,不代表这两个对象⼀定是同⼀个对象,也可能是两个对象
    • 如果两个对象相等,那么他们的hashCode就⼀定相同

    在Java的⼀些集合类的实现中,在⽐较两个对象是否相等时,会根据上⾯的原则,会先调⽤对象的 hashCode()⽅法得到hashCode进⾏⽐较,如果hashCode不相同,就可以直接认为这两个对象不相 同,如果hashCode相同,那么就会进⼀步调⽤equals()⽅法进⾏⽐较。⽽equals()⽅法,就是⽤来最终 确定两个对象是不是相等的,通常equals⽅法的实现会⽐较重,逻辑⽐较多,⽽hashCode()主要就是得 到⼀个哈希值,实际上就⼀个数字,相对⽽⾔⽐较轻,所以在⽐较两个对象时,通常都会先根据 hashCode想⽐较⼀下。

    所以我们就需要注意,如果我们重写了equals()⽅法,那么就要注意hashCode()⽅法,⼀定要保证能遵 守上述规则。

    # String、StringBuffer、StringBuilder的区别

    1. String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是可 变的
    2. StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效 率会更⾼

    # 泛型中extends和super的区别

    1. <? extends T>表示包括T在内的任何T的⼦类
    2. <? super T>表示包括T在内的任何T的⽗类

    # ==和equals⽅法的区别

    • ==:如果是基本数据类型,⽐较是值,如果是引⽤类型,⽐较的是引⽤地址
    • equals:具体看各个类重写equals⽅法之后的⽐较逻辑,⽐如String类,虽然是引⽤类型,但是 String类中重写了equals⽅法,⽅法内部⽐较的是字符串中的各个字符是否全部相等。

    # 重载和重写的区别

    • 重载(Overload): 在⼀个类中,同名的⽅法如果有不同的参数列表(⽐如参数类型不同、参数个数 不同)则视为重载。
    • 重写(Override): 从字⾯上看,重写就是 重新写⼀遍的意思。其实就是在⼦类中把⽗类本身有的⽅ 法重新写⼀遍。⼦类继承了⽗类的⽅法,但有时⼦类并不想原封不动的继承⽗类中的某个⽅法,所 以在⽅法名,参数列表,返回类型都相同(⼦类中⽅法的返回值可以是⽗类中⽅法返回值的⼦类)的 情况下, 对⽅法体进⾏修改,这就是重写。但要注意⼦类⽅法的访问修饰权限不能⼩于⽗类的。

    # List和Set的区别

    • List:有序,按对象插⼊的顺序保存对象,可重复,允许多个Null元素对象,可以使⽤Iterator取出 所有元素,在逐⼀遍历,还可以使⽤get(int index)获取指定下标的元素
    • Set:⽆序,不可重复,最多允许有⼀个Null元素对象,取元素时只能⽤Iterator接⼝取得所有元 素,在逐⼀遍历各个元素

    # ArrayList和LinkedList区别

    1. ⾸先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实 现的
    2. 由于底层数据结构不同,他们所适⽤的场景也不同,ArrayList更适合随机查找,LinkedList更适合 删除和添加,查询、添加、删除的时间复杂度不同
    3. 另外ArrayList和LinkedList都实现了List接⼝,但是LinkedList还额外实现了Deque接⼝,所以 LinkedList还可以当做队列来使⽤

    # ConcurrentHashMap的扩容机制

    • 1.7版本
      1. 1.7版本的ConcurrentHashMap是基于Segment分段实现的
      2. 每个Segment相对于⼀个⼩型的HashMap
      3. 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
      4. 先⽣成新的数组,然后转移元素到新数组中
      5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
    • 1.8版本
      1. 1.8版本的ConcurrentHashMap不再基于Segment实现
      2. 当某个线程进⾏put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进⾏扩容
      3. 如果某个线程put时,发现没有正在进⾏扩容,则将key-value添加到ConcurrentHashMap中,然 后判断是否超过阈值,超过了则进⾏扩容
      4. ConcurrentHashMap是⽀持多个线程同时扩容的
      5. 扩容之前也先⽣成⼀个新的数组
      6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或 多组的元素转移⼯作

    # Jdk1.7到Jdk1.8 HashMap 底层发⽣了什么变化

    1. 1.7中底层是数组+链表,1.8中底层是数组+链表+红⿊树,加红⿊树的⽬的是提⾼HashMap插⼊和 查询整体效率
    2. 1.7中链表插⼊使⽤的是头插法,1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要 判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法
    3. 1.7中哈希算法⽐较复杂,存在各种右移与异或运算,1.8中进⾏了简化,因为复杂的哈希算法的⽬的 就是提⾼散列性,来提供HashMap的整体效率,⽽1.8中新增了红⿊树,所以可以适当的简化哈希 算法,节省CPU资源

    # HashMap的Put⽅法

    HashMap的Put⽅法的⼤体流程:

    1. 根据Key通过哈希算法与与运算得出数组下标
    2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中 是Node对象)并放⼊该位置
    3. 如果数组下标位置元素不为空,则要分情况讨论
      • 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对 象,并使⽤头插法添加到当前位置的链表中
      • 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node ⅰ. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个 过程中会判断红⿊树中是否存在当前key,如果存在则更新value
      • 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插 法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会 判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链 表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成 红⿊树
      • 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要 就扩容,如果不需要就结束PUT⽅法

    # 深拷贝和浅拷贝

    深拷⻉和浅拷⻉就是指对象的拷⻉,⼀个对象中存在两种类型的属性,⼀种是基本数据类型,⼀种是实 例对象的引⽤。

    1. 浅拷⻉是指,只会拷⻉基本数据类型的值,以及实例对象的引⽤地址,并不会复制⼀份引⽤地址所 指向的对象,也就是浅拷⻉出来的对象,内部的类属性指向的是同⼀个对象
    2. 深拷⻉是指,既会拷⻉基本数据类型的值,也会针对实例对象的引⽤地址所指向的对象进⾏复制, 深拷⻉出来的对象,内部的属性指向的不是同⼀个对象

    # HashMap的扩容机制原理

    • 1.7版本

      1. 先⽣成新数组
      2. 遍历⽼数组中的每个位置上的链表上的每个元素
      3. 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标
      4. 将元素添加到新数组中去
      5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
    • 1.8版本

      1. 先⽣成新数组
      2. 遍历⽼数组中的每个位置上的链表或红⿊树
      3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
      4. 如果是红⿊树,则先遍历红⿊树,先计算出红⿊树中每个元素对应在新数组中的下标位置 a. 统计每个下标位置的元素个数 b. 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对应 位置 c. 如果该位置下的元素个数没有超过8,那么则⽣成⼀个链表,并将链表的头节点添加到新数组的 对应位置
      5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

    # CopyOnWriteArrayList的底层原理

    1. ⾸先CopyOnWriteArrayList内部也是⽤过数组来实现的,在向CopyOnWriteArrayList添加元素 时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上进⾏
    2. 并且,写操作会加锁,防⽌出现并发写⼊丢失数据的问题
    3. 写操作结束之后会把原数组指向新数组
    4. CopyOnWriteArrayList允许在写操作时来读取数据,⼤⼤提⾼了读的性能,因此适合读多写少的应 ⽤场景,但是CopyOnWriteArrayList会⽐较占内存,同时可能读到的数据不是实时最新的数据,所 以不适合实时性要求很⾼的场景

    # 什么是字节码?采⽤字节码的好处是什么?

    编译器(javac)将Java源⽂件(.java)⽂件编译成为字节码⽂件(.class),可以做到⼀次编译到处运⾏, windows上编译好的class⽂件,可以直接在linux上运⾏,通过这种⽅式做到跨平台,不过Java的跨平 台有⼀个前提条件,就是不同的操作系统上安装的JDK或JRE是不⼀样的,虽然字节码是通⽤的,但是 需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各⾃ 的JDK或JRE。 采⽤字节码的好处,⼀⽅⾯实现了跨平台,另外⼀⽅⾯也提⾼了代码执⾏的性能,编译器在编译源代码 时可以做⼀些编译期的优化,⽐如锁消除、标量替换、⽅法内联等。

    # Java中的异常体系

    • Java中的所有异常都来⾃顶级⽗类Throwable。
    • Throwable下有两个⼦类Exception和Error。
    • Error表示⾮常严重的错误,⽐如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError, 通常这些错误出现时,仅仅想靠程序⾃⼰是解决不了的,可能是虚拟机、磁盘、操作系统层⾯出现 的问题了,所以通常也不建议在代码中去捕获这些Error,因为捕获的意义不⼤,因为程序可能已经 根本运⾏不了了。
    • Exception表示异常,表示程序出现Exception时,是可以靠程序⾃⼰来解决的,⽐如 NullPointerException、IllegalAccessException等,我们可以捕获这些异常来做特殊处理。
    • Exception的⼦类通常⼜可以分为RuntimeException和⾮RuntimeException两类
    • RunTimeException表示运⾏期异常,表示这个异常是在代码运⾏过程中抛出的,这些异常是⾮检查 异常,程序中可以选择捕获处理,也可以不处理。这些异常⼀般是由程序逻辑错误引起的,程序应该从逻 辑⻆度尽可能避免这类异常的发⽣,⽐如NullPointerException、IndexOutOfBoundsException等。
    • ⾮RuntimeException表示⾮运⾏期异常,也就是我们常说的检查异常,是必须进⾏处理的异常,如果 不处理,程序就不能检查异常通过。如IOException、SQLException等以及⽤户⾃定义的Exception异 常。

    # 在Java的异常处理机制中,什么时候应该抛出异常,什么时候捕 获异常

    异常相当于⼀种提示,如果我们抛出异常,就相当于告诉上层⽅法,我抛了⼀个异常,我处理不了这个 异常,交给你来处理,⽽对于上层⽅法来说,它也需要决定⾃⼰能不能处理这个异常,是否也需要交给 它的上层。 所以我们在写⼀个⽅法时,我们需要考虑的就是,本⽅法能否合理的处理该异常,如果处理不了就继续 向上抛出异常,包括本⽅法中在调⽤另外⼀个⽅法时,发现出现了异常,如果这个异常应该由⾃⼰来处 理,那就捕获该异常并进⾏处理。

    # Java中有哪些类加载器

    JDK⾃带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。

    • BootStrapClassLoader是ExtClassLoader的⽗类加载器,默认负责加载%JAVA_HOME%lib下的 jar包和class⽂件。
    • ExtClassLoader是AppClassLoader的⽗类加载器,负责加载%JAVA_HOME%/lib/ext⽂件夹下的 jar包和class类。
    • AppClassLoader是⾃定义类加载器的⽗类,负责加载classpath下的类⽂件。

    # 类加载器双亲委派模型

    JVM中存在三个默认的类加载器:

    1. BootstrapClassLoader
    2. ExtClassLoader
    3. AppClassLoader AppClassLoader的⽗加载器是ExtClassLoader,ExtClassLoader的⽗加载器是 BootstrapClassLoader。 JVM在加载⼀个类时,会调⽤AppClassLoader的loadClass⽅法来加载这个类,不过在这个⽅法中,会 先使⽤ExtClassLoader的loadClass⽅法来加载类,同样ExtClassLoader的loadClass⽅法中会先使⽤ BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果 BootstrapClassLoader没有加载到,那么ExtClassLoader就会⾃⼰尝试加载该类,如果没有加载到, 那么则会由AppClassLoader来加载这个类。 所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进⾏加载,如果没加载到才由⾃⼰ 进⾏加载。
    #Java
    JVM原理

    JVM原理→

    最近更新
    01
    innovation create future
    12-13
    02
    RabbitMQ
    12-06
    03
    StarRocks的应用
    09-11
    更多文章>
    Theme by Vdoing | Copyright © 2022-2024 spiderman | 粤ICP备2023019992号-1 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式