XiaoJian's Blog - DS_xiao_jian
type: channel
link: https://xiaojian.domcer.com/index.php/author/1/
description: DS_xiao_jian
items: 多线程编程的一些面试题
items: MQ相关面试题(RocketMQ)
items: JVM一些面试题
items: 数据库MySQL的一些面试题
items: Java基础面试题
items: 自我介绍话术以及项目介绍
items: Spring 的一些面试题
items: Spring Boot 单服务解决前端跨域问题的配置
items: 关于集合的一些面试题
items: Spring Boot WebSocket 的使用与配置

多线程编程的一些面试题
type: item
date: 2023-08-27T09:14:00+08:00
description: volatile关键词的作用是什么适用于多线程环境，限制代码重排序优化，提供可见性保证。不保证原子性，不能替代锁。volatile如何保证线间可见和避免指今重排可见性：对于被声明为 volatile 的变量，在一个线程中的写操作会立即刷新到主内存，同时，其他线程读取该变量时会从主内存中获取最新的值。这样可以确保对该变量的修改对其他线程是可见的，避免了线程之间的数据不一致性。禁止指令重排序：volatile 关键字禁止编译器和处理器对被修饰变量相关指令进行重排序优化。这样可以确保在多线程环境下，程序的执行顺序与程序员编写的顺序保持一致，避免了可能导致并发问题的指令重排序。创建线程的几种方式继承Thread类并重写run方法，然后通过创建该类的实例来启动线程。实现Runnable接口并实现run方法，然后通过创建Thread对象，将Runnable实例作为参数传递给Thread，并调用start()方法启动线程。使用线程池来管理和复用线程，可以通过Executors类的工厂方法创建不同类型的线程池。通过将Runnable或Callable实例提交给线程池，线程池会自动调度和执行任务。调用start方法和调用run方法的区别start是启动线程的方法，run是线程中方法的具体实现，如果直接调用run方法他不会创建一个新的线程线程的常用调度方法sleep 让当前线程休眠指定的毫秒数。yield 用于提高线程的执行效率，但不能保证一定会让出CPU。join 会阻塞当前线程，直到被调用的线程执行完毕后才会继续执行当前线程。wait notify notifyAll 这些方法用于实现线程间的协作和通信。interrupt 需要在线程的代码中通过检查中断标志位，并做相应处理来实现线程的中断。线程的状态有几种新建 New运行 Runnable阻塞 Blocked等待 Waiting超时等待 Timed Waiting终止 TerminatedThreadLocal本质是什么是一种线程封闭的机制，它是Java中的一个类，用于在多线程环境下为每个线程提供独立的变量副本，使得每个线程都可以访问自己独立的变量，互不干扰。使用他可能会造成内存泄露，所以要注意管理好变量的生命周期，确保在适当的时候进行清理和回收，以免造成资源浪费。ThreadLocal内存泄漏是什么引起的ThreadLocal内存泄漏通常由于线程池未正确清理或ThreadLocal对象生命周期过长。在使用ThreadLocal时，要及时清理ThreadLocal，特别是在使用线程池时，确保在线程执行前或执行后重置ThreadLocal，以避免内存泄漏。ThreadLocalMap的结构他是ThreadLocal类中的一个内部类，用于在多线程环境下为每个线程提供独立的变量副本。它是一个哈希表，使用ThreadLocal对象的弱引用作为key，存储线程独立的变量副本作为value。通过ThreadLocalMap，每个线程都可以独立地维护自己的变量副本，实现了线程级别的数据隔离。ThreadLocalMap使用开放地址法解决哈希冲突，并在扩容时进行再散列。为了避免内存泄漏，使用完ThreadLocal后，应该及时调用remove()方法清理当前线程的ThreadLocal副本。父子线程怎么共享数据通过在父线程和子线程之间使用共享变量来实现数据共享。使用ThreadLocal可以在父线程和子线程之间实现数据隔离，每个线程都有独立的变量副本。父线程设置ThreadLocal变量，子线程可以通过ThreadLocal访问和修改自己的副本。父线程和子线程可以通过线程通信机制，如wait/notify或者Lock/Condition来实现数据的交换和共享。使用线程池中的ThreadLocal：如果使用线程池创建线程，可以通过ThreadLocal在父线程和子线程之间传递数据。synchronized锁升级过程在没有竞争的情况线程可以直接访问不需要加锁，当有一个线程访问被synchronized修饰的代码块的时候jvm会给他上一个偏向锁，这个线程可以在未来的执行中直接获取锁，无需再进行同步操作，提高了单线程场景下的性能，当多线程尝试获取同一个锁时JVM会尝试将偏向锁升级为轻量锁，适用于锁竞争不激烈的情况，如果轻量锁尝试失败或者出现堵塞就会升级重度锁。synchronized锁优化synchronized锁的优化包括偏向锁、轻量级锁、自旋锁、锁消除和锁粗化。偏向锁适用于无竞争情况，轻量级锁适用于竞争不激烈的情况，自旋锁避免线程阻塞，锁消除消除不必要的锁开销，锁粗化合并连续的同步操作。synchronized锁消除这个是在jvm在运行的时候会进行逃逸分析的优化措施，当jvm发现这个代码不会被其他线程所访问的时…

MQ相关面试题(RocketMQ)
type: item
date: 2023-08-26T21:41:00+08:00
description: 你使用过的MQ技术有哪些RabbitMQ、RocketMQ、Kafka如何保证消息不丢失如果在生产者中丢失，可以使用事物机制，但是他是同步的很消耗性能，也可以开启确认机制，生产者在将消息发送到MQ后，等待MQ的确认响应。只有在收到MQ的确认后，生产者才认为消息已安全发送。如果没有收到确认，生产者可以重试发送消息。如果在MQ中丢失，可以开启持久化将消息存储到硬盘，可以保证持久性。如果在消费者中丢失，消费者在成功处理消息后发送ACK给MQ，表示消息已被处理。如果没有收到ACK，MQ可以将消息重新投递给其他消费者。RocketMQ如何保证消息的可用性 / 可靠性 / 不丢失呢？在生产者是通过请求确认机制，同步发送的时候没有发生议程就会响应OK，发生异常就会响应异常，异步发送的时候，要在回调方法中检查，如果发送失败应该去重试在存储是配置可靠优先的Broker参数来避免宕机消息丢失，消息会持久化到CommitLog日志中，即使宕机也会重新消费，他Broker的刷盘机制不管是同步或者异步都可以保证消息一定存储在内存中，它是生产者发送消息后等数据持久化到磁盘之后再返回响应给生产者。在消费者是消费时的确认机制，要等都执行完了后再发送消费确认。RocketMQ如何处理消息重复的问题呢？(如何保证消息幂等性)可以在业务消费逻辑上保证幂等性，就是多次调用和一次调用都是一样的也可以消息去重，就是保证每条消息都有一个唯一id，可以简历一个消费记录表，拿到消息后会去数据插入这个唯一id，如果出现重复消费就会出现主键冲突，那就不再处理这条消息就可以了。如何处理消息积压可以扩充消费者，来提高消费能力也可以把消息迁移到新的Topic，临时的Topic可以扩容消费者这样就可以处理消息积压RocketMQ中如何实现顺序消息他分为部分顺序和全局顺序全局顺序要把Topic 的读写队列数设置为 一，然后 Producer Consumer 的并发设置，也要是一，这样牺牲了高并发变成单线程他就可以实现顺序消息了。部分顺序的话要在Producer把同 ID 的消息发送到同一个 Message Queue里面，消费的时候要从同一个Message Queue里面顺序处理RocketMQ中如何实现消息过滤在 Broker 端按照 Consumer 的去重逻辑进行过滤，可以避免了无用的消息传输到 Consumer 端，缺点是加重了 Broker 的负担，实现起来相对复杂。在 Consumer 端过滤，比如按照消息设置的 tag 去重，缺点是有大量无用的消息到达了 Consumer 端只能丢弃不处理。RocketMQ中如何实现延迟消息在发送消息时，可以设置消息的延迟级别。RocketMQ 提供了几种预定义的延迟级别，分别表示不同的延迟时间。实际上他就是一个临时存储+定时轮询的方式实现了延迟消息，当Topic收到延迟消息后他不会直接把消息放到队列中去，会创建一个定时的队列，通过一个定时任务轮询这些队列，到期后，把消息投递到目标 Topic 的队列中，从而实现了延迟消息的效果RocketMQ中的死信死信队列就是用于处理无法被正常消费的消息的，当消息消费失败超过最大重试次数的时候就会把消息放入死信队列，我们应该监控死信队列去把无法消费的消息手动消费RocketMQ 中 Broker 是怎么保存数据的呢？主要的存储文件包括 CommitLog 文件、ConsumeQueue 文件、Indexfile 文件。CommitLog他默认大小1G，消息主要是顺序写入日志文件，当文件满了，写入下一个文件。ConsumeQueue是基于 Topic 的 CommitLog的索引文件IndexFile提供了一种可以通过 key 或时间区间来查询消息的方法。RocketMQ是怎么会消息文件进行读写的PageCache、顺序读写、零拷贝PageCache： 利用操作系统的PageCache来缓存文件内容，提高写入和读取的效率，避免频繁的磁盘IO操作。顺序读写： 消息的写入和读取都是基于顺序的方式进行，减少了磁盘寻道操作，提高了读写效率。零拷贝技术： 在数据传输过程中避免数据从一个缓冲区拷贝到另一个缓冲区，减少了数据拷贝的开销，提高了数据传输效率。

JVM一些面试题
type: item
date: 2023-08-26T08:29:00+08:00
description: JVM内存结构程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存对象创建的过程JVM如何保证线程安全的JVM通过分配内存空间、初始化对象和调用构造函数等阶段保证对象创建的线程安全性。尽管创建过程中存在线程不安全的阶段，JVM通过独占性保证每个线程在初始化和构造对象时不受干扰。然而，在构造函数中可能存在耗时操作，需注意其他线程可能在对象未完全构造前访问，导致问题。因此，在编写构造函数时，要考虑多线程并发访问情况，确保构造函数的线程安全性。什么是内存溢出和内存泄漏常见的内存溢出和内存泄漏操作大对象长期不被清理，长生命周期对象持有生命周期对象，递归调用层次太深或者方法调用过多，IO流没有关闭CGRoots,是什么垃圾收集器通过判断哪些对象是可以被访问到的来确定哪些对象是存活的，而这些能够被访问到的对象通常包括以下几种类型虚拟机栈中的本地变量和引用方法区中的类静态属性引用的对象方法区中常量引用的对象本地方法栈中JNI（Java Native Interface）引用的对象如何判断对象仍然存活通过引用计数法与可达性分析方式来判断对象是否存活finalize方法功能是什么堆内存结构常见的垃圾回收算法标记-清除算法，它是一个基础回收算法，它分为两个阶段：标记阶段，从根对象出发，标记所有可达的对象；清除阶段，遍历堆中的所有对象，清除未标记的对象，回收空间，它缺点是堆空间会产生碎片，影响堆空间连续分配复制算法，将堆分为两个区域，每次只使用其中一个区域。当一个区域用尽时，将活动对象复制到另一个区域，然后清除已经不再使用的对象。复制算法避免了内存碎片，但要求堆空间至少为对象的两倍。标记-压缩算法，它先标记所有可达的对象，将所有未标记的对象压缩到堆的一端然后清除，并更新对象引用，它解决了碎片问题，但是压缩也会占用性能分代回收算法，它是根据对象的存活周期不同分为新生代和老年代，新生代使用复制清除算法，老年代使用标记算法，压缩算法三色标记算法，是一种增量算法，使用三种颜色来标记对象状态，避免长时间停顿并发标记-清除算法，使用并发线程进行垃圾回收，减少垃圾回收的停顿时间，提高系统的响应性。什么时候触发youngGC当Eden没有足够空间的时候会触发youngGC什么时候触发FulIGC对象youngGC触发前检查Old区是否充足，youngGC执行后将对象移动到Old区发现没有足够空间，在Old区空间不足的时候什么时候进入老年代在YoungGC每次迭代都会增加年龄。当年龄达到15的时候就会移动对象到老年代，当占用大量连续内存空间的对象回直接在老年代创建常见的垃圾回收器有哪些垃圾回收器和垃圾回收算法的关联什么是STW什么是OOPMAPCMS的垃圾回收过程G1的垃圾回收过程线上服务CPU过高如何排查linux的话使用top命令查看是哪个进程导致的，如果是java进程通过PID查询他进程ID 在通过线程ID，再通过线程ID打印具体哪个方法导致的，再去代码中找导致CPU飙升的地方

数据库MySQL的一些面试题
type: item
date: 2023-08-25T11:34:00+08:00
description: 数据库设计的三大范式每一列字段都是原子的，比如产品编码列中，每个单元只能有一个编码；在满足第一范式的基础上，确保非主键属性完全依赖于主键；在满足第一二范式，确保非主键属性之间没有传递依赖。drop delete truncate区别dorp是用于表的删除，这个删除是无法进行恢复的；delete是用于表中数据的一行，可以使用ROLLBACK进行回滚的；truncate是清空表，会删除表中所有数据并且保留薄的结构，也是无法使用ROLLBACK进行撤销操作。count(1) count() count(列名)区别count(1)和count()都是统计所有行数，而count(1)会忽略具体列中的具体数据查询效率较高，count(列名)是查询该列中非NULL的所有列。SQL查询语句的执行顺序FROM 子句 ：从指定的表中获取数据。WHERE 子句 ：根据指定的条件筛选数据。GROUP BY 子句 ：根据指定的列对数据进行分组。HAVING 子句 ：对分组后的数据进行筛选，类似于 WHERE，但作用于分组数据。SELECT 子句 ：选择要查询的列，进行计算或数据处理。ORDER BY 子句 ：对结果进行排序。LIMIT/OFFSET 子句 ：根据指定的数量和偏移量返回结果的子集。InnoDB和MyISAM区别InnoDB 支持事务而MyISAM不支持事物InnoDB是行级锁而MyISAM是表级锁InnoDB支持崩溃恢复而MyISAM没有崩溃恢复机制InnoDB从 MySQL 5.6.4 开始支持全文索引，而MyISAM很早就支持不过它性能和功能方面相对较弱InnoDB会将数据和索引存储在内存缓存中，适合于对内存要求较高的应用，而MyISAM 也支持内存缓存，但与 InnoDB 相比，性能相对较低索引分类主键索引（Primary Key Index） ：主键索引是一种唯一性索引，用于标识表中的唯一记录。在主键索引下，数据行的值是唯一的，因此可以快速定位到特定的数据行。唯一索引（Unique Index） ：唯一索引保证索引列的值在整个表中都是唯一的，但可以有 NULL 值。通过唯一索引可以确保数据的完整性，同时提高检索效率。普通索引（Normal Index 或 Non-Unique Index） ：普通索引用于加速对表中数据的搜索和检索，但允许索引列有重复值。适用于频繁用于查询、排序和过滤的列。全文索引（Full-Text Index） ：全文索引用于对文本数据进行全文搜索，允许对文本内容进行关键词匹配。适用于需要进行文本搜索的场景，如文章内容、评论等。空间索引（Spatial Index） ：空间索引用于处理地理空间数据，如地理坐标点、地理区域等。可以用于查询地理位置相关的信息，如距离计算、区域判断等。组合索引（Composite Index 或 Compound Index） ：组合索引是由多个列组合而成的索引，用于加速多个列的联合查询。可以优化查询条件中涉及多个列的情况。索引在什么情况下会失效在使用模糊查询、使用or链接的条件、数据量过少、连接查询的时候选择了不正确的链接索引，这些都会导致索引失效。索引不适合哪些场景不适用于数据量小、增删改频繁的、大字段的列、维度表、频繁的批量操作索引存储的数据结构一般使用B+数的结构 也有B树结构为什么使用B+而不是普通二又树因为他更适合硬盘存储，减少了磁盘 I/O 次数，有序性强，平衡性好，能够减少高层索引的维护成本，查询效率稳定。聚簇索引和非聚簇索引区别区别主要在于数据存储方式、唯一性、查询性能和更新性能。聚簇索引下数据行的物理存储顺序与索引顺序相同，用于范围查询，要求键值唯一。非聚簇索引维护了键和数据行的映射，适用于插入和更新频繁的情况。选择索引类型要根据具体需求和数据库表的特性。回表和覆盖索引回表是指查询过程中需要通过索引再次查找表中的数据行，而覆盖索引是指索引已经覆盖了查询所需的字段，无需回表操作，从而提高了查询性能。优化查询性能时，选择合适的索引和避免回表操作是重要的考虑因素。什么是最左匹配原则最左匹配原则是数据库索引优化中的一个概念，在复合索引中，从索引的左边开始匹配查询条件，以便实现最佳的索引利用。如果一个复合索引包含多个列，那么在查询时，数据库会从复合索引的最左边开始，依次匹配查询条件中的列。只有当查询条件中的列依次从左到右与索引中的列匹配，才能有效地利用这个索引。一旦遇到一个不匹配的列，索引就不能继续使用。MySQL中的锁有哪几种共享锁（Shared Lock，也称读锁）：共享锁允许多个事务同时获取同一资源的锁，用于保证读操作的并发性，多个事务可以共享同一个资源的共享锁，不会互相阻塞。排他锁（Exclusive Lock，也称写锁）：排他锁只允许一个事务获取资源的锁，其他事务…

Java基础面试题
type: item
date: 2023-08-22T09:27:00+08:00
description: Java 是值传递，还是引用传递java语言是值传递，如果是基本数据类型他是值的拷贝，不会影响原来的变量，如果是引用数据类型他是地址的拷贝因为指向了一个对象所以可以修改对象中的变量的值，如果是String类型他虽然是地址拷贝，但是String是不可修改的所以不能改原变量的值。深拷贝和浅拷贝如果是基本数据类型无论深拷贝还是浅拷贝都是直接复制一份新的如果是对象，浅拷贝是复制了一份地址，深拷贝是在堆空间中又复制了一份对象== 和 equals 的区别== 如果是基本数据类型他是直接比对值，如果是引用数据类型他比对的是地址值equals 他是Object中的方法，他如果不重写equals和hashCode方法的话是和==效果一样的都是比对地址值，如果重写了就会根据重新的内容去比对对象中地址值。String 和 StringBuilder、StringBuffer 的区别？String在创建后是不可更改的StringBuffer和String差不多他是线程安全的StringBuilder也是做字符串拼接的但是他是线程不安全的String str1 = new String("abc") 和 String str2 = "abc" 和 区别？区别的话是创建对象数量不一样，String str1 = new String("abc") 首先会在堆空间中创建一个String 如果字符串常量池中没有"abc"的话就会创建2个对象，如果有就是1个对象，而String str2 = "abc"会直接查字符串常量池中是否已经存在，如果存在就不会创建对象，如果不存在就会创建1哥对象。说一下你对注解的理解注解可以理解为是一个标记，可以在编译或者运行的时候处理一些事情，比如lombok可以在编译期间生成get set 构造等方法。什么是反射？应用？原理？就是在运行期间动态地获取类信息、创建类实例、调用类方法，原理的话就是JVM 进行类加载的时候，会加载字节码文件，将类型相关的所有信息加载进方法区，反射就是去获取这些信息，然后进行各种操作，在Spring中的一些注解都是使用了反射。JDK1.8 都有哪些新特性接口可以有默认方法Lambda 表达式和函数式接口Stream流式操作日期时间 APIOptional 类Stream 流用过吗用过的，在集合的操作的时候我喜欢使用Stream进行操作他可以使用map方法把集合中每个值做一个映射去操作Filter方法可以过滤一下内容，collect方法可以把每一个映射重新聚合成List、Map等

自我介绍话术以及项目介绍
type: item
date: 2023-08-20T18:08:00+08:00

Spring 的一些面试题
type: item
date: 2023-08-14T20:55:00+08:00
description: Spring中的循环依赖多个Bean之间互相引用，形成了一个循环的依赖关系。这种情况下，当Spring容器初始化这些Bean时，可能会出现问题，因为每个Bean在初始化时需要先创建依赖的Bean，而依赖的Bean又需要依赖其他的Bean，从而导致循环依赖问题。Spring怎么解决循环依赖的Spring会提前暴露还未初始化完成的Bean对象，这样在创建Bean时，即使有循环依赖，也可以通过提前暴露的引用来解决。这种方式适用于单例Bean，Spring也会为存在循环依赖的Bean创建一个代理对象，先创建一个代理对象作为依赖的引用，等到Bean初始化完成后，再将代理对象替换为实际的Bean，也可以使用构造器的方式进行注入，这样Bean在初始化时就可以得到它所依赖的Bean。为什么要三级缓存?二级不行吗?不行，他三级缓存是为了生成代理对象，如果是没有代理对象的情况他使用二级缓存是没有问题的。@Autowired的实现原理当Spring容器初始化时，会自动查找所有的Bean定义，然后尝试根据类的类型、名称等信息进行自动装配。在自动装配的过程中，Spring会查找与字段或方法参数类型匹配的Bean，然后将匹配的Bean注入到标注了@Autowired的地方。Spring会通过Bean的构造函数、Setter方法或直接字段注入来完成依赖注入。怎么理解Spring中的AOP他是一种面向切面编程的思想，能够实现横切关注点的模块化，提高代码的复用性和可维护性，同时把横切关注点集中管理，使代码更清晰和可维护。你平时怎么使用AOP的在一些日志的打印和性能监控方面说说JDK动态代理和CGLIB动态代理JDK动态代理适用于接口，CGLIB动态代理适用于无接口类，通过生成子类实现代理。选用取决于是否有接口，JDK更通用，CGLIB适合无接口情况。Spring事务隔离级别Spring框架支持以下五种事务隔离级别：DEFAULT（默认） ：使用底层数据库默认的事务隔离级别。READ_UNCOMMITTED（读未提交） ：允许一个事务读取另一个事务未提交的数据。可能导致脏读、幻读、不可重复读问题。READ_COMMITTED（读已提交） ：保证一个事务只能读取到另一个事务已提交的数据，可以避免脏读问题。REPEATABLE_READ（可重复读） ：保证一个事务在整个过程中可以多次读取同一数据，不会受其他事务的影响。可以避免脏读、不可重复读问题。SERIALIZABLE（串行化） ：最高的隔离级别，确保所有事务按照顺序依次执行，避免所有并发问题。Spring事务传播行为Spring框架中定义了七种事务传播行为，用于控制方法在不同事务上下文中的行为。以下是这些事务传播行为：REQUIRED（默认） ：如果当前存在事务，则加入该事务；如果没有事务，则创建一个新事务。是最常用的传播行为。SUPPORTS ：如果当前存在事务，则加入该事务；如果没有事务，则以非事务方式执行。MANDATORY ：要求当前必须存在事务，否则会抛出异常。REQUIRES_NEW ：无论当前是否存在事务，都会创建一个新事务，并在方法执行完毕后提交。NOT_SUPPORTED ：无论当前是否存在事务，都会以非事务方式执行。NEVER ：要求当前不存在事务，如果存在则抛出异常。NESTED ：如果当前存在事务，则在嵌套事务中执行；如果没有事务，则执行REQUIRED类似的操作。声明式事务在哪些情况下会失效当在同一个类的方法中调用另一个方法，而调用的方法带有声明式事务注解，事务可能会失效；捕获了异常而不重新抛出，声明式事务可能无法正确处理异常，导致事务无法回滚；非公开方法（private、protected等）上的声明式事务无法被代理，因为代理通常基于接口或类的public方法，在声明式事务中，如果使用编程式事务（如TransactionTemplate）来管理事务，可能会导致事务失效；如果运行时异常被捕获并不再次抛出，事务可能无法正确回滚；在多线程环境中，声明式事务可能无法正确传播，因为线程之间的事务隔离可能导致事务不生效。Spring MVC的工作流程客户端HTTP请求到Spring MVC应用的前端控制器DispatcherServletDispatcherServlet是Spring MVC的核心控制器，它接收到所有的客户端请求，并将请求分发给不同的控制器。：DispatcherServlet会查询Handler Mapping，它会根据请求的URL找到匹配的控制器（Handler）。找到匹配的控制器后，DispatcherServlet会将请求交给对应的Controller进行处理。Controller处理请求后，会返回一个ModelAndView对象，其中包含了模型数据和视图名。View …

Spring Boot 单服务解决前端跨域问题的配置
type: item
date: 2023-08-04T16:44:00+08:00
description: @Configuration public class GlobalCorsConfig { /** * 允许跨域调用的过滤器 */ @Bean public CorsFilter corsFilter() { // 创建一个跨域的配置类 CorsConfiguration config = new CorsConfiguration(); // 允许白名单域名进行跨域调用 // 这个是配置允许哪些域名进行跨域调用 *代表所有域名 // 这个配置可以写多个 config.addAllowedOrigin("*"); // 允许跨越发送 cookie config.setAllowCredentials(true); // 放行全部原始头信息 config.addAllowedHeader("*"); // 允许所有请求方法跨域调用 // 这个配置也可以写多个 POST PUT GET DELETE OPTIONS config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 访问这个项目的所有请求都会跨域调用 source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

关于集合的一些面试题
type: item
date: 2023-07-16T14:30:00+08:00
description: ArrayList和Vector有什么区别?Vector 他是线程安全的动态数组 Vector 扩容是扩大一倍，ArrayList是或扩充50%ArrayList 和 LinkedList 有什么区别？ArrayList底层是数组，他总体来说增删慢查询快，因为每次增删都需要调整数据在数组中的位置，除了在头尾，查询可以根据下标直接拿到LinkedList底层是双向链表，他总体来说查询慢增删快，因为增删只需要调整数据的前后指针即可，除了头尾，但是他查询的话需要遍历整个链表ArrayList 的扩容机制了解吗？他会先检查是否需要扩容，如果当前容量+1大于数组长度就会触发扩容，在扩容的时候会新建出一个原来数组长度的1.5倍然后把旧数据拷贝过去，再进行添加新的元素。有哪几种实现 ArrayList 线程安全的方法？使用CopyOnWriteArrayList使用Vector使用Collections.synchronizedList 包装 ArrayList使用 ArrayList 时，应用程序通过同步机制去控制 ArrayList 的读写。HashMap的数据结构在1.8以前是数组+链表的结构，1.8以后是会在链表数据超过一定阈值的时候转换为数组+红黑树的结构来提高查询效率。为什么使用红黑树而不是二叉树或平衡树因为二叉树或平衡树他们横向扩展差，数据过多的话会让树的高度变高导致查找、插入和删除操作的效率下降，而红黑树就会解决这个问题HashMap的put流程首先会对key做哈希运算取到哈希值，会根据这个哈希值找到他在数组中的位置，这个时候会出现两种情况，如果当前的位置是空的就会直接将值插入，如果这个位置不是空的就会比对他的key值是否相等，如果相等就会对已有的值进行替换，如果不想等就会说明发生了哈希冲突会在这个位置添加一个新的节点，如果链表长度超过8的时候且当前HashMap的容量达到了一个较大的阈值（负载因子超过0.75），会触发扩容操作，将HashMap的容量增加一倍，并重新计算每个元素在新数组中的索引位置。HashMap的get流程会先根据key做哈希运算取到哈希值，根据这个哈希值找到数组的对应位置，如果这个位置没值就会返回null，如果有值那么有可能是多个或者一个，一个的话就会直接取到值并进行返回，如果是多个值会遍历链表并判断是否是这个key并取到值，如果遍历会没有值就会返回null。HashMap扩容机制如果链表长度超过8的时候且当前HashMap的容量达到了一个较大的阈值（负载因子超过0.75），会触发扩容操作，将HashMap的容量增加一倍，并重新计算每个元素在新数组中的索引位置，扩容后，负载因子会相应地降低，这样可以提供更好的性能和更低的冲突概率。初始化HashMap传递初始容量17数组长度实际为多少HashMap在设置初始容量并不是直接设置数组长度，而是通过将传递的初始容量减去1后进行位运算，最终的数组长度就是经过位运算得到的值，所以初始容量是17数组长度实际为32。如何解决Hash冲突链表+红黑树，通过将链表转换为红黑树，可以减少链表查找的时间复杂度。这样，即使在有大量哈希冲突的情况下，HashMap仍然能够保持较高的性能。链表转红黑树的阈值为什么是8根据以实际测试与优化得出阈值为8时是一个平衡性能和内存消耗的考量，如果全部转为红黑树他每个节点都需要存颜色、父节点、左子节点、右子节点的指针会导致内存消耗增加。扩容因子为什么是0.75经过实践和优化，这个值既允许在数组中保持一定的空闲槽位，以减少冲突的概率，又可以有效利用内存空间，所以扩容因子选择为0.75是为了在HashMap的负载因子和内存利用率之间找到一个平衡点，以提供较好的性能和空间利用率。为什么 HashMap 的容量是 2 的倍数呢？我看过HashMap的底层源码他注释上写的容量是2的时候在时间与存储上都是最佳的。HashMap1.7和1.8的区别数据结构从数组+链表 改成了数组+链表+红黑树链表的插入方式从头插法改成尾插法，因为头插法在多线程环境扩容的时候可能会产生死循环1.7是先判断是否需要扩容再插入数据，1.8以后是先插入数据再判断是否需要扩容1.7散列函数做了4次位移和4次异或，1.8只做了1次HashMap和HashTable的区别?HashTable是线程安全的，HashMap是线程不安全的HashTable的key和balue都不可以出现null，而HashMap可以HashTable默认容量是11并且扩容不要求一定是2的整数次幂触发扩容时会变成原来的2倍加1，HashMap默认容量是16扩容必须是2的整数次幂触发扩容时会变成原来的2倍分段锁CAS+synchronized如何保证HashMap的线程安全HashMap内部节点是有序的吗Link…

Spring Boot WebSocket 的使用与配置
type: item
date: 2023-07-04T16:17:22+08:00
description: maven坐标 org.springframework.boot spring-boot-starter-websocket 配置类/** * WebSocket配置类，用于注册WebSocket的Bean */ @Configuration public class WebSocketConfiguration { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }WebSocket 服务/** * WebSocket服务 */ @Component @ServerEndpoint("/ws/{sid}") public class WebSocketServer { //存放会话对象 private static Map<String, Session> sessionMap = new HashMap(); /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("sid") String sid) { System.out.println("客户端：" + sid + "建立连接"); sessionMap.put(sid, session); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, @PathParam("sid") String sid) { System.out.println("收到来自客户端：" + sid + "的信息:" + message); } /** * 连接关闭调用的方法 * * @param sid */ @OnClose public void onClose(@PathParam("sid") String sid) { System.out.println("连接断开:" + sid); sessionMap.remove(sid); } /** * 群发 * * @param message */ public void sendToAllClient(String message) { Collection<Session> sessions = sessionMap.values(); for (Session session : sessions) { try { //服务器向客户端发送消息 session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } }