当前位置: 首页 > news >正文

compose map 源码解析

目录

TileCanvas

ZoomPanRotateState

ZoomPanRotate

布局,手势处理完了,就开始要计算tile了

MapState

TileCanvasState


telephoto的源码已经分析过了.它的封装好,扩展好,适用于各种view.

最近又看到一个用compose写的map,用不同的方式,有点意思.分析一下它的实现流程与原理.

https://github.com/p-lr/MapCompose.git  这是源码.

TileCanvas
Canvas(modifier = modifier.fillMaxSize()) {withTransform({translate(left = -zoomPRState.scrollX, top = -zoomPRState.scrollY)scale(scale = zoomPRState.scale, Offset.Zero)}) {for (tile in tilesToRender) {val bitmap = tile.bitmap ?: continueval scaleForLevel = visibleTilesResolver.getScaleForLevel(tile.zoom)?: continueval tileScaled = (tileSize / scaleForLevel).toInt()val l = tile.col * tileScaledval t = tile.row * tileScaledval r = l + tileScaledval b = t + tileScaleddest.set(l, t, r, b)drawIntoCanvas {it.nativeCanvas.drawBitmap(bitmap, null, dest, null)}}}}

我把旋转的代码删除了.telephoto是在绘制前把偏移量计算好了,而这个绘制是得到tile后,在绘制的时候设置偏移量,使用的是bitmap,所以不支持多平台.

使用画布前要初始化一些数据:

val zoomPRState = state.zoomPanRotateStatekey(state) {ZoomPanRotate(modifier = modifier.clipToBounds().background(state.mapBackground),gestureListener = zoomPRState,layoutSizeChangeListener = zoomPRState,) {TileCanvas(modifier = Modifier,zoomPRState = zoomPRState,visibleTilesResolver = state.visibleTilesResolver,tileSize = state.tileSize,tilesToRender = state.tileCanvasState.tilesToRender,)content()}}
zoomPRState最重要的就是这个,保存了各种状态,变量.它的初始化就要追溯到mapstate了.因为它是用于map,所以它会先设置一个地图的总大小,层级.
val state = MapState(4, 4096, 4096) {scale(1.0f)}.apply {addLayer(tileStreamProvider)shouldLoopScale = true//enableRotation()}
TileStreamProvider就是根据这个缩放级别,行列去获取对应的图片,示例中是使用assets里面的图片,都是放好切片的.
ZoomPanRotateState

这个类管理了缩放,平移,旋转功能.

看一个缩放功能,先把缩放值作一个范围取值,避免超出最大与最小值.然后更新中心点,再通知监听者

fun setScale(scale: Float, notify: Boolean = true) {this.scale = constrainScale(scale)updateCentroid()if (notify) notifyStateChanged()}

中心点的计算,取当前布局的大小的半的值,加上偏移滚动的量 

private fun updateCentroid() {pivotX = layoutSize.width.toDouble() / 2pivotY = layoutSize.height.toDouble() / 2centroidX = (scrollX + pivotX) / (fullWidth * scale)centroidY = (scrollY + pivotY) / (fullHeight * scale)}

这个state它不是自己更新的,是外部触发的,哪里触发先放着.

缩放有了,滚动的与缩放类似.需要确定边界值,然后更新中心点.

fun setScroll(scrollX: Float, scrollY: Float) {this.scrollX = constrainScrollX(scrollX)this.scrollY = constrainScrollY(scrollY)updateCentroid()notifyStateChanged()}

这三个方法是这个类的核心方法,其它都是通过它们计算得到的.

ZoomPanRotate

这算是入口页面,自定义了Layout,它与普通的图片不同,它是多层级别的,所以要自定义一个.

它的布局大小改变的时,通过layoutSizeChangeListener,告诉zoomPanRotateState,

override fun onSizeChanged(composableScope: CoroutineScope, size: IntSize) {scope = composableScopevar newScrollX: Float? = nullvar newScrollY: Float? = nullif (layoutSize != IntSize.Zero) {newScrollX = scrollX + (layoutSize.width - size.width) / 2newScrollY = scrollY + (layoutSize.height - size.height) / 2}layoutSize = sizerecalculateMinScale()if (newScrollX != null && newScrollY != null) {setScroll(newScrollX, newScrollY)}/* Layout was done at least once, resume continuations */for (ct in onLayoutContinuations) {ct.resume(Unit)}onLayoutContinuations.clear()}

这时,zoomPanRotateState的一切就开始了.

先设置size,这是屏幕的大小.fullwidth/fullheight,这是页面图片的高宽值.在初始化时,我们设置了4096.然后将屏幕大小与这个计算缩放值.

gestureListener = zoomPRState,我们从这里面可以看出,layout里面的手势事件全部是通过这个传递给zoomPanRotateState, 这种方式与之前看过的一些库的源码思路确实不一太一样.

回到前面的zoomPanRotateState更新问题,现在解决了.

到这里,没有看到tile,只了解了整个画布,高宽,缩放平移这些.阶段总结一下:

ZoomPanRotate,这个类处理了布局.手势通过zoomPanRotateState来处理.包含缩放,平移.它有几个关键的属性,总大小与图片原始大小.layoutSize, fullWidth, fullHeight.

初始化时,它从ZoomPanRotate得到view的大小.

并根据图片原始大小计算出它与view的缩放比例.它的模式有三种

when (mode) {Fit -> min(minScaleX, minScaleY)Fill -> max(minScaleX, minScaleY)is Forced -> mode.scale
}

然后ZoomPanRotate中如果触发了手势,则回调到zoomPanRotateState中,重新计算平移,缩放值.

布局,手势处理完了,就开始要计算tile了

zoomPanRotateState不管是首次布局,还是手势,都会触发下面的方法,而它触发了mapstate中的状态变化:

private fun notifyStateChanged() {if (layoutSize != IntSize.Zero) {stateChangeListener.onStateChanged()}}
MapState

里面包含了zoomPanRotateState和tileCanvasState.

其它的state暂时不讨论,比如marker,path这些,这里只关注它如何管理缩放,平移这些.

override fun onStateChanged() {consumeLateInitialValues()renderVisibleTilesThrottled()stateChangeListener?.invoke(this)}

它先处理延迟初始化的的值,然后就启动tile渲染工作,发送事件throttledTask.trySend(Unit)

最后如果它还有外部的回调则调用.

启动任务后,开始计算可见的tile:先更新viewport

private suspend fun renderVisibleTiles() {val viewport = updateViewport()tileCanvasState.setViewport(viewport)}

这个是从zoomPanRotateState获取的偏移与view的大小,并处理了padding.viewport差不多是最大的视图区域大小了,并不是图片大小.

private fun updateViewport(): Viewport {val padding = preloadingPaddingreturn viewport.apply {left = zoomPanRotateState.scrollX.toInt() - paddingtop = zoomPanRotateState.scrollY.toInt() - paddingright = left + zoomPanRotateState.layoutSize.width + padding * 2bottom = top + zoomPanRotateState.layoutSize.height + padding * 2angleRad = zoomPanRotateState.rotation.toRad()}}

mapstate其实是一个管家,它本身并不直接管理,而是通过其它类来管理.这样有利于它的扩展.

更新了viewport后,就可以计算tile了.它通过另一个类来计算:

TileCanvasState

来看它的计算方法:启动协程, 用VisibleTilesResolver计算

suspend fun setViewport(viewport: Viewport) {/* Thread-confine the tileResolver to the main thread */val visibleTiles = withContext(Dispatchers.Main) {visibleTilesResolver.getVisibleTiles(viewport)}withContext(scope.coroutineContext) {setVisibleTiles(visibleTiles)}}

它的声明是在mapstate里面的:

internal val visibleTilesResolver =VisibleTilesResolver(levelCount = levelCount,fullWidth = fullWidth,fullHeight = fullHeight,tileSize = tileSize,magnifyingFactor = initialValues.magnifyingFactor) {zoomPanRotateState.scale}

回到计算tile:

fun getVisibleTiles(viewport: Viewport): VisibleTiles {val scale = scaleProvider.getScale()val level = getLevel(scale, magnifyingFactor)val scaleAtLevel = scaleForLevel[level] ?: throw AssertionError()val relativeScale = scale / scaleAtLevel/* At the current level, row and col index have maximum values */val maxCol = max(0.0, ceil(fullWidth * scaleAtLevel / tileSize) - 1).toInt()val maxRow = max(0.0, ceil(fullHeight * scaleAtLevel / tileSize) - 1).toInt()fun Int.lowerThan(limit: Int): Int {return if (this <= limit) this else limit}val scaledTileSize = tileSize.toDouble() * relativeScalefun makeVisibleTiles(left: Int, top: Int, right: Int, bottom: Int): VisibleTiles {val colLeft = floor(left / scaledTileSize).toInt().lowerThan(maxCol).coerceAtLeast(0)val rowTop = floor(top / scaledTileSize).toInt().lowerThan(maxRow).coerceAtLeast(0)val colRight = (ceil(right / scaledTileSize).toInt() - 1).lowerThan(maxCol)val rowBottom = (ceil(bottom / scaledTileSize).toInt() - 1).lowerThan(maxRow)val tileMatrix = (rowTop..rowBottom).associateWith {colLeft..colRight}val count = (rowBottom - rowTop + 1) * (colRight - colLeft + 1)return VisibleTiles(level, tileMatrix, count, getSubSample(scale))}return if (viewport.angleRad == 0f) {makeVisibleTiles(viewport.left, viewport.top, viewport.right, viewport.bottom)} else {val xTopLeft = viewport.leftval yTopLeft = viewport.topval xTopRight = viewport.rightval yTopRight = viewport.topval xBotLeft = viewport.leftval yBotLeft = viewport.bottomval xBotRight = viewport.rightval yBotRight = viewport.bottomval xCenter = (viewport.right + viewport.left).toDouble() / 2val yCenter = (viewport.bottom + viewport.top).toDouble() / 2val xTopLeftRot =rotateX(xTopLeft - xCenter, yTopLeft - yCenter, viewport.angleRad) + xCenterval yTopLeftRot =rotateY(xTopLeft - xCenter, yTopLeft - yCenter, viewport.angleRad) + yCentervar xLeftMost = xTopLeftRotvar yTopMost = yTopLeftRotvar xRightMost = xTopLeftRotvar yBotMost = yTopLeftRotval xTopRightRot =rotateX(xTopRight - xCenter, yTopRight - yCenter, viewport.angleRad) + xCenterval yTopRightRot =rotateY(xTopRight - xCenter, yTopRight - yCenter, viewport.angleRad) + yCenterxLeftMost = xLeftMost.coerceAtMost(xTopRightRot)yTopMost = yTopMost.coerceAtMost(yTopRightRot)xRightMost = xRightMost.coerceAtLeast(xTopRightRot)yBotMost = yBotMost.coerceAtLeast(yTopRightRot)val xBotLeftRot =rotateX(xBotLeft - xCenter, yBotLeft - yCenter, viewport.angleRad) + xCenterval yBotLeftRot =rotateY(xBotLeft - xCenter, yBotLeft - yCenter, viewport.angleRad) + yCenterxLeftMost = xLeftMost.coerceAtMost(xBotLeftRot)yTopMost = yTopMost.coerceAtMost(yBotLeftRot)xRightMost = xRightMost.coerceAtLeast(xBotLeftRot)yBotMost = yBotMost.coerceAtLeast(yBotLeftRot)val xBotRightRot =rotateX(xBotRight - xCenter, yBotRight - yCenter, viewport.angleRad) + xCenterval yBotRightRot =rotateY(xBotRight - xCenter, yBotRight - yCenter, viewport.angleRad) + yCenterxLeftMost = xLeftMost.coerceAtMost(xBotRightRot)yTopMost = yTopMost.coerceAtMost(yBotRightRot)xRightMost = xRightMost.coerceAtLeast(xBotRightRot)yBotMost = yBotMost.coerceAtLeast(yBotRightRot)makeVisibleTiles(xLeftMost.toInt(),yTopMost.toInt(),xRightMost.toInt(),yBotMost.toInt())}}

先获取缩放值,如果发生手势缩放,它就不再只是图片与view的比例,需要加上手势的缩放,这部分是在zoomPanRotateState里面计算过的.

然后处理层级.处理层级的目的是为了从asset中取图片.

接着计算行列数.

这里忽略了旋转,那么只关注不旋转的计算makeVisibleTiles.它得到的是一个取值范围,不像telephoto那样具体偏移.因为asset里面的图片是固定块大小的,名字是数字,只要有这个取值范围,然后根据这个去取就行了.

计算完tile的可见区,开始渲染:

private val renderTask = scope.throttle(wait = 34) {/* Evict, then render */val (lastVisible, ids, opacities) = visibleStateFlow.value ?: return@throttleevictTiles(lastVisible, ids, opacities)renderTiles(lastVisible, ids)}

先是回收,如果缩放级别不同,应该回收旧的tile.

接着根据优先级排序,然后设置要渲染的tile

private fun renderTiles(visibleTiles: VisibleTiles, layerIds: List<String>) {/* Right before sending tiles to the view, reorder them so that tiles from current level are* above others. */val tilesToRenderCopy = tilesCollected.sortedBy {val priority =if (it.zoom == visibleTiles.level && it.subSample == visibleTiles.subSample) 100 else 0priority + if (layerIds == it.layerIds) 1 else 0}tilesToRender = tilesToRenderCopy}
tilesToRender,这个就是最外层的绘制部分的内容.

到这里渲染其实没有开始,只是监听渲染结果visibleStateFlow,这个是在当前类初始化的时候,启动了协程来监听.

collectNewTiles()中visibleStateFlow.collectLatest(),它变化的时候,才触发真正的图片解码

初始化还启动了其它的协程:

init {/* Collect visible tiles and send specs to the TileCollector */scope.launch {collectNewTiles()}/* Launch the TileCollector */tileCollector = TileCollector(workerCount.coerceAtLeast(1), bitmapConfig, tileSize)scope.launch {_layerFlow.collectLatest { layers ->tileCollector.collectTiles(tileSpecs = visibleTileLocationsChannel,tilesOutput = tilesOutput,layers = layers,bitmapPool = bitmapPool)}}/* Launch a coroutine to consume the produced tiles */scope.launch {consumeTiles(tilesOutput)}scope.launch(Dispatchers.Main) {for (t in recycleChannel) {val b = t.bitmapt.bitmap = nullb?.recycle()}}}

注释也比较清晰了,一个是收集tiles,一个是消费tiles.

tileCollector.collectTiles,这里触发图片的获取.
{val tilesToDownload = Channel<TileSpec>(capacity = Channel.RENDEZVOUS)val tilesDownloadedFromWorker = Channel<TileSpec>(capacity = 1)repeat(workerCount) {worker(tilesToDownload,tilesDownloadedFromWorker,tilesOutput,layers,bitmapPool)}tileCollectorKernel(tileSpecs, tilesToDownload, tilesDownloadedFromWorker)}

worker是具体的解码了.

整个过程比较复杂.在收集tile的协程里面它使用channel去接收tile的变化.

for (spec in tilesToDownload) {if (layers.isEmpty()) {tilesDownloaded.send(spec)continue}val bitmapForLayers = layers.mapIndexed { index, layer ->async {val bitmap = createBitmap(512, 512, Config.ARGB_8888)val canvas = Canvas(bitmap)val paint = Paint()paint.textSize = 60fpaint.strokeWidth = 4fpaint.isAntiAlias = truepaint.style = Paint.Style.STROKEcanvas.drawARGB(255, 0, 255, 0)paint.setColor(Color.WHITE)val rect = Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())paint.setColor(Color.YELLOW)canvas.drawRect(rect, paint)paint.setColor(Color.RED)canvas.drawText(index.toString(), 130f, 130f, paint)BitmapForLayer(bitmap, layer)/*val i = layer.tileStreamProvider.getTileStream(spec.row, spec.col, spec.zoom)if (i != null) {getBitmap(subSamplingRatio = subSamplingRatio,layer = layer,inputStream = i,isPrimaryLayer = index == 0)} else BitmapForLayer(null, layer)*/}}.awaitAll()val resultBitmap = bitmapForLayers.firstOrNull()?.bitmap ?: run {tilesDownloaded.send(spec)/* When the decoding failed or if there's nothing to decode, then send back the Tile* just as in normal processing, so that the actor which submits tiles specs to the* collector knows that this tile has been processed and does not immediately* re-sends the same spec. */tilesOutput.send(Tile(spec.zoom,spec.row,spec.col,spec.subSample,layerIds,layers.map { it.alpha }))null} ?: continue // If the decoding of the first layer failed, skip the restif (layers.size > 1) {canvas.setBitmap(resultBitmap)for (result in bitmapForLayers.drop(1)) {paint.alpha = (255f * result.layer.alpha).toInt()if (result.bitmap == null) continuecanvas.drawBitmap(result.bitmap, 0f, 0f, paint)}}println("getBitmap.Tile:zoom:${spec.zoom}, row-col:${spec.row}-${spec.col}, ${spec.subSample}")val tile = Tile(spec.zoom,spec.row,spec.col,spec.subSample,layerIds,layers.map { it.alpha }).apply {this.bitmap = resultBitmap}tilesOutput.send(tile)tilesDownloaded.send(spec)}

这里截取解码的部分代码.我取消了asset的图片获取,转为创建一个空的bitmap.解码的过程并不复杂,但它的代码看着没有那么舒服.

tilesToDownload,是要解码的tile.根据参数 解码,然后创建tile,其中还处理了layer,因为地图总是与缩放的层级相关的.

解码完就通过channel发送出去.然后就到了外面的接收方:

TileCanvasState.consumeTiles(tilesOutput)

如果layoerid相同才有用,否则要回收.renderthrottled前面有介绍过,就是最后触发最外层的渲染了.

private suspend fun consumeTiles(tileChannel: ReceiveChannel<Tile>) {for (tile in tileChannel) {val lastVisible = lastVisibleif ((lastVisible == null || lastVisible.contains(tile))&& !tilesCollected.contains(tile)&& tile.layerIds == visibleStateFlow.value?.layerIds) {tile.prepare()tilesCollected.add(tile)renderThrottled()} else {tile.recycle()}fullEvictionDebounced()}}

整个流程看着没那么舒服.也比较复杂,它不像图片查看器,只有一层,都需要关注layer.

不管是渲染,解码都是channel来通信.

先这样吧,下次再补充

相关文章:

compose map 源码解析

目录 TileCanvas ZoomPanRotateState ZoomPanRotate 布局,手势处理完了,就开始要计算tile了 MapState TileCanvasState telephoto的源码已经分析过了.它的封装好,扩展好,适用于各种view. 最近又看到一个用compose写的map,用不同的方式,有点意思.分析一下它的实现流程与原…...

IDEA202403 常用设置【持续更新】

文章目录 1、设置maven2、设置JDK3、菜单栏固定展示4、连接Gitee第一步、安装插件第二步、Gitee账号配置 IDEA 是程序员的编程利器&#xff0c;需要具备其的各种配置&#xff0c;提高工作效率。Java项目启动&#xff0c;两个关键设置&#xff1a;Maven 和 JDK设置。 1、设置mav…...

从零开始开发纯血鸿蒙应用之语音输入

从零开始开发纯血鸿蒙应用 〇、前言一、认识 speechRecognizer1、使用方式2、依赖权限3、结果回写 二、实现语音识别功能1、创建语音识别引擎2、设置事件监听3、启动识别4、写入音频数据5、操作控制 三、总结 〇、前言 除了从图片中识别文本外&#xff0c;语音输入也是一种现代…...

c++ STL常用工具的整理和思考

蓝桥杯后&#xff0c;我整理了这些常用的C STL工具 作为一个算法竞赛的中等生&#xff0c;以前总觉得STL“花里胡哨”&#xff0c;不如自己写数组和循环踏实。但这次蓝桥杯发现&#xff0c;合理用STL能省很多时间&#xff0c;甚至避免低级错误。下面是我总结的常用知识点和踩过…...

Go:复合数据结构

数组 定义&#xff1a;数组是固定长度、元素数据类型相同的序列 。元素通过索引访问&#xff0c;索引从 0 到数组长度减 1 。可用len函数获取元素个数 。 初始化&#xff1a;默认元素初始值为类型零值&#xff08;数字为 0 &#xff09; 。可使用数组字面量初始化&#xff0c;…...

SQL 语句基础(增删改查)

文章目录 一、SQL 基础概念1. SQL 简介2. 数据库系统的层次结构 二、SQL 语句分类1. DDL&#xff08;Data Definition Language 数据定义语言&#xff09;1.1 CREATE1.1.1 创建数据库1.1.2 创建数据表1.1.3 创建用户 1.2 ALTER1.2.1 AlTER 添加字段名1.2.2 ALTER 修改字段名1.2…...

【蓝桥杯 CA 好串的数目】题解

题目链接 考虑令 p r e [ i ] pre[i] pre[i] 表示 [ p r e [ i ] , i ] [pre[i], i] [pre[i],i] 是连续非递减子串&#xff0c;这可以类似双指针 O ( n ) O(n) O(n) 预处理&#xff1a; std::vector<int> pre(n); for (int r 1, l 0; r < n; r) {if (s[r] ! s[…...

Oracle for Linux安装和配置(11)——Linux命令

11.1. Linux命令 Linux是目前比较常用和流行的操作系统,现在很多生产环境就会用到它。随着其功能、性能、稳定性和可靠性等方面的日渐增强和完善,加之其成本上的优势,其市场占有率逐日攀升,也得到越来越多广大用户的关注和青睐。但作为一种操作系统,其安装、配置、管理和…...

Linux基础7

一、逻辑卷管理 查看所有物理卷&#xff1a;pvs 查看当前系统卷组&#xff1a;vgs 查看所有逻辑卷&#xff1a;lvs 新创建系统卷组&#xff1a;vgcreate [参数] ​ [volume name] url/sdb[1-2] ​ eg&#xff1a;vgcreate vg_Test /dev/sdb{1,2} >…...

C#打开文件及目录脚本

如果每天开始工作前都要做一些准备工作&#xff0c;比如打开文件或文件夹&#xff0c;我们可以使用代码一键完成。 using System.Diagnostics; using System.IO;namespace OpenFile {internal class Program{static void Main(string[] args){Console.WriteLine("Hello, …...

Docker 镜像 的常用命令介绍

拉取镜像 $ docker pull imageName[:tag][:tag] tag 不写时&#xff0c;拉取的 是 latest 的镜像查看镜像 查看所有本地镜像 docker images or docker images -a查看完整的镜像的数字签名 docker images --digests查看完整的镜像ID docker images --no-trunc只查看所有的…...

Python数组学习之旅:数据结构的奇妙冒险

Python数组学习之旅:数据结构的奇妙冒险 第一天:初识数组的惊喜 阳光透过窗帘缝隙洒进李明的房间,照亮了他桌上摊开的笔记本和笔记本电脑。作为一名刚刚转行的金融分析师,李明已经坚持学习Python编程一个月了。他的眼睛因为昨晚熬夜编程而微微发红,但脸上却挂着期待的微…...

Vue 3 和 Vue 2 的区别及优点

Vue.js 是一个流行的 JavaScript 框架&#xff0c;广泛用于构建用户界面和单页应用。自 Vue 3 发布以来&#xff0c;很多开发者开始探索 Vue 3 相较于 Vue 2 的新特性和优势。Vue 3 引入了许多改进&#xff0c;优化了性能、增强了功能、提升了开发体验。本文将详细介绍 Vue 2 和…...

特殊定制版,太给力了!

今天给大家分享一款超棒的免费录屏软件&#xff0c;真的是录屏的好帮手&#xff01; 这款软件功能可以录制 MP4、AVI、WMV 格式的标清、高清、原画视频&#xff0c;满足你各种需求。 云豹录屏大师 多功能录屏神器 它的界面特别简洁&#xff0c;上手超快&#xff0c;用起来很顺…...

Vue事件修饰符课堂练习

Vue事件修饰符课堂练习 题目‌&#xff1a;基于 Vue 2.0&#xff0c;使用事件修饰符 .stop、.prevent、.capture、.self 和 .once&#xff0c;为按钮绑定 click 事件&#xff0c;并展示每个修饰符的作用。 要求‌&#xff1a; 创建一个 Vue 实例&#xff0c;并绑定到一个 HT…...

Y1——ST表

知识点 ST表 只能询问&#xff0c;不能修改 ST表的预处理&#xff1a; 使用了DP的思想&#xff0c;设a是要求区间最值的数列&#xff0c;f(i,j)表示从第i个数起连续2^j个数中的最大值 状态转移方程 f [ i , j ]max( f [ i , j-1 ], f [ i 2 ^ j-1,j - 1]) 建立ST表 vo…...

Python Cookbook-5.14 给字典类型增加排名功能

任务 你需要用字典存储一些键和“分数”的映射关系。你经常需要以自然顺序(即以分数的升序)访问键和分数值&#xff0c;并能够根据那个顺序检查一个键的排名。对这个问题&#xff0c;用dict 似乎不太合适。 解决方案 我们可以使用 dict 的子类&#xff0c;根据需要增加或者重…...

第二十二: go与k8s、docker相关编写dockerfile

实战演示k8s部署go服务&#xff0c;实现滚动更新、重新创建、蓝绿部署、金丝雀发布-CSDN博客 go 编写k8s命令&#xff1a; 怎么在go语言中编写k8s命令 • Worktile社区 k8s中如何使用go 在K8s编程中如何使用Go-阿里云开发者社区 go build - o : -o&#xff1a;指定输出文件…...

Servlet、HTTP与Spring Boot Web全面解析与整合指南

目录 第一部分&#xff1a;HTTP协议与Servlet基础 1. HTTP协议核心知识 2. Servlet核心机制 第二部分&#xff1a;Spring Boot Web深度整合 1. Spring Boot Web架构 2. 创建Spring Boot Web应用 3. 控制器开发实践 4. 请求与响应处理 第三部分&#xff1a;高级特性与最…...

事件过滤器

1.简介 事件过滤器是指在程序分发到event事件之前进行的一次高级拦截。 2.使用步骤 给控件安装事件过滤器重写eventfilter事件 3.具体实现 3.1安装事件过滤器 代码&#xff1a; //给label1安装事件过滤器ui->label->installEventFilter(this); 3.2重写eventfilter…...

AI识别与雾炮联动:工地尘雾治理新途径

利用视觉分析的AI识别用于设备联动雾炮方案 背景 在建筑工地场景中&#xff0c;人工操作、机械作业以及环境因素常常导致局部出现大量尘雾。传统监管方式存在诸多弊端&#xff0c;如效率低、资源分散、监控功能单一、人力效率低等&#xff0c;难以完美适配现代工程需求。例如…...

Kubernetes nodeName Manual Scheduling practice (K8S节点名称绑定以及手工调度)

Manual Scheduling 在 Kubernetes 中&#xff0c;手动调度框架允许您将 Pod 分配到特定节点&#xff0c;而无需依赖默认调度器。这对于测试、调试或处理特定工作负载非常有用。您可以通过在 Pod 的规范中设置 nodeName 字段来实现手动调度。以下是一个示例&#xff1a; apiVe…...

Nacos注册中心

Nacos注册中心 本地环境搭建 准备挂载的文件夹 在拉取 Nacos 镜像之前&#xff0c;在 E:\docker 文件夹下&#xff0c;创建一个 /nacos 文件夹&#xff0c;等会运行容器时&#xff0c;用于将 Nacos 容器中的配置文件、持久化文件挂载出来&#xff0c;防止容器重启时数据丢失…...

除了 `task_type=“SEQ_CLS“`(序列分类),还有CAUSAL_LM,QUESTION_ANS

task_type="SEQ_CLS"是什么意思:QUESTION_ANS 我是qwen,不同模型是不一样的 SEQ_CLS, SEQ_2_SEQ_LM, CAUSAL_LM, TOKEN_CLS, QUESTION_ANS, FEATURE_EXTRACTION. task_type="SEQ_CLS" 通常用于自然语言处理(NLP)任务中,SEQ_CLS 是 Sequence Classif…...

二战蓝桥杯所感

&#x1f334; 前言 今天是2025年4月12日&#xff0c;第十六届蓝桥杯结束&#xff0c;作为二战的老手&#xff0c;心中还是颇有不甘的。一方面&#xff0c;今年的题目比去年简单很多&#xff0c;另一方面我感觉并没有把能拿的分都拿到手&#xff0c;这是我觉得最遗憾的地方。不…...

深度解析自动化工作流工具:n8n 与 Dify 的对比分析

深度解析自动化工作流工具&#xff1a;n8n 与 Dify 的对比分析 随着企业数字化转型的加速&#xff0c;自动化工具在提高工作效率、降低人工成本方面扮演着越来越重要的角色。市面上有多种自动化工作流工具可供选择&#xff0c;其中 n8n 和 Dify 是两个备受关注的开源和商业产品…...

深度剖析Python中的生成器:高效迭代的秘密武器

深度剖析Python中的生成器&#xff1a;高效迭代的秘密武器 在Python的编程世界里&#xff0c;生成器&#xff08;Generator&#xff09;是一个强大而又迷人的特性&#xff0c;它为开发者提供了一种高效处理大量数据的方式&#xff0c;尤其在涉及到迭代操作时&#xff0c;能显著…...

Mac 下载 PicGo 的踩坑指南

Mac 下载 PicGo 的踩坑指南 一、安装问题 下载地址&#xff1a;https://github.com/Molunerfinn/PicGo/releases 下载之后直接安装即可&#xff0c;此时打开会报错&#xff1a;Picgo.app 文件已损坏&#xff0c;您应该将它移到废纸篓。 这是因为 macOS 为了保护用户不受恶意…...

网页布局汇总

1. 盒模型 容器大小 内容大小 内边距(padding) 边框大小 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">&l…...

基于 Maven 构建的 Thingsboard 3.8.1 项目结构

一、生命周期&#xff08;Lifecycle&#xff09; Maven 的生命周期定义了项目构建和部署的各个阶段&#xff0c;图中列出了标准的生命周期阶段&#xff1a; clean&#xff1a;清理项目&#xff0c;删除之前构建生成的临时文件和输出文件。validate&#xff1a;验证项目配置是否…...

MySQL 中为产品添加灵活的自定义属性(如 color/size)

方案 1&#xff1a;EAV 模型&#xff08;最灵活但较复杂&#xff09; 适合需要无限扩展自定义属性的场景 -- 产品表 CREATE TABLE products (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100),price DECIMAL(10,2) );-- 属性名表 CREATE TABLE attributes (id INT PRIMA…...

C++语言程序设计——02 变量与数据类型

目录 一、变量与数据类型&#xff08;一&#xff09;变量的数据类型&#xff08;二&#xff09;变量命名规则&#xff08;三&#xff09;定义变量&#xff08;四&#xff09;变量赋值&#xff08;五&#xff09;查看数据类型 二、ASCII码三、进制表示与转换&#xff08;一&…...

第三篇:Python数据结构深度解析与工程实践

第一章&#xff1a;列表与字典 1.1 列表的工程级应用 1.1.1 动态数组实现机制 Python列表底层采用动态数组结构&#xff0c;初始分配8个元素空间&#xff0c;当空间不足时按0,4,8,16,25,35...的公式扩容&#xff0c;每次扩容增加约12.5%的容量 通过sys模块可验证扩容过程&a…...

dcsdsds

我将为您在页面顶部添加欢迎内容&#xff0c;同时保持整体风格的一致性。以下是修改后的代码&#xff0c;主要修改了模板部分和对应的样式&#xff1a; vue 复制 <template><div class"main-wrapper"><!-- 新增欢迎部分 --><div class"…...

Vitis: 使用自定义IP时 Makefile错误 导致编译报错

参考文章: 【小梅哥FPGA】 Vitis开发中自定义IP的Makefile路径问题解决方案 Vitis IDE自定义IP Makefile错误&#xff08;arm-xilinx-eabi-gcc.exe: error: *.c: Invalid argument&#xff09;解决方法 Vitis 使用自定义IP时: Makefile 文件里的语句是需要修改的&#xff0c;…...

应急响应练习靶机-web1

1&#xff09;背景 小李在值守的过程中&#xff0c;发现有CPU占用飙升&#xff0c;出于胆子小&#xff0c;就立刻将服务器关机&#xff0c;这是他的服务器系统&#xff0c;请你找出以下内容&#xff0c;并作为通关条件&#xff1a; 1.攻击者的shell密码 2.攻击者的IP地址 3.攻击…...

cdp-(Chrome DevTools Protocol) browserscan检测原理逆向分析

https://www.browserscan.net/zh/bot-detection 首先,打开devtools后访问网址,检测结果网页显示红色Robot,标签插入位置,确定断点位置可以hook该方法,也可以使用插件等方式找到这个位置,本篇不讨论. Robot标签是通过insertBefore插入的. 再往上追栈可以发现一个32长度数组,里面…...

MCU刷写——Hex文件格式详解及Python代码

工作之余来写写关于MCU的Bootloader刷写的相关知识,以免忘记。今天就来聊聊Hex这种文件的格式,我是分享人M哥,目前从事车载控制器的软件开发及测试工作。 学习过程中如有任何疑问,可底下评论! 如果觉得文章内容在工作学习中有帮助到你,麻烦点赞收藏评论+关注走一波!感谢…...

SpringBoot(一)

快速入门 1.概念 SpringBoot 简单、快速地创建一个独立的、生产级别的 Spring 应用&#xff08;说明SpringBoot底层是Spring&#xff09; 大多数 SpringBoot 应用只需要编写少量配置即可快速整合 Spring 平台以及第三方技术 特性&#xff1a; 快速创建独立 Spring 应用 SSM&…...

学习Mysql对库和表的操作以及对数据的操作

对库操作 SHOW DATABASES;可以查看数据库服务器中有哪些数据库(注意databases最后的s不要忘记) SELECT DATABASE();可以查看到目前是在哪个数据库下。 CREATE DATABASE 库名;可以创建一个数据库 DROP DATABASE 库名;可以删除一个数据库 USE 库名;切换到当前数据库 对表操…...

微软office填表无法打勾✔,解决办法!

最近在使用office 填表的时候&#xff0c;碰到需要在选择框中打勾的情况&#xff0c;但是找了半天发现找不到打勾的按钮。为此&#xff0c;记录该问题解决办法&#xff1a; 以这个界面为例&#xff0c;如果点击打勾发现无法✔。 这里因为office和wps的编写不一样&#xff0c;所…...

Python实现链接KS3,并批量下载KS3文件数据到本地

前言 本文是该专栏的第56篇,后面会持续分享python的各种干货知识,值得关注。 在本专栏的上篇文章《Python实现链接KS3,并将文件数据批量上传到KS3》中,笔者有详细介绍基于Python,实现链接KS3并将文件数据批量上传。而本文,笔者将基于在上一篇文章的基础之上,实现链接KS…...

构建智能期货交易策略分析应用:MCP与AI的无缝集成

引言 随着金融科技的快速发展&#xff0c;数据驱动的交易决策已成为期货交易领域的重要趋势。本文将深入探讨一个结合了Model Content Protocol (MCP)和AI技术的期货交易策略分析应用——Futures MCP。该应用不仅提供了丰富的技术分析工具&#xff0c;还通过MCP协议与大型语言…...

区块链点燃游戏行业新未来——技术变革与实践指南

区块链点燃游戏行业新未来——技术变革与实践指南 在数字时代&#xff0c;游戏行业无疑是创新的热土。从简单像素风的街机游戏到沉浸式的虚拟现实&#xff0c;我们见证了技术如何一步步塑造游戏的样貌。然而&#xff0c;在传统游戏模式中&#xff0c;玩家权益往往无法得到保障…...

Jmeter中如何实现关联?

在JMeter中实现关联(Correlation)是性能测试中处理动态数据(如Session ID、Token、动态参数等)的核心技能。以下是详细操作指南,涵盖原理、工具和实战示例: 一、关联的本质与场景 作用:从服务器响应中提取动态数据,供后续请求复用(如登录Token、订单ID、验证码等)。 …...

在MATLAB中使用MPI进行并行编程

在MATLAB中使用MPI进行并行编程 MATLAB支持通过MPI (Message Passing Interface) 进行并行编程&#xff0c;这通常通过Parallel Computing Toolbox和MATLAB Parallel Server实现。以下是使用MPI进行并行编程的基本方法&#xff1a; 基本设置 确保安装了必要的工具箱&#xff…...

15.【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--如何拆分单体

单体应用&#xff08;Monolithic Application&#xff09;是指将所有功能模块集中在一个代码库中构建的应用程序。它通常是一个完整的、不可分割的整体&#xff0c;所有模块共享相同的运行环境和数据库。这种架构开发初期较为简单&#xff0c;部署也较为方便&#xff0c;但随着…...

C++: char类型既不是signed char也不是unsigned char

对于 int, short, long, long long 类型&#xff0c; 增加 signed&#xff0c; 类型不变。 对于 char 类型&#xff0c; 增加 signed&#xff0c; 类型变了。 char 既不是 signed char&#xff0c; 也不是 unsigned char。 虽然 char 的取值范围&#xff0c; 一定是&#xff1…...

测试第二课-------测试分类

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…...

16.【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--微服务的部署与运维

部署与运维是微服务架构成功实施的关键环节。一个良好的部署与运维体系能够保障微服务的高可用性、可扩展性和可靠性。在这一阶段&#xff0c;重点包括微服务的容器化与编排、API 网关的实现以及日志与监控体系的建设。 一、容器化与编排 1.1 使用 Docker 容器化微服务 容器…...