Created with Sketch.

- 技术学习 - 共 15 篇

Git(Windows) https://git-scm.com/install nvm / npm(Windows) 前往 Github 下载最新版本并运行 安装指定版本 设置镜像源 IDEA 配置 Edit bin/idea.properties Activate Help -> Change Memory Settings Settings Appearance & Behavior -> System Settings -> HTTP Proxy -> Auto-detect proxy settings Editor -> General -> Editor Tabs -> Show tabs in: Multiple rows Editor -> Code Style -> Hard wrap at: 140 columns Advanced Settings -> Maximum number of results to show in Find in Files/Show Usage preview: 1000 Maven Install plugins Alibaba Java Coding Guidelines (Fix Some Bug) ElementUI Gerry Themes: Legacy Gerry Darcula Gemini Code Assist Go Python Statistic Disable unused plugins Gradle Flyway Liquibase Docker Kubernetes WildFly Chinese / Japanese / Korean Angular Karma React Vite Kotlin Code With Me Eclipse Interoperability IDE Features Trainer

目录 概述 Kafka RocketMQ 常见问题 一、概述 使用场景 解耦:生产者 / 消费者只负责发送 / 消费消息,而不关心对方状态;消费者挂掉后,消息只会积压在队列中,不影响整体业务。 削峰:高并发场景下,把大量请求写入 MQ,消费者按自己的处理能力拉取,避免直接冲垮数据库或服务。 异步:将无需同步完成的步骤放到 MQ 异步处理,提升接口响应速度。 各 MQ 对比 Kafka:性能高,分区有序,采用 sendfile。 RocketMQ:有序,采用 mmap(需要读取消息内容实现死信队列等)。 消息模型 队列模型:一个消息只能被一个消费者消费,未被消费的消息保持在队列中,直到被消费或超时。 发布-订阅模型:消息发送到 Topic,会被所有订阅 Topic 的消费者消费。 二、Kafka 核心概念 Broker:Kafka 集群中的每一个实例。 Partition:每个 Topic 有多个逻辑分区,分布在多个 Broker 上实现并发能力。 Replica:每个分区有 Leader 和 Follower 两种副本,分布在不同 Broker 上。 Leader:生产者和消费者只与这个唯一的 Leader 副本交互。 Follower:Leader 故障时,会从 Follower 中选举出一个 Leader。 Consumer Group:消费者组,共同消费同一个 topic,每个分区同一时间只能由一个消费者读取,故消费者数量与分区数相同时,吞吐量最大。 消息参数 topic、partition、key(相同 key 的消息进入同一 partition)、data 持久化 先写到内存的 PageCache 中,再由操作系统决定什么时候刷盘(默认),可以设置 log.flush.interval.messages 和log.flush.interval.ms 参数,控制日志达多少条、或保留时间超过多少时强制刷盘。 死信队列 Kafka 原生不支持死信队列,因为设计理念专注于消息存储和传递,把消费失败的处理交给上层应用来实现,但是可以通过一些框架实现。 Spring Kafka:在 @KafkaListener 同类下创建 @DltHandler 注解的方法,自动将消息内容、topic、partition、offset 以及异常信息等写入 .DLT 的 topic 中。注意,死信队列本身依赖 Kafka 的可用性,只能作为一道保险,最好增加持久化兜底。 三、RocketMQ 核心概念 Broker:并非 Kafka 中的独立服务实例,而是有主从之分;与 Topic 多对多。 NameServer:注册 Broker 信息,实现 Broker 管理和路由。 Queue:每个 Topic 拆分成多个 Queue 供读写,消息在 Queue 内有序。 持久化 设置 Broker 的 FlushDiskType 参数: 同步刷盘(SYNC_FLUSH):需等待刷盘 ack,安全性高,但性能较低。 异步刷盘(ASYNC_FLUSH):后台异步线程提交,Broker 宕机时会丢失数据。 主从同步 同步复制:只有消息同步双写到主从节点上时才返回写入成功。 异步复制:消息写入主节点之后就直接返回写入成功。 异步复制不影响可靠性,仅作为高可用手段,而可靠性依赖刷盘策略。RocketMQ 不支持自动主从切换,但消费者可以自动切换到从节点进行消费。 单主从架构下,主节点挂掉后整个系统无法生产,可用性差,因此生产环境一般采用多主多从部署。 功能特性 延时消息:将消息放入延时 Topic 队列,通过定时任务检查实现。 顺序消息:保证严格有序,需设置消息组,把消息映射到同一个 Queue。 分布式事务(最终一致性) (1)服务 A 发送 Half Message(消费者不可见)。 (2)服务 A 收到 MQ 响应后,执行本地事务。 (3)执行成功则发送 commit 到 MQ,消息变为可消费。 (4)执行失败则发送 rollback 到 MQ,删除半消息。 (5)如果 MQ 一直未收到服务 A 返回,回调服务 A 回查。 (6)根据回查结果再次 commit 或 rollback。 回溯消费:按照时间维度来回退消费进度。 四、常见问题 重复消费 生产者端:MQ 自身接口保证不会加入重复消息(如缓存已处理的 message_id)。 消费者端:需消费者做幂等,或拉取到消息就响应(会消息丢失,可配合对账)。 消息丢失 生产者端:生产者在 MQ 返回异常时,设置合理的消息重发逻辑。 Kafka:send() 方法返回 ListenableFuture,为异步操作,除了用 get() 使其变成同步操作外,还可以 addCallback() 为其添加回调。 消息队列:持久化 + 集群副本防止单点故障。 Kafka:可配置如下参数。 - acks:默认为 1,L

问题定义 SQL 语句 索引 TBL_XXX 表上有 idx_a、idx_b 两个非唯一索引。 问题 由于 A、B 非联合索引,因此只会用到 idx_a;当 A 列筛选出的行数很大时,结果集的每一行都要做回表,去判断是否满足 B 列条件,从而造成查询超时。 解决方案 改进后 SQL 语句 原理剖析 通过 EXPLAIN 指令可以发现,三条 SELECT 语句的 Extra 列有的是 Using where、有的是 Using intersect,这是因为 MySQL 根据 B 条件不同的取值计算出了不同的扫描行数,选择了该条件下它所认为的最优方式。 Using intersect 能够对多个扫描结果取交集,这里相当于是对 B 列每个条件得到的结果集 与 A 列分别取了交集做了多次查询,再将最后得到的结果集取并集,从而无需再回表;即使 SQL 语句后面还有 C = "xyz" 的非索引列条件,也仅需在交集上做回表,大幅降低了回表次数。 Using intersect 条件 SQL 语句为多个索引的 AND 条件时,满足: 如果是二级索引,必须是等值查询,且覆盖其中所有索引列 如果是主键索引,可以是范围查询 参考 索引合并,能不用就不要用吧!

目录 场景题 智力题 场景题 假设有 40 亿 QQ 号,但只有 1G 内存,如何实现去重? 布隆过滤器:部分数据可能误判。 位数组 (BitMap) :用 QQ 号为下标的空间存储 0(不存在)和 1(存在),占用空间 4000000000 bit / 1024 / 1024 / 1024 / 8 = 0.465 GB。 10 GB 文件找出重复最多的数字 将数字按照区间分块,找出每个区间重复最多的数字及出现次数,再排序各区间的次数。 抢红包如何设计随机? 实时分配 / 提前分配 保证均值:例如一个 100 的红包十个人抢,第一个人在 0-20 的范围内随机,后续的人根据前面的人抢的红包大小调整上限,这样保证了均值,但后面的方差会变大。 设置上、下界限 ...... 智力题 1000 个草堆有 1 个有毒,怎么用尽量少的兔子去找出? 2^10 = 1024 ,每一个草堆的编号转换为二进制,让 10 只兔子去对应草堆编号的 10 个位,位为 1 则让对应兔子吃一口,根据 10 只兔子的死亡情况得到二进制编号来唯一确定草堆,最多用掉 9 只兔子。 25 只马每次只能比较 5 只,最少多少次决出前三名? 分为 5 组决出每一组顺序,5 次; 每组的第 1 名比赛一次,决出第一名,1 次; 此时只剩下第 1 名组的 2, 3 名、第 2 名组的 1, 2 名,第 3 名组的第 1 名,这 5 只再比赛一次,决出第二、三名,1 次; 共 7 次比赛决出前三名。 8 个小球找出其中 1 个重量比其它重的要几次? 取 6 个小球,天平两端各放 3 个; 如果一样重,剩下的 2 个小球再比较一次; 如果不一样重,在重的 3 个小球里任选 2 个比较; 故 8 个球最多 2 次。 N 个球: log3(n) 次。 5L 水桶 + 3L 水桶怎么取 4L 水?

目录 概述 日志 事务 类型 索引 优化 主从架构 概述 基础架构 MySQL 主要分为 Server 层和存储引擎层: Server 层:所有跨存储引擎的功能都在这一层实现,如存储过程、触发器、视图,函数等,还有一个通用的 binlog 日志模块。 查询缓存在 MySQL 8.0 后被移除,因为缓存失效在实际业务场景中可能会非常频繁,如果对一个表更新,这个表上的所有的查询缓存都会被清空。 存储引擎:主要负责数据的存储和读取,采用可替换的插件式架构,支持 InnoDB、MyISAM 等多个存储引擎,其中 InnoDB 自带 redo log 日志模块,采用聚簇索引,支持事务、行锁、外键等(以下所有内容都是基于 InnoDB 的)。 查询语句 先检查该语句是否有权限,如果没有权限直接返回错误信息;如果有,以这条 SQL 语句为 key 在内存中查询是否有缓存(MySQL 8.0 以前),无缓存则执行下一步。 通过分析器进行词法分析,提取 SQL 语句的关键元素,如这个语句是 select,查询的表名、列,以及查询条件等。然后判断是否有语法错误。 接下来优化器会根据自己的优化算法选择其所认为执行效率最高的方案(有时不一定最好),例如上面的 SQL 可以有两种执行方案: a. 先查询表中姓名为“张三”的学生,再判断年龄是否是 18 岁。 b. 先找出学生中年龄为 18 岁的,再查询姓名为“张三”的学生。 确认了执行计划后,进行权限校验,如果没有权限就会返回错误信息,否则调用数据库引擎接口,返回引擎的执行结果。 Server 层每从存储引擎读到一条记录就会发送给客户端,之所以客户端是直接显示所有记录的,是因为客户端是等查询语句完成后才会显示。 更新语句(两阶段提交) 在 InnoDB 引擎下,这个语句的执行流程如下: 先通过 where 条件查询到张三这条数据,如果该行数据不在 InnoDB Buffer Pool(内存)中,则从磁盘加载对应的数据页。 InnoDB 修改内存中的该行数据,同时生成 undo log(更新前的值,用于回滚)。 WAL(Write-Ahead Logging)机制:InnoDB 写入 redo log(保证宕机后可恢复数据),进入 prepare 状态,并通知执行器准备提交事务。 执行器记录 binlog(逻辑日志,用于主从复制、数据恢复),记录该语句。 执行器调用 InnoDB 引擎提交 redo log,将其改为 commit 状态,更新完成。 两阶段提交 上述过程中,redo log 的写入拆成了两个步骤 prepare 和 commit。 写入 binlog 时发生异常时:MySQL 根据 redo log 日志恢复数据时,发现 redo log 还处于 prepare 阶段,且没有对应 binlog 日志,就会回滚该事务。 redo log 在 commit 阶段发生异常时:虽然 redo log 处于 prepare 状态,但是能通过事务 id 找到对应的 binlog 日志,所以 MySQL 认为是完整的,就会提交事务。 日志 binlog MySQL 中的逻辑日志,用于记录语句的原始逻辑。数据备份、主从复制需要依靠 binlog 来同步数据。它有三种格式: statement:SQL 语句原文,但 update_time=now() 会获取当前系统时间。 row:update_time=now() 变成了具体的时间。 mixed:前两者的混合,因为 row 更占用空间,恢复与同步时会更消耗 IO 资源。 写入机制 一个事务的 binlog 不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为 binlog cache。 单个线程 binlog cache 的大小可以由参数 binlog_cache_size 控制,如果存储内容超过了这个参数,就要暂存到磁盘。 write 和 fsync 的时机,可以由参数 sync_binlog 控制。 在出现 IO 瓶颈的场景里,将 sync_binlog 设置成较大的值可以提升性能。 0:每次提交事务都只 write,由系统自行判断什么时候执行 fsync 。 1:每次提交事务都会执行 fsync ,防止宕机时 cache 中 binlog 丢失。 N(N>1):每次提交事务都 write,但累积 N 个事务后才 fsync。 undo log undo log 属于逻辑日志,记录的是 SQL 语句,比如事务执行一条 DELETE 语句,那 undo log 就会记录一条相对应的 INSERT 语句。同时,undo log 的信息也会被记录到 redo log 中,因为 undo log 也要实现持久性保护。当执行事务过程中出现错误或者需要执行回滚操作的话,MySQL 可以利用 undo log 将数据恢复到事

目录 知识点 注册中心 配置中心 知识点 微服务A调用过微服务B时,A会在本地维护一个实例缓存,之后即使 Nacos 宕机,也不再依赖注册中心,远程调用仍能完成,直到缓存过期或更新。 如果 Properties 和 Nacos 配置中心有相同的配置项,以配置中心的配置为准。 如果从 Nacos 导入的多个配置有相同的配置项,以引入时靠前的配置项为准。 注册中心 pom.xml 中引入依赖: properties 中写入配置: 配置中心 在 Nacos 配置列表中创建 aaa.properties 和 bbb.properties 。 在微服务中写入配置: 通过 @RefreshScope 注解可以使配置变更后自动刷新;也可以通过创建一个类来实现,例如 Nacos 中的配置如下: 创建类: 调用: 通过实现 ApplicationRunner 函数式接口,实现在应用启动并初始化完成后,启动配置变更监听: OpenFeign 远程调用 确认生产者的微服务已经在 Nacos 上注册。 消费者在 pom.xml 中引入依赖: 消费者在启动类上添加 @EnableFeignClients 注解。 消费者创建接口: 消费者调用接口:

目录 Java 内存区域 JVM 垃圾回收 类加载器与类加载器 HotSpot 虚拟机对象实现 一、Java 内存区域 程序计数器 当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 虚拟机栈 除Native方法外,所有的方法都是通过栈来实现的。方法调用的数据需要通过栈进行传递,每次方法调用,都会有一个对应的栈帧被压入栈中,每次方法调用结束,都会有一个栈帧被弹出。栈的生命周期也和线程相同。 局部变量表:存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)。 操作数栈:作为方法调用的中转站,用于存放方法执行过程中的临时操作数和中间计算结果。由于JVM的无寄存器设计,操作数栈成为核心的计算区域。例如: 动态链接:指向运行时常量池中的方法引用(Method References)。用于在方法中调用其他方法时,将Class文件常量池中指向方法的符号引用(因为编译期无法知道实际内存地址)转化为其在内存地址中的直接引用。 程序运行中栈可能会出现两种错误: StackOverFlowError:栈的内存大小不允许动态扩展时,线程请求栈的深度超过当前虚拟机栈最大深度(如函数调用陷入无限循环)。 OutOfMemoryError:栈的内存大小允许动态扩展时,虚拟机在动态扩展栈时无法申请到足够的内存空间。 本地方法栈 与虚拟机栈类似,区别在于其为Native方法服务,HotSpot中与虚拟机栈合二为一。 堆 在JVM启动时创建,是由所有线程共享的一块最大的内存区域。堆的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 JDK1.7后默认开启逃逸分析,如果某些方法中的对象引用没有被返回或未被外面使用(即未逃逸),那么对象可以直接在栈上分配内存。 堆也称为GC堆,是垃圾收集器管理的主要区域。收集器基本都采用分代垃圾收集算法,因此堆可以细分为:新生代、老年代、永久代,JDK8后永久代被元空间取代。新生代还能划分为Eden、Survivor。详见内存分配与回收原则。 程序运行中堆最容易出现 OutOfMemoryError 错误,例如: java.lang.OutOfMemoryError: GC Overhead Limit Exceeded:当JVM花费过多的时间进行GC,却只能回收很少的堆空间时。 java.lang.OutOfMemoryError: Java heap space:堆内存不足以存放新创建的对象。(受制于配置的最大堆内存 -Xmx 和物理内存大小。) 方法区 方法区是JVM运行时线程共享的一块逻辑区域(抽象概念),在不同的虚拟机上,方法区的实现(如永久代、元空间)是不同的。当JVM要使用一个类时,它会读取并解析Class文件获取相关信息(类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等),再将信息存入到方法区。 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) ? 永久代受制于JVM设置的固定大小 -XX:MaxPermSize ,启动后无法调整,加载大量类时容易出现 java.lang.OutOfMemoryError: PermGen space 错误;而元空间使用本地内存,加载多少个类的元数据只受制于本机实际可用内存,溢出的概率更小。(元空间溢出时会出现 java.lang.OutOfMemoryError: MetaSpace 错误) -XX:MaxMetaspaceSize:设置最大元空间大小,默认值为unlimited。 -XX:MetaspaceSize:设置元空间的初始大小,如果未指定,则根据运行时的应用程序需求动态调整。 永久代与堆共享垃圾回收器,增加了GC的复杂度,且回收效率低。 JDK8以前,HotSpot通过永久代存储类的元数据,而JRockit中没有类似的概念,因此在两者合并后,就没有必要额外设置一个永久代了。 运行时常量池 Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量和符号引用的常量池表(Constant Pool Table)。常量池表会在类加载后存放到方法区或元空间的运行时常量池中,内存受制于方法区或元空间。 字面量:程序中显式写出的值,如数字、字符、字符串等。 符号引用:对类、字段、方法、接口方法等的引用。解析阶段就是JVM把常量池中的符号引用替换成直接引用的过程。 字符串常量池 字符串常量池是JVM为了提

目录 概述 HTTP/HTTPS (应用层) DNS(应用层) TCP/UDP(传输层) NAT(网络层) ARP(网络层) ICMP(网络层) 其它 一、概述 OSI 和 TCP/IP 网络分层模型 各层传输的数据单位 | 单位 | 层级 | 描述 | |-----------------------------|----------------|------------------------------------------------| | 数据(Data) | 应用层 | 例如 HTTP 请求、FTP 数据等 | | 报文段(Segment) | 传输层 | TCP 使用报文段 | | 数据报(Datagram) | 传输层 | UDP 使用数据报 | | 数据包(Packet) | 网络层 | 包含 IP 地址,用于跨网络的节点通信 | | 帧(Frame) | 链路层 | 包含 MAC 地址,用于同一局域网内的节点通信 | | 比特(Bits) | 物理层 | 最基本的传输单位,通过物理介质传输 0/1 | 二、HTTP/HTTPS (应用层) HTTP vs HTTPS HTTP 运行在 TCP 之上,传输的内容都是明文,客户端和服务端间无法验证身份。 HTTPS 运行在 SSL/TLS 之上,SSL/TLS 运行在 TCP 之上,建立连接时,通过证书颁发机构(CA,Certificate Authority)颁发的证书进行非对称加密,此后传输的内容采用对称加密(非对称加密效率低且加密的数据长度受限),但对称加密的密钥用服务器方的证书进行了非对称加密,相比 HTTP 安全性更高,但会耗费更多服务器资源。 HTTPS 请求流程 设有客户端 C,服务器 S,第三方信赖机构 CA,攻击者 A: 如果攻击者 A 向 C 发送一个诈包,假装是 S 公钥,C 后续就会用 A 的公钥对数据进行加密,在公开信道传输,那么 A 将捕获这些加密包,用 A 的私钥解密,就截获了 C 本要给 S 发送的内容。 为解决这一问题,SSL/TLS 采用了数字签名:CA 采用散列技术对证书生成摘要,通过 CA 私钥对其加密,附在证书下方。 TLS Handshake:C 向 S 发送 ClientHello(包含支持的加密算法、协议版本等)。 S 返回证书:S 选择加密算法,并向 C 发送 CA 颁发的证书(包含 S 的公钥和域名、CA 信息、证书有效期、数字签名)。 C 验证证书:C 根据本地的 CA 信任列表验证 CA 是否可信,确认证书是否已过期,以及检查证书上的域名是否与访问的域名匹配;之后 C 获取 CA 公钥,将签名解密成摘要,并对证书进行相同散列处理得到摘要,两个摘要如果相同,则信任 S 的公钥。 C 发送预主密钥:C 生成一个随机的预主密钥,用 S 的公钥加密并发送给 S。 双方计算会话秘钥:S 用自己的私钥解密后,C 和 S 共同计算最终的对称会话密钥。 预主密钥(Pre-Master Secret)作用:前向安全性(即使 S 私钥泄露,过去的会话密钥仍然安全);master secret 长度非常大,会增加通信延迟。 HTTP/1.0 vs HTTP/1.1 连接方式:HTTP/1.0 为短连接,HTTP/1.1 支持长连接。 状态响应码:HTTP/1.1 中新加入了大量的状态码。 缓存机制:HTTP/1.0 中主要使用 Header 里的 If-Modified-Since 、Expires 作为缓存判断的标准,HTTP/1.1 引入了更多的缓存控制策略,如 Entity tag 、If-Unmodified-Since 、If-Match 等。 带宽:HTTP/1.1 在请求头引入了 range 头域,允许只请求资源的某个部分而非整个对象,返回码为 206(Partial Content),实现断点续传,避免了带宽浪费。 Host 头:HTTP/1.1 引入了 Host 头字段,允许在同一IP地址上托管多个域名,从而支持虚拟主机的功能。 HTTP/1.1 vs HTTP/2.0 多路复用:HTTP/1.1 为了弥补串行化(一个请求完成后才能处理下一个请求)的限制,通常会打开多个 TCP 连接;而 HTTP/2.0 在同一连接上可以并行传输多个请求和响应,减少了 TCP 连接数量和网络延迟。 二进制帧:HTTP/1.1 使用文本格式的报文进行数据传输;HTTP/2.0 使用更加紧凑的二进制帧,减少了数据传输量和带宽消耗。 头部压缩:HTTP/1.1 仅支持对 Body 压缩,HTTP/2.0 支持对 Header 压缩。 服务器推送:HTTP/2.0 支持服务器推送,在客户端请求一个资源时,将其他相关资源一并推送给客户端,从而减少了客户端的请求次数。 RPC(Remote Procedure Call)基于 TC

目录 单例模式 装饰器模式 适配器模式 工厂模式 观察者模式 代理模式 命令模式 单例模式 装饰器模式 适配器模式 工厂模式 策略模式 相较于工厂模式,更关注状态的切换。 观察者模式 代理模式 使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,扩展目标对象的功能,提供额外的功能操作。 例子:下面将通过多种方式去增强 send 方法。 1. 静态代理 2. JDK 动态代理 在 Java 动态代理机制中,InvocationHandler 接口用于自定义处理逻辑,Proxy 类的 newProxyInstance() 方法用于生成代理对象。 实现 3. CGLIB 动态代理 依赖 实现 4. 三种方式的对比 静态代理中,接口一旦增加新方法,目标对象和代理对象都要进行修改;而动态代理不需要实现接口,可以直接代理实现类,并且不需要对每个目标类都创建一个代理类。 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件;而动态代理是在运行时动态生成类字节码,并加载到JVM中的。 JDK动态代理只能代理实现了接口的类或者直接代理接口,而CGLIB可以代理未实现任何接口的类。 JDK动态代理效率相较于CGLIB更加优秀。 CGLIB是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。 命令模式

目录 概述 语法和重要概念 数据类型 集合 异常 一、概述 Java语言的特点 面向对象:封装、继承、多态。 封装(Encapsulation):将数据和操作数据的方法包装在一起,隐藏内部细节,只能通过对外提供的接口,对封装在内部的属性和方法进行访问和操作。 继承(Inheritance):子类复用和扩展父类的属性和方法,实现层次结构。 多态(Polymorphism):调用相同方法做出不同行为。两种实现方式:通过继承(子类方法重写)、通过接口。 平台无关性:JVM实现“Write Once, Run Anywhere.”。 可靠性:异常处理、自动内存管理机制。 安全性:如访问权限修饰符、限制程序直接访问操作系统资源。 编译与解释共存 .java 文件编译 为 *字节码*(JVM能理解的代码,即 .class 文件),字节码解释为 *机器码* 。由于字节码只面向JVM,因此程序无须重新编译便可在不同操作系统上运行。 JIT(Just in Time Compilation)编译器:运行时编译。当JIT编译器完成第一次编译后,会将字节码对应的机器码保存下来,下次直接使用。 AOT(Ahead of Time Compilation):在程序被执行前就将其编译成机器码,可以提高程序的启动速度,避免预热时间长,但无法支持反射、动态代理、动态加载、JNI(Java Native Interface)等。 二、语法和重要概念 重载和重写 重载(Overload):同一类中方法名相同,参数列表不同。发生在编译期。 重写(Override):子类重写父类方法,子类方法的返回值类型和异常比父类方法更小或相等;发生在运行期。 浅拷贝和深拷贝 浅拷贝:在堆上创建一个新对象,如果原对象内部属性有引用类型,浅拷贝会直接复制内部对象的引用地址,也就是说和原对象共用同一个内部对象。 深拷贝:会完全复制整个对象,包括其所包含的内部对象。 序列化和反序列化 位于 TCP/IP 协议中的应用层:将应用层的用户数据进行处理转换为二进制流。 序列化:将数据结构或对象转换成二进制字节流、JSON 、XML 等。 反序列化:将序列化过程中生成的数据转换为原始数据结构或对象的过程。 应用场景:网络传输、存储到文件、存储到缓存数据库、存储到内存。 JDK自带的序列化方式:实现 java.io.Serializable 接口。 serialVersionUID 用于标识类的序列化版本,没有被序列化。 static 修饰的变量因为不属于任何对象,所以不会被序列化。 transient 修饰的变量不会被序列化,反序列化后会被置成类型默认值。 缺点:不支持跨语言调用、性能差、安全问题。 其它序列化方式:Kryo、Protobuf、ProtoStuff、Hessian。 强引用/软引用/弱引用/虚引用 | 类型 | 特点 | 典型用途 | |-----------------|--------------------------------------------------------------------------------------------------|---------------------------------| | 强引用 | 普通的对象引用,垃圾回收器不会回收。 | 常规对象使用。 | | 软引用 | 内存不足时回收对象; 可以和引用队列联合使用。 | 缓存数据(如图片缓存)。 | | 弱引用 | 不论内存是否充足,GC时都会回收; 可以和引用队列联合使用。 | 缓存、引用池中的对象引用。 | | 虚引用 | 不能访问对象,用于跟踪对象回收; 必须和引用队列联合使用。 | 监控对象回收、清理资源。 | 反射 / 注解 代码块执行顺序 顺序:静态代码块(只执行一次)->普通代码块->构造函数->普通代码块->构造函数 值传递 Java只有值传递,没有引用传递,方法接收的是实参值的拷贝(可以是实参的地址),会创建副本。 三、数据类型 包装类型 包装类型与基本类型的区别 用途:除了定义一些常量和局部变量之外,方法参数、对象属性中常用包装类型。包装类型可用于泛型,而基本类型不行。 存储方式:基本类型的局部变量存放在栈的局部变量表中,成员变量(未被 static 修饰)存放在堆中。包装类型属于对象类型,因此存在堆中。 占用空间:基本类型占用的空间往往更小。 默认值:基本类型有默认值,而包装类型为 null。 比较方式:因为包装类型是对象,== 比较的是内存地址,equals() 比较的是值。 阿里Java开发手册: 【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定默认值。 【强制】所有的 POJO 类属性必须使用包装数据类型。 【强制】RPC 方法的返回值和参数必须使用包装数据类型。 【推荐】所有的局部变量使用基本数据类型。 包装类型的缓存机