Go基本数据结构

news2024/10/10 4:21:07

1.jdk丰富的数据结构

Jdk提供了许多基本数据结构的实现,这些数据结构是Java Collections Framework的一部分,位于java.util包中。以下是一些常见的数据结构:

  1. ArrayList:一个可调整大小的数组实现,支持快速随机访问。

  2. LinkedList:一个双向链表实现,支持快速插入和删除。

  3. HashSet:基于哈希表的Set实现,不保证集合的迭代顺序。

  4. LinkedHashSet:类似于HashSet,但维护着一个运行于所有元素的双重链表。

  5. TreeSet:基于红黑树的NavigableSet实现,元素处于排序状态。

  6. HashMap:基于哈希表的Map实现,不保证映射的顺序。

  7. LinkedHashMap:类似于HashMap,维护着一个运行于所有条目的双重链表。

  8. TreeMap:基于红黑树的NavigableMap实现,键处于排序状态。

  9. PriorityQueue:基于优先级堆的无界队列,元素处于自然排序状态。

  10. ArrayDeque:一个双端队列实现,支持从两端插入和移除。

  11. ConcurrentHashMap:一个线程安全的HashMap实现。

  12. CopyOnWriteArrayList:一个线程安全的变长数组实现。

  13. CopyOnWriteArraySet:一个线程安全的Set实现。

JDK提供了如此多的数据结果,程序员只需了解每种数据结果的特点,即可应付大部分日常开发。说实在的,对于数据结构,即使经验丰富的开发(包括笔者自己),很多人都是只知其然而不知其所以然

在这里分享一个面试经历,笔者曾经一个同事,中山大学硕士生,技术杠杠的。然而在一场面试却挂了, 面试官问了一个问题,讲讲红黑树的数据结构。

说这个故事不是说数据结构不重要,只是侧面说明了jdk内嵌的数据结果多么丰富,可以解决实际开发90%以上的问题。

2.Go基本数据结构

Go以简洁著称,只提供3种数据结构(后续可能会拓展,毕竟效率是第一生产力啊)

2.1.数组

数组是一个固定长度的数据类型,用于存储一段具有相同类型元素的数据块。其占有的内存是连续的,可以快速迭代数组里的每个元素。

2.1.1.申明数组

当数组初始化时,每个元素被初始化为对应的零值

// 申明包含5个元素的整形数组
var array [5]int
// 初始化为零值 [0 0 0 0 0]
fmt.Print(array) 

使用字面量声明数组

import "fmt"
func main() {
   // 申明一个包含5个元素的数组,并进行初始化
   array1 := [5]int{1,2,3,4,5}
   // 使用...自动计算数组长度
   array2 := [...]int{1,2,3,4,5,6}
   
   fmt.Println(array1)
   fmt.Println(array2)
}

2.1.2.基本操作

func main() {
   // 申明一个包含5个元素的数组,并进行初始化
   array := [5]int{1,2,3,4,5}
   // 修改索引为2的元素
   array[2] = 30
   // 遍历数组
   for i := 0; i < len(array); i++ {
        fmt.Println(array[i])
   }
   // 使用range遍历
   for i,value := range array {
        fmt.Println(i, value)
   }
   
   // 数组复制,只有类型及长度都一样才可进行!
   var array2 [5]int = array
   fmt.Println(array2)
}

2.1.3.在函数间传递数组

在函数之间传递变量时,总是以值的方式。如果数组很大,无疑开销很大。因为无论数组有多长,都会完整复制一份新的。(这一点与C语言不同,C传递数组给函数时,传递的是首元素的地址,相当于传递了指针)

import "fmt"
func main() {
  // 100万个元素,需要8M
  var array [1e6]int64 
  // 将数组复制传递给函数
  test(array) 
  // 函数内部的操作不会影响到原数组
  fmt.Println(array[1])
}

func test(a [1e6]int64) {
   a[1]=100
   fmt.Println(len(a))
   // 函数内部修改了元素值,操作的是复制品
   fmt.Println(a[1])
}

2.1.4.传递引用

为了避免耗时耗内存的复制操作,可以只传入指向数组的指针

func main() {
   // 100万个元素,需要8M
  var array [1e6]int64 
  test(&array) 
  // 函数内部的修改,会影响原数组的内容
  fmt.Println(array[1])
}

func test(a *[1e6]int64) {
   // 修改索引为1的元素的值
   a[1]=100
   fmt.Println(len(a))
   // 内部通过指针修改元素值
   fmt.Println(a[1])
}

2.2.切片

切片,实质上为动态数组,相当于java的ArrayList。这种结构可以通过append函数按需自动增长,也可以对切片再次切片来缩小一个切片的大小。

2.2.1.内部实现

切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。切片内部有3个字段,分别是指向数组的指针,元素个数(长度)和切片允许增长的元素个数(容量)。如下图所示:

2.2.2.创建与初始化

使用make函数创建切片

func main() {
    // 只指定长度,那么切片的容量等于长度
   slice := make([]int, 5)
   // 同时指定长度,容量
   slice2 := make([]int, 5, 10)
}

 通过切面字面量申明切片(与通过字面量申明数组非常相似,只是中括号内部少了...)

color := []string{"red", "green", "blue"}

2.2.3.nil与空切片

nil切片

有时(例如函数要求返回一个切片但发生异常),程序可能需要申明一个值为nil的切片。只需在申明时不做任何初始化。如下所示:

// 创建nil切片
var slice []int

空切片

空切片表示,在底层数组包含0个元素,也没有分配任何存储空间。使用空切片来表示空集合,例如,数组库查询返回0个查询结果。

// 使用make创建空切片
slice := make([]int,0)
// 使用字面量创建空切片
slice2 := []int{}

2.2.4.基本操作

package main

import (
	"fmt"
	"sort"
)

func main() {
	// 声明一个整型切片
	var slice []int

	// 使用内置的make函数初始化切片
	slice = make([]int, 5) // 分配一个长度为5的切片,容量也为5

	// 使用数组初始化切片
	array := [5]int{1, 2, 3, 4, 5}
	slice = array[:5] // 创建一个切片,包含整个数组

	// 访问和修改元素
	slice[0] = 10 // 修改索引为0的元素为10
	fmt.Println("Modified slice:", slice)

	// 追加元素
	slice = append(slice, 6)
	fmt.Println("Slice after append:", slice)

	// 获取切片长度和容量
	length := len(slice)
	capacity := cap(slice)
	fmt.Println("Length:", length)
	fmt.Println("Capacity:", capacity)

	// 切片的切片
	subSlice := slice[1:4]
	fmt.Println("Sub-slice:", subSlice)

	// 遍历切片
	fmt.Println("Iterating over slice:")
	for index, value := range slice {
		fmt.Println(index, value)
	}

	// 使用for循环遍历切片
	fmt.Println("Iterating with for loop:")
	for index := 0; index < len(slice); index++ {
		fmt.Println(index, slice[index])
	}

	// 清空切片
	slice = slice[:0]
	fmt.Println("Slice after clearing:", slice)

	// 扩展切片
	slice = slice[:cap(slice)] // 扩展切片到其容量
	fmt.Println("Slice after expansion:", slice)

	// 截断切片
	slice = slice[:4] // 将切片截断到4
	fmt.Println("Slice after truncation:", slice)

	// 复制切片
	copySlice := append([]int(nil), slice...)
	fmt.Println("Copy of slice:", copySlice)

	// 排序切片
	sort.Ints(slice)
	fmt.Println("Sorted slice:", slice)

	// 删除元素
	slice = append(slice[:1], slice[2:]...)
	fmt.Println("Slice after deletion:", slice)

	// 反转切片
	for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
		slice[i], slice[j] = slice[j], slice[i]
	}
	fmt.Println("Reversed slice:", slice)
}

2.2.5.切片的切片

这里重点讲下对切片进行切片。切片之所以被称为切片,是因为创建一个新的切片就是把底层数组切出一部分。如下代码:

   // 创建一个长度与容量都是5的切片
   myNum := []int{10,20,30,40,50}
   // 创建一个新切片,长度为2,容量为4
   newNum := myNum[1:3]
   // 向第二个切片新增元素
   newNum = append(newNum, 60)
   fmt.Println(myNum)  //输出: [10 20 30 60 50]
   fmt.Println(newNum) //输出: [20 30 60]

执行完代码之后,我们有两个切片,它们共享同一个底层数组,但不同的切片看到同一个数组的不同部分,如下

第一个切片myNum能够看到底层数组全部5个元素,而newNum一开始只能看到索引为1和索引为2的元素,在执行append之后,可以看到新增的元素60,同时新增的元素也影响了myNum切片。从程序输出可以看出,myNum索引为3的元素从40变成60了。

2.2.6.限制再切片的容量

在创建切片的切片时,还可以指定第三个参数,用来控制新切片的容量。该参数可以更好控制追加操作,为底层数组提供了一定的保护。(为了显示行数,下面的例子使用截图而非文本)

对于第三个参数的解释

第9行代码执行 newNum   := myNum[2:3:4],这里的4代表的是原切片myNum的索引为4的位置,典型地,对于slice[i:j:k],示例 [2:3:4]

长度公式为:j - i = 3-2 = 1

容量公式为:k - i = 4-2 = 2

执行第12行,向newNum追加一个元素600,因为newNum容量为2,没有超出(此时newNum长度为2,容量为2),所以共享底层数组的myNumq切片也多了一个元素600(见第13行输出)

然而,执行第13行,向newNum继续追加一个元素700,因为超出newNum的容量,程序会创建一个新的底层数组,这个数组会将原来newNum的元素全部复制过来,新切片的容量为原切片进行翻倍。

从上面的例子可以看出,如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个append操作创建新的底层数组,从而使新切片与原切片彻底分类,这样,新切片就可以安全进行后续操作了。

2.2.7.在函数间传递切片

在函数间传递切片是以值的方式,由于切片本身属于引用类型,将切片传递给函数时,在64位架构的机器上,一个切片需要8字节的指针,8字节的长度以及8字节的容量,总共需要24字节。底层的数组不会被复制,所以在函数间传递切片非常快速。

2.3.映射

映射(map)是一种基于哈希表实现的内置数据结构,它允许存储键值对,并提供了快速的查找、插入和删除操作(相当于java的HashMap)。以下是 map 的一些核心特性和基本操作:

  • 无序:map 是无序的,不能通过索引访问,也没有固定的长度。
  • 动态:map 的大小是动态的,可以随时添加或删除键值对。
  • 唯一键:map 的键是唯一的,不允许重复。
  • 非并发安全:标准的 map 不是并发安全的,如果需要在多个 goroutine 中共享 map,应该使用 sync.Map 或者在操作 map 时使用互斥锁。

2.3.1.创建和初始化

使用make和字面量创建映射

// 创建一个映射,key为string,value为int
dict := make(map[string]int)

// 创建一个映射,key,value均为string
// 使用两个键值对初始化
dict2 := map[string]string{"name":"gforgame", "url":"https://github.com/kingston-csj/gforgame"}

关于键的类型

映射的键可以是任何值,只要这个值可以使用==运算符做比较。这个值的类型可以是内置的类型,也可以是结构类型。但切片、函数这些类型由于具有引用语义,因此不能作为映射的键。

2.3.2.基本操作

package main

import (
	"fmt"
)

func main() {
	// 初始化 map
	myMap := make(map[string]int)

	// 赋值
	myMap["apple"] = 1
	myMap["banana"] = 2
	myMap["cherry"] = 3

	// 读取
	value, ok := myMap["apple"]
	if ok {
		fmt.Println("apple:", value)
	} else {
		fmt.Println("apple not found")
	}

	// 删除
	delete(myMap, "banana")
	if _, ok := myMap["banana"]; !ok {
		fmt.Println("banana has been deleted")
	}

	// 遍历
	fmt.Println("Iterating over map:")
	for key, value := range myMap {
		fmt.Println(key, value)
	}

	// 获取大小
	size := len(myMap)
	fmt.Println("Size of map:", size)

	// 检查键是否存在
	if _, exists := myMap["cherry"]; exists {
		fmt.Println("cherry exists in the map")
	} else {
		fmt.Println("cherry does not exist in the map")
	}

	// 预分配空间
	myMapPreallocated := make(map[string]int, 5)
	myMapPreallocated["orange"] = 5
	fmt.Println("Preallocated map size:", len(myMapPreallocated))

	// 清空 map
	for key := range myMapPreallocated {
		delete(myMapPreallocated, key)
	}
	fmt.Println("Map after clearing:", myMapPreallocated)
}

2.3.3.在函数间传递映射

映射是引用类型,因此在函数间传递映射时,传递的是映射的引用。这意味着,如果在函数内部修改了映射的内容,这些修改将会影响到原始的映射。

下面是一个示例,展示了如何在函数间传递映射,并在函数内部修改映射

package main

import "fmt"

// AddItem 向映射中添加一个元素
func AddItem(itemMap map[string]int, key string, value int) {
	itemMap[key] = value // 添加或更新键值对
}

// RemoveItem 从映射中删除一个元素
func RemoveItem(itemMap map[string]int, key string) {
	delete(itemMap, key) // 删除键值对
}

func main() {
	myMap := make(map[string]int)

	// 向映射中添加元素
	AddItem(myMap, "apple", 1)
	AddItem(myMap, "banana", 2)

	// 打印映射
	fmt.Println("After adding items:")
	fmt.Println(myMap) // 输出: map[apple:1 banana:2]
	
	// 从映射中删除一个元素
	RemoveItem(myMap, "banana")
	// 打印映射
	fmt.Println("After removing an item:")
	fmt.Println(myMap) // 输出: map[apple:1]
}

3.第三方数据结构

由于官方提供的数据结构确实非常少,因此社区相关的数据结构实现非常多,下面列举几个比较有名的。

  • go-datastructures  这是一个集合,包含了许多有用的、性能优异的、线程安全的 Go 数据结构。例如,增强树、位数组、队列、斐波那契堆、区间树、集合等。
  • gods  Go 数据结构库,包含链表、栈、哈希表、树等。

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

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

相关文章

[Algorithm][贪心][合并区间][无重叠区间][用最少数量的箭引爆气球]详细讲解

目录 1.合并区间1.题目链接2.算法原理详解3.代码实现 2.无重叠区间1.题目链接2.算法原理详解3.代码实现 3.用最少数量的箭引爆气球1.题目链接2.算法原理详解3.代码实现 1.合并区间 1.题目链接 合并区间 2.算法原理详解 区间问题思路&#xff1a; 排序 左端点(本题)右端点 根…

Docker安装及使用记录

本文汇总一下 Docker 的安装过程和使用过程中的问题 安装过程 Windows Linux 更新软件源&#xff1a;Linux安装前可先更新以下各自发行版包管理器的软件源 卸载旧版本&#xff1a;如果之前安装过的话&#xff0c;可以先卸载 yum remove docker docker-common docker-sel…

『网络游戏』自适应制作登录UI【01】

首先创建项目 修改场景名字为SceneLogin 创建一个Plane面板 - 将摄像机照射Plane 新建游戏启动场景GameRoot 新建空节点重命名为GameRoot 在子级下创建Canvas 拖拽EventSystem至子级 在Canvas子级下创建空节点重命名为LoginWnd - 即登录窗口 创建公告按钮 创建字体文本 创建输入…

基于SpringBoot“花开富贵”花园管理系统【附源码】

效果如下&#xff1a; 系统注册页面 系统首页界面 植物信息详细页面 后台登录界面 管理员主界面 植物分类管理界面 植物信息管理界面 园艺记录管理界面 研究背景 随着城市化进程的加快和人们生活质量的提升&#xff0c;越来越多的人开始追求与自然和谐共生的生活方式&#xf…

RabbitMQ(学习前言)

目录 学习MQ之前有必要先去温故下微服务知识体系&#xff0c;以加深本章节的理解 一、微服务间的通讯方式 1. 基本介绍 2. 同步通讯 2.1. 什么是同步通讯 2.2. 同步通讯存在的问题 问题一&#xff1a;耦合度高 问题二&#xff1a;性能和吞吐能力下降 问题三&#xff1a…

YOLOv11改进,YOLOv11添加DCNv4可变性卷积(windows系统成功编译),二次创新C2f结构,全网最详细教程

改进训练结果前: 二次创新C2f结构训练结果: 摘要 引入了可变形卷积 v4 (DCNv4),这是一种为广泛视觉应用设计的高效且有效的操作算子。DCNv4通过两项关键增强解决了其前身DCNv3的局限性:1. 移除空间聚合中的softmax归一化,以增强其动态特性和表达能力;2. 优化内存访问以…

【动态规划-4.2 最长递增子序列(LIS)】力扣300. 最长递增子序列

给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的 子序列 。 示例 1&…

LVM——让Linux磁盘空间的弹性管理

什么是LVM&#xff1f; LVM(Logical Volume Manager)逻辑卷管理是在Linux2.4内核以上实现的磁盘管理技术。它是Linux环境下对 磁盘分区进行管理的一种机制。现在不仅仅是Linux系统上可以使用LVM这种磁盘管理机制&#xff0c;对于其它的类UNIX操作系统&#xff0c;以及windows操…

用Manim简单解释奇异值分解(SVD)和图像处理方面的应

一&#xff0c;介绍 奇异值分解&#xff08;SVD&#xff09;是一种重要的矩阵分解技术&#xff0c;在统计学、信号处理和机器学习等领域有广泛应用。对于任意给定的矩阵 A&#xff08;可以是任意形状的矩阵&#xff09;&#xff0c;SVD将其分解为三个特定的矩阵的乘积&#x…

【时间盒子】-【9.任务设置项】自定义任务名称、任务时长等设置项组件

Tips: Stage、Link装饰器的使用&#xff1b; 参考我的帖子&#xff1a;https://developer.huawei.com/consumer/cn/forum/topic/0208152234389094513?fid0101587866109860105 一、预览 红色框&#xff1a;任务设置项列表&#xff0c;把它定义为一个组件对象SettingList。绿…

Python 工具库每日推荐【PyPDF2】

文章目录 引言Python PDF 处理库的重要性今日推荐:PyPDF2 工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:PDF文件合并案例分析高级特性加密和解密PDF添加水印扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScript 设计模式 专栏…

Vue基础(2)检测数据原理~生命周期

文章目录 检测数据原理1.更新时遇到的问题2.检测数据的原理-对象3. vue.set()的使用 收集表单数据过滤器内置指令1.v-text2.v-html3.v-cloak4.v-once5.v-pre 自定义指令生命周期1.挂载流程2.更新流程3.销毁流程 检测数据原理 1.Vue会监视data中的所有层次的数据 2.如何监测对象…

10月8日星期二今日早报简报微语报早读

10月8日星期二&#xff0c;农历九月初六&#xff0c;早报#微语早读。 1、我国自主研制的300兆瓦级F级重型燃气轮机在上海首次点火成功&#xff1b; 2、2024国庆档超21亿收官&#xff1a;《志愿军&#xff1a;存亡之战》票房8亿夺冠&#xff1b; 3、维克托安布罗斯&#xff0…

STM32工程环境搭建(库函数开发)

目录 1、移植固件库&标准库 2、新建工程 以STM32f401作为例子进行环境搭建 1、移植固件库&标准库 ①桌面创建工程文件夹并且提取内核文件 用户文件&#xff1a;用户自己编写的程序文件 .c .h文件 .c文件&#xff1a;具体函数功能源代码 .h文件&#xff1a;宏定义…

ctf.bugku - bp (弱密码top1000)

题目来源&#xff1a; bp - Bugku CTF 首先&#xff0c;下载top1000 &#xff0c;弱密码文本&#xff1a; PasswordDic/top1000.txt at master k8gege/PasswordDic GitHub 访问页面&#xff0c;随便输入个密码 发送请求到 intruder 以密码问参数 加载top1000.txt 密码文本&…

springboot开发网站-使用redis数据库定时特征限制指定ip的访问次数

springboot开发网站-使用redis数据库定时特征限制指定ip的访问次数。近期网站经常有人恶意访问&#xff0c;提交了很多垃圾信息。为了屏蔽这类灌水帖&#xff0c;打算屏蔽ip地址&#xff0c;限制24小时内只能访问1次某个接口。下面是测试的案例代码内容。 1&#xff1a;首先&am…

实验三 Web基础-JavaScript

实验三 Web基础-JavaScript 目的&#xff1a; 1、理解和掌握Javascript基本语法 2、掌握JavaScript操作表单对象的方法 3、理解和掌握JavaScript的函数与事件 4、理解JavaScript的内置对象 实验要求&#xff1a; 1、使用JavaScript语言实现实验要求 2、要求提交实验报告&…

HTB:Pennyworth[WriteUP]

目录 连接至HTB服务器并启动靶机 1.What does the acronym CVE stand for? 2.What do the three letters in CIA, referring to the CIA triad in cybersecurity, stand for? 3.What is the version of the service running on port 8080? 4.What version of Jenkins i…

【C++11】可变模板参数

文章目录 可变模板参数的概念递归函数方式展开参数包 STL容器中的empalce相关的接口函数emplace 与 insert / push_back 的区别 可变模板参数的概念 可变参数模板是 C11 引入的一种模板特性&#xff0c;允许定义可以接收任意数量参数的模板&#xff0c;广泛应用于函数和类的设计…

【js逆向学习】极志愿 javascript+python+rpc

JSRPC使用方式 逆向目标逆向过程逆向分析1、什么是 websocket2、websocket的原理3、总体过程3.1 环境说明3.2 python服务端代码3.3 python客户端代码 4、Sekiro-RPC4.1 执行方式4.2 客户端环境4.3 参数说明4.4 SK API4.5 python代码调试4.6 代码注入流程 逆向总结 逆向目标 网…