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

40分钟学 Go 语言高并发:Go程序性能优化方法论

Go程序性能优化方法论

一、性能指标概述

指标类型关键指标重要程度优化目标
CPU相关CPU使用率、线程数、上下文切换⭐⭐⭐⭐⭐降低CPU使用率,减少上下文切换
内存相关内存使用量、GC频率、对象分配⭐⭐⭐⭐⭐减少内存分配,优化GC
延迟指标响应时间、处理延迟、等待时间⭐⭐⭐⭐降低延迟,提高响应速度
吞吐量QPS、TPS、并发数⭐⭐⭐⭐提高系统吞吐量

让我们通过代码示例来展示如何进行性能优化:

package mainimport ("fmt""runtime""sync""testing""time"
)// 性能基准测试示例
func BenchmarkSliceAppend(b *testing.B) {for i := 0; i < b.N; i++ {var s []intfor j := 0; j < 1000; j++ {s = append(s, j)}}
}// 优化后的版本
func BenchmarkSliceAppendOptimized(b *testing.B) {for i := 0; i < b.N; i++ {s := make([]int, 0, 1000)for j := 0; j < 1000; j++ {s = append(s, j)}}
}// 内存优化示例
type DataBlock struct {mu    sync.Mutexitems map[string][]byte
}// 未优化版本
func (db *DataBlock) ProcessDataUnoptimized(key string, data []byte) {db.mu.Lock()defer db.mu.Unlock()// 创建一个新的切片并复制数据dataCopy := make([]byte, len(data))copy(dataCopy, data)db.items[key] = dataCopy
}// 优化后的版本 - 使用对象池
var dataBlockPool = sync.Pool{New: func() interface{} {return make([]byte, 0, 1024)},
}func (db *DataBlock) ProcessDataOptimized(key string, data []byte) {// 从对象池获取缓冲区buf := dataBlockPool.Get().([]byte)buf = buf[:len(data)]// 复制数据copy(buf, data)db.mu.Lock()db.items[key] = bufdb.mu.Unlock()
}// CPU优化示例
func CalculateSum(numbers []int) int64 {var sum int64for _, n := range numbers {sum += int64(n)}return sum
}// 优化后的并行版本
func CalculateSumParallel(numbers []int) int64 {if len(numbers) < 1000 {return CalculateSum(numbers)}numGoroutines := runtime.NumCPU()var wg sync.WaitGroupch := make(chan int64, numGoroutines)// 计算每个goroutine处理的数量batchSize := len(numbers) / numGoroutinesfor i := 0; i < numGoroutines; i++ {wg.Add(1)start := i * batchSizeend := start + batchSizeif i == numGoroutines-1 {end = len(numbers)}go func(start, end int) {defer wg.Done()var sum int64for _, n := range numbers[start:end] {sum += int64(n)}ch <- sum}(start, end)}// 等待所有goroutine完成go func() {wg.Wait()close(ch)}()// 汇总结果var totalSum int64for sum := range ch {totalSum += sum}return totalSum
}// 性能测试工具
type PerformanceMetrics struct {StartTime    time.TimeEndTime      time.TimeMemStats     runtime.MemStatsNumGoroutine int
}func NewPerformanceMetrics() *PerformanceMetrics {return &PerformanceMetrics{StartTime: time.Now(),}
}func (pm *PerformanceMetrics) Stop() {pm.EndTime = time.Now()runtime.ReadMemStats(&pm.MemStats)pm.NumGoroutine = runtime.NumGoroutine()
}func (pm *PerformanceMetrics) Report() string {duration := pm.EndTime.Sub(pm.StartTime)return fmt.Sprintf("Performance Report:\n"+"Duration: %v\n"+"Memory Allocated: %v MB\n"+"Number of GC Cycles: %v\n"+"Number of Goroutines: %v\n",duration,pm.MemStats.Alloc/1024/1024,pm.MemStats.NumGC,pm.NumGoroutine,)
}func main() {// 创建测试数据data := make([]int, 1000000)for i := range data {data[i] = i}// 测试未优化版本metrics := NewPerformanceMetrics()sum1 := CalculateSum(data)metrics.Stop()fmt.Printf("Unoptimized version result: %d\n", sum1)fmt.Println("Unoptimized version metrics:")fmt.Println(metrics.Report())// 测试优化版本metrics = NewPerformanceMetrics()sum2 := CalculateSumParallel(data)metrics.Stop()fmt.Printf("Optimized version result: %d\n", sum2)fmt.Println("Optimized version metrics:")fmt.Println(metrics.Report())
}

二、性能优化方法

1. CPU优化

主要优化方向:

  1. 算法优化

    • 降低时间复杂度
    • 减少不必要的计算
    • 使用更高效的算法
  2. 并行处理

    • 合理使用goroutine
    • 避免过度并行
    • 控制并发数量
  3. 缓存利用

    • 使用本地缓存
    • 避免频繁GC
    • 减少内存分配

2. 内存优化

主要优化方向:

  1. 内存分配

    • 预分配内存
    • 使用对象池
    • 减少临时对象
  2. GC优化

    • 控制GC触发频率
    • 减少GC压力
    • 使用合适的GC参数
  3. 数据结构

    • 选择合适的数据结构
    • 控制切片容量
    • 减少指针使用

3. 并发优化

  1. goroutine管理

    • 控制goroutine数量
    • 避免goroutine泄露
    • 使用合适的并发模型
  2. 锁优化

    • 减少锁竞争
    • 使用细粒度锁
    • 采用无锁算法

三、基准测试

1. 编写基准测试

package mainimport ("sync""testing"
)// 字符串连接基准测试
func BenchmarkStringConcat(b *testing.B) {b.ResetTimer()for i := 0; i < b.N; i++ {var s stringfor j := 0; j < 100; j++ {s += "a"}}
}// 使用 strings.Builder 的优化版本
func BenchmarkStringBuilder(b *testing.B) {b.ResetTimer()for i := 0; i < b.N; i++ {var builder strings.Builderfor j := 0; j < 100; j++ {builder.WriteString("a")}_ = builder.String()}
}// 内存分配基准测试
func BenchmarkSliceAllocation(b *testing.B) {b.ResetTimer()for i := 0; i < b.N; i++ {data := make([]int, 1000)for j := range data {data[j] = j}}
}// 使用对象池的优化版本
var slicePool = sync.Pool{New: func() interface{} {return make([]int, 1000)},
}func BenchmarkSlicePool(b *testing.B) {b.ResetTimer()for i := 0; i < b.N; i++ {data := slicePool.Get().([]int)for j := range data {data[j] = j}slicePool.Put(data)}
}// 并发基准测试
func BenchmarkConcurrentMap(b *testing.B) {m := make(map[int]int)var mu sync.Mutexb.RunParallel(func(pb *testing.PB) {for pb.Next() {mu.Lock()m[1] = 1mu.Unlock()}})
}// 使用sync.Map的优化版本
func BenchmarkSyncMap(b *testing.B) {var m sync.Mapb.RunParallel(func(pb *testing.PB) {for pb.Next() {m.Store(1, 1)}})
}// 子测试基准测试
func BenchmarkCalculation(b *testing.B) {nums := make([]int, 1000000)for i := range nums {nums[i] = i}b.Run("Sequential", func(b *testing.B) {for i := 0; i < b.N; i++ {_ = CalculateSum(nums)}})b.Run("Parallel", func(b *testing.B) {for i := 0; i < b.N; i++ {_ = CalculateSumParallel(nums)}})
}

2. 运行基准测试

# 运行所有基准测试
go test -bench=.# 运行特定基准测试
go test -bench=BenchmarkStringConcat# 包含内存统计
go test -bench=. -benchmem# 指定运行时间
go test -bench=. -benchtime=10s

3. 分析测试结果

基准测试输出解释:

BenchmarkStringConcat-8    1000000    1234 ns/op    2048 B/op    3 allocs/op
  • 8: 使用的CPU核心数
  • 1000000: 执行的迭代次数
  • 1234 ns/op: 每次操作的平均时间
  • 2048 B/op: 每次操作分配的内存
  • 3 allocs/op: 每次操作的内存分配次数

继续完成性能采样部分的内容。

四、性能采样

1. CPU Profiling

package mainimport ("fmt""log""os""runtime/pprof""time"
)// CPU密集型操作示例
func cpuIntensiveTask() {// 创建CPU profile文件f, err := os.Create("cpu.prof")if err != nil {log.Fatal(err)}defer f.Close()// 启动CPU profilingif err := pprof.StartCPUProfile(f); err != nil {log.Fatal(err)}defer pprof.StopCPUProfile()// 执行CPU密集型操作start := time.Now()result := 0for i := 0; i < 10000000; i++ {result += fibonacci(20)}duration := time.Since(start)fmt.Printf("计算完成,耗时: %v, 结果: %d\n", duration, result)
}func fibonacci(n int) int {if n <= 1 {return n}return fibonacci(n-1) + fibonacci(n-2)
}func main() {fmt.Println("开始CPU profiling...")cpuIntensiveTask()fmt.Println("CPU profiling完成,使用以下命令查看结果:")fmt.Println("go tool pprof cpu.prof")
}

2. 内存 Profiling

package mainimport ("fmt""log""os""runtime""runtime/pprof"
)// 内存分配示例
type BigStruct struct {data []bytestr  string
}func memoryIntensiveTask() {// 创建内存profile文件f, err := os.Create("mem.prof")if err != nil {log.Fatal(err)}defer f.Close()// 分配大量内存var structs []*BigStructfor i := 0; i < 1000; i++ {s := &BigStruct{data: make([]byte, 1024*1024), // 1MBstr:  fmt.Sprintf("large string %d", i),}structs = append(structs, s)}// 触发GCruntime.GC()// 写入内存profileif err := pprof.WriteHeapProfile(f); err != nil {log.Fatal(err)}// 打印内存统计信息var m runtime.MemStatsruntime.ReadMemStats(&m)fmt.Printf("Alloc = %v MiB\n", m.Alloc/1024/1024)fmt.Printf("TotalAlloc = %v MiB\n", m.TotalAlloc/1024/1024)fmt.Printf("Sys = %v MiB\n", m.Sys/1024/1024)fmt.Printf("NumGC = %v\n", m.NumGC)
}func main() {fmt.Println("开始内存profiling...")memoryIntensiveTask()fmt.Println("内存profiling完成,使用以下命令查看结果:")fmt.Println("go tool pprof mem.prof")
}

3. 协程 Profiling

package mainimport ("fmt""log""net/http"_ "net/http/pprof""runtime""sync""time"
)// 模拟协程泄露
func leakyGoroutine() {// 永远阻塞的通道ch := make(chan struct{})go func() {<-ch // 永远不会收到数据}()
}// 模拟协程阻塞
func blockingGoroutine(wg *sync.WaitGroup) {defer wg.Done()var mu sync.Mutexmu.Lock()go func() {time.Sleep(time.Second)mu.Unlock()}()mu.Lock() // 会阻塞mu.Unlock()
}func startProfileServer() {go func() {log.Println(http.ListenAndServe("localhost:6060", nil))}()
}func goroutineIntensiveTask() {var wg sync.WaitGroup// 创建一些泄露的协程for i := 0; i < 100; i++ {leakyGoroutine()}// 创建一些阻塞的协程for i := 0; i < 10; i++ {wg.Add(1)go blockingGoroutine(&wg)}// 等待一段时间time.Sleep(2 * time.Second)// 打印协程数量fmt.Printf("当前协程数量: %d\n", runtime.NumGoroutine())
}func main() {// 启动profile serverstartProfileServer()fmt.Println("Profile server started at http://localhost:6060/debug/pprof")// 记录初始协程数量fmt.Printf("初始协程数量: %d\n", runtime.NumGoroutine())// 执行协程密集型任务goroutineIntensiveTask()fmt.Println("使用以下命令查看协程profile:")fmt.Println("go tool pprof http://localhost:6060/debug/pprof/goroutine")// 保持程序运行select {}
}

4. 性能分析工具使用流程

  1. 收集性能数据
# 收集CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30# 收集内存profile
go tool pprof http://localhost:6060/debug/pprof/heap# 收集协程profile
go tool pprof http://localhost:6060/debug/pprof/goroutine
  1. 分析性能数据
# 查看top N的耗时函数
(pprof) top 10# 查看特定函数的详细信息
(pprof) list functionName# 生成可视化报告
(pprof) web
  1. 优化建议
问题类型现象优化方向
CPU瓶颈CPU使用率高,响应慢优化算法、减少计算、并行处理
内存问题内存使用高,GC频繁减少分配、使用对象池、控制对象大小
并发问题协程数量多,竞争严重控制并发数、减少锁竞争、优化通信

5. 性能优化实践建议

  1. 制定优化目标

    • 明确性能指标
    • 设定具体目标
    • 评估优化成本
  2. 选择优化方向

    • 找到性能瓶颈
    • 分析收益成本比
    • 制定优化策略
  3. 实施优化方案

    • 循序渐进
    • 及时验证效果
    • 保证代码质量
  4. 长期维护

    • 持续监控
    • 定期评估
    • 及时调整

6. 注意事项

  1. 优化原则

    • 先性能分析,后优化
    • 优化最有价值的部分
    • 保持代码可维护性
  2. 避免过早优化

    • 确认真实瓶颈
    • 评估优化收益
    • 权衡开发成本
  3. 注意测试

    • 完整的测试覆盖
    • 验证优化效果
    • 确保功能正确

怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

相关文章:

40分钟学 Go 语言高并发:Go程序性能优化方法论

Go程序性能优化方法论 一、性能指标概述 指标类型关键指标重要程度优化目标CPU相关CPU使用率、线程数、上下文切换⭐⭐⭐⭐⭐降低CPU使用率&#xff0c;减少上下文切换内存相关内存使用量、GC频率、对象分配⭐⭐⭐⭐⭐减少内存分配&#xff0c;优化GC延迟指标响应时间、处理延…...

一文解析Kettle开源ETL工具!

ETL&#xff08;Extract, Transform, Load&#xff09;工具是用于数据抽取、转换和加载的软件工具&#xff0c;用于支持数据仓库和数据集成过程。Kettle作为传统的ETL工具备受用户推崇。本文就来详细说下Kettle。 一、Kettle是什么&#xff1f; Kettle 是一款开源的 ETL&#x…...

Tomcat新手成长之路:安装部署优化全解析(上)

文章目录 1.Tomcat简介2.Tomcat原理架构2.1.总体架构2.2.连接器2.2.1.具体功能2.2.2.IO模型2.2.3.逻辑处理流程2.2.4.内部处理流程 2.3.容器2.4.启动过程2.5.请求过程 3.Tomcat适用场景4.Tomcat与其他Web容器对比5.Tomcat安装和启动5.1.Java环境变量5.2.系统服务5.3.启动关闭 6…...

跟我学C++中级篇——通信的数据的传递形式

一、通信的数据传递 在开发程序中&#xff0c;无可避免的会进行数据的传递。这种传递方式有很多种&#xff0c;字节流、消息、Json、参数以及对象甚至可能的方法。那么在传递这些数据时&#xff0c;如何正确的采用更合适的方法&#xff0c;就成为了一个设计的首选的问题。 二…...

C语言 qsort及应用

qsort及应用 qsort:快速排序函数,需要引用stdlib.h文件. void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *, const void *) ); 参数: base:需要排序的数组 num:数据个数(数组长度) width:每个数据的字节数(sizeof(数据类型)) compa…...

【C语言】连接陷阱探秘(4):检查外部类型

目录 一、外部类型概述 1.1. 外部类型的重要性 1.2. 外部类型在C语言中的使用 1.3. 注意事项 二、常见的外部类型陷阱 2.1. 结构体和联合体的大小不匹配 2.1.1. 示例代码 2.1.2. 正确的做法 2.2. 枚举类型的值不匹配 2.3. 函数签名不一致 2.3.1. 函数签名不一致的问…...

打造双层环形图:基础与高级渐变效果的应用

在数据可视化领域&#xff0c;环形图因其独特的展示方式而广受欢迎。今天&#xff0c;我们将通过ECharts库来创建一个具有双层渐变效果的高级环形图。本文将详细介绍如何实现这种视觉效果。 1. 环形图基础 首先&#xff0c;我们需要了解环形图的基本构成。环形图由内外两个圆…...

【Git】Git 完全指南:从入门到精通

Git 完全指南&#xff1a;从入门到精通 Git 是现代软件开发中最重要的版本控制工具之一&#xff0c;它帮助开发者高效地管理项目&#xff0c;支持分布式协作和版本控制。无论是个人项目还是团队开发&#xff0c;Git 都能提供强大的功能来跟踪、管理代码变更&#xff0c;并保障…...

【mac】mac自动定时开关机和其他常用命令,管理电源设置的工具pmset

一、操作步骤 1、打开终端 2、pmset 是用于管理电源设置的强大工具&#xff0c;我们将使用这个命令 &#xff08;1&#xff09;查询当前任务 pmset -g sched查看到我当前的设置是 唤醒电源开启在 工作日的每天早上8点半 上班时不用手动开机了 &#xff08;2&#xff09;删…...

【Leecode】Leecode刷题之路第62天之不同路径

题目出处 62-不同路径-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 62-不同路径-官方解法 方法1&#xff1a;动态规划 思路&#xff1a; 代码示例&#xff1a;&#xff08;Java&…...

基于深度学习的手势识别算法

基于深度学习的手势识别算法 概述算法原理核心逻辑效果演示使用方式参考文献 概述 本文基于论文 [Simple Baselines for Human Pose Estimation and Tracking[1]](ECCV 2018 Open Access Repository (thecvf.com)) 实现手部姿态估计。 手部姿态估计是从图像或视频帧集中找到手…...

helm部署golang服务

Helm 是 Kubernetes 的一个包管理工具,类似于 Linux 中的 apt 或 yum。它使得在 Kubernetes 上部署和管理应用程序变得更加简单和高效。 安装 https://get.helm.sh/helm-v3.16.3-linux-amd64.tar.gz具体版本号可以在github上看到最新的版本号,然后替换上面链接来获取。gith…...

DreamCamera2相机预览变形的处理

最近遇到一个问题&#xff0c;相机更换了摄像头后&#xff0c;发现人像角度顺时针旋转了90度&#xff0c;待人像角度正常后&#xff0c;发现 预览时图像有挤压变形&#xff0c;最终解决。在此记录 一人像角度的修改 先放示意图 设备预览人像角度如图1所示&#xff0c;顺时针旋…...

Mysql误删表中数据与误删表的恢复方法

查看数据库是否开启binlog日志 mysql> show variables like %log_bin%; ------------------------------------------------------------------------ | Variable_name | Value | ------------------------------------…...

lapack、blas、solver库的区别和联系

LAPACK、BLAS、Solver 库 是数值计算领域的重要组成部分,它们各自的功能和设计目标有所不同,但也存在密切的联系。 1. 概述 库主要功能设计目标BLAS提供基础的线性代数操作,如向量运算、矩阵-向量乘法、矩阵-矩阵乘法等。提供高度优化的基础线性代数操作,作为更高级库的底层…...

deepin 安装 chrome 浏览器

deepin 安装 chrome 浏览器 最近好多小伙伴儿和我说 deepin 无法安装最新的谷歌浏览器 其实是因为最新的 谷歌浏览器 其中的一个依赖需要提前安装 提前安装依赖然后再安装谷歌浏览器就可以了 安装 fonts-liberationsudo apt -y install fonts-liberation安装 chrome 浏览器sudo…...

永久免费的PDF万能水印删除工具

永久免费的PDF万能水印删除工具 1.简介 PDF万能水印删除工具&#xff0c;可以去除99.9%的PDF水印。例如&#xff1a;XObject水印&#xff08;含图片水印&#xff09;、文本水印、绘图水印/曲线水印、注释水印、工件水印、剪切路径水印等等。本软件是永久免费&#xff0c;无有…...

Linux网络——NAT/代理服务器

一.NAT技术 1.NAT IP转换 之前我们讨论了, IPv4 协议中, IP 地址数量不充足的问题&#xff0c;NAT 技术就是当前解决 IP 地址不够用的主要手段, 是路由器的一个重要功能。 NAT 能够将私有 IP 对外通信时转为全局 IP. 也就是一种将私有 IP 和全局IP 相互转化的技术方法: 很…...

大米中的虫子检测-检测储藏的大米中是否有虫子 支持YOLO,VOC,COCO格式标注,4070张图片的数据集

大米中的虫子检测-检测储藏的大米中是否有虫子 支持YOLO&#xff0c;VOC&#xff0c;COCO格式标注&#xff0c;4070张图片的数据集 数据集分割 4070总图像数 训练组 87&#xff05; 3551图片 有效集 9&#xff05; 362图片 测试集 4% 157图片 预处理 自动定向…...

基于Java的小程序电商商城开源设计源码

近年来电商模式的发展越来越成熟&#xff0c;基于 Java 开发的小程序电商商城开源源码&#xff0c;为众多开发者和企业提供了构建个性化电商平台的有力工具。 基于Java的电子商城购物平台小程序的设计在手机上运行&#xff0c;可以实现管理员&#xff1b;首页、个人中心、用户…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

CSS3相关知识点

CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...

【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)

旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据&#xff01;该数据集源自2025年4月发表于《地理学报》的论文成果…...