- 博客(90)
- 收藏
- 关注
原创 19 | 散列表(中): 如何打造一个工业级水平的散列表?
/capicity 表示散列表的大小。优点 - 对内存利用率高 - 对大装载因子的容忍度更高 - 更加灵活,支持更多优化策略。先从新散列表中查找,如果没有找到,再去老散列表中查找。// 返回的是 Java 对象的 hash code。装载因子 = 填入表中的元素个数 / 散列表的长度。每次插入一个数据到散列表,我们都重复上面的过程。中,并且从老的散列中拿出一个数据放到新散列表。删除数据时,需要特殊标记已删除掉的数据。,但并不将老数据搬移到新散列表中。数据量比较小,装载因子小的时候。
2025-04-16 22:48:56
238
原创 18 | 散列表(上): Word 文档中的单词拼写检查功能是如何实现的?
顾名思义,是一个函数。可以定义成 hash(key),其中 key 表示元素的键值,hash(key) 的值表示经过散列函数计算得到的散列值。当散列表中插入数据越来越多时,散列冲突发生的可能性就会越来越大,空闲位置会越来越少,线性探测的时间就会越来越久。当我们按照键(key)查询元素时,我们用同样的散列函数,将键值转化数组下标,从对应数组下标的位置取数据。我们通过散列函数求出要查找元素的键值对应的散列值,然后比较数组中下标为散列值的元素和要查找的元素。,还没有找到,就说明要查找的元素并没有在散列表中。
2025-04-16 22:45:54
126
原创 17 | 跳表:为什么 Redis 一定要用跳表来实现有序集合?
通过上面的公式,我们可以得出 n/(2^h)=2,求得 h=log_2n-1。假设,原始链表大小为 n ,第一级索引大约在 n/2 个结点,第二级索引大约在 n/4 个结点,以此类推,每上升一级就减少一半,直到剩下 2 个结点。当我们不停的往跳表中插入数据时,如果我们不更新索引,就有可能出现某 2 个索引结点间的数据非常多的情况。我们在跳表中查询某个数据的时候,如果每一层都要遍历 m 个结点,那在跳表查询一个数据的时间复杂度就是。的时间复杂度定位区间的起点,然后,在原始链表中顺序往后遍历就可以了。
2025-04-16 22:40:34
220
原创 06-Spring IoC依赖注入(7~10):字段注入、方法注入、接口回调注入、依赖注入选择
其中,@Autowired是Spring 3.30的新API,并由Spring作者Rudolph Elemets提出,该注解用途广泛,可以应用于方法、参数和类上。在方法注入中,不必严格使用set方法名,只要方法参数中有相关依赖类型,并且加上特定的注解(@Autowired或@Resource),就可以实现方法注入。可以通过创建方法(如initial user),并在该方法参数中传入依赖类型,并使用@Autowired或@Resource注解进行方法注入。
2025-04-16 22:28:15
621
原创 02 | 从哪些维度评判代码质量的好坏?如果具备写出高质量代码的能力?
代码预留了一些功能扩展点,你可以把新功能代码,直接直接插到扩展点上,而不需要因为要添加一个功能大动干戈,改动大量的原始代码。相反,如果修复一个 bug或添加一个功能,需要花费很长的时间,那我们就可以主观地认为代码对我们来说。,修改、添加功能能够轻松完成,那我们就可以主观的认为代码对我们来说。我们不能通过单一的维度去评价一段代码的好坏。代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。原有代码的情况下,通过扩展的方式添加新的功能代码。
2025-04-16 10:11:10
190
原创 01 | 我们为什么要学习设计模式?
应对面试中的设计模式相关问题。提供复杂代码的设计和开发能力。让读源码、学框架事半功倍。告别写被人吐槽的烂代码。为你的职场发展做铺垫。
2025-04-16 10:10:03
24
原创 15 | 二分查找(上):如何用最省内存的方式实现快速查找功能?
我们希望这个功能不要占用太多的内存空间,最多不超过 100MB,你会怎么做呢?如果处理的数据量很小,完全没有必要用二分查找,顺序查找就足够了。的数据集合,查找思想有点类似分治思想。其中 n/2k=1 时,k 的值就是总共缩小的次数。中,内存占用差不多是 80MB,符合内存的限制。,直到找到要查找的元素,或者区间被缩小为 0。二分查找依赖的是顺序表结构,简单点就是。,不管数据量大小,我都推荐二分查找。,就可以快速地查找到想到的数据了。对比,将待查找的区间缩小为之前的。,对内存的要求比较苛刻。
2025-04-16 09:55:54
409
原创 14 | 排序优化:如何实现一个通用的、高性能的排序函数?
如果数据原来就是有序的或者接近有序的,每次分区点都选择最后一个数据,那快速排序算法就会变得非常糟糕,时间复杂度就会退化为 O(n^2)。是原地排序算法,通过优化 partition 函数,可以实现原地排序,空间复杂度是 O(1)最坏情况下的时间复杂度是 O(n^2)此法不能保证每次分区点选的比较好,但,从概率论角度,也不大可能每次都很差。进行排序,时间复杂度是 O(nlogn) 的算法更加高效。为什么最坏情况下快速排序的时间复杂度是 O(n^2) 呢?进行排序,可以选择时间复杂度是 O(n2) 的算法。
2025-04-16 09:48:11
122
原创 13 | 线性排序:如何根据年龄给 100 万用户数据排序?
比如,当扫描到 3 时,我们可以从数组 C 中取出下标为 3 的值 7,也就是说,到目前为止,包括自己在内,分数小于等于 3 的考生有 7 个,也就是说 3 是数组 R 中的第 7 个元素(也就是数组 R 中下标为 6 的位置)。(如手机号有 11 位),需要可以分割出独立的“位”来比较,而且位之间有递进的关系,如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了。这 8 个考生的成绩我们放在一个数组 A[8]中,它们分别是:2,5,3,0,2,3,0,3。// 计数排序,a是数组,n是数组大小。
2025-04-16 09:41:11
508
原创 12 | 排序(下):如何用快排思想,在 O(n) 内查找第 K 大元素?
我们可以利用分区的思想,来解答开篇的问题:O(n) 的时间复杂度内求无序数组中的第 K 大元素。根据分治、递归的思想,我们可以用递归排序下标从 p 到 q - 1 间的数据和下标从 q + 1 到 r 间的数据,直到区间缩小为 1, 就说明所有数据都有序了。,那它的空间复杂度得是 O(1),那 partition() 分区函数就不能占用太多额外的内存空间,我们就需要在 A[p…如果要排序的数组中下标从 p 到 r 间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot (分区点)。
2025-04-16 09:37:56
655
原创 11 | 排序(上):为什么插入排序比冒泡排序更受欢迎?
对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变,所以插入排序是稳定的排序算法。是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,不涉及额外的申请空间。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,我们不做交换。if (a[j] > a[j+1]) { // 交换。if (a[j] > a[j+1]) { // 交换。
2025-04-16 09:28:51
663
原创 游戏服务器数据存储策略和宕机保护
Redis借助了fork命令的copy on write机制在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件。降低DB频率、区分重要数据(不频繁的数据价值越高/回写时间可能不同)、回写合并。共享内存 + 定时回写DB + 关键数据操作日志 + 工具回档关键数据。DB操作异步、回调、顺序、克隆(copy on write)DBProxy、逻辑和数据分开(解耦)、降低逻辑进程压力。内存、文件、redis、mongo的速度。逻辑进程即时异步 + 增量更新。
2025-04-15 22:46:57
162
原创 Java 项目中 mongo 问题总结
数据库本身是一个服务,对外提供网络服务,监听端口客户端访问数据库是一个c/s,socket交互数据库驱动本身实现可以是同步,可以是异步(网络层实现)同步的实现可以简单理解为一个连接一个线程处理异步的实现可以简单理解为一个线程可以处理多个连接扩展:nginx与apache,tomcat与netty,unix 5种i/o模型客户端访问则通常要设置数据库连接池最小、最大连接数最大空闲数超时数据库的瓶颈主要是cpu(不考虑磁盘和内存)所以数据库的线程数目设置很重要。
2025-04-15 22:41:05
329
原创 08 | 栈:如何实现浏览器的前进和后退功能?
如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。如果能够匹配,比如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。当点击后退按钮时,再依次从栈 X 中出栈,并将出栈的数据依次放入栈 Y。当我们点击前进按钮时,我们依次从栈 Y 中取出数据,放入栈 X 中。// 栈为空,则直接返回 null。
2025-04-15 11:07:30
835
原创 06-Spring IoC依赖注入(5~6):Setter 方法注入、构造器注入 [持续更新中...]
在Spring 框架中,注解配置和XML配置都是实现bean定义和依赖注入的不同方式。注解配置,基于Java注解,直接在代码中通过@PropertySource、@Component等注解实现bean的定义和依赖关联。XML配置,则是通过XML文件来描述bean及其依赖关系。无论是哪种方式,其底层原理都是将这些配置信息映射到特定的BeanDefinition上,进而完成依赖注入。
2025-04-14 23:50:44
746
原创 06 | 链表(上):如何实现LRU缓存淘汰算法?
所以,为了找前驱结点,我们还是需要从头结点开始遍历链表,直到 p->next=q,说明 p 是 q 的前驱结点。,因为双向链表中结点已经保存了前驱结点的指针,不需要像单向链表那样遍历。我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表头部。链表中每个结点都需要消耗额外的存储空间去存储一份指向下一个结点的指针,所以。因为不管缓存有没有满,我们都需要遍历一次链表,所以,时间复杂度是。,则链表尾结点删除,将新的数据结点插入到链表的头部。,要删除结点 q ,需要知道其前驱结点。
2025-04-14 23:22:22
1078
原创 05 | 数组,为什么大多数编程语言中,数组要从 0 开始编号,而不是从 1 开始?
大多数主流虚拟机采用可达性分析算法来判断对象是否存活,在标记阶段,会遍历所有 GC ROOTS,将所有 GC ROOTS 可达的对象标记为存活。在一定程度上减少 C 语言程序员学习 Java 的学习成本(C 语言的设计者用 0 开始计数数组下标),因此继续沿用了从 0 开始计数的习惯。n)/n=O(n);:就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后的两个方向。缺点:删除、插入时,为保证连续性,就需要做大量的数据搬移。JVM 的标记清除垃圾回收算法的核心理念?对于 m * n 的数组,
2025-04-14 23:16:00
780
原创 04 | 复杂度分析(下):最好、最坏、平均、均摊时间复杂度
这个时候,我们就可以将这一组操作放在一块儿分析,看是否将较高时间复杂度那次操作的耗时,:每一次 O(n) 的插入操作,都会跟着 n-1 次 O(1) 的插入操作,所以,根据概率乘法法则,要查找的数据在 0~n-1 中任意位置的概率就是。而且,在能够应用均摊复杂度分析的场合,一般均摊时间复杂度。这一组连续的操作的均摊时间复杂度就是 O(1)。之所以引入这几个复杂度的概念,是因为,查找的变量 x 在数组中的位置,有。情况下,时间复杂度都很低,只有。,执行这段代码的时间复杂度。,执行这段代码的时间复杂度。
2025-04-14 23:06:43
326
原创 03 | 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
所以,我们在表示复杂度的时候,就不能简单地利用加法法则,省略掉其中一个。所以,下面代码的时间复杂度就是。,即使有成千上万行的代码,其时间复杂度也是。采用大 O 标记复杂度的时候,可以忽略。,表示算法的存储空间与数据规模之间的。表示代码执行时间随数据规模增长的。乘法法则:嵌套代码的复杂度等于。常见的空间复杂度就是 O(1)一般情况下,只要算法中不存在。下面我们主要看几种常见的。算法的执行效率的方法。加法法则:总复杂度等于。
2025-04-14 23:03:00
740
原创 01/02 数据结构与算法 开篇
广义数据结构指一组数据的存储结构。算法就是操作数据的一组方法。狭义本专栏要讲的,某些著名的数据结构和算法。数据结构是为算法服务的,算法要作用在特定的数据结构之上。
2025-04-14 22:50:18
184
原创 06-Spring IoC依赖注入(1~4) 持续更新中...
例如,Autowiring、JSR-330 @Inject 等注解方式,它们底层原理类似,都是通过注解实现依赖注入。自定义依赖注入注解在官方文档中没有明确说明,但可以通过实践和解读源码实现简单或复杂的自定义方法。这通常涉及到对现有实现的操作或者完全通过编程方式,在Spring的生命周期内进行操作。
2025-04-13 23:18:18
630
原创 09-Vert.x 的消息传递和事件流
摄取服务通过 AMQP 接收设备步数更新,确保消息持久化与可靠传输。:活动服务将每日步数事件写入 Kafka,祝贺服务消费事件并发送邮件。:将 JSON 数据转换为 Kafka 记录。实现并行消费与负载均衡。:监听主题并处理事件。
2025-04-11 15:47:38
723
原创 08-Vert.x Web栈开发实战指南
基于 HTTP 方法和路径分发请求,支持链式中间件。:发起 HTTP 请求获取 JWT Token。串联多个处理逻辑(如身份验证、日志记录)。:调用内部服务(如用户服务、活动服务)。基于 RxJava 的响应式编程模型。支持 JSON 自动编解码(:通过 Vert.x 的。提供编译后的前端资源。:Vue 应用入口。
2025-04-11 12:20:54
808
原创 06-Vert.x服务化与测试实战
客户端通过代理调用服务,自动序列化请求并监听响应。调用者只需调用接口方法,而无需关心消息的发送和接收。:服务端实现接口,处理事件总线消息并回调结果。Vert.x服务化与测试实战。
2025-04-10 23:55:18
669
原创 05-Vert.x 异步编程模型精要
回调嵌套导致代码可读性差(回调地狱),错误处理分散。复杂事件流处理(如合并多传感器数据)。将异步代码写成同步风格,避免回调嵌套。)缓解问题,但复杂逻辑仍难维护。Vert.x 异步编程模型精要。
2025-04-10 21:39:30
456
原创 04-异步数据和事件流
流模型:统一抽象读写操作,简化异步数据处理。背压:避免内存溢出的关键,优先使用pipeTo自动管理。解析器:通过灵活处理文本/二进制流。合理选择模式:推送(Push)适合实时流,拉取(Pull)适合批量处理。资源释放:及时关闭流(close())防止资源泄漏。
2025-04-10 21:03:28
821
原创 03-EventBus:Vert.x 应用程序的支柱
事件总线是 Vert.x 的核心通信机制,通过异步消息传递实现 Verticle 间的解耦协作。消息以 JSON 或字符串形式传输,支持本地和分布式通信。
2025-04-10 19:22:30
258
原创 02-Vert.x 的基本处理单元-Verticles
Verticle 是 Vert.x 的最小功能单元,封装异步事件处理逻辑(如HTTP请求、数据库操作、定时任务)。
2025-04-10 13:36:37
851
原创 01-vertx 基础知识
---- 来自Vert.x 网站 (https://vertx.io/)的介绍。Vert.x 应用程序是一个模块组合,提供您真正需要的东西,仅此而已。Vert.x 支持大多数流行的 JVM 语言:JavaScript、Ruby、Kotlin、Scala、Groovy 等。:它不为您的应用程序提供预定义的基础,因此您可以自由地将 Vert.x 用作更大代码库中的库。Vert.x 的重点是处理异步事件,主要来自非阻塞 I/O,线程模型在事件循环中处理事件。Vert.x 是一个。
2025-04-10 13:34:23
204
原创 Git 基础实践
对于gitlab的权限管理,最终是根据账号做权限管理的,在authorized_keys除了公钥还有对应的user(可以在gitlab#user#setttings#配置这个公钥),进而根据user去做权限验证。可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。即使不配置,你有权限,也可以push等,只不过用的是自动的PC的user。推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并。
2025-04-09 11:06:58
631
原创 记录一个线上 quartz 阻塞问题
debug 看一下 url,发现有时候是connect连接不上,直接超时;线上的错误是 connect连接上了,但是socket#read超时了,所以应该再加上一个socketTimeout。所以,即使类似调用 socket.read 时,线程会挂起 ,但此时状态还是 runnable。JVM 将I/O阻塞视为“等待资源”,与CPU调度无关,因此,统一归为。注意:此时线程阻塞,但是状态是 runnable。发现: 所有的 quartz 线程都在。简化了线程状态管理,牺牲了状态细粒度。
2025-04-09 09:48:07
161
原创 JVM 内存非典型术语介绍(shallow/retained/rss/reserved/committed)
在服务器性能优化内存这一项时,有一些现象很诡异。如top显示的RES很大,但是实际jvm堆内存占用很小,同时使用nmt发现committed更大。所以决定写这篇wiki大概介绍一下top 显示的res是指进程常驻内存。目前从经验上看这个常驻内存会比堆内存大 这个很容易理解 因为jvm除了堆内存外,还有metaspace、stack、jvm本身、堆外内存等使用 nmt工具可以看到committed size比较大 比rss要大。
2025-04-08 12:40:54
542
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人