【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用
文章目录
- 引言
- 一、切片究竟是什么?
- 1.1 基础的创建数组示例
- 1.2 基础的创建切片示例
- 1.3 切片与数组的关系
- 二、切片的高级特性:动态扩容
- 2.1 使用 `append` 函数扩容
- 2.2 容量管理与性能考量
- 2.3 切片的截取与缩容
- 三、尽量使用cap参数创建切片
- 3.1 减少内存分配与复制
- 3.2 避免意外的内存增长
- 3.3 提升函数间数据传递效率
- 3.4 利用容量进行高效截取
- 3.5 实践建议
- 四、总结
引言
在Go语言的编程实践中,切片(slice) 是一个无处不在且功能强大的数据结构。它基于数组,却比数组更加灵活多变。切片允许我们高效地处理和操作数据的子集,无需复制整个数据集,这一特性在处理大数据集时尤为重要。本文将深入探讨切片的本质,以及如何通过创建切片来充分利用其动态和灵活的特性。我们将从切片的基础定义开始,逐步深入到其高级特性,如动态扩容,并讨论如何在创建切片时优化性能。最后,我们将总结切片的优势,并说明为何在Go语言编程中,切片是一个不可或缺的工具。现在,让我们一同揭开切片的神秘面纱,探索其强大的功能吧。
一、切片究竟是什么?
在Go语言中,数组是一种固定长度的数据结构,用于存储相同类型的元素。每个元素在数组中的内存地址是连续的,这使得数组的访问速度非常快。然而,数组的长度是固定的,一旦定义就无法改变,这在处理可变长度的数据集合时会显得不够灵活。
为了解决这个问题,并提供更灵活的序列操作,Go引入了切片(slice)的概念。切片是对数组的一个连续片段的引用,它提供了对数组子序列的动态窗口。切片是引用类型,它包含三个组件:指向底层数组的指针、切片的长度以及切片的容量。
切片本质上是对数组的一个“窗口”或“视图”,它包含三个关键信息:
- 指向底层数组的指针:切片通过这个指针来引用底层数组中的元素。
- 切片的长度(len):表示切片当前包含的元素数量。
- 切片的容量(cap):表示从切片的起始位置到底层数组末尾的元素数量。
为了更直观地理解切片,我们可以从基础的数组和切片的创建开始讲起。
1.1 基础的创建数组示例
Go中的数组是具有固定长度的序列,其中每个元素都具有相同的类型。数组的长度是类型的一部分,因此[5]int
和[10]int
被视为不同的数据类型。数组是值类型,当你将一个数组赋值给另一个数组时,实际上是进行了整个数组的拷贝。
以下是如何创建数组的示例:
package mainimport "fmt"func main() {// 示例1: 声明并初始化一个整型数组var arr1 [3]int = [3]int{1, 2, 3}fmt.Println("arr1:", arr1) // [1 2 3]// 示例2: 使用...来自动计算数组长度arr2 := [...]int{4, 5, 6, 7, 8}fmt.Println("arr2:", arr2) // [4 5 6 7 8]
}
1.2 基础的创建切片示例
切片是基于数组的,但比数组更加灵活。以下是如何创建切片的示例:
package mainimport "fmt"func main() {// 示例1: 基于已存在的数组创建切片array := [5]int{1, 2, 3, 4, 5} // 切片字面量,实际上是基于一个隐式数组的切片slice1 := array[1:4] // 创建一个切片,包含数组索引1到3的元素fmt.Println("slice1:", slice1) // [2 3 4]// 示例2: 使用make函数创建切片slice2 := make([]int, 3) // 创建一个长度为3的切片slice2[0] = 6slice2[1] = 7slice2[2] = 8fmt.Println("slice2:", slice2) // [6 7 8]// 示例3: 直接初始化切片slice3 := []int{9, 10, 11}fmt.Println("slice3:", slice3) // [9 10 11]
}
通过这些示例,我们可以看到切片是如何从数组中派生出来的,以及如何使用make
函数或直接初始化来创建切片。切片提供了更大的灵活性,允许我们动态地调整大小,并且易于在函数间传递和操作。这使得切片在处理可变长度的数据集合时成为了一个非常强大的工具。
1.3 切片与数组的关系
- 数组是切片的底层存储:切片通常基于一个数组创建,它提供了对该数组某个子序列的视图。
- 切片是动态的:与固定长度的数组不同,切片可以在运行时增长或缩小(通过内置的
append
函数)。 - 性能优势:由于切片是引用类型,传递切片时不会发生数据拷贝,这提高了性能并减少了内存使用。
- 更灵活的操作:切片支持更多的动态操作,如添加、删除元素等,而不需要像数组那样事先确定大小。
总结来说,切片是Go语言中一种基于数组的、长度可变的、连续的元素序列。它通过引用底层数组来实现动态长度和高效访问,是处理可变长度数据集合的重要工具。通过使用切片,我们可以轻松地访问、修改和操作数组的一部分,而无需对整个数组进行复制或重新分配内存。
二、切片的高级特性:动态扩容
切片的一个重要特性是其动态扩容的能力,这使得在处理数据集合时能够更加灵活地适应数据量的变化,而无需预先知道确切的大小。以下是几个关键点,展示了切片如何实现动态扩容以及相关操作:
2.1 使用 append
函数扩容
append
是 Go 语言中用于向切片追加元素的内置函数,它能够自动处理切片的扩容。当现有切片没有足够的容量来容纳新元素时,append
函数会执行以下操作:
- 检查容量: 首先,
append
会检查切片的当前容量是否足够。如果足够,则直接在切片的末尾添加元素。 - 扩容: 如果容量不足,
append
会创建一个新的、容量更大的数组,并将原切片的内容复制到新数组中,然后在新数组中添加新元素。新切片的容量通常会按照一定的规则(比如加倍原容量)增加,以减少频繁扩容的开销。 - 返回新切片: 扩容和追加操作完成后,
append
返回一个新的切片,该切片引用了新的底层数组。
示例代码如下:
package mainimport "fmt"func main() {slice := []int{1, 2, 3}slice = append(slice, 4) // 在切片末尾添加元素fmt.Println("After appending 4:", slice) // [1 2 3 4]// 追加多个元素slice = append(slice, 5, 6)fmt.Println("After appending 5 and 6:", slice) // [1 2 3 4 5 6]// 使用...操作符追加一个切片anotherSlice := []int{7, 8, 9}slice = append(slice, anotherSlice...) // 注意这里使用了'...'来展开另一个切片fmt.Println("After appending another slice:", slice) // [1 2 3 4 5 6 7 8 9]
}
2.2 容量管理与性能考量
虽然动态扩容提供了便利,但也需要注意以下几点以优化性能和资源使用:
- 避免频繁扩容: 频繁的扩容操作会导致额外的内存分配和数据复制,影响性能。在已知大概数据量的情况下,可以预估一个合适的初始容量来减少扩容次数。
- 容量与长度的区别: 明确区分切片的长度(实际元素数量)和容量(可容纳的元素最大数量),合理规划以避免不必要的内存浪费。
- 利用
cap
函数: 可以使用cap
函数查询切片的当前容量,从而做出是否需要手动调整容量的决策。
2.3 切片的截取与缩容
除了动态扩容,切片还支持截取操作来创建新的切片,这可以看作是一种“软缩容”。通过指定新的起始索引和结束索引,可以从现有切片中创建出一个只包含部分元素的新切片,而不会影响原切片的容量。但是,这并不直接改变原始切片的容量,只是创建了对原数组不同部分的视图。
综上所述,切片的动态扩容机制极大地增强了其处理动态数据集合的能力,结合恰当的容量管理和操作技巧,可以确保既高效又灵活地处理各种规模的数据需求。
三、尽量使用cap参数创建切片
在实际开发过程中,预估并设置切片的容量(cap
)是一个提高程序效率的有效策略。尽管切片能够自动扩容,但明确指定容量可以在很多场景下避免不必要的性能开销,具体体现在以下几个方面:
3.1 减少内存分配与复制
当通过append
等操作导致切片需要扩容时,如果没有预留足够的容量,Go 会分配一块更大的内存空间,然后将原有数据复制到新内存区域,最后释放旧内存。这个过程涉及内存分配和数据迁移,对于大型数据集来说,成本高昂。通过在创建切片时准确或大致估计并设定容量,可以显著减少这种因扩容而导致的内存操作,提升程序运行效率。
package mainimport "fmt"func main() {// 预先分配足够容量以容纳未来追加的元素slice := make([]int, 0, 10) // 初始化长度为0,容量为10的切片// 追加元素,此时即使超过初始长度也不会立即触发扩容for i := 0; i < 10; i++ {slice = append(slice, i)}fmt.Println(slice) // 输出: [0 1 2 3 4 5 6 7 8 9]
}
3.2 避免意外的内存增长
未明确指定容量时,使用make
函数创建切片默认提供的容量可能不符合特定场景的需求。例如,默认情况下,make([]T, n)
创建的切片容量等于其长度,而make([]T, n, cap)
允许你直接指定容量。明确容量可以帮助开发者控制内存使用,避免在数据量激增时,因容量估算不足而引发的频繁再分配问题。
package mainimport "fmt"func handleData(data []int) {// 假设此函数需要对数据进行多次操作,每次操作可能追加新元素// 如果传入的切片没有足够的容量,内部的追加操作将导致频繁扩容for _, value := range data {// 模拟数据处理逻辑,这里简化处理fmt.Println(value)}
}func main() {// 正确做法:明确预测可能的扩容需求,预先分配足够的容量dataWithCapacity := make([]int, 5, 10) // 初始化长度为5,容量为10for i := 0; i < 5; i++ {dataWithCapacity[i] = i}handleData(dataWithCapacity) // 传入具有足够容量的切片// 错误做法示例(注释掉,仅做对比说明):// dataWithoutCapacity := make([]int, 5) // 若不明确指定容量,追加元素时可能导致频繁扩容// handleData(dataWithoutCapacity)
}
3.3 提升函数间数据传递效率
切片作为引用类型,在函数间传递时仅传递其描述信息(指针、长度、容量),不涉及底层数组的复制。因此,通过预设合适容量的切片作为函数参数或返回值,可以在处理大量数据时保持高效的内存使用和传递效率,减少系统开销。
package mainimport "fmt"// processData 接收一个切片并执行处理逻辑,假设处理过程可能包括追加数据
func processData(data []int) []int {// 追加新元素的示例逻辑,假设根据处理逻辑决定追加的数量newData := append(data, 99) // 这里假设99为新增数据return newData
}func main() {// 创建一个带有额外容量的切片以供函数使用initialData := make([]int, 0, 10) // 长度为0,容量为10,准备接受数据initialData = append(initialData, 1, 2, 3, 4, 5) // 初始化数据// 将切片传递给函数,由于容量充足,函数内追加数据不会导致频繁扩容processedData := processData(initialData)fmt.Println("Processed Data:", processedData)
}
3.4 利用容量进行高效截取
预先设定的较大容量不仅便于数据追加,也便于进行切片的截取操作。当从大容量的切片中截取出新的子切片时,即使子切片的长度较小,它也可能继承较大的容量,这意味着后续对子切片的追加操作可能不需要立即触发扩容,从而提升了程序的运行效率。
package mainimport "fmt"func main() {// 创建一个大容量的切片largeSlice := make([]int, 5, 20)// 截取其中一部分作为新切片,新切片会保留原切片的容量subSlice := largeSlice[:3]// 向子切片追加元素,由于子切片容量足够,不会触发扩容subSlice = append(subSlice, 11, 12, 13)fmt.Println(subSlice) // 输出: [0 1 2 11 12 13]
}
3.5 实践建议
- 评估需求: 在创建切片前,根据应用场景预估所需的最大数据量,合理设定容量。
- 使用
make
函数: 当确切知道所需容量时,使用make([]T, length, capacity)
形式创建切片,特别是当预计会有频繁的追加操作时。 - 监控与调整: 在程序开发初期,可以通过性能测试和监控来观察切片的实际使用情况,根据反馈适时调整容量设定,达到最优配置。
总之,虽然切片的自动扩容功能为编程带来了便利,但在追求高性能的应用中,主动管理切片的容量是提高程序效率和降低资源消耗的关键策略之一。
四、总结
总结而言,Go语言中的切片是处理可变长度数据集合的强大工具,它在数组的基础上提供了动态大小调整、高效内存管理和灵活操作的特性。切片的核心优势在于其动态扩容能力,借助内置的append
函数,切片能够自动适应数据量变化,同时通过合理管理容量(cap
)参数,可以显著优化性能,减少内存分配与复制的成本。
具体实践中,明确指定切片的容量在创建时能够避免因自动扩容导致的性能损耗,特别是在数据增长可预期的场景。通过利用make
函数预设容量,开发者能够更好地控制内存使用,提升函数间数据传递的效率,以及在切片截取操作中保持高效的容量继承。此外,监控和适时调整容量设定,依据实际应用需求进行优化,是实现高效内存管理的必要步骤。
总之,理解并有效利用切片的高级特性,尤其是通过主动管理其容量,是Go程序设计中实现高效数据处理、优化性能和资源管理的关键实践。
相关文章:
【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用
🔥 个人主页:空白诗 文章目录 引言一、切片究竟是什么?1.1 基础的创建数组示例1.2 基础的创建切片示例1.3 切片与数组的关系 二、切片的高级特性:动态扩容2.1 使用 append 函数扩容2.2 容量管理与性能考量2.3 切片的截取与缩容 三…...
Python与C语言:深入探索两者的奥秘与差异
Python与C语言:深入探索两者的奥秘与差异 在编程的世界里,Python和C语言如同两位性格迥异的伙伴,各自拥有独特的魅力和应用场景。Python以其简洁易懂的语法和强大的库支持赢得了众多开发者的青睐,而C语言则以其接近硬件的低级特性…...
图像编解码器在AI绘画中的革新作用
随着人工智能技术的飞速发展,AI绘画已经从一个简单的概念演变为一个充满创意与可能性的领域。在这场技术与艺术的融合中,图像编解码器扮演着至关重要的角色。它们不仅提升了AI绘画的质量和效率,还拓宽了艺术创造的边界。本篇博客将深入探讨图…...
SecureCRT[po破] for Mac SSH终端操作工具[解] 安装教程
文章目录 效果一、准备工作二、开始安装1、双击运行软件,将其从左侧拖入右侧文件夹中,等待安装完毕2、 应用程序显示软件图标,表示安装成功 三、输入对应参数1、解决“软件已损坏,无法打开,要移到废纸篓”问题解决步骤…...
【大数据架构】基于流式数据的大数据架构升级
背景 团队在升级大数据架构,摒弃了原来基于hadoop的架构,因此抛弃了hive,hdfs,mapreduce这一套,在讨论和摸索中使用了新的架构。 后端使用kafka流式数据通过rest catalog写入iceberg,存储于minio。在写入iceberg的时候,首先是写data数据文件,然后再写iceberg的metada…...
OpenCV中的圆形标靶检测——斑点检测算法(二)
前面的章节中我们已经大致介绍了算法流程,也对一些算法中用到的相关概念做了简要介绍,同时给出了算法调用的API,现在我们开始算法检测接口实现源码的分析。 1. 斑点的分组与加权 这里我们选择后者,先了解算法的处理流程,再分析各个模块的实现。算法流程图如下图所示,上一…...
网线制作(双绞线+水晶头)——T568B标准
参考视频:https://www.bilibili.com/video/BV1KQ4y1i7zP/ 1、使用剥线器 2、将线捋顺、排序、剪掉牵引线 记忆技巧 1.线序颜色整体是一浅一深 2.颜色顺序是黄、蓝、绿、棕 一个黄种人、从上向下看,分别看到的是蓝天、青草(绿)、泥土(棕色) 3.中间两根浅…...
湖南源点(市场研究咨询)如何产出更加有意义的竞品调研
湖南源点咨询认为:当前,任何项目都不能盲目开始,前期的准备工作必不可少。在基础架构搭建的同时,设计上对于前端功能、用户体验的调研就优先开始了。在这个阶段,大部分设计师都会分配很多调研任务,疯狂对竞…...
Qt/C++音视频开发76-获取本地有哪些摄像头名称/ffmpeg内置函数方式
一、前言 上一篇文章是写的用Qt的内置函数方式获取本地摄像头名称集合,但是有几个缺点,比如要求Qt5,或者至少要求安装了多媒体组件multimedia,如果没有安装呢,或者安装的是个空的呢,比如很多嵌入式板子&am…...
09 platfrom 设备驱动
platform 设备驱动,也叫做平台设备驱动。请各位重点学习! 1、驱动的分离与分层 1)驱动的分隔与分离 Linux 操作系统,代码的重用性非常重要。驱动程序占用了 Linux 内核代码量的大头,如果不对驱动程序加以管理,用不了多久 Linux 内核的文件数量就庞大到无法接受的地步。…...
【C#】C#读写Excel文件
1.工具库选择 使用EPPlus读取Excel文件,在visual studio2022中安装最新NuGet。 2.读文件测试 using OfficeOpenXml; using OfficeOpenXml.Packaging.Ionic.Zip; using OfficeOpenXml.Style; using System; using System.Collections.Generic; using System.IO; u…...
数据流图(DFD)绘制规范
软件数据流图(Data Flow Diagram,DFD)是一种重要的工具,用于表示系统中数据的流动和处理。DFD帮助开发团队和利益相关者理解系统的功能和数据处理过程。绘制DFD时应遵循一定的规范和步骤,以确保图表的清晰性和一致性。…...
有待挖掘的金矿:大模型的幻觉之境
人工智能正在迅速变得无处不在,在科学和学术研究中,自回归的大型语言模型(LLM)走在了前列。自从LLM的概念被整合到自然语言处理(NLP)的讨论中以来,LLM中的幻觉现象一直被广泛视为一个显著的社会…...
常见八大排序(纯C语言版)
目录 基本排序 一.冒泡排序 二.选择排序 三.插入排序 进阶排序(递归实现) 一.快排hoare排序 1.单趟排序 快排步凑 快排的优化 (1)三数取中 (2)小区间优化 二.前后指针法(递归实现) 三.快排的非…...
vue2学习(06)----vuex
目录 一、vuex概述 1.定义 优势: 2.构建环境步骤 3.state状态 4.使用数据 4.1通过store直接访问 4.2通过辅助函数 5.mutations修改数据(同步操作) 5.1定义 5.2步骤 5.2.1定义mutations对象,对象中存放修改state数据的方…...
webflux 拦截器验证token
在WebFlux中,我们可以使用拦截器(Interceptor)来验证Token。以下是一个简单的示例: 1. 首先,创建一个名为TokenInterceptor的类,实现HandlerInterceptor接口: java import org.springframewor…...
C++中的继承方式
目录 摘要 1. 公有继承(Public Inheritance) 2. 保护继承(Protected Inheritance) 3. 私有继承(Private Inheritance) 4. 多重继承(Multiple Inheritance) 继承列表的项数 摘要…...
Vue进阶之Vue无代码可视化项目(四)
Vue无代码可视化项目 左侧栏第一步LeftPanel.vueLayoutView.vuebase.css第二步LayoutView.vueLeftPanel.vue编排引擎smooth-dnd安装创建文件SmoothDndContainer.tsutils.tsSmoothDndDraggable.tsLeftPanel.vue左侧栏 第一步 创建LeftPanel LeftPanel.vue <script setup…...
day40--Redis(二)实战篇
实战篇Redis 开篇导读 亲爱的小伙伴们大家好,马上咱们就开始实战篇的内容了,相信通过本章的学习,小伙伴们就能理解各种redis的使用啦,接下来咱们来一起看看实战篇我们要学习一些什么样的内容 短信登录 这一块我们会使用redis共…...
使用Ollama+OpenWebUI本地部署Gemma谷歌AI开放大模型完整指南
🏡作者主页:点击! 🤖AI大模型部署与应用专栏:点击! 🤖Ollama部署LLM专栏:点击! ⏰️创作时间:2024年6月4日10点50分 🀄️文章质量࿱…...
react的自定义组件
// 自定义组件(首字母必须大写) function Button() {return <button>click me</button>; } const Button1()>{return <button>click me1</button>; }// 使用组件 function App() {return (<div className"App">{/* // 自闭和引用自…...
海宁代理记账公司-专业的会计服务
随着中国经济的飞速发展,企业的规模和数量日益扩大,在这个过程中,如何保证企业的财务活动合规、准确无误地进行,成为了每个企业面临的重要问题,专业、可靠的代理记账公司应运而生。 海宁代理记账公司的主要职责就是为各…...
matlab 计算三维空间点到直线的距离
目录 一、算法原理二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 直线的点向式方程为: x − x 0 m = y...
YOLOv5车流量监测系统研究
一. YOLOv5算法详解 YOLOv5网络架构 上图展示了YOLOv5目标检测算法的整体框图。对于一个目标检测算法而言,我们通常可以将其划分为4个通用的模块,具体包括:输入端、基准网络、Neck网络与Head输出端,对应于上图中的4个红色模块。Y…...
单元测试覆盖率
什么是单元测试覆盖率 关于其定义,先来看一下维基百科上的一段描述: 代码覆盖(Code coverage)是软件测试中的一种度量,描述程序中源代码被测试的比例和程度,所得比例称为代码覆盖率。 简单来理解ÿ…...
逻辑这回事(三)----时序分析与时序优化
基本时序参数 图1.1 D触发器结构 图1.2 D触发器时序 时钟clk采样数据D时,Tsu表示数据前边沿距离时钟上升沿的时间,MicTsu表示时钟clk能够稳定采样数据D的所要求时间,Th表示数据后边沿距离时钟上升沿的时间,MicTh表示时钟clk采样…...
[JAVASE] 类和对象(二) -- 封装
目录 一. 封装 1.1 面向对象的三大法宝 1.2 封装的基本定义与实现 二. 包 2.1 包的定义 2.2 包的作用 2.3 包的使用 2.3.1 导入类 2.3.2 导入静态方法 三. static 关键字 (重要) 3.1 static 的使用 (代码例子) 3.1.1 3.1.2 3.1.3 3.1.4 四. 总结 一. 封装 1.1 面向对象…...
开发网站,如何给上传图片的服务器目录授权
开发网站,上传图像时提示”上传图片失败,Impossible to create the root directory /var/www/html/xxxxx/public/uploads/avatar/20240608.“ 在Ubuntu上,你可以通过调整文件夹权限来解决这个问题。首先,确保Web服务器(…...
特别名词Test Paper2
特别名词Test Paper2 cabinet 橱柜cable 电缆,有线电视cafe 咖啡厅cafeteria 咖啡店,自助餐厅cage 笼子Cambridge 剑桥camel 骆驼camera 相机camp 露营campus 校园candidate 候选人,考生candle 蜡烛canteen 食堂capital 资金,首都…...
数据结构-AVL树
目录 二叉树 二叉搜索树的查找方式: AVL树 AVL树节点的实现 AVL树节点的插入操作 AVL树的旋转操作 右旋转: 左旋转: 左右双旋: 右左双旋: AVL树的不足和下期预告(红黑树) 二叉树 了…...
wordpress加速/惠州抖音seo策划
springmvc 支持ant风格的路径表达式,我们先了解一下ant风格是什么个东东? ant匹配url有三种 ? 匹配任何单字符 * 匹配0或者任意数量的字符 ** 匹配0或者更多的目录 1.?只匹配一个字符,比如说/springmvc/?abc/index.jsp …...
wordpress滑动门/青岛app开发公司
鉴于numpy中矩阵的运算居多,所以以下内容中参与运算的数据元素都默认为矩阵,且以矩阵的逐元素相乘这个操作为例子进行说明。 numpy中的广播(broadcast)说白了就是干一件事——扩展矩阵,而且扩展对象都是纬度比较小的矩阵,将维度较…...
周口学做网站/网站之家
2019独角兽企业重金招聘Python工程师标准>>> 一、点击链接 https://natapp.cn/ 注册个免费的账户 NATAPP官网 二、登陆进去以后查看authtoken。复制这个,等下要在客户端用到! 分配的authtoken 三、点击右侧配置,做一下配置&#x…...
网站备案主体注销/百度推广的几种方式
要求:手机端打开某个页面的详细信息,因为网速或者别的原因,响应太慢,因为图片大的原因,希望先进来,图片在网页运行的情况再慢慢加载(jquer延迟加载) http://www.w3cways.com/1765.ht…...
怎么搭建个人网站/襄阳seo培训
mixins一般有两种用途: 1.当你已经写好了构造器之后,需要增加方法或者临时的活动时使用的方法,这是用混入会减少源代码的污染; 下面的需求是构造器已经完成,突然要额外增加控制台打印变化的数据,操作如下: <body><div id"app">{{num}}<p><bu…...
酒店网站可以怎么做/软件培训机构排行榜
为什么80%的码农都做不了架构师?>>> 具体做法: 不同环境的配置设置一个配置文件,例如:dev环境下的配置配置在application-dev.properties中;prod环境下的配置配置在application-prod.properties中。在appl…...