289M→259M得物包体积治理实践

一、前言

iOS应用的包体积大小是衡量得物性能的重要指标,过大包体积会降低用户对应用的下载意愿,还会增加用户的下载等待时间以及用户手机的存储空间,本文重点介绍在包体积治理中的新思路以及原理与实践。

二、原理介绍

Macho产物测试

我们拿测试工程单独依赖一个组件,比如DemoModule,进行编译MarchO得出整合前的大小:58929120Byte。同时为了方便分析,我们也导出Linkmap.txt文件。

Linkmap文件中记录MachO文件中每个符号所占用的体积大小,因此通过分析Linkmap可以分析MachO具体符号占用变化,由于Linmap介绍不是本文重点,不多做赘述,更多详情可参考网上文章https://juejin.cn/post/6844904168096792583。

随后将组件工程中的文件编码整合10~20个,得出整合后MachO的大小:58894688Byte。(下图为编码前和编码后的MarchO占用磁盘大小) 

LinkMap分析

整合后的文件变小了34K,我们继续分析产物导出的Linkmap,具体查看是哪里变小了。

  • 通过对比Linkmap.txt发现:Text段减小10.6K、en_frame段减小了2K。

Linkmap.txt文件第一列展示的是符号的起始地址,第二列展示的是大小,16进制,将16进制转换为10进制,即是大小。相减得出变化大小。

__text段存储的机器编译后的代码。

en_frame存储了函数调用入口帧信息。具体查看https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html。

Linkmap符号变化

继续解析Linkmap每个组件的变化,我们发现,DemoModule组件减小15K、连接器自动生成符号的变化减小2K。

组建变化

链接符号变化

通过对Linkmap的分析,确实存储代码段和函数入口帧信息减小使得编译后的.o文件变小了,那么.o文件编码整合为DemoModule.a文件也随之变小了,那么到底是哪块代码变小了呢?我们继续往下分析。

Mach-o代码内容分析

通过上面Linkmap的分析,我们知道了是代码段以及函数调用符号占用的体积变小了,我们通过objcdump将MarchO符号进行导出。

objdump --macho -d  --start-address=0x10025FDD0 --stop-address=0x100257668  ~/Desktop/IPATestProj > ~/Desktop/result.txt
objdump --macho -d  --start-address=0x10025FDD0 --stop-address=0x100257668  ~/Desktop/IPATestProj-after > ~/Desktop/result-after.txt

对比发现

针对s13DemoModule0A29TSearchHotRecommendDemoModuleCACycfC优化了28Byte。

allocWithZone以及objc的init方法。调用了DemoModule0A21FollowBrandDemoModuleCACycfC, DemoModule0A21FollowBrandDemoModuleCACycfC 调用了s13DemoModule0A17PaySendDemoModuleCACycfC,s13DemoModule0A17PaySendDemoModuleCACycfC里实现了alloc with zone和init方法。也就是说编译器优化了经过编码的alloc with zone方法,只会有一个alloc with zone 的实现。

针对s13DemoModule0A29TSearchHotRecommendDemoModuleCMr

DemoModule.ExampleModule.__deallocating_deinit优化了32Byte

优化了meta的deinit与寻找metaclass的过程。s13DemoModule0A29DemoModuleCMa调用了s13DemoModule0A31tDemoModuleCMaTm - ,而 和SearchDemoModule同时继承了CustomRequestDemoModule。

Alloc with zone前后对比

  • 整合编码之前的逆向机器码
_$s13DemoModule0A29TSearchHotRecommendDemoModuleCACycfC:
1002fe8c8:        fd 7b bf a9        stp        x29, x30, [sp, #-16]!
1002fe8cc:        fd 03 00 91        mov        x29, sp
1002fe8d0:        e0 03 14 aa        mov        x0, x20
1002fe8d4:        3d 2a 20 94        bl        0x100b091c8 ; symbol stub for: _objc_allocWithZone
1002fe8d8:        28 64 00 b0        adrp        x8, 3205 ; 0x100f83000
1002fe8dc:        01 79 44 f9        ldr        x1, [x8, #2288] ; Objc selector ref: init
1002fe8e0:        fd 7b c1 a8        ldp        x29, x30, [sp], #16
1002fe8e4:        7b 2a 20 14        b        0x100b092d0 ; Objc message: -[x0 init]
_$s13DemoModule0A29TSearchHotRecommendDemoModuleCACycfc
  • 整合编码之后的逆向机器码
1002577a0:        b2 ff ff 97        bl        _$s13DemoModule0A29TSearchHotRecommendDemoModuleCACycfc
1002577a4:        fd 7b 41 a9        ldp        x29, x30, [sp, #16]
1002577a8:        f4 4f c2 a8        ldp        x20, x19, [sp], #32
1002577ac:        c0 03 5f d6        ret
_$s13DemoModule0A29TSearchHotRecommendDemoModuleCfD:
1002577b0:        60 fe ff 10        adr        x0, #-52
1002577b4:        1f 20 03 d5        nop
1002577b8:        f2 fd ff 17        b        _$s13DemoModule0A31IdentComTrendDelLightDemoModuleCfDTm
_$s13DemoModule0A26TMeasureRecordAiUpdateSkinCACycfc:
  • Deinit前后对比-整合编码之前
_$s13DemoModule0A29TSearchHotRecommendDemoModuleCMa:
1002fe9fc:        fd 7b bf a9        stp        x29, x30, [sp, #-16]!
1002fea00:        e8 03 00 aa        mov        x8, x0
1002fea04:        89 71 00 b0        adrp        x9, 3633 ; 0x10112f000
1002fea08:        20 7d 40 f9        ldr        x0, [x9, #248]
1002fea0c:        80 00 00 b4        cbz        x0, 0x1002fea1c
1002fea10:        01 00 80 d2        mov        x1, #0
1002fea14:        fd 7b c1 a8        ldp        x29, x30, [sp], #16
1002fea18:        c0 03 5f d6        ret
1002fea1c:        41 4c 00 90        adrp        x1, 2440 ; 0x100c86000
1002fea20:        21 80 24 91        add        x1, x1, #2336
1002fea24:        e0 03 08 aa        mov        x0, x8
1002fea28:        f7 2c 20 94        bl        0x100b09e04 ; symbol stub for: _swift_getSingletonMetadata
1002fea2c:        fd 7b c1 a8        ldp        x29, x30, [sp], #16
1002fea30:        c0 03 5f d6        ret
  • Deinit前后对比-整合编码之后
0x10025777C        0x00000014        [583] _$s13DemoModule0A29TSearchHotRecommendDemoModuleCMa

由此可以得出结论。

  1. 编译器针对不同的Class,经过编码整合后,编译时会触发编译优化,alloc with zone、deinit寻找metaclass方法。将文件编码后整合会优化为一个。
  2. 同时相关的寻址和寄存器的addr,以及mov、内存地址的存储已随之删除,具体对比结果可以看上面的产物对比。

三、落地实践

经过上文的原理探究,整合一个组件有34K的收益,得物全工程是一个由1100+组件组成的Swift工程,那么我们基于组件的维度,将1100+组件做整合,那么就能拿到收益了,为了做到文件编码整合,拿到收益,我们需要在稳定性的基础上做到如下的目标。

  1. 需要满足线上包、灰度包、测试包等所有CI流程出的包都是文件编码整合后的包,并且需要保证相同的版本,文件编码整合的一致性。
  • 得物工程的组件化CI是使用Cocoapods来实现的,因此需要改造Cocoapods 的download流程,将文件编码整合嵌入到所有的发版与打包的CI中。
  • 为了满足大家的正常使用,需要为pod定制命令,比如--megre-file --clean-sanbox,正常开发默认命令不生效,为打包机等CI任务配置命令,做到开发无感知,发版无缝整合。
  1. 需要判断整个工程盘点出可能存在的风险点,并在整合前做改造,盘点出主要的改造点:
    • 项目中存在同名的public或者open声明的extension方法,之前存在于不同的文件编码中,不会造成编译报错,经过编码后之后会出现大量的编译报错,整合后通过编译器去识别项目中同名的方法,在改造发版,每次改造编译源码都需要大量的时间,这显然是不现实的,因此我们需要通过indexstore-db与 SwiftSytax将项目中所有的同名extension方法做识别统一改造,并一次性的编译
    • 项目中存在已#fileID、#file、#line方法与业务耦合,做调用位置判断,由于文件编码整合行号与,打包时的文件名都发生的变化,因此我们也需要通过SwiftSytax将所有方法导出,并做甄别改造。
  1. 需要分节奏,分版本,做好充分的灰度测试,灰度上线逐步拿到收益。
    • 为了做到组件分版本灰度,需要为cocoapod bin pod命令增加版本的概念,为每个上线的组件配置好版本号,满足配置的组件在执行整合,不满足的组件走原有download流程。

如何满足上述3项目标,下面为大家逐一介绍。

Cocoapods原理与实践

介绍流程:当我们执行pod install时,会在你的电脑发生如下步骤。

1. 执行pod install时会进入到本机电脑的/usr/local/bin/pod我们发现是一个快捷方式。

2. 右键点击显示原项目,我们就进入到了真正的执行指令的入口目录。

由于笔者的pod是使用homebrew装的,因此pod可执行文件在homebrew的安装目录: /opt/homebrew/Cellar,这个pod文件本质是个bash sh文件,咱们将文件已编辑器打开有如下的内容。

GEM_HOME="/opt/homebrew/Cellar/ruby环境根目录" exec "/opt/homebrew/Cellar/真实的cocoapods目录/bin/pod"  "$@"

继续打开后面的执行文件,发现这个pod文件就是cocoapods安装文件下的pod,pod是一个ruby文件,也就是cocoapods最终的命令入口,内容如下:

#!/usr/bin/env ruby
require 'rubygems'
if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('cocoapods', 'pod', version)
else
gem "cocoapods", version
load Gem.bin_path("cocoapods", "pod", version)
end

3. 随后就进入到了cocoapods的cocoapods/cocoapods.rb,cocoapods.rb引入了 核心类,比如:

    Xcodeproj::PlainInformative.send(:include, CLAide::InformativeError)
    autoload :Command,                   'cocoapods/command' # 命令行入口
    autoload :ExternalSources,           'cocoapods/external_sources' # git 依赖,本地依赖处理类
    autoload :Installer,                 'cocoapods/installer' # pod install核心 类

cocoapods/command是一个基类,每个命令pod install、pod update、pod repo add都会有相应command重写。可查看下面的截图:

4. 我们单纯拿pod install看文件里的内容。就知道我们如何给pod命令传递参数了。

  • 从文件内容可以看到class Install继承了 Command,def initialize中定义需要传递的参数其中clean_install就是我们常用pod install --clean-install命令。
  • def run函数进入了install的流程,下面也为大家简单注释了每个函数的作用。
      def initialize(argv)
        super
        @deployment = argv.flag?('deployment', false)
        @clean_install = argv.flag?('clean-install', false)
      end

      def run
        verify_podfile_exists! # 校验 工程目录Podfile 文件是否存在
        installer = installer_for_config # 根据config 生成 installer
        installer.repo_update = repo_update?(:default => false) # 配置是否需要更新索引库
        installer.update = false # 由于是install 因此 update 是false
        installer.deployment = @deployment 
        installer.clean_install = @clean_install
        installer.install! # 进入真正的install 流程
      end

那installer.install里面做了什么呢?我们继续往下看:

5.下面是installer.install的源码,我们可以简单将install分为以下步骤:

# install 源代码
def install!
  prepare
  resolve_dependencies # 依赖链分析
  download_dependencies # 
  validate_targets
  clean_sandbox
  if installation_options.skip_pods_project_generation?
    show_skip_pods_project_generation_message
    run_podfile_post_install_hooks
  else
    integrate
  end
  write_lockfiles
  perform_post_install_actions
end
  • resolve_sependencies会更新索引库,得到pods target对应的数据,得到aggregate target对应的数组,并提前加载git依赖与本地依赖的组件。为下载pod依赖做环境准备。

1. 为了能将:git、:branch、:commit依赖与本地:path依赖做代码编码并整合,我们需要再resolve_dependencies中加载git依赖与本地依赖阶段时做hook,满足本地依赖与git依赖组件集成时做代码编码整合。

2. 为了能对正常pod"Example"组件做整合,我们需要再download_dependencies中对组件做整合。具体实现整合与定制参数传入,我们继续往下看。

Pod命令改造:引入pod update --transform-local --transform-file。

1. 上文我们了解了,每个command都有一个命令类,为了不污染cocoapods的源码,使得能正常随着cocoapods更新进行升级,我们模仿cocoapods设计的想法,在cocoapods/cocoapods.rb核心类引入hook/hook_option.rb,cocoapods.rb加入的内容如下:

# hook_file用于统一管理du_hook文件
# 判断有没有hook_file
if File.exist?(File.join(__dir__, 'cocoapods/du_hook/hook_file.rb'))
  require 'cocoapods/hook/hook_file'
end

2. 在hook_file中,Cat同学为cocoapods做了热更新机制,同时引入了main_hook.rb、main_hook.rb中引入了得物为cocoapods做的魔改部分。代码如下:

热更机制简单理解就是每次执行pod命令时会执行git操作,将魔改部分的仓库代码保持到最新,Cat这一巧妙的设计让得物iOSer都能实时享受到cocoapods改造带来的新功能。

require 'cocoapods/hook/cocoapods-hook/cocoapods_concurrent_hook' # 魔改的高并发下载
require 'cocoapods/hook/cocoapods-hook/cocoapods_option' # 为cocoapod 加入 命令参数的入口

3. 我们继续往下看,在cocoapods_option.rb文件中。咱们模仿cocoapods的设计逻辑,对命令解析。如果有传入--transform-file --transform-local 参数,那么就引入 cocoapods_transform_file.rb 文件,进入文件编码整合的入口。

module Pod
  class Command
    module Options
      module Demo
        module Options
        def initialize(argv)
          # 每个电脑都有一个全局的环境变量,在执行命令的生命周期内是一直存在的,给环境变量传入配置,不改动cocoapods的config源配置文件,不入侵cocoapods的源代码。
          ENV['transform_FILE'] = '1' if @transform_file 
          ENV['ransform_LOCAL'] = '1' if @transform_local
          super
        end
      end
    end
  end
end

Ruby是一个运行时的动态语言,在required cocoapods_transform_file文件中,将指定的cocoapods函数进行重写,就能实现HOOK的功能。

  1. 因此在cocoapods_transform_file.rb文件中覆盖cocoapods/external_sources/path_source.rb 下class PathSource的fetch方法就能定制为本地依赖的组件、git依赖的组件的组件执行定制的整合能力
  2. 覆盖cocoapods/downlod.rb下的Module Downloader self.download module类方法就能定制为pod"Example"的组件执行定制的整合。

具体的整合思路我们继续往下看。

Pod组件编码整合介绍

  • 我们为每个组件配置了整合的版本号,每次需要进行整合时会传入版本号,默认是不整合,当一个组件进入download流程。会优先判断组件配置的版本号是否满足。
  • 如果不满足那么不进行整合,正常执行常规的下载流程。
  • 如果满足:
    • 会继续判断是否在存在已经缓存好的文件夹,如果存在,直接将整合好的缓存文件Copy到Pods文件夹
      • Cococoapods的组件缓存目录在~/Library/Caches/Cocoapods/Pods/Release/<版本号>-hash
      • 我们为了能提高整合的效率为每个整合好的组件也进行缓存,这样能明显提高cocoapods的下载效率。命令规则会在原目录下多一份~/Library/Caches/Cocoapods/Pods/Release/<版本号>-hash-setuped。
  • 如果不存在缓存文件,那么解析podspec,拿到待整合文件数组,执行整合,保存整合后的缓存,并将整合后的缓存Copy到Pods文件夹。

本地依赖组件整合介绍

  • 当本地依赖组件进入fetch方法,判断组件配置的版本号是否满足,不满足不执行整合。
  • 满足则执行整合,并将整合的内容保存到新文件中,保存到pod target数组中以备后续cocoapods生成本地组件的pod targets。

Native代码整改

为什么要改造?

  • 针对所有文件做编码并整合,会使得分散在不同文件中的同名方法名称冲突,使得工程无法编译成功,因此需要扫描出工程中所有的同名方法,并扫描出同名方法的上层调用。

如何改造?

  • 扫描工程中所有的方法可以借助swift-syntax或者SwiftLint自定义规则具体扫描代码可参考如下。

SwiftLint中依赖了Swiftsytax,本质都是借助Swiftsyntax进行词法分析,扫描出工程的所有extension同名函数,并进行改造。

       override func visitPost(_ node: ExtensionDeclSyntax) {
            let functionList = _isFunctionDecl(node)
            guard !functionList.isEmpty else { return }
            for funcItem in functionList {
                // 如果是private function 那么不纳入考虑范围
                guard !_isPrivateFunction(funcItem) else { continue }
                // 如果不是public的extension,并且函数也不是public 那么这个函数就不是公开函数,也可以忽略
                if !isPublicExtension && !_isPublicFunction(node: funcItem) {
                    continue
                }
                violations.insert(ReasonedRuleViolation(position: funcItem.position, reason: funcItem.resolvedName(), severity: .warning), at: violations.count)
            }
        }
  • 扫描出同名方法后,使用indexstore-db将方法签名传入,通过扫描产物,可得出方法的上层调用,并进行统一改造,indexstore-db使用可参考如下

Indexstore-db是一个用于存储和管理源代码索引数据的开源工具。indexstore-db工具可以收集和存储源代码的元数据信息,包括符号、模块依赖关系、引用关系等,以便在开发工具(如Xcode)中进行快速的代码导航和搜索。它在构建大型代码库时尤其有用,可以提高代码编辑、查找引用、代码重构等操作的效率。

func testExtensionSymbol() throws {
        // indexstore-db 的动态加载库
        let libIndexStore = try! IndexStoreLibrary(dylibPath: "/Applications/Xcode.app/xxx/libIndexStore.dylib")
        // 生成indexstore 实例
        let indexWait = try IndexStoreDB(storePath: "/Users/xxx/Library/Developer/Xcode/DerivedData/.../DataStore", databasePath: "/Users/xxx/Downloads/aaa", library: libIndexStore, waitUntilDoneInitializing: true)
        indexWait.pollForUnitChangesAndWait()
        // 假设我们需要扫描如下的文件
       let symbols = indexWait.symbols(inFilePath: "/Users/xxx/Project/String+Demo.swift")
      for symbol in symbols {
              // 假设我们需要扫描searchAtRange函数。
          guard symbol.name == "searchAtRange()" else { continue}
          let res = indexWait.occurrences(ofUSR: symbol.usr, roles: .reference)
          for x in res {
              debugPrint(x.relations.compactMap({ symbol in
                  return symbol.symbol.usr
              }))
          }
      }
  }

组件发版流程重构

为什么要改造?

  • 将cocoapods与同名方法改造完后,我们进行全工程源码编译是可以通过的,而且由于做了编码整合,编译时长也降低了5~8分钟,但是当发布组件发布CI时发现,未整合的组件二进制与整合的源码会出现link时符号不对齐的问题。

未整合的组件二进制符号是确定的,调用下游的符号签名也是确定的,Swift有 fileprivate的函数定义,当函数由A文件经过编码迁移到整合后的文件时,函数的签名也会变化。因此会出现函数签名符号不对齐。

如何改造?

  • 得物工程每个版本都有一个源码索引库和二进制索引库,因此在组件发版时,我们需要再创建一个索引库,编码整合后的二进制索引库,并重新建立一套编码整合的二进制的CICD打包流程。具体流程可参考如下。

开发者开发时使用正常的二进制制作任务。发版与出包的打包机会使用整合二进制索引库。这样设计使得对日常开发无感知,而且能保证对外提测的任务都是整合后的包。

整合符号表

  • 上述的改造解决了编译和出包的问题,但编译后的报错工程师阅读会比较困难,为了解决这个问题,引入了整合符号表,能根据符号表,反推出源工程的文件名以及行号,这就解决了编译报错阅读难的问题。

四、总结与收益

经过了深度的治理以及组件编码整合,期间cocoapods的改造与ruby原理的学习得益与Cat的请教,并得到各个iOS开发伙伴的无条件支持,同时将整个构建打包流程做了重构,以满足组件编码,经过多个版本的治理,得物的包大小在业务代码迭代有增量的前提下,从289.3M降低至259.3M。

下面列出每个阶段治理做个小结。

五、展望

后续包体的主要方向,是结合产物的扫描,与函数命名域治理以及全工程分析无用功能与可下线功能的持续跟进,并在防裂化上做好每次不合理增量的把控。把包体积的任务指标share至各个业务域。让包体积能在深度优化与防裂化两条路并行。

引用:

https://github.com/apple/indexstore-db
https://github.com/realm/SwiftLint
https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
https://juejin.cn/post/6844904168096792583

*文/明颉
本文属得物技术原创,更多精彩文章请看:得物技术
未经得物技术许可严禁转载,否则依法追究法律责任!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

想要修改word文档怎么移除编辑权限?学会这两个方法,轻松搞定

日常办公和学习中&#xff0c;Word文档是我们不可或缺的工具。然而&#xff0c;有时我们可能会遇到一些设置了编辑权限的文档&#xff0c;这可能是由于文档的创建者希望控制文档的修改和传播&#xff0c;或者是因为文档在某些共享或协作环境中被设置为只读模式。在这种情况下&a…

网工内推 | 网络运维工程师,H3CIE认证优先,13薪,享股票期权

01 畅读 &#x1f537;招聘岗位&#xff1a;高级网络运维工程师 &#x1f537;职责描述&#xff1a; 1.负责线上业务网络技术运维工作&#xff0c;保障并优化线上网络质量&#xff1b; 2.规划并构建公司线上业务网络架构&#xff1b; 3.规划线上业务网络质量评估与监控体系&…

mysql中 redo日志(上)

大家好。我们知道InnoDB 存储引擎是以页为单位来管理存储空间的&#xff0c;我们进行的增删改查操作其实本质上都是在访问页面。而在真正访问页面之前&#xff0c;需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。那么我们思考一个问题&#xff1a;如果我们只在内…

vue2中使用tinymce

vue2中使用tinymce的记录 本篇文章主要实现的功能&#xff1a; &#xff08;1&#xff09;【查看】时禁用编辑 &#xff08;2&#xff09;【编辑】时某些内容是不可编辑的 &#xff08;3&#xff09;【内容】前端拼接编辑器模板 &#xff08;4&#xff09;【内容】编辑器模板中…

【漏洞复现】锐捷校园网自助服务系统 login_judge.jsf 任意文件读取漏洞(XVE-2024-2116)

0x01 产品简介 锐捷校园网自助服务系统是锐捷网络推出的一款面向学校和校园网络管理的解决方案。该系统旨在提供便捷的网络自助服务&#xff0c;使学生、教职员工和网络管理员能够更好地管理和利用校园网络资源。 0x02 漏洞概述 校园网自助服务系统/selfservice/selfservice…

Java核心: 为图片生成水印

今天干了一件特别不务正业的事&#xff0c;做了一个小程序用来给图片添加水印。事情的起因是需要将自己的身份证照片分享给别人&#xff0c;手边并没有一个趁手的工具来生成图片水印。很多APP提供了水印的功能&#xff0c;但会把我的图片上传到他们的服务器&#xff0c;身份证太…

台式机安装Windows 11和Ubuntu 22双系统引导问题

一、基本情况 1.1、硬件情况 电脑有2个NVMe固态硬盘&#xff0c;1个SATA固态硬盘&#xff0c;1个机械硬盘。其中一个NVMe固态硬盘是Windows系统盘&#xff0c;另一个NVMe固态为Windows软件和文件盘&#xff0c;SATA固态硬盘为Ubuntu专用&#xff0c;机械硬盘为数据备份盘。 …

Find My电动螺丝刀|苹果Find My技术与螺丝刀结合,智能防丢,全球定位

电动螺丝刀&#xff0c;别名电批、电动起子&#xff0c;是用于拧紧和旋松螺钉用的电动工具。它不仅提高了工作效率&#xff0c;还大大减轻了工作者的体力负担。在装配线等生产环境中&#xff0c;电动螺丝刀已经成为了不可或缺的工具。电动螺丝刀的批头还具备接地防静电功能&…

Leetcode:四数之和

题目链接&#xff1a;18. 四数之和 - 力扣&#xff08;LeetCode&#xff09; 普通版本&#xff08;排序 双指针&#xff09; 主旨&#xff1a;类似于三数之和的解法&#xff0c;但需要多加一些限制&#xff0c;同时为了防止多个数组元素的相加之和出现整型溢出问题还要将整型…

IDEA 2022

介绍 【尚硅谷IDEA安装idea实战教程&#xff08;百万播放&#xff0c;新版来袭&#xff09;】 jetbrains 中文官网 IDEA 官网 IDEA 从 IDEA 2022.1 版本开始支持 JDK 17&#xff0c;也就是说如果想要使用 JDK 17&#xff0c;那么就要下载 IDEA 2022.1 或之后的版本。 公司…

《TCP/IP网络编程》(第十三章)多种I/O函数(2)

使用readv和writev函数可以提高数据通信的效率&#xff0c;它们的功能可以概括为**“对数据进行整合传输及发送”**。 即使用writev函数可以将分散在多个缓冲中的数据一并发送&#xff0c;使用readv函数可以由多个缓冲分别接受&#xff0c;所以适当使用他们可以减少I/O函数的调…

Refused to load the stylesheet问题解决方案

今天项目部署的过程中遇到一个安全策略问题的报错&#xff0c;大概意思就是处于安全考虑&#xff0c;不允许src外链其他不安全的静态文件 解决这种问题的一个思路大概就是找到index.html文件先看下是否存在 <meta http-equiv"Content-Security-Policy" content&…

用PlayCanvas打造一个令人惊叹的3D图在线展示

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 PlayCanvas实例化渲染&#xff1a;大规模渲染优化 应用场景 在游戏开发中&#xff0c;经常需要渲染大量相同或相似模型。传统方法需要为每个模型创建单独的渲染对象&#xff0c;这会消耗大量内存和GPU资源。实…

问你为什么选择Kafka,你会怎么回答?

可靠的含义在百度百科的解释是&#xff1a;可以信赖、可以相信、可靠的朋友。那Kafka究竟是不是一个可靠的朋友呢&#xff1f;既然全世界绝大部分高可用系统都有Kafka的支持&#xff0c;Kafka必定有其过人之处&#xff0c;跟着我来分析分析。 另外多提一嘴Kafka在GitHub目前已…

【AIGC X UML 落地】通过多智能体实现自然语言绘制UML图

前天写了篇博文讲到用PlantUML来绘制C类图和流程图。后台有读者留言&#xff0c;问这步能否自动化生成&#xff0c;不想学习 PlantUML 语法。 我想了下&#xff0c;发现这事可行&#xff0c;确实可以做到通过自然语言的描述就能实现 UML图的绘制&#xff0c;昨天晚上加了个班到…

安装TPMmanager

sudo apt-get install qt4-qmake sudo apt-get install libqt4-dev下载TPMManager&#xff0c;解压之后拖入Ubuntu&#xff0c;进入目录 https://gitcode.com/Rohde-Schwarz/TPMManager/overview?utm_sourcecsdn_github_accelerator&isLogin1 cd tpmmanager-master qmake…

快速排序(Quick Sort)(C语言) 超详细解析!!!

生活的本质是什么呢? 无非就是你要什么就不给你什么. 而生活的智慧是什么呢? 是给你什么就用好什么. ---马斯克 索引 一. 前言二. 快速排序的概念三. 快速排序的实现1. hoare2. 挖坑法3. 前后指针法 总结 正文开始 一. 前言 接上文, 前面我们了解了插入排序, 与优化版本希尔…

Vulnhub-DC-2

靶机IP:192.168.20.135 网络有问题的可以看下搭建Vulnhub靶机网络问题(获取不到IP) kaliIP:192.168.20.128 扫描靶机端口及服务版本 发现开放了80和7744端口 并且是wordpress建站 dirsearch扫描目录 访问前端界面&#xff0c;发现存在重定向 在hosts文件中增加192.168.2…

【UML用户指南】-09-对基本结构建模-类图

目录 1、概述 2、引入 3、过程 4、常用建模技术 4.1、对简单协作建模 4.2、对逻辑数据库模式建模 4.3、正向工程 1、概述 类图是面向对象系统建模中最常见的图。 类图显示一组类、接口、协作以及它们之间的关系 类图用于对系统静态设计视图建模。其大多数涉及到对系统的…

这个世界,对于心态好的人,就是个大游乐场,越刺激越好玩。对于胆小鬼,那就是地狱,随时随地都会受伤

心态决定你的世界&#xff1a;游乐场还是地狱 在这个充满变数的世界里&#xff0c;我们的心态决定了我们看待世界的方式。对于心态积极的人来说&#xff0c;世界就像一个巨大的游乐场&#xff0c;每一个挑战都是一个新的游戏&#xff0c;每一个刺激都是乐趣的一部分。而对于那…