compose map 源码解析

news2025/4/15 7:38:44

目录

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 ?: continue
                val scaleForLevel = visibleTilesResolver.getScaleForLevel(tile.zoom)
                    ?: continue
                val tileScaled = (tileSize / scaleForLevel).toInt()
                val l = tile.col * tileScaled
                val t = tile.row * tileScaled
                val r = l + tileScaled
                val b = t + tileScaled
                dest.set(l, t, r, b)

                drawIntoCanvas {
                    it.nativeCanvas.drawBitmap(bitmap, null, dest, null)
                }
            }
        }
    }

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

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

val zoomPRState = state.zoomPanRotateState
    key(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() / 2
        pivotY = layoutSize.height.toDouble() / 2

        centroidX = (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 = composableScope

        var newScrollX: Float? = null
        var newScrollY: Float? = null
        if (layoutSize != IntSize.Zero) {
            newScrollX = scrollX + (layoutSize.width - size.width) / 2
            newScrollY = scrollY + (layoutSize.height - size.height) / 2
        }

        layoutSize = size
        recalculateMinScale()
        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 = preloadingPadding
        return viewport.apply {
            left = zoomPanRotateState.scrollX.toInt() - padding
            top = zoomPanRotateState.scrollY.toInt() - padding
            right = left + zoomPanRotateState.layoutSize.width + padding * 2
            bottom = top + zoomPanRotateState.layoutSize.height + padding * 2
            angleRad = 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() * relativeScale

        fun 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.left
            val yTopLeft = viewport.top

            val xTopRight = viewport.right
            val yTopRight = viewport.top

            val xBotLeft = viewport.left
            val yBotLeft = viewport.bottom

            val xBotRight = viewport.right
            val yBotRight = viewport.bottom

            val xCenter = (viewport.right + viewport.left).toDouble() / 2
            val yCenter = (viewport.bottom + viewport.top).toDouble() / 2

            val xTopLeftRot =
                rotateX(xTopLeft - xCenter, yTopLeft - yCenter, viewport.angleRad) + xCenter
            val yTopLeftRot =
                rotateY(xTopLeft - xCenter, yTopLeft - yCenter, viewport.angleRad) + yCenter
            var xLeftMost = xTopLeftRot
            var yTopMost = yTopLeftRot
            var xRightMost = xTopLeftRot
            var yBotMost = yTopLeftRot

            val xTopRightRot =
                rotateX(xTopRight - xCenter, yTopRight - yCenter, viewport.angleRad) + xCenter
            val yTopRightRot =
                rotateY(xTopRight - xCenter, yTopRight - yCenter, viewport.angleRad) + yCenter
            xLeftMost = xLeftMost.coerceAtMost(xTopRightRot)
            yTopMost = yTopMost.coerceAtMost(yTopRightRot)
            xRightMost = xRightMost.coerceAtLeast(xTopRightRot)
            yBotMost = yBotMost.coerceAtLeast(yTopRightRot)

            val xBotLeftRot =
                rotateX(xBotLeft - xCenter, yBotLeft - yCenter, viewport.angleRad) + xCenter
            val yBotLeftRot =
                rotateY(xBotLeft - xCenter, yBotLeft - yCenter, viewport.angleRad) + yCenter
            xLeftMost = xLeftMost.coerceAtMost(xBotLeftRot)
            yTopMost = yTopMost.coerceAtMost(yBotLeftRot)
            xRightMost = xRightMost.coerceAtLeast(xBotLeftRot)
            yBotMost = yBotMost.coerceAtLeast(yBotLeftRot)

            val xBotRightRot =
                rotateX(xBotRight - xCenter, yBotRight - yCenter, viewport.angleRad) + xCenter
            val yBotRightRot =
                rotateY(xBotRight - xCenter, yBotRight - yCenter, viewport.angleRad) + yCenter
            xLeftMost = 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@throttle
        evictTiles(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 0
            priority + 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.bitmap
                t.bitmap = null
                b?.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 = 60f
                    paint.strokeWidth = 4f
                    paint.isAntiAlias = true
                    paint.style = Paint.Style.STROKE
                    canvas.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 rest

            if (layers.size > 1) {
                canvas.setBitmap(resultBitmap)

                for (result in bitmapForLayers.drop(1)) {
                    paint.alpha = (255f * result.layer.alpha).toInt()
                    if (result.bitmap == null) continue
                    canvas.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 = lastVisible
            if (
                (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来通信.

先这样吧,下次再补充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334390.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go语言--语法基础4--基本数据类型--整数类型

整型是所有编程语言里最基础的数据类型。 Go 语言支持如下所示的这些整型类型。 需要注意的是&#xff0c; int 和 int32 在 Go 语言里被认为是两种不同的类型&#xff0c;编译器也不会帮你自动做类型转换&#xff0c; 比如以下的例子会有编译错误&#xff1a; var value2 in…

智慧乡村数字化农业全产业链服务平台建设方案PPT(99页)

1. 农业全产业链概念 农业全产业链是依托数字化、电子商务、云计算等技术&#xff0c;整合规划咨询、应用软件设计与开发等服务&#xff0c;推动农业产业升级和价值重塑&#xff0c;构建IT产业融合新生态。 2. 产业链技术支撑 利用云计算、大数据、区块链等技术&#xff0c;为…

信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(二)

个人笔记整理---仅供参考 第二章信息技术发展 2.1信息技术及其发展 2.1.1计算机软硬件 2.1.2计算机网络 2.1.3存储和数据库 2.1.4信息安全 公钥公开&#xff0c;私钥保密 2.1.5信息技术的发展 2.2新一代信息技术及应用 2.2.1物联网 2.2.2云计算 2.2.3大数据 2.2.4区块链 2.2.5…

基于Springboot+Mysql的闲一品(含LW+PPT+源码+系统演示视频+安装说明)

系统功能 管理员功能&#xff1a;首页、个人中心、用户管理、零食分类管理、零食信息管理、订单评价管理、系统管理、订单管理。用户功能&#xff1a;首页、个人中心、订单评价管理、我的收藏管理、订单管理。前台首页功能&#xff1a;首页、零食信息、零食资讯、个人中心、后…

stm32week11

stm32学习 八.stm32基础 2.stm32内核和芯片 F1系统架构&#xff1a;4个主动单元和4个被动单元 AHB是内核高性能总线&#xff0c;APB是外围总线 总线矩阵将总线和各个主动被动单元连到一起 ICode总线直接连接Flash接口&#xff0c;不需要经过总线矩阵 AHB&#xff1a;72MHz&am…

从三次方程到复平面:复数概念的奇妙演进(二)

注&#xff1a;本文为 “复数 | 历史 / 演进” 相关文章合辑。 因 csdn 篇幅限制分篇连载&#xff0c;此为第二篇。 生料&#xff0c;不同的文章不同的点。 机翻&#xff0c;未校。 History of Complex Numbers 复数的历史 The problem of complex numbers dates back to …

基于视觉语言模型的机器人实时探索系统!ClipRover:移动机器人零样本视觉语言探索和目标发现

作者&#xff1a;Yuxuan Zhang 1 ^{1} 1, Adnan Abdullah 2 ^{2} 2, Sanjeev J. Koppal 3 ^{3} 3, and Md Jahidul Islam 4 ^{4} 4单位&#xff1a; 2 , 4 ^{2,4} 2,4佛罗里达大学电气与计算机工程系RoboPI实验室&#xff0c; 1 , 3 ^{1,3} 1,3佛罗里达大学电气与计算机工程系F…

LabVIEW往复式压缩机管路故障诊断系统

往复式压缩机作为工业领域的关键设备&#xff0c;广泛应用于石油化工、能源等行业&#xff0c;承担着气体压缩的重要任务。然而&#xff0c;其管路故障频发&#xff0c;不仅降低设备性能、造成能源浪费&#xff0c;还可能引发严重安全事故。因此&#xff0c;开发精准高效的管路…

springboot 项目 jmeter简单测试流程

测试内容为 主机地址随机数 package com.hainiu.example;import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotat…

算法思想之位运算(二)

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;算法思想之位运算(二) 发布时间&#xff1a;2025.4.13 隶属专栏&#xff1a;算法 目录 滑动窗口算法介绍六大基础位运算符常用模板总结 例题判定字符是否唯一题目链接题目描述算法思路代码实现 汉明距离题目链接题目…

【计网】网络交换技术之报文交换(复习自用,了解,重要3)

复习自用的&#xff0c;处理得比较草率&#xff0c;复习的同学或者想看基础的同学可以看看&#xff0c;大佬的话可以不用浪费时间在我的水文上了 另外两种交换技术可以直接点击链接访问相关笔记&#xff1a; 电路交换 分组交换 一、报文交换的定义 报文交换&#xff08;Me…

【动态规划】深入动态规划:背包问题

文章目录 前言01背包例题一、01背包二、分割等和子集三、目标和四、最后一块石头的重量|| 完全背包例题一、完全背包二、 零钱兑换三、零钱兑换||四、完全平方数 前言 什么是背包问题&#xff0c;怎么解决算法中的背包问题呢&#xff1f; 背包问题 (Knapsack problem) 是⼀种组…

BUUCTF-web刷题篇(25)

34.the mystery of ip 给出链接&#xff0c;输入得到首页&#xff1a; 有三个按钮&#xff0c;flag点击后发现页面窃取客户端的IP地址&#xff0c;通过给出的github代码中的php文件发现可以通过XFF或Client-IP传入值。使用hackbar或BP 使用XSS&#xff0c;通过github给出的目录…

StringBuilder类基本使用

文章目录 1. 基本介绍2. StringBuilder常用方法3. String、StringBuffer 和 StringBuilder 的比较4. String、StringBuffer 和 StringBuilder 的效率测试5. String、StringBuffer 和 StringBuilder 的选择 1. 基本介绍 一个可变的字符序列。此类提供一个与StringBuffer兼容的A…

设计模式 --- 访问者模式

访问者模式是一种行为设计模式&#xff0c;它允许在不改变对象结构的前提下&#xff0c;定义作用于这些对象元素的新操作。 优点&#xff1a; 1.​​符合开闭原则&#xff1a;新增操作只需添加新的访问者类&#xff0c;无需修改现有对象结构。 ​​2.操作逻辑集中管理​​&am…

HashTable,HashMap,ConcurrentHashMap之间的区别

文章目录 线程安全方面性能方面总结 线程安全方面 HashMap线程不安全&#xff0c;HashMap的方法没有进行同步&#xff0c;多个线程同时访问HashMap&#xff0c;并至少有一个线程修改了其内容&#xff0c;则必须手动同步。 HashTable是线程安全的&#xff0c;在HashMap的基础上…

LeetCode.225. 用队列实现栈

用队列实现栈 题目解题思路1. push2. pop3. empty CodeQueue.hQueue.cStack.c 题目 225. 用队列实现栈 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现…

LVGL AnalogClock控件和Dclock控件详解

LVGL AnalogClock控件和Dclock控件详解 一、AnalogClock控件详解1. 概述2. 创建模拟时钟2.1 函数2.2 参数2.3 返回值 3. 设置时间3.1 函数3.2 参数 4. 获取时间4.1 函数4.2 参数 5. 设置样式5.1 常用样式属性5.2 示例代码 6. 更新时间6.1 定时器回调示例6.2 创建定时器 7. 示例…

【C++初学】课后作业汇总复习(七) 指针-深浅copy

1、 HugeInt类:构造、、cout Description: 32位整数的计算机可以表示整数的范围近似为&#xff0d;20亿到&#xff0b;20亿。在这个范围内操作一般不会出现问题&#xff0c;但是有的应用程序可能需要使用超出上述范围的整数。C可以满足这个需求&#xff0c;创建功能强大的新的…

探索加密期权波动率交易的系统化实践——动态对冲工具使用

Trading Volatility – What Are My Options? 在本文中&#xff0c;我们将介绍一些如何交易资产波动性&#xff08;而非资产价格&#xff09;的示例。为了帮助理解&#xff0c;我们将使用 Deribit 上提供的几种不同产品&#xff0c;包括但不限于期权。我们将尽可能消除对标的价…