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

【Golang】Golang进阶系列教程--Go 语言切片是如何扩容的?

文章目录

  • 前言
  • 声明和初始化
  • 扩容时机
  • 源码分析
    • go1.17
    • go1.18
    • 内存对齐
  • 总结

前言

在 Go 语言中,有一个很常用的数据结构,那就是切片(Slice)。

切片是一个拥有相同类型元素的可变长度的序列,它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片是一种引用类型,它有三个属性:指针,长度和容量。
在这里插入图片描述

底层源码定义如下:

type slice struct {array unsafe.Pointerlen   intcap   int
}
  • 指针: 指向 slice 可以访问到的第一个元素。
  • 长度: slice 中元素个数。
  • 容量: slice 起始元素到底层数组最后一个元素间的元素个数。

比如使用 make([]byte, 5) 创建一个切片,它看起来是这样的:
在这里插入图片描述

声明和初始化

切片的使用还是比较简单的,这里举一个例子,直接看代码吧。

func main() {var nums []int  // 声明切片fmt.Println(len(nums), cap(nums)) // 0 0nums = append(nums, 1)   // 初始化fmt.Println(len(nums), cap(nums)) // 1 1nums1 := []int{1,2,3,4}    // 声明并初始化fmt.Println(len(nums1), cap(nums1))    // 4 4nums2 := make([]int,3,5)   // 使用make()函数构造切片fmt.Println(len(nums2), cap(nums2))    // 3 5
}

扩容时机

当切片的长度超过其容量时,切片会自动扩容。这通常发生在使用 append 函数向切片中添加元素时。

扩容时,Go 运行时会分配一个新的底层数组,并将原始切片中的元素复制到新数组中。然后,原始切片将指向新数组,并更新其长度和容量。

需要注意的是,由于扩容会分配新数组并复制元素,因此可能会影响性能。如果你知道要添加多少元素,可以使用 make 函数预先分配足够大的切片来避免频繁扩容。

接下来看看 append 函数,签名如下:

func Append(slice []int, items ...int) []int

append 函数参数长度可变,可以追加多个值,还可以直接追加一个切片。使用起来比较简单,分别看两个例子:
追加多个值:

package mainimport "fmt"func main() {s := []int{1, 2, 3}fmt.Println("初始切片:", s)s = append(s, 4, 5, 6)fmt.Println("追加多个值后的切片:", s)
}

输出结果为:

初始切片: [1 2 3]
追加多个值后的切片: [1 2 3 4 5 6]

再来看一下直接追加一个切片:

package mainimport "fmt"func main() {s1 := []int{1, 2, 3}fmt.Println("初始切片:", s1)s2 := []int{4, 5, 6}s1 = append(s1, s2...)fmt.Println("追加另一个切片后的切片:", s1)
}

输出结果为:

初始切片: [1 2 3]
追加另一个切片后的切片: [1 2 3 4 5 6]

再来看一个发生扩容的例子:

package mainimport "fmt"func main() {s := make([]int, 0, 3) // 创建一个长度为0,容量为3的切片fmt.Printf("初始状态: len=%d cap=%d %v\n", len(s), cap(s), s)for i := 1; i <= 5; i++ {s = append(s, i) // 向切片中添加元素fmt.Printf("添加元素%d: len=%d cap=%d %v\n", i, len(s), cap(s), s)}
}

输出结果为:

初始状态: len=0 cap=3 []
添加元素1: len=1 cap=3 [1]
添加元素2: len=2 cap=3 [1 2]
添加元素3: len=3 cap=3 [1 2 3]
添加元素4: len=4 cap=6 [1 2 3 4]
添加元素5: len=5 cap=6 [1 2 3 4 5]

在这个例子中,我们创建了一个长度为 0,容量为 3 的切片。然后,我们使用 append 函数向切片中添加 5 个元素。

当我们添加第 4 个元素时,切片的长度超过了其容量。此时,切片会自动扩容。新的容量是原始容量的两倍,即 6。

表面现象已经看到了,接下来,我们就深入到源码层面,看看切片的扩容机制到底是什么样的。

源码分析

在 Go 语言的源码中,切片扩容通常是在进行切片的 append 操作时触发的。在进行 append 操作时,如果切片容量不足以容纳新的元素,就需要对切片进行扩容,此时就会调用 growslice 函数进行扩容。

growslice 函数定义在 Go 语言的 runtime 包中,它的调用是在编译后的代码中实现的。具体来说,当执行 append 操作时,编译器会将其转换为类似下面的代码:

slice = append(slice, elem)

在上述代码中,如果切片容量不足以容纳新的元素,则会调用 growslice 函数进行扩容。所以 growslice 函数的调用是由编译器在生成的机器码中实现的,而不是在源代码中显式调用的。

切片扩容策略有两个阶段,go1.18 之前和之后是不同的,这一点在 go1.18 的 release notes 中有说明。

下面我用 go1.17 和 go1.18 两个版本来分开说明。先通过一段测试代码,直观感受一下两个版本在扩容上的区别。

package mainimport "fmt"func main() {s := make([]int, 0)oldCap := cap(s)for i := 0; i < 2048; i++ {s = append(s, i)newCap := cap(s)if newCap != oldCap {fmt.Printf("[%d -> %4d] cap = %-4d  |  after append %-4d  cap = %-4d\n", 0, i-1, oldCap, i, newCap)oldCap = newCap}}
}

上述代码先创建了一个空的 slice,然后在一个循环里不断往里面 append 新元素。
然后记录容量的变化,每当容量发生变化的时候,记录下老的容量,添加的元素,以及添加完元素之后的容量。
这样就可以观察,新老 slice 的容量变化情况,从而找出规律。
运行结果(1.17 版本):

[0 ->   -1] cap = 0     |  after append 0     cap = 1   
[0 ->    0] cap = 1     |  after append 1     cap = 2   
[0 ->    1] cap = 2     |  after append 2     cap = 4   
[0 ->    3] cap = 4     |  after append 4     cap = 8   
[0 ->    7] cap = 8     |  after append 8     cap = 16  
[0 ->   15] cap = 16    |  after append 16    cap = 32  
[0 ->   31] cap = 32    |  after append 32    cap = 64  
[0 ->   63] cap = 64    |  after append 64    cap = 128 
[0 ->  127] cap = 128   |  after append 128   cap = 256 
[0 ->  255] cap = 256   |  after append 256   cap = 512 
[0 ->  511] cap = 512   |  after append 512   cap = 1024
[0 -> 1023] cap = 1024  |  after append 1024  cap = 1280
[0 -> 1279] cap = 1280  |  after append 1280  cap = 1696
[0 -> 1695] cap = 1696  |  after append 1696  cap = 2304

运行结果(1.18 版本):

[0 ->   -1] cap = 0     |  after append 0     cap = 1
[0 ->    0] cap = 1     |  after append 1     cap = 2   
[0 ->    1] cap = 2     |  after append 2     cap = 4   
[0 ->    3] cap = 4     |  after append 4     cap = 8   
[0 ->    7] cap = 8     |  after append 8     cap = 16  
[0 ->   15] cap = 16    |  after append 16    cap = 32  
[0 ->   31] cap = 32    |  after append 32    cap = 64  
[0 ->   63] cap = 64    |  after append 64    cap = 128 
[0 ->  127] cap = 128   |  after append 128   cap = 256 
[0 ->  255] cap = 256   |  after append 256   cap = 512 
[0 ->  511] cap = 512   |  after append 512   cap = 848 
[0 ->  847] cap = 848   |  after append 848   cap = 1280
[0 -> 1279] cap = 1280  |  after append 1280  cap = 1792
[0 -> 1791] cap = 1792  |  after append 1792  cap = 2560

根据上面的结果还是能看到区别的,具体扩容策略下面边看源码边说明。

go1.17

扩容调用的是 growslice 函数,我复制了其中计算新容量部分的代码。

// src/runtime/slice.gofunc growslice(et *_type, old slice, cap int) slice {// ...newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {if old.cap < 1024 {newcap = doublecap} else {// Check 0 < newcap to detect overflow// and prevent an infinite loop.for 0 < newcap && newcap < cap {newcap += newcap / 4}// Set newcap to the requested cap when// the newcap calculation overflowed.if newcap <= 0 {newcap = cap}}}// ...return slice{p, old.len, newcap}
}

在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容:

  • 如果期望容量大于当前容量的两倍就会使用期望容量;
  • 如果当前切片的长度小于 1024 就会将容量翻倍;
  • 如果当前切片的长度大于等于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;

go1.18

// src/runtime/slice.gofunc growslice(et *_type, old slice, cap int) slice {// ...newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {const threshold = 256if old.cap < threshold {newcap = doublecap} else {// Check 0 < newcap to detect overflow// and prevent an infinite loop.for 0 < newcap && newcap < cap {// Transition from growing 2x for small slices// to growing 1.25x for large slices. This formula// gives a smooth-ish transition between the two.newcap += (newcap + 3*threshold) / 4}// Set newcap to the requested cap when// the newcap calculation overflowed.if newcap <= 0 {newcap = cap}}}// ...return slice{p, old.len, newcap}
}

和之前版本的区别,主要在扩容阈值,以及这行代码:newcap += (newcap + 3*threshold) / 4。

在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容:

  • 如果期望容量大于当前容量的两倍就会使用期望容量;
  • 如果当前切片的长度小于阈值(默认 256)就会将容量翻倍;
  • 如果当前切片的长度大于等于阈值(默认 256),就会每次增加 25% 的容量,基准是 newcap + 3*threshold,直到新容量大于期望容量;

内存对齐

分析完两个版本的扩容策略之后,再看前面的那段测试代码,就会发现扩容之后的容量并不是严格按照这个策略的。

那是为什么呢?

实际上,growslice 的后半部分还有更进一步的优化(内存对齐等),靠的是 roundupsize 函数,在计算完 newcap 值之后,还会有一个步骤计算最终的容量:

capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)

这个函数的实现就不在这里深入了,先挖一个坑,以后再来补上。

总结

切片扩容通常是在进行切片的 append 操作时触发的。在进行 append 操作时,如果切片容量不足以容纳新的元素,就需要对切片进行扩容,此时就会调用 growslice 函数进行扩容。
切片扩容分两个阶段,分为 go1.18 之前和之后:

一、go1.18 之前:

  • 如果期望容量大于当前容量的两倍就会使用期望容量;
  • 如果当前切片的长度小于 1024 就会将容量翻倍;
  • 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;

二、go1.18 之后:

  • 如果期望容量大于当前容量的两倍就会使用期望容量;
  • 如果当前切片的长度小于阈值(默认 256)就会将容量翻倍;
  • 如果当前切片的长度大于等于阈值(默认 256),就会每次增加 25% 的容量,基准是 newcap + 3*threshold,直到新容量大于期望容量;

相关文章:

【Golang】Golang进阶系列教程--Go 语言切片是如何扩容的?

文章目录 前言声明和初始化扩容时机源码分析go1.17go1.18内存对齐 总结 前言 在 Go 语言中&#xff0c;有一个很常用的数据结构&#xff0c;那就是切片&#xff08;Slice&#xff09;。 切片是一个拥有相同类型元素的可变长度的序列&#xff0c;它是基于数组类型做的一层封装…...

【数据结构】顺序表(SeqList)(增、删、查、改)详解

一、顺序表的概念和结构 1、顺序表的概念&#xff1a; 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。在数组上完成数据的增删查改。 2、顺序表的结构&#xff1a; &#xff08;1&#xff09;静态顺序表&#xff1a;使…...

[golang gin框架] 42.Gin商城项目-微服务实战之后台Rbac微服务角色增删改查微服务

一.重构后台Rbac用户登录微服务功能 上一节讲解了后台Rbac微服务用户登录功能以及Gorm数据库配置单独抽离&#xff0c;Consul配置单独抽离&#xff0c;这一节讲解后台Rbac微服务角色增删改查微服务功能&#xff0c;Rbac微服务角色增删改查微服务和后台Rbac用户登录微服务是属于…...

项目篇:Echo论坛系统项目

一、登录注册模块 1、注册功能 1.1、注册流程图 1.2、注册代码 /*** 用户注册* param user* return Map<String, Object> 返回错误提示消息&#xff0c;如果返回的 map 为空&#xff0c;则说明注册成功*/public Map<String, Object> register(User user) {Map&l…...

数据可视化(2)

1.柱状图 #柱状图 #bar(x,height,width,*,aligncenter,**kwargs) #height柱子的高度&#xff0c;即y轴上的数据 #width数组的宽度&#xff0c;默认值0.8 #*表示后面的参数为匿名关键字&#xff0c;必须传入参数 #kwargs关键字参数x[1,2,3,4,5] height[random.randint(10,100)f…...

MD-MTSP:斑马优化算法ZOA求解多仓库多旅行商问题MATLAB(可更改数据集,旅行商的数量和起点)

一、斑马优化算法ZOA 斑马优化算法&#xff08;Zebra Optimization Algorithm&#xff0c;ZOA&#xff09;Eva Trojovsk等人于2022年提出&#xff0c;其模拟斑马的觅食和对捕食者攻击的防御行为。斑马优化算法&#xff08;Zebra Optimization Algorithm&#xff0c;ZOA&#x…...

【笔试强训选择题】Day32.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01; 文章目录 前言 一、Da…...

抖音seo账号矩阵系统源码如何开发布局?

目录 一、 抖音SEO账号矩阵系统源码的开发布局步骤如下&#xff1a; 二。 开发部署源码 三、 开发部署功能设计 1. 短视频AI智能创作 2. 托管式账号管理: 3. 数据分析 4. 智能营销获客 四。 抖音seo源码开发部署交付技术文档包含 五。 开发代码展示&#xff1a; 一、 抖…...

vue项目cdn打包优化

0.用vue ui可以查看项目打包后的情况。 1.定义包的排除 let externals {axios: axios,element-ui: ELEMENT,echarts: echarts,} configureWebpack: {externals: externals }2.配置cdn包资源 // 配置 let cdn {css: [// element-ui csshttps://unpkg.com/element-ui/lib/th…...

Android 之 MediaPlayer 播放音频与视频

本节引言&#xff1a; 本节带来的是Android多媒体中的——MediaPlayer&#xff0c;我们可以通过这个API来播放音频和视频 该类是Androd多媒体框架中的一个重要组件&#xff0c;通过该类&#xff0c;我们可以以最小的步骤来获取&#xff0c;解码 和播放音视频。它支持三种不同的…...

React中事件处理器的基本使用

在React中&#xff0c;为了提高性能&#xff0c;跨浏览器兼容性和开发体验&#xff0c;React实现了一套自己的事件机制&#xff0c;利用事件委托和合成事件的方式统一管理事件订阅和分发。 为了让组件能够响应用户的交互行为&#xff0c;React提供了一系列的事件处理器&#xf…...

RobotFramework

一、RobotFramework的简介和特点 1、关键字驱动&#xff1a; 把项目中的业务逻辑封装成一个一个的关键字&#xff0c;然后调用不同的关键字组成不同的业务 2、数据驱动 把测试数据放到excel&#xff1a;yaml文件中 通过改变文件中的数据去驱动测试用例执行 3、特点&#xff…...

【Matplotlib 绘制折线图】

使用 Matplotlib 绘制折线图 在数据可视化中&#xff0c;折线图是一种常见的图表类型&#xff0c;用于展示随着变量的变化&#xff0c;某个指标的趋势或关系。Python 的 Matplotlib 库为我们提供了方便易用的功能来绘制折线图。 绘制折线图 下面的代码展示了如何使用 Matplo…...

ARM汇编基本变量的定义和使用

一、ARM汇编中基本变量是什么? 数字变量: GBLA LCLA SETA 逻辑变量:GBLL LCLL SETL 字符串:GBLS LCLS SETLS 注意需要TAB键定义变量和行首改变值 二、使用步骤 1.引入库 代码如下(示例): GBLA led_num Reset_Handler PROCEXPORT Reset_Handler [WEA…...

排序算法汇总

每日一句&#xff1a;你的日积月累终会成为别人的望尘莫及 目录 常数时间的操作 选择排列 冒泡排列 【异或运算】 面试题&#xff1a; 1&#xff09;在一个整形数组中&#xff0c;已知只有一种数出现了奇数次&#xff0c;其他的所有数都出现了偶数次&#xff0c;怎么找到…...

cocos2d 中UserDefault在windows平台下的路径问题

在使用cocos2dx c开发项目时&#xff0c;通常使用cocos自带的UserDefault来存储一些项目所用到的一些配置信息&#xff1a;如游戏的音量&#xff0c;游戏的闯关数等... 但是windows平台下&#xff0c;测试发现如果用户的帐户名使用是中文&#xff0c;在启动程序时会报错&#…...

ChatGPT与高等教育变革:价值、影响及未来发展

最近一段时间&#xff0c;ChatGPT吸引了社会各界的目光&#xff0c;它可以撰写会议通知、新闻稿、新年贺信&#xff0c;还可以作诗、写文章&#xff0c;甚至可以撰写学术论文。比尔盖茨、马斯克等知名人物纷纷为此发声&#xff0c;谷歌、百度等知名企业纷纷宣布要提供类似产品。…...

Matlab Image Processing toolbox 下载安装方法

当安装好Matlab之后&#xff0c;发现没有Image Processing toolbox这个图像处理工具箱 从新安装一遍&#xff0c; 选上 Image Processing toolbox 但是不用选matlab即可 1.找到之前安装时的Setup安装程序包&#xff0c;按照之前安装Matlab步骤&#xff0c;到选择需要安装的Ma…...

什么是消息键(Key)?如何使用消息键进行消息顺序性保证?

消息键&#xff08;Key&#xff09;是Kafka消息的一个可选属性&#xff0c;用于标识消息的逻辑关联关系。每条消息可以携带一个关键字作为其键&#xff0c;这个键可以是字符串、整数等数据类型。 使用消息键可以在Kafka中实现消息的顺序性保证&#xff0c;具体方式如下&#x…...

慎思笃行,兴业致远:金融行业的数据之道

《中庸》中说&#xff0c;“博学之&#xff0c;审问之&#xff0c;慎思之&#xff0c;明辨之&#xff0c;笃行之”。这段话穿越千年&#xff0c;指引着中国千行百业的发展。对于金融行业来说&#xff0c;庞大的数据量可以说是“博学”的来源。但庞大的数据体量&#xff0c;既是…...

Git-分支管理

文章目录 1.分支管理2.合并冲突3.合并模式4.补充 1.分支管理 Git分支管理是指在Git版本控制系统中&#xff0c;使用分支来管理项目的不同开发线路和并行开发的能力。通过分支&#xff0c;开发者可以在独立的环境中进行功能开发、bug修复等工作&#xff0c;而不会影响到主分支上…...

[Ubuntu 22.04] containerd配置HTTP方式拉取私仓Harbor

文章目录 1. 基础环境配置2. Docker安装3. 部署Harbor&#xff0c;HTTP访问4. 部署ContainerD5. 修改docker配置文件&#xff0c;向harbor中推入镜像6. 配置containerd6.1. 拉取镜像验证6.2. 推送镜像验证 1. 基础环境配置 [Ubuntu 22.04] 安装K8S基础环境准备脚本 2. Docker安…...

入门指南:深入解析OpenCV的copyTo函数及其与rect的应用场景

文章目录 导言copyTo函数的示例copyTo函数与rect的应用场景结论 导言 OpenCV是一个功能强大的开源计算机视觉库&#xff0c;广泛应用于图像处理和计算机视觉任务。在OpenCV中&#xff0c;copyTo函数是一个重要的图像处理函数&#xff0c;它允许我们在不同的图像之间复制像素数…...

2018年全国硕士研究生入学统一考试管理类专业学位联考写作试题——解析版

2018年1月真题 四、写作&#xff1a;第56~57小题&#xff0c;共65分。其中论证有效性分析30 分&#xff0c;论说文35分。 56.论证有效性分析&#xff1a; 分析下述论证中存在的缺陷和漏洞&#xff0c;选择若干要点&#xff0c;写一篇600字左右的文章&#xff0c;对该论证的有…...

系统集成|第七章(笔记)

目录 第七章 范围管理7.1 项目范围管理概念7.2 主要过程7.2.1 规划范围管理7.2.2 收集需求7.2.3 定义范围7.2.4 创建工作分解结构 - WBS7.2.5 范围确认7.2.6 范围控制 上篇&#xff1a;第六章、整体管理 第七章 范围管理 7.1 项目范围管理概念 概述&#xff1a;项目范围管理就…...

Qt —— Vs2017编译hiredis源码并测试调用(附调用hiredis库源码)

下载hiredis源码 编译hiredis源码 1、解压下载的hiredis源码包,如图使用Vs2017打开hiredis_win.sln 2、如下两图,Vs2017打开.sln后点击升级。 分别对两个工程的debug、release进行配置。Debug配置为多线程调试DLL(MDd)、Release配置为多线程DLL(/MD),这样做是为了配合被调用…...

深入理解设计模式:设计模式定义、设计原则以及组织编目

文章目录 一、设计模式1.1 设计模式的起源1.2 设计模式的定义1.3 记录要素1.4 合理使用模式 二、设计模式的六大原则2.1 开闭原则(Open-Closed Principle, OCP)2.1.1 定义2.1.2 原则分析2.1.3 开闭原则的意义所在 2.2 单一职责原则(Single Responsibility Principle, SRP)2.4.1…...

鸿鹄协助管理华为云与炎凰Ichiban

炎凰对华为云的需求 在炎凰日常的开发中&#xff0c;对于服务器上的需求&#xff0c;我们基本都是采用云服务。目前我们主要选择的是华为云&#xff0c;华为云的云主机比较稳定&#xff0c;提供的云主机配置也比较多样&#xff0c;非常适合对于不同场景硬件配置的需求&#xff…...

Vite创建Vue+TS项目引入文件路径报错

使用vite搭建vue3脚手架的时候&#xff0c;发现main.ts中引入App.vue编辑器会报错&#xff0c;但是不影响代码运行。 报错信息&#xff1a;TS2307: Cannot find module ‘./App.vue’ or its corresponding type declarations. 翻译过来是找不到模块或者相关的声明类型&#…...

计算机里基本硬件的组成以及硬件协同

文章目录 冯诺依曼体系输入设备输出设备存储器运算器控制器协同工作的流程 冯诺依曼体系 世界上第一台通用计算机&#xff0c;ENIAC&#xff0c;于1946年诞生于美国一所大学。 ENIAC研发的前期&#xff0c;需要工作人员根据提前设计好的指令手动接线&#xff0c;以这种方式输入…...

网站制作公司茂名/网站链接交易

文&#xff0f;孙不熟日前&#xff0c;国家发改委等九部委联合发布《关于全力做好2020年春运工作的意见》&#xff0c;我认为有两个重要信息值得关注。一是指出&#xff0c;随着我国城乡、区域发展更加平衡协调&#xff0c;春运迁移人口总量增长放缓&#xff0c;长距离迁移比例…...

jsp网站开发标准/最新新闻事件今天国内大事

独立按键 使用独立按键时&#xff0c;请将开发板上面的J5跳线帽接到BTN端&#xff0c;此时为独立按键模式。 独立按键原理很简单&#xff0c;根据原理图 此时一旦按键按下&#xff0c;相应的I/O口就是接地状态&#xff0c;为低电平。 写单片机时我们都知道按键按下需要消抖&…...

贵阳网站建设网站制作/seo关键词有话要多少钱

众所周知&#xff0c;运维工程师的工作比较繁琐杂乱&#xff0c;且经常是背锅侠。所以要想舒舒服服做好IT运维工作&#xff0c;就要用堡垒机&#xff01;用了堡垒机&#xff0c;从此告别背锅侠&#xff01;下面我们小编就给大家简单讲解一下堡垒机的定义、作用、功能等等&#…...

网站建设资讯版块如何做用户运营/长沙优化科技

解决方法 pycharm配置环境变量: PYTHONUNBUFFERED1;SPARK_HOMEF:\spark-3.3.0-bin-hadoop3;PYSPARK_PYTHONpython 或者配置他们到windows环境变量,然后重启pycharm....

网站怎么添加统计代码/让顾客进店的100条方法

1. 系统权限unlimited tablespace是隐含在dba, resource角色中的一个系统权限. 当用户得到dba或resource的角色时, unlimited tablespace系统权限也隐式受权给用户. 2. 系统权限unlimited tablespace不能被授予role, 可以被授予用户. 3. 系统权限unlimited tablespace不会随着r…...

一个网站做app/爱站权重查询

Linux之ARM&#xff08;IMX6U&#xff09;BSP工程管理实验1、工程管理简介1.1、创建bsp、imx6ul、obj和project这四个文件夹1.2、文件分类2、实验程序编写2.1、创建 imx6ul.h 文件2.2、创建个.vscode文件修改includePath2.2.1、修改includePath2.3、编写led驱动文件2.2.1、 bsp…...