风之子博客-测试源码分享 - Java
type: channel
link: https://www.canace.cn/index.php/category/Java
items: 八股自编
items: SpringBoot集成RabbitMq，实现监听数据变化后更新内容
items: springboot3+canal+rabbitMQ实现对缓存进行清除
items: 如何查询树形结构的数据
items: 完美解决方案-雪花算法ID到前端之后精度丢失问题
items: SpringBoot2.7.9+Mybatis-Plus3.5.3.1+ShardingSphere-JDBC5.3.1实现分库分表
items: Java基础
items: Java中HashMap、LinkedHashMap和TreeMap区别使用场景

八股自编
type: item
date: 2025-11-16T22:54:00+08:00
description: Java基础1. 面向对象有哪些特性？面向对象四大特性：封装，继承，多态，抽象封装就是将类的信息隐藏在类内部，不允许外部程序直接访问，而是通过该类的方法实现对隐藏信 息的操作和访问。 良好的封装能够减少耦合。继承是从已有的类中派生出新的类，新的类继承父类的属性和行为，并能扩展新的能力，大大增加 程序的重用性和易维护性。在Java中是单继承的，也就是说一个子类只有一个父类。 （Java是单继承，通过多层继承、内部类和接口 可以实现多继承）多态是同一个行为具有多个不同表现形式的能力。在不修改程序代码的情况下改变程序运行时绑定 的代码。实现多态的三要素：继承、重写、父类引用指向子类对象。 静态多态性：通过重载实现， 相同的方法有不同的參数列表，可以根据参数的不同，做出不同的处理。 动态多态性：在子类中重 写父类的方法。运行期间判断所引用对象的实际类型，根据其实际类型调用相应的方法。抽象。把客观事物用代码抽象出来。2. 自动装箱和拆箱public void testAutoBox() { int a = 100; Integer b = 100; System.out.println(a == b); // true Integer c = 100; Integer d = 100; System.out.println(c == d); // true Integer e = 200; Integer f = 200; System.out.println(e == f); //false }为什么第三个输出是false？Integer e = 200; 会调用 调⽤ Integer.valueOf(200) 。而从Integer的valueOf()源码可以看到，这里的实现并不是简单的new Integer，而是用IntegerCache做一个cache。默认Integer cache 的下限是-128，上限默认127。当赋值200给Integer时，不在cache 的范围内，所以会new Integer并返回，当然==比较的结果是不相等的。3. String不可变，String, StringBuffer 和 StringBuilder区别对于String来说，是把数据存放在了常量池中，因为所有的String，默认都是以常量形式保存，且由final修饰，因此在线程池中它是线程安全的。4.两个对象的hashCode()相同，则 equals()是否也一定 为 true？equals()比较之前会先比较hashCode()是否相等，只有hashCode()相等之后才会比较equals。这是由于hash算法是二进制算法，计算机本质是二进制，所有hash算法速度很快。**以下是关于hashcode的一些规定：两个对象相等，hashcode一定相等两个对象不等，hashcode不一定不等hashcode相等，两个对象不一定相等hashcode不等，两个对象一定不等**5. Java创建对象的方式用new语句创建对象。使用反射，使用Class.newInstance()创建对象。调用对象的clone()方法。运用反序列化手段，调用java.io.ObjectInputStream对象的readObject()方法。6. final, finally, finalize 的区别final 用于修饰属性,方法和类, 分别表示属性不能被重新赋值, 方法不可被覆盖, 类不可被继承。finally 是异常处理语句结构的一部分，一般以ty-catch-finally出现，finally代码块表示总是被执行。finalize 是Object类的一个方法，该方法一般由垃圾回收器来调用，当我们调用System.gc() 方法的时候，由垃圾回收器调用finalize()方法，回收垃圾，JVM并不保证此方法总被调用。final关键字的作用final 修饰的类不能被继承。final 修饰的方法不能被重写。final 修饰的变量叫常量，常量必须初始化，初始化之后值就不能被修改。7.常见的Exception有哪些？RuntimeExceptionClassCastException //类型转换异常IndexOutOfBoundsException //数组越界异常NullPointerException //空指针ArrayStoreException //数组存储异常NumberFormatException //数字格式化异常ArithmeticException //数学运算异常unchecked ExceptionNoSuchFieldException //反射异常，没有对应的字段ClassNotFoundException //类没有找到异常Illegal…

SpringBoot集成RabbitMq，实现监听数据变化后更新内容
type: item
date: 2024-02-23T16:49:00+08:00
description: 集成Spring AMQP1.可通过如下 Docker 命令 安装 RabbiMQ: docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management2.登录 RabbiMQ 的 web 管理界面，创建虚拟主机novel: 3.项目中加入如下的 maven 依赖：<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>4.在 application.yml 配置文件中加入 RabbitMQ 的连接配置:spring: rabbitmq: addresses: "amqp://guest:guest@47.106.243.172" virtual-host: novel template: retry: # 开启重试 enabled: true # 最大重试次数 max-attempts: 3 # 第一次和第二次重试之间的持续时间 initial-interval: "3s"相关配置1.建立相关常量:/** * @author canace * @version 1.0 * @description AMQP 相关常量 * @date 2024/2/23 10:15 */ public class AmqpConsts { /** * 小说信息改变MQ */ public static class BookChangeMQ { /** * 小说信息改变交换机 */ public static final String EXCHANGE_NAME = "EXCHANGE-BOOK-CHANGE"; /** * Elasticsearch book 索引更新的队列 */ public static final String QUEUE_ES_UPDATE = "QUEUE-ES-BOOK-UPDATE"; /** * Redis book 缓存更新的队列 */ public static final String QUEUE_REDIS_UPDATE = "QUEUE-REDIS-BOOK-UPDATE"; } }2.创建 AMQP 配置类，配置各个交换机、队列以及绑定关系：/** * @author canace * @version 1.0 * @description AMPQ 配置类 * @date 2024/2/23 10:25 */ @Configuration public class AmqpConfig { /** * 小说信息改变交换机(广播模式) */ @Bean public FanoutExchange bookChangeExchange() { return new FanoutExchange(AmqpConsts.BookChangeMQ.EXCHANGE_NAME); } /** * Elasticsearch book 索引更新队列 */ @Bean public Queue esBookUpdateQueue() { return new Queue(AmqpConsts.BookChangeMQ.QUEUE_ES_UPDATE); } /** * Elasticsearch book 索引更新队列绑定到小说信息改变交换机 */ @Bean public Binding esBookUpdateQueueBinding() { return BindingBuilder.bind(esBookUpdateQueue()).to(bookChangeExchange()); } }3.配置 AMQP 消息管理类：/** * @author canace * @version 1.0 * @description AMQP 消息管理器 * @date 2024/2/23 10:32 */ @Component @RequiredArgsConstructor public class AmqpMsgManager { private final AmqpTemplate amqpTemplate; // 判断配置是否启动 RabbitMq @Value("${spring.amqp.enabled:false}") private boolean amqpEnabled; // 在小说进行信息更新或者新建时，只需调用这个接口即可实现 // 这里没有写更新书籍信息的接口，不做演示 // amqpMsgMana…

springboot3+canal+rabbitMQ实现对缓存进行清除
type: item
date: 2024-01-30T19:11:51+08:00
description: Canal 工作原理Canal 模拟 MySQL slave 的交互协议，伪装自己为 MySQL slave ，向 MySQL master 发送dump 协议MySQL master 收到 dump 请求，开始推送 binary log 给 slave (即 canal )Canal 解析 binary log 对象(原始为 byte 流)环境准备本项目所有的环境都是在 Docker 环境下进行了，需要自行在安装 DockerMysql安装# 拉取镜像 docker pull mysql # 创建文件并写入内容 vim /usr/local/mysql/conf/my.cnf [client] default-character-set=utf8mb4 [mysql] default-character-set=utf8mb4 [mysqld] # 设置东八区时区 default-time_zone = '+8:00' # 设置密码验证规则，default_authentication_plugin参数已被废弃 # 改为authentication_policy #default_authentication_plugin=mysql_native_password authentication_policy=mysql_native_password # 限制导入和导出的数据目录 # 为空，不限制导入到处的数据目录； # 指定目录，必须从该目录导入到处，且MySQL不会自动创建该目录； # 为NULL，禁止导入与导出功能 #secure_file_priv=/var/lib/mysql secure_file_priv= init_connect='SET collation_connection = utf8mb4_0900_ai_ci' init_connect='SET NAMES utf8mb4' character-set-server=utf8mb4 collation-server=utf8mb4_0900_ai_ci skip-character-set-client-handshake skip-name-resolve [mysqld] log-bin=mysql-bin # 开启binlog binlog-format=ROW # 选择ROW模式 server_id=1 # 配置MySQL replaction需要定义，不和Canal的slaveId重复即可 # 运行mysql容器 docker run -p 3306:3306 --name mysql --restart=always --privileged=true \ -v /usr/local/mysql/log:/var/log/mysql \ -v /usr/local/mysql/data:/var/lib/mysql \ -v /usr/local/mysql/conf:/etc/mysql \ -v /etc/localtime:/etc/localtime:ro \ -e MYSQL_ROOT_PASSWORD=123456 -d mysql:latestCanal安装这里建议使用v1.1.6版本，v1.1.7版本使用时报错# mysql创建授权账号 CREATE USER canal IDENTIFIED BY 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%'; -- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ; FLUSH PRIVILEGES; # 先创建一个临时容器 docker run --name canal -p 11111:11111 \ -d canal/canal-server:v1.1.6 # 复制配置文件 docker cp canal:/home/admin/canal-server/conf/canal.properties /mydata/canal/conf docker cp canal:/home/admin/canal-server/conf/example/instance.properties /mydata/canal/conf # 删除该临时容器 docker rm -f canal # 新建canal容器 docker run --name canal -p 11111:11111 \ -v /mydata/canal/conf/instance.properties:/home/admin/canal-server/con…

如何查询树形结构的数据
type: item
date: 2023-04-14T11:44:00+08:00
description: 如何查询树形结构的数据在遇到数据库保存的数据为树形结构，如多级分类列表或者评论等，此处以分类列表举例我们需要向前端返回如下部分数据[ { "id": "1-1", "name": "前端开发", "label": "前端开发", "parentid": "1", "isShow": 1, "orderby": 1, "isLeaf": 0, "childrenTreeNodes": [ { "id": "1-1-1", "name": "HTML/CSS", "label": "HTML/CSS", "parentid": "1-1", "isShow": 1, "orderby": 1, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-10", "name": "其它", "label": "其它", "parentid": "1-1", "isShow": 1, "orderby": 10, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-2", "name": "JavaScript", "label": "JavaScript", "parentid": "1-1", "isShow": 1, "orderby": 2, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-3", "name": "jQuery", "label": "jQuery", "parentid": "1-1", "isShow": 1, "orderby": 3, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-4", "name": "ExtJS", "label": "ExtJS", "parentid": "1-1", "isShow": 1, "orderby": 4, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-5", "name": "AngularJS", "label": "AngularJS", "parentid": "1-1", "isShow": 1, "orderby": 5, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-6", "name": "ReactJS", "label": "ReactJS", "parentid": "1-1", "isShow": 1, "orderby": 6, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-7", "name": "Bootstrap", "label": "Bootstrap", "parentid": "1-1", "isShow": 1, "orderby": 7, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-8", "name": "Node.js", "label": "Node.js", "parentid": "1-1", "isShow": 1, "orderby": 8, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-1-9", "name": "Vue", "label": "Vue", "parentid": "1-1", "isShow": 1, "orderby": 9, "isLeaf": 1, "childrenTreeNodes": null } ] }, { "id": "1-10", "name": "研发管理", "label": "研发管理", "parentid": "1", "isShow": 1, "orderby": 10, "isLeaf": 0, "childrenTreeNodes": [ { "id": "1-10-1", "name": "敏捷开发", "label": "敏捷开发", "parentid": "1-10", "isShow": 1, "orderby": 1, "isLeaf": 1, "childrenTreeNodes": null }, { "id": "1-10-2", "name": "软件设计", "label": "软件设计", "parentid": "1-10", "isShow": 1, "orderby": 2,…

完美解决方案-雪花算法ID到前端之后精度丢失问题
type: item
date: 2023-04-07T22:15:00+08:00
description: 一、现象是这样的下面我把异常的现象给大家描述一下，小伙伴建了一张表，表的主键是id BigINT，用来存储雪花算法生成的ID，嗯，这个没有问题！CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', #其他字段省略 );使用Long 类型对应数据库ID数据。嗯，也没有问题，雪花算法生成的就是一串数字，Long类型属于标准答案！@Data public class User { private Long id; //其他成员变量省略在后端下断点。看到数据响应以JSON响应给前端，正常{ id：1297873308628307970, //其他属性省略 }最后，这条数据返回给前端，前端接收到之后，修改这条数据，后端再次接收回来。奇怪的问题出现了：后端重新接收回来的id变成了： 12978733086283000000 ，不再是 1297873308628307970 二、分析问题我的第一感觉是，开发小伙伴把数据给搞混了，张冠李戴了，把XXX的对象ID放到了YYY对象的ID上。所以，就按照代码从前端到后端、从后端到前端调试跟踪了一遍。从代码的逻辑角度上没有任何问题。这时，我有点烦躁了，真的是耽误我下班了！但开工没有回头箭，既然坐下来了就得帮他解决，不然以后这队伍怎么带？想到这我又静下心来，开始思考。1297873308628300000 ---> 1297873308628307970 这两个数长得还挺像的，似乎是被四舍五入了。此时脑袋里面冒出一个想法，是精度丢失了么？哪里能导致精度丢失？服务端都是Long类型的id，不可能丢失前端是什么类型，JSON字符串转js对象，接收Long类型的是number上网查了一下Number精度是16位（雪花ID是19位的），So：JS的Number数据类型导致的精度丢失。问题是找到了！小伙伴投来敬佩的眼光，5分钟就把这问题发现了。可是发现了有什么用？得解决问题啊！三、解决问题开发小伙伴说：那我把所有的数据库表设计，id字段由Long类型改成String类型吧。我问他你有多少张表？他说100多张吧。100多张表还有100多个实体类需要改还有各种使用到实体类的Service层要改Service等改完Controller层要改关键的是String和Long都是常用类型，他还不敢批量替换小伙伴拿起电话打算订餐，说今晚的加班是无法避免了。我想了想说：你最好别改，String做ID查询性能会下降，我再想想！后端A到前端B出现精度丢失，要么改前端，要么改后端，要么…… 。“哎哎，你等等先别订餐，后端A到前端B你用的什么做的序列化？” 小伙伴告诉我说使用的是Jackson，这就好办了，Jackson我熟悉啊！解决思路：后端的ID(Long) ==> Jackson(Long转String) ==> 前端使用String类型的ID,前端使用js string精度就不会丢失了。 那前端再把String类型的19位数字传回服务端的时候，可以用Long接收么？当然可以，这是Spring反序列化参数接收默认支持的行为。最终方案就是：前端用String类型的雪花ID保持精度，后端及数据库继续使用Long(BigINT)类型不影响数据库查询执行效率。剩下的问题就是：在Spring Boot应用中，使用Jackson进行JSON序列化的时候怎么将Long类型ID转成String响应给前端。方案如下： import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; /** * 解决后端传给前端Long类型，前端处理后有可能失精问题 * 雪花算法1…

SpringBoot2.7.9+Mybatis-Plus3.5.3.1+ShardingSphere-JDBC5.3.1实现分库分表
type: item
date: 2023-03-22T19:06:00+08:00
description: ShardingSphere-JDBC采用Docker实现主从复制实现新增主节点配置文件mkdir -p /wolfcode/mysql/write_db/conf vi /wolfcode/mysql/write_db/conf/my.cnf #新增内容如下 [mysqld] #服务器唯一id，注意在集群中不要出现一样的 server-id=1 binlog_format=STATEMENT log-bin=master-bin log-bin-index=master-bin.index启动MySQL主节点docker run -d --name write_db -p 3306:3306 \ -v /wolfcode/mysql/write_db/conf:/etc/mysql/conf.d \ -v /wolfcode/mysql/write_db/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=root \ mysql进行master容器，登录mysqldocker exec -it write_db /bin/bash mysql -uroot -proot #查看主节点状态 show master status;在mysql内部创建slave同步账号-- 创建slave用户 create user 'slave_user'@'%'; -- 设置密码 alter user 'slave_user'@'%' identified with mysql_native_password by 'root'; -- 授予复制权限 grant replication slave on *.* to 'slave_user'@'%'; -- 刷新权限 flush privileges;从节点1配置信息#退出mysql和docker exit; exit; mkdir -p /wolfcode/mysql/read_db0/conf vi /wolfcode/mysql/read_db0/conf/my.cnf #新增内容如下 [mysqld] #服务器唯一id，注意在集群中不要出现一样的 server-id=2 relay-log-index=slave-relay-bin.index relay-log=slave-replay-bin #启动从节点1 docker run -d --name read_db0 -p 3307:3306 \ -v /wolfcode/mysql/read_db0/conf:/etc/mysql/conf.d \ -v /wolfcode/mysql/read_db0/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=root \ mysql #进入从节点容器，并登录mysql docker exec -it read_db0 /bin/bash mysql -uroot -proot #进入mysql执行命令 #docker inspect -f '{{.Name}} => {{.NetworkSettings.IPAddress }}' $(docker ps -aq) 查看ip change master to master_host='172.17.0.2', master_user='slave_user', master_password='root', master_port=3306, master_log_file='master-bin.000004', master_log_pos=156; #启动从节点1并查看状态 start slave; show slave status \G # Slave_IO_Running: Yes Slave_SQL_Running: Yes从节点2配置信息#退出mysql和docker exit; exit; mkdir -p /wolfcode/mysql/read_db1/conf vi /wolfcode/mysql/read_db1/conf/my.cnf #新增内容如下 [mysqld] #服务器唯一id，注意在集群中不要出现一样的 server-id=3 relay-log-index=slave-relay-bin.index relay-log=slave-replay-bin #启动从节点2 docker run -d --name read_db1 -p 3308:3306 \ -v /wolfcode/mysql/read_db1/conf:/etc/mysql/conf.d \ -v /wolfcode/mysql/read_db1…

Java基础
type: item
date: 2023-03-03T19:31:00+08:00
description: Java快速入门Java基础语法Java基础语法类型转换运算符键盘录入技术程序流程控制程序流程控制是控制代码怎么去执行的顺序结构代码按照顺便正常执行分支结构循环结构跳转关键字（break,continue）随机数Random类数组作用一个容器，用于在程序中存储一批同种类型的数据定义遍历常见问题方法作用封装一段代码的语法结构，可以被重复调用，以此提高代码的复用性，提高开发效率，让程序逻辑更清晰定义格式修饰符 返回值类型 方法名（形参列表）{ 方法体代码（需要执行的功能代码） return 返回值; } //如果方法没有返回结果，返回值类型申明为:void //方法需要调用才能执行注意事项方法重载return跳出并立即结束当前方法的执行面向对象编程（oop）定义（思想）java 万物皆对象创建对象构造器this关键字封装常用API（String、ArrayList)StringArrayListstatic饿汉单例代码示例：/** 使用饿汉单例实现单例类 */ public class SingleInstance { /** 2、饿汉单例是在获取对象前，对象已经提前准备好了一个。 这个对象只能是一个，所以定义静态成员变量记住。 */ public static SingleInstance instance = new SingleInstance(); /** 1、必须把构造器私有化。 */ private SingleInstance(){ } }懒汉单例代码示例：/** 懒汉单例 */ public class SingleInstance2 { /** 2、定义一个静态的成员变量负责存储一个对象。 只加载一次，只有一份。 注意：最好私有化，这样可以避免给别人挖坑！ */ private static SingleInstance2 instance; /** 3、提供一个方法，对外返回单例对象。 */ public static SingleInstance2 getInstance() { if(instance == null){ // 第一次来拿对象 ：此时需要创建对象。 instance = new SingleInstance2(); } return instance; } /** 1、私有化构造器 */ private SingleInstance2(){ } } 继承权限修饰符final常量枚举抽象类接口多态定义多态是同一个行为具有不同的表现形式或形态的能力同一方法可以根据发送对象的不同而采用不同的行为方式多态就是事物的多种形态，一个对象在不同条件下所表现的不同形式存在条件继承或实现：在多态中必须存在有继承或实现关系的子类和父类方法的重写：子类对父类中的某些方法进行重新定义（重写，使用@Override注解进行重写）基类引用指向派生类对象，即父类引用指向子类对象，父类类型：指子类对象继承的父类类型，或实现的父接口类型格式父类类型 变量名 = new 子类类型（）； 然后通过 变量名.方法名（）调用在子类中重写的方法 多态体现为父类引用变量可以指向子类对象：定义了一个父类类型的引用，指向新建的子类类型的对象，由于子类是继承他的父类的，所以父类类型的引用是可以指向子类类型的对象的特点多态情况下，子类和父类存在同名的成员变量时，访问的时父类的成员变量多态情况下，子父类存在同名的非静态成员方法时，访问的是子类中重写的方法多态情况下，子父类存在同名的静态成员变量成员方法时，访问的是父类的成员函数多态情况下，不能访问子类独由的方法instanceof关键字格式变量名 instanceof 数据类型 //如果变量属于该数据类型或者其子类型，返回true //如果变量不属于该数据类或者其子类型，返回false代码示例 public class DemoApplication { public static void main(String[] args) { //向上转型 //父类类型 对象 = new 子类类型（） Animal animal = new Cat(); //向下转型 //子类类型 子类变量名 = （子类类型） 父类变量名 if ( animal instanceof Cat){ Cat cat = (Cat) animal; cat.sleep(); }else if(animal instanceof Dog){ Dog dog = (Dog) animal; dog.walk(); } } }集合数组的长度是固定的。集合的长度是可变的。数组中存储的是同一类型的元素，可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候，使用集合进行存储。Colloction…

Java中HashMap、LinkedHashMap和TreeMap区别使用场景
type: item
date: 2021-02-26T11:24:00+08:00
description: HashMap中k的值没有顺序，常用来做统计。2.LinkedHashMap吧。它内部有一个链表，保持Key插入的顺序。迭代的时候，也是按照插入顺序迭代，而且迭代比HashMap快。TreeMap的顺序是Key的自然顺序（如整数从小到大），也可以指定比较函数。但不是插入的顺序。4.Hashtable与 HashMap类似,它继承自Dictionary类、不同的是:它不允许记录的键或者值为空;它支持线程的同步、即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。 HashMap HashMap 的底层数据结构是 “数组 + 链表”LinkedHashMapLinkedHashMap 拥有 HashMap 的所有特性，它比 HashMap 多维护了一个双向链表，因此可以按照插入的顺序从头部或者从尾部迭代，是有序的，不过因为比 HashMap 多维护了一个双向链表，它的内存相比而言要比 HashMap 大，并且性能会差一些，但是如果需要考虑到元素插入的顺序的话， LinkedHashMap 不失为一种好的选择 TreeMap与 HashMap 不同， TreeMap 的底层就是一颗红黑树，它的 containsKey , get , put and remove 方法的时间复杂度是 log(n) ，并且它是按照 key 的自然顺序（或者指定排序）排列，与 LinkedHashMap 不同， LinkedHashMap 保证了元素是按照插入的顺序排列。 典型回答：Hashtable、HashMap、TreeMap都实现了Map接口，使用键值对的形式存储数据和操作数据。Hashtable是java早期提供的，方法是同步的（加了synchronized）。key和value都不能是null值。HashMap的方法不是同步的，支持key和value为null的情况。行为上基本和Hashtable一致。由于Hashtable是同步的，性能开销比较大，一般不推荐使用Hashtable。通常会选择使用HashMap。HashMap进行put和get操作，基本上可以达到常数时间的性能TreeMap是基于红黑树的一种提供顺序访问的Map，和HashMap不同，它的get或put操作的时间复杂度是O(log(n))。具体的顺序由指定的Comparator来决定，或者根据键key的具体顺序来决定。