无题
PriorityQueue优先级的无界队列PriorityQueue简介PriorityQueue 是 Java 集合框架中的一部分,位于java.util包下,它实现了一个基于优先级的无界队列。
在 PriorityQueue 中,队头的元素总是具有最高优先级的元素。这对于需要快速访问最小(或最大,取决于构造方式)元素的场景非常有用。
特点:
①、有序性:自动维护队列元素的排序,通常是按照自然顺序或提供的比较器进行排序。②、无界:PriorityQueue 是无界的,意味着它可以动态地扩容。③、性能:提供了对头部元素的快速访问,插入和删除操作(offer、poll)的平均时间复杂度为 O(log(n)),其中 n 是队列中的元素数量。④、堆实现:内部通过一个完全二叉树(以数组形式存储,即堆[小顶堆或大顶堆])来实现。⑤、非线程安全:PriorityQueue 不是线程安全的,如果需要在多线程环境中使用,应考虑使用 PriorityBlockingQueue。
使用场景:实现优先级调度算法。维护一个经常变动但需要快速访问最小值的数据集合,如事件驱动模拟中的事件队列。
PriorityQ ...
零线刷新幂等去重 缓存数组逐元素比较
现象零线对齐初始化阶段,发现磁盘被频繁读取、初始化很慢。profile 后看到 reloadBuffer 被重复触发了很多次。
原因初始化时有多个事件源(不同组件的状态变更)都会触发 reloadBuffer(重新从磁盘读雷达数据并刷新)。这些事件在短时间内密集到达,虽然最终零线状态没变,但每次都老老实实重新读一遍磁盘,做了大量重复 IO。
去重:缓存 + 逐元素比较要让刷新幂等——相同输入只处理一次。缓存上一次的 zeroPositions 数组,新事件来时先比较新旧:
fun onZeroLineUpdate(newPositions: DoubleArray) { if (newPositions contentEquals lastPositions) return // 没变,跳过 lastPositions = newPositions.copyOf() reloadBuffer()}
为什么不用哈希第一反应可能是算个哈希比较,快。但哈希有碰撞风险:两个内容不同的数组哈希撞了,就会被误判为“没变”而漏掉刷新,雷达图就错位了。对于“ ...
雷达采样直方图从 O(N×M) 优化到 O(N) 单遍桶计数
背景雷达图渲染前要统计采样值的分布(算百分位、做增益和对比度映射),这步叫“采样直方图”。打开一个多文件的测线时,每打开一个文件都要算一遍。
原来的 O(N×M)最早的实现是“对每个待统计的输出桶,遍历一遍所有采样点统计落点”。N 个采样点 × M 个桶,复杂度 O(N×M)。单文件几百万采样点时,这一步就把打开速度拖慢一大截,多文件叠加更明显。
改成 O(N) 单遍桶计数其实直方图根本不用每个桶都扫一遍数据。一次遍历,把每个采样点直接丢进它对应的桶即可:
fun histogram(samples: IntArray, min: Int, max: Int, bucketCount: Int): IntArray { val buckets = IntArray(bucketCount) val range = (max - min).coerceAtLeast(1) for (v in samples) { val idx = ((v - min).toLong() * bucketCount / range).toInt() ...
Kotlin 协程生命周期 SupervisorJob 与 Mutex 超时保护
SupervisorJob:别让一个子协程的失败连累全家默认情况下,协程作用域用 Job(),父子关系是“一根绳上的蚂蚱”:任意一个子协程抛异常,整个作用域(包括其他正常运行的兄弟协程)都会被取消。对于“加载多条测线、其中一条失败不该影响其他”的场景,这太脆了。
换成 SupervisorJob(),子协程的失败不会向上传播,其他兄弟照常跑:
class RadarViewModel { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)}
@PreDestroy:组件销毁时取消作用域协程如果不主动取消,它会一直挂着,引用着 ViewModel / UI,造成内存泄漏,甚至组件销毁后还去刷新已经无效的 UI。把 scope 的取消绑到生命周期上:
@PreDestroyfun onDestroy() { scope.cancel() // 取消所有子协程}
Mutex + 超时:防并发覆盖和死等共有 / 复合病害的计算 ...
JavaFX Canvas 踩 GPU 纹理上限 视口裁剪方案
现象某条超长层位线(20000+ 道)在雷达图上只显示了一部分,甚至整段空白;而短一点的层位线正常。
原因原来的渲染是把整段层位线一次性画到一个 Canvas 上,Canvas 的宽度等于段的道数。一条 20000 道的层位线,Canvas 宽度就是 20000 像素起步(缩放后更高)。
而 GPU 纹理有尺寸上限,通常 16384 ~ 32768 像素(因显卡而异)。Canvas 宽度一旦超过这个上限,渲染直接失败——而且是静默失败,表现为空白或只画了一截,没有任何报错,排查起来很费劲。
方案:视口裁剪思路是只画当前能看见的那部分,而不是整段都画。Canvas 宽度始终约等于视口宽度(屏幕上可见的范围),内容随滚动 / 缩放重绘:
根据当前滚动位置和缩放,算出视口覆盖的道号区间 [from, to]。
只把这个区间内的层位线点画到 Canvas 上。
Canvas 宽度固定为视口宽度,永远不会超过 GPU 纹理上限。
用户滚动或缩放时,重新计算区间并重绘。
附带的优化视口裁剪还顺带解决了性能问题:以前不管看哪都在画 20000 个点,现在只画视口里的几百个点。再加一个缓 ...
SQLite 批量更新 CASE WHEN 与 999 变量上限
痛点病害的“共有 / 复合”状态计算完要持久化。几百上千条病害,最初是逐条 UPDATE,两个问题:一是慢,每次 UPDATE 都是一次磁盘事务;二是没整体事务保护,中途失败会留下半新半旧的数据。
CASE WHEN 批量更新把 N 条逐条 UPDATE 合成一条,用 CASE WHEN 给每行不同的值:
UPDATE defectSET compound_level = CASE id WHEN 101 THEN 'AA' WHEN 102 THEN 'A1' WHEN 103 THEN 'B' END, is_compound = CASE id WHEN 101 THEN 1 WHEN 102 THEN 1 WHEN 103 THEN 0 ENDWHERE id IN (101, 102, 103)
一次往返更新多行多列,磁盘事务次数从 N 次降到 1 次。配合 TransactionTemplate 包一层 ...
用事件总线解耦模块 零线变更的级联重算
问题用户调整“零线”后,会引发一连串级联反应:受影响文件要重算厚度不足段、层位段平均深度、病害深度,层位识别和病害标注模块的雷达图和列表都要同步刷新。
如果让零线模块直接去调用各模块的刷新方法,耦合就死死的——零线模块得知道谁需要刷新、调用谁的哪个方法,以后新增模块还得回来改零线代码。
事件总线引入一个轻量事件总线,零线模块只负责“发布事件”,谁关心谁“订阅”:
// 发布方:零线模块,不关心谁来处理eventBus.publish(ZeroLineChangedEvent(affectedFileIds))// 订阅方:层位识别模块,自己决定怎么响应@Subscribefun onZeroLineChanged(e: ZeroLineChangedEvent) = scope.launch { recalcLayerSegments(e.affectedFileIds) refreshChart()}
收益:
解耦:发布方和订阅方互不认识,新增模块只要订阅事件即可,零线代码不用动。
异步编排:重算(耗时)和刷新(UI)分别在不同协程里跑,互不阻塞。 ...
用并查集识别隧道相邻测线的共有病害
业务场景隧道里有多条平行测线,相邻两条测线(编号相差 1)上如果各有一个病害,且里程范围有重叠,很可能是同一个病害在两条测线上的不同反映——这种要标成“共有病害”。
难点:传递性判断不能只做两两配对。因为重叠有传递性:A 和 B 重叠、B 和 C 重叠,那 A/B/C 实际上是同一组,应该归到一起。如果只两两标记,A-B 一组、B-C 一组,C 和 A 的关系就丢了,分组是碎的。
并查集(Union-Find)这种“传递性分组”正是并查集的拿手好戏:
class UnionFind(n: Int) { val parent = IntArray(n) { it } val rank = IntArray(n) fun find(x: Int): Int { if (parent[x] != x) parent[x] = find(parent[x]) // 路径压缩 return parent[x] } fun union(a: Int, b: Int) ...
Excel 导入的两个坑 字段截断与 32767 字符限制
背景做历史层位 / 病害 Excel 导入导出时,连踩了两个“长度上限”的坑,一个在数据库,一个在 Excel 本身,记录一下。
坑一:SQLite 字段 VARCHAR(5000) 截断导入历史层位线后,滚动到中间位置,长段层位线突然变成了一条水平直线。排查发现,存深度数据的 depth_index_array 列定义成了 VARCHAR(5000),超长段的数据被静默截断——SQLite 写入时不报错,读出来就是一截残缺数据,渲染自然就错了。
解决:把该列改成 TEXT,不限长度。SQLite 的 TEXT 本身不限长,VARCHAR(N) 的 N 只是个 hint 并不会真正限制(SQLite 弱类型),但某些驱动 / ORM 层会按声明长度截断,所以声明成 TEXT 最保险。
教训:别用 VARCHAR 存可能很长的内容,长文本一律 TEXT,避免被中间某层按声明长度截断。
坑二:Excel 单元格 32767 字符上限层位导出时,原始数据那列把整个深度数组塞进一个单元格,结果导出直接失败。原因是 Excel 对单个单元格的字符数有硬上限:32767 个字符 ...
JavaFX 截图卡死 UI 的根治 Java2D 纯数据渲染
痛点病害导出时每条病害要生成一张雷达图截图。原来的实现是直接对界面上的 JavaFX 节点调 snapshot() 截图。问题是 snapshot() 必须在 FX Application Thread 执行,导出几十上百张图时,主线程被截图任务占满,整个 UI 完全卡死,进度条都不动。
思路卡死的根因是渲染绑死在 UI 线程上。要彻底解决,就得让渲染脱离 JavaFX 节点,变成可以在任意线程跑的纯计算。于是把截图渲染从“拍 UI”改成“用 Java2D 纯数据画”。
改造按 MVVM 三层切:
View 层:只负责从 UI 组件里提取纯数据(当前视口的里程范围、采样值数组、增益、颜色映射等),不参与画图。
ViewModel 层:负责渲染调度和参数构建,把 View 提取的数据组装成绘图参数。
Service 层:纯绘图,用 BufferedImage + Graphics2D 画雷达图、层位线、病害框、信息面板,完全不接触 JavaFX。
// Service 层,任意线程可跑fun render(params: RenderParams): BufferedImage ...
