知识点总结--02(java基础部分)

目录

1重载与重写的区别?

[2== 与 equals 的区别?](#2== 与 equals 的区别?)

[3String,StringBuilder 和 StringBuffer 的区别?](#3String,StringBuilder 和 StringBuffer 的区别?)

[4 说说 Java 中的异常?](#4 说说 Java 中的异常?)

5你知道的数据结构有哪些?

[6说说 java 中常见的集合类?](#6说说 java 中常见的集合类?)

[7HashMap 原理(数据结构)?](#7HashMap 原理(数据结构)?)

[8HashMap 原理(扩容)?](#8HashMap 原理(扩容)?)

[9HashMap 原理(方法执行流程)?](#9HashMap 原理(方法执行流程)?)

[10说说 BIO、NIO、AIO?](#10说说 BIO、NIO、AIO?)

[11 IO流?](#11 IO流?)

[12ThreadLocal 的原理?](#12ThreadLocal 的原理?)

13解释悲观锁与乐观锁?

[14synchronized 原理?](#14synchronized 原理?)

[15【追问】 synchronized 锁升级?](#15【追问】 synchronized 锁升级?)

[16对比 synchronized 和 volatile?](#16对比 synchronized 和 volatile?)

[17对比 synchronized 和 Lock?](#17对比 synchronized 和 Lock?)

18线程池的核心参数?

[19JVM 堆内存结构?](#19JVM 堆内存结构?)

20垃圾回收算法?

21【追问】伊甸园、幸存区、老年代细节?

22Lambda表达式?

23什么是反射?

24什么是泛型?

25Tomcat优化?

尾页


1重载与重写的区别?

重载是对象的方法之间,它们方法名相同,但方法的参数列表不同

重写是父子类(包括接口与实现类)中两个同名方法,它们方法名相同,且方法的参数列表相同

重载是在编译阶段,由编译器根据传递给方法的参数列表来区分方法

重写是在运行阶段,由虚拟机的解释器来获取引用对象的实际类型,根据类型判断是哪个方法

  • 语法细节,问了再说,不问不必说

    • 重写时,子类方法的访问修饰符要 >= 父类方法的访问修饰符

    • 重写时,子类方法抛出的检查异常类型要 <= 父类方法抛出的检查异常类型,或子类不抛异常

    • 重写时,父子类的方法的返回值类型要一样,或子类方法返回值是父类方法返回值的子类

2== 与 equals 的区别?

对于基本数据类型来说,==判断两边值是否相同

对于引用数据类型来说,==判断两边的地址是否相同,即是否引用同一个对象

equals要看实现

对与Objects.equals();来说,判断是否是引用同一个对象

  • 比如 String,它的内部实现就是去比较两个字符串中每个字符是否相同,比较的是内容

  • 比如 ArrayList,它的内部实现就是去比较两个集合中每个元素是否 equals,比较的也是内容

3String,StringBuilder 和 StringBuffer 的区别?

他们都表示字符串对象

String表示的字符串对象是不可变的,后两者表示字符串的内容是可变的

String和StringBuffer线程是安全的,StringBuilder 不是线程安全的

适用场景:

大部分场景下,都是使用String

多线程下,建议使用String和StringBuffer

单线程的话可以使用StringBuilder

在需要大量字符串拼接的场景下,StringBuilder 和 StringBuffer比较合适

4 说说 Java 中的异常?

Throwable是所有异常的顶层父类

它有Error和Exception两个子类,其中Error表示不可恢复的异常,即使捕获也无法使程序正常运行

Exception表示可恢复异常,处理方式有两种:

1是自己处理,使用catch进行操作

2是使用throw将异常抛给上一层调用者

Exception还有一个子类RuntimeException,二者不同之处在于

Exception是检查异常,必须对异常进行处理

RuntimeException是非检查异常,在语法层面对这类异常并不要求强制处理

5你知道的数据结构有哪些?

数据结构分为线性结构和非线性结构

线性结构中有

动态数组:相较于普通数组可以自动扩容

java中的ArrayList属于动态数组

它的特点是元素是连续存储的

链表由多个节点连接在一起

java中的LinkedList属于链表

它的元素是不连续存储的,需要根据当前节点找到下一个节点

:符合先进后出的原则

java中的LinkedList可以充当栈

其中push()方法往栈顶添加元素

pop()方法从栈顶移除元素

peek()方法从栈顶获取元素

**队列:**符合先进先出的原则

java中的LinkedList可以充当队列

其中offer()方法可以在队头添加元素

poll()方法可以从队尾移除元素

非线性结构中有:

**优先级队列:**在队列的基础上加了优先级,可以根据优先级调整元素的顺序,保证 优先级高的元素先出队

java中的PriorityQueue可以作为优先级队列

它的底层是用大根堆/小根堆实现的

适合用于排行榜,任务调度等编码

**Hash表:**由多个key-value组成,根据key的hash码将value分散存储数组中,其中key的hash码与数组的索引相对应

java中的hashMap,hashtable都属于Hash表

可以用于快速查找数据

红黑树:可以自平衡的二叉查找树

java中的TreeMap属于红黑树

**跳表:**多级链表结构,也能达到与红黑树同级的性能,且实现更为简单

java 中的 ConcurrentSkipListMap 用跳表结构实现

redis 中的 SortedSet 也是用跳表实现

**B+ 树:**可以自平衡的 N 叉查找树

关系型数据库的索引常用 B+ 树实现

6说说 java 中常见的集合类?

接口有四个:Collection,set,list,map

collection是父接口,list和set是其子接口

map调用entryset(),keyset()时会创建set的实现

map调用values()时,会用到collection的实现

list实现(常见3个)

arraylist:基于数组实现

随机访问性能高,但是增删性由于要移动元素性能不高

  • 【进阶】但如果增、删操作的是数组尾部不牵涉移动元素

linkedlist:基于链表实现:随机访问性能低,但增删性能高

  • 【进阶】说它随机访问性能低是相对的,如果是头尾节点,无论增删改查都快

  • 【进阶】说它增删性能高也是有前提的,并没有包含定位到该节点的时间,把这个算上,增删性能并不高

vector:基于数组实现:相较与前两个线程安全

  • 【进阶】一些说法说 Vector 已经被舍弃,这是不正确的

set实现

hashset内部组合了hashmap,利用map key唯一的特点来实现set

  • 集合中元素唯一,注意需要为元素实现 hashCode 和 equals 方法

  • 【进阶】Set 的特性只有元素唯一,有些人说 Set 无序,这得看实现,例如 HashSet 无序,但TreeSet 有序

map实现(5个)

  • HashMap 底层是 Hash 表,即数组 + 链表,链表过长时会优化为红黑树

    • 集合中 Key 要唯一,并且它需要实现 hashCode 和 equals 方法
  • LinkedHashMap 基于 HashMap,只是在它基础上增加了一个链表来记录元素的插入顺序

    • 【进阶】这个链表,默认会记录元素插入顺序,这样可以以插入顺序遍历元素

    • 【进阶】这个链表,还可以按元素最近访问来调整顺序,这样可以用来做 LRU Cache 的数据结构

  • TreeMap 底层是红黑树

  • Hashtable 底层是 Hash 表,相对前面三个实现来说,线程安全

    • 【进阶】它的线程安全实现方式是在 put,get 等方法上都加了 synchronized,锁住整个对象
  • ConcurrentHashMap 底层也是 Hash 表,也是线程安全的

    • 【进阶】它的 put 方法执行时仅锁住一个链表,并发度比 Hashtable 高

    • 【进阶】它的 get 方法执行不加锁,是通过 volatile 保证数据的可见性

7HashMap 原理(数据结构)?

底层数据结构是:数组+链表+红黑树

数组的作用是存取元素时利用key的hashcode来计算它在数组中的索引,这样能在没有冲突的情况下,让时间复杂度达到o(1)

数组的大小毕竟优先,就算元素的hashcode唯一,但是当大小为n的数组放入n+1个元素时也有引起冲突;

解决冲突的办法就是使用链表,将这些冲突的元素链接起来,此时在链表中存取元素,时间复杂度会达到o(1);

树化的目的是防止链表过长引起整个hashmap的性能下降,红黑树的时间复杂度是o(logn)

有一些细节问题可以继续回答,比如树化的时机【进阶】

  • 时机:在数组容量达到 >= 64 且链表长度 >= 8 时,链表会转换成红黑树

  • 如果树中节点做了删除,节点少到已经没必要维护树,那么红黑树也会退化为链表

8HashMap 原理(扩容)?

扩容因子:0.75

比如说初始容量为16,当放入第13个元素后,超过了0.75,就会扩容,扩容容量翻倍;

扩容后,会重新计算key对应的桶下标,这样一部分key会移动到 其他桶中

9HashMap 原理(方法执行流程)?

以 put 方法为例进行说明

  1. 产生 hash 码。

    1. 先调用 key.hashCode() 方法

    2. 为了让哈希分布更均匀,还要对它返回结果进行二次哈希,这个结果称为 hash

    3. 二次哈希就是把 hashCode 的高 16 位与低 16 位做了个异或运算

  2. 搞定数组。

    1. 如果数组还不存在,会创建默认容量为 16 的数组,容量称为 n

    2. 否则使用已有数组

  3. 计算桶下标。

    1. 利用 (n - 1) & hash 得到 key 对应的桶下标(即数组索引)

    2. 也可以用 hash % n 来计算,但效率比前面的方法低,且有负数问题

    3. 用 (n - 1) & hash 有前提,就是容量 n 必须是 2 的幂(如 16,32,64 ...)

  4. 计算好桶下标后,分三种情况

    1. 如果该桶位置还空着,直接根据键值创建新的 Node 对象放入该位置即可

    2. 如果该桶是一条链表,沿着链表找,看看是否有值相同的 key,有走更新,没有走新增

      • 走新增逻辑的话,是把节点链到尾部(尾插法)

      • 新增后还要检查链表是否需要树化,如果是,转成红黑树

      • 新增的最后要检查元素个数 size,如果超过阈值,要走扩容逻辑

    3. 如果该桶是一棵红黑树,走红黑树新增和更新逻辑,同样新增的最后要看是否需要扩容

10说说 BIO、NIO、AIO?

BIO是同步阻塞IO:采用请求-连接-线程的模式

客户端连接后,服务器会一直阻塞到数据读写完成

优点是简单易用

缺点是并发能力差,线程开销大

NIO是同步非阻塞IO:基于Reactor模式,核心组件有Channel,bufffer,selector;

一个selector可以管理多个channel,线程不断轮询就绪事件,不用阻塞等待

服务端一个线程可以处理多个请求

优点:并发能力强,资源占用少

缺点:编程复杂

AIO是异步非阻塞IO:基于Preactor模式

读写操作完全异步,操作系统完成后才通知应用程序

应用不需要轮询,也不需要处理数据读写,直接回调

优点:性能更高,编程更简洁

11 IO流?

字节流:读写时以 字节为单位,抽象父类是Inputstream和outputstream

字符流:读写时以 字符为单位,抽象父类是writer和reader

转换流:用来把字节流转换为字符流,抽象父类是Inputstreamreader和outputstreamwriter

缓冲流:增加缓冲来提高读写效率,抽象父类是buffered 还是那四个(如bufferedreader)

对象流:配合序列化技术将java对象转换成字节流或逆操作,相关类:objectInputstream和objectoutputstream

12ThreadLocal 的原理?

ThreadLocal主要目的是用来多线程环境下的变量隔离;

每个线程对象内部都有一个ThreadLocalMap,它用来存储这些需要线程隔离的资源

资源的种类有很多,通过ThreadLocal来区分,它作为ThreadLocalMap的key,需要隔离的资源作为value

有ThreadLocal.set存储隔离资源

ThreadLocal.get获取隔离资源

使用完ThreadLocal 后一定要remove()避免内存泄漏

13解释悲观锁与乐观锁?

悲观锁:像synchronized,lock都属于悲观锁

如果发生竞争,失败的线程会阻塞

乐观锁:像atomicinteger, Atomicreferance等原子类,都属于乐观锁

如果发生竞争,失败的线程不会阻塞,仍然会重试

适用场景

  • 如果竞争少,能很快占有共享资源,适合使用乐观锁

  • 如果竞争多,线程对共享资源的独占时间长,适合使用悲观锁

14synchronized 原理?

以重量级锁为例,比如t0,t1两个线程同时执行加锁代码,发生了竞争

当执行到加锁代码时,会根据对象的对象头找到或创建此对象的monitor对象

检查monitor对象的owner属性,用cas操作去设置owner为当前现成,cas是原子操作,只能有一个线程能成功

假设t0cas成功,那么to就能加锁,就能访问执行synchronized 代码块内的部分

t1这边cas失败,会自旋若干次,重新尝试加锁,如果

自选期间t0释放锁,那么t1不必阻塞,加锁成功

自选期间t0没有释放锁,那么t1会加入到monitor的等待队列阻塞,当t0释放锁后会重新唤起它恢复并运行

15【追问】 synchronized 锁升级?

synchronized锁有三个级别:偏向锁,轻量级锁,重量级锁,性能依次降低

当就一个线程对一个对象进行加锁,用偏向锁

当两个线程交替为对象加锁,但没有发生竞争,就用轻量级锁

当多个线程加锁发生竞争,就使用重量级锁

16对比 synchronized 和 volatile?

并发编程需要从原子性,可见性,有序性考虑线程安全;

volatile修饰共享变量,可以保证它的可见性和有序行,但是不能保证其原子性

synchronized代码块,不仅能保证共享变量的可见性、有序性,同时也能保证原子性

补充:

17对比 synchronized 和 Lock?

synchronized是关键字,Lock是java的接口

synchronized底层是由C++实现锁,Lock是靠java代码实现锁

Lock功能更多,可以设置公平锁还是非公平锁,还可以设置加锁时间,可打断等

Lock提供多种扩展方式,可以根据场景选择更合适的实现

Lock 释放锁需要调用 unlock 方法,而 synchronzied 在代码块结束无需显式调用就可以释放锁

18线程池的核心参数?

记忆七个参数

核心线程数:常驻线程池的线程

最大线程数:

  1. 如果同时执行的任务数超过了核心线程数,且队列已满,会创建新的线程来救急

  2. 总线程数(新线程+原有的核心线程)不超这个最大线程数

存活时间:

  1. 超过核心线程数的线程一旦闲下来,会存活一段时间,然后被销毁

存活时间单位:

工作队列:

  1. 如果同时执行的任务数超过了核心线程数,会把暂时无法处理的任务放入此队列

线程工厂:

  1. 可以控制池中线程的命名规则,是否是守护线程等(不太重要的参数)

拒绝策略:

  1. AbortPolicy 报错策略,直接抛异常

  2. CallerRunsPolicy 推脱策略,线程池不执行任务,推脱给任务提交线程

  3. DiscardOldestPolicy 抛弃最老任务策略,把队列中最早的任务抛弃,新任务加入队列等待

  4. DiscardPolicy 抛弃策略,直接把新任务抛弃不执行

19JVM 堆内存结构?

JVM 堆内存结构与垃圾收集器有关

在传统的垃圾收集器中JVM 堆内存结构分为新生代和老年代

年轻代又分为

  • 伊甸园 Eden

  • 幸存区 S0,S1

如果是 G1 垃圾回收器,会把内存划分为一个个的 Region,每个 Region 都可以充当

伊甸园

幸存区

老年代

巨型对象区

20垃圾回收算法?

  1. 标记-清除算法。优点是回收速度快,但会产生内存碎片

  2. 标记-整理算法。相对清除算法,不会有内存碎片,当然速度会慢一些

  3. 标记-复制算法。将内存划分为大小相等的两个区域 S0 和 S1

    1. S0 的职责用来存储对象,S1 始终保持空闲

    2. 垃圾回收时,只需要扫描 S0 的存活对象,把它们复制到 S1 区域,然后把 S0 整个清空,最后二者互换职责即可

    3. 不会有内存碎片,特别适合存活对象很少时(因为此时复制工作少)

21【追问】伊甸园、幸存区、老年代细节?

  • 对象最初都诞生在伊甸园,这些对象通常寿命都很短,在伊甸园空间不足,会触发年轻代回收,还活着的对象进入幸存区 S0,年轻代回收适合采用标记-复制算法

  • 接下来再触发年轻代回收时,会将伊甸园和 S0 仍活着的对象复制到 S1,清空 S0,交换 S0 和 S1 职责

  • 经过多次回收仍不死的对象,会晋升至老年代,老年代适合放那些长时间存活的对象

  • 老年代回收如果满了,会触发老年代垃圾回收,会采用标记-整理或标记-清除算法。老年代回收时的暂停时间通常比年轻代回收更长

晋升条件

注意不同垃圾回收器,晋升条件不一样

在 parallel 里,经历 15 次(默认值)新生代回收不死的对象,会晋升

可以通过 -XX:MaxTenuringThreshold 来调整

例外:如果幸存区中的某个年龄对象空间占比已经超过 50%,那么大于等于这个年龄的对象会提前晋升

大对象的处理

首先大对象不适合存储在年轻代,因为年轻代是复制算法,对象移动成本高

注意不同垃圾回收器,大对象处理方式也不一样

在 serial 和 cms 里,如果对象大小超过阈值,会直接把大对象晋升到老年代

这个阈值通过 -XX:PretenureSizeThreshold 来设置

在 g1 里,如果对象被认定为巨型对象(对象大小超过了 region 的一半),会存储在巨型对象区

Region 大小是堆内存总大小 / 2048(必须取整为2的幂),或者通过 -XX:G1HeapRegionSize 来设置

22Lambda表达式?

什么是 Lambda 表达式

  • 文献中把 Lambda 表达式一般称作匿名函数 ,语法为 (参数部分) -> 表达式部分

  • 它本质上是一个函数对象

  • 它可以用在那些需要将行为参数化的场景,例如 Stream API,MyBatisPlus 的 QueryWrapper 等地方

Lambda 与匿名内部类有何异同

  • 它们都可以用于需要行为参数化的场景

  • Lambda 表达式必须配合函数式接口使用,而匿名内部类不必拘泥于函数式接口,其它接口和抽象类也可以

  • Lambda 表达式比匿名内部类语法上更加简洁

  • 匿名内部类是在编译阶段由程序员编写提供,而 Lambda 表达式是在运行阶段动态生成它所需的类

  • 【进阶】Lambda 中 this 含义与匿名内部类中的 this 不同

23什么是反射?

  • 反射是 java 提供的一套 API,通过这套 API 能够在运行期间

    • 根据类名加载类

    • 获取类的各种信息,如类有哪些属性、哪些方法、实现了哪些接口 ...

    • 类型参数化,根据类型创建对象

    • 方法、属性参数化,以统一的方式来使用方法和属性

  • 反射广泛应用于各种框架实现,例如

    • Spring 中的 bean 对象创建、依赖注入

    • JUnit 单元测试方法的执行

    • MyBatis 映射查询结果到 java 对象

    • ...

  • 反射在带来巨大灵活性的同时也不是没有缺点,那就是反射调用效率会受一定影响

24什么是泛型?

  • 泛型的主要目的是实现类型参数化,java 在定义类、定义接口、定义方法时都支持泛型

  • 泛型的好处有

    • 提供编译时类型检查,避免运行时类型转换错误,提高代码健壮性

    • 设计更通用的类型,提高代码通用性

25Tomcat优化?

Tomcat 优化主要从内存、线程、连接器、配置禁用、部署方式几个方面入手,目的是提高并发、减少卡顿、避免 OOM。

1JVM 内存优化:

  • 堆初始值与最大值一致,避免扩容开销
  • 合理设置元空间,防止类过多 OOM

2线程池优化:使用 NIO/NIO2 模式,比 BIO 并发高很多,合理配置线程池的参数

3禁用 DNS 反向解析

4开启 GZIP 压缩,减少传输体积;

5动静分离,静态资源交给 Nginx,Tomcat 只处理业务;

6会话分布式存储,提升集群能力。

尾页

相关推荐
Sunshine for you2 小时前
C++中的对象池模式
开发语言·c++·算法
暮冬-  Gentle°2 小时前
编译器优化屏障使用
开发语言·c++·算法
华科大胡子2 小时前
此电脑网络位置异常的AD域排错指南
开发语言·php
m0_730115112 小时前
模板编程中的SFINAE技巧
开发语言·c++·算法
mldlds2 小时前
Spring Boot 实战:轻松实现文件上传与下载功能
java·数据库·spring boot
2401_874732532 小时前
Python Web爬虫入门:使用Requests和BeautifulSoup
jvm·数据库·python
xxjj998a2 小时前
Spring Boot 整合 Apollo 配置中心实战
java·spring boot·后端
2401_831824962 小时前
高性能计算集群部署
开发语言·c++·算法
武超杰2 小时前
Spring 纯注解配置全解析(进阶版)
java·开发语言