Go分布式爬虫笔记(二十一)

news2024/12/24 11:28:17

文章目录

  • 21 切片和哈希表
  • 切片
    • 底层结构
    • 截取
    • 扩容
  • 哈希表
    • 原理
    • 哈希碰撞
      • 拉链法
      • 开放寻址法(Open Addressing)
    • 读取
    • 重建原理
    • 删除原理
  • 思考题
    • Go 的哈希表为什么不是并发安全的?
    • 在实践中,怎么才能够并发安全地操作哈希表?
      • 拉链法
      • 开放寻址法(Open Addressing)

21 切片和哈希表

切片

下面的代码中,foo 与 bar 最后的值是什么?

foo := []int{0,0,0,42,100}
bar := foo[1:4]
bar[1] = 99
fmt.Println("foo:", foo) 
fmt.Println("bar:", bar) 

// foo: [0 0 99 42 100]
// bar: [0 99 42]

下面程序输出什么?

x := []int{1, 2, 3, 4}
y := x[:2]
fmt.Println(cap(x), cap(y))
y = append(y, 30)
fmt.Println("x:", x)
fmt.Println("y:", y)

// Output
//  4, 4
//  1,2,30,4
//  1,2,30

底层结构

和 C 语言中的数组是一个指针不同,Go 中的切片是一个复合结构。一个切片在运行时由指针(data)、长度(len)和容量(cap)三部分构成。

type SliceHeader struct {
  Data uintptr // 指向切片元素对应的底层数组元素的地址。
  Len int      // 对应切片中元素的数目,总长度不能超过容量。
  Cap int      // 提供了额外的元素空间,可以在之后更快地添加元素。容量的大小一般指的是从切片的开始位置到底层数据的结尾位置的长度。
}

image

截取

切片在被截取时的一个特点是,截取后的切片长度和容量可能会发生变化。

和数组一样,切片中的数据仍然是内存中一片连续的区域。要获取切片某一区域的连续数据,可以通过下标的方式对切片进行截断。被截取后的切片,它的长度和容量都发生了变化。就像下面这个例子,numbers 切片的长度为 8。number1 截取了 numbers 切片中的第 2、3 号元素。number1 切片的长度变为了 2,容量变为了 6(即从第 2 号元素开始到元素数组的末尾)。

numbers:= []int{1,2,3,4,5,6,7,8}
// 从下标2 一直到下标4,但是不包括下标4
numbers1 :=numbers[2:4]
// 从下标0 一直到下标3,但是不包括下标3
numbers2 :=numbers[:3]
// 从下标3 一直到结尾
numbers3 :=numbers[3:]

切片在被截取时的另一个特点是,被截取后的数组仍然指向原始切片的底层数据。

例如之前提到的案例,bar 截取了 foo 切片中间的元素,并修改了 bar 中的第 2 号元素。

foo := []int{0,0,0,42,100}
bar := foo[1:4]
bar[1] = 99

image

这时,bar 的 cap 容量会到原始切片的末尾,所以当前 bar 的 cap 长度为 4。

这意味着什么呢?我们看下面的例子,bar 执行了 append 函数之后,最终也修改了 foo 的最后一个元素,这是一个在实践中非常常见的陷阱。

foo := []int{0, 0, 0, 42, 100}
bar := foo[1:4]
bar = append(bar, 99)
fmt.Println("foo:", foo) // foo: [0 0 0 42 99]
fmt.Println("bar:", bar) // bar: [0 0 42 99]

//解决
foo := []int{0,0,0,42,100}
bar := foo[1:4:4] // 在截取时指定容量:
bar = append(bar, 99)
fmt.Println("foo:", foo) // foo: [0 0 0 42 100]
fmt.Println("bar:", bar) // bar: [0 0 42 99]

foo[1:4:4] 这种方式可能很多人没有见到过。这里,第三个参数 4 代表 cap 的位置一直到下标 4,但是不包括下标 4。 所以当前 bar 的 Cap 变为了 3,和它的长度相同。当 bar 进行 append 操作时,将发生扩容,它会指向与 foo 不同的底层数据空间

扩容

Go 语言内置的 append 函数可以把新的元素添加到切片的末尾,它可以接受可变长度的元素,并且可以自动扩容。如果原有数组的长度和容量已经相同,那么在扩容后,长度和容量都会相 应增加。

不过,Go 语言并不会每增加一个元素就扩容一次,这是因为扩容常会涉及到内存的分配,频繁扩容会减慢 append 的速度。append 函数在运行时调用了 runtime/slice.go 文件下的 growslice 函数:


func growslice(et *_type, old slice, cap int) slice {
  newcap := old.cap
  doublecap := newcap + newcap
  if cap > doublecap {
    newcap = cap
  } else {
    if old.len < 1024 {
      newcap = doublecap
    } else {
      for 0 < newcap && newcap < cap {
        newcap += newcap / 4
      }
      if newcap <= 0 {
        newcap = cap
      }
    }
  }
  ...
}
  • 如果新申请容量(cap)大于旧容量(old.cap)的两倍,则最终容量(newcap)是新申请的容量(cap);
  • 如果旧切片的长度小于 1024,则最终容量是旧容量的 2 倍,即“newcap=doublecap”;
  • 如果旧切片的长度大于或等于 1024,则最终容量从旧容量开始循环增加原来的 1/4,直到最终容量大于或等于新申请的容量为止;
  • 如果最终容量计算值溢出,即超过了 int 的最大范围,则最终容量就是新申请容量。

切片动态扩容的机制启发我们,在一开始就要分配好切片的容量。否则频繁地扩容会影响程序的性能。所以我们可以将爬虫项目的容量扩展到 1000,注意长度需要为 0。

var seeds = make([]*collect.Request, 0, 1000)

哈希表

实践中,我们通常将哈希表看作 o(1) 时间复杂度的操作,可以通过一个键快速寻找其唯一对应的值(Value)。

在很多情况下,哈希表的查找速度明显快于一些搜索树形式的数据结构,因此它被广泛用于关联数组缓存数据库缓存等场景。

原理

哈希表的原理是将多个键 / 值对(Key/Value)分散存储在 Buckets(桶)中。给定一个键(Key),哈希(Hash)算法会计算出键值对存储的桶的位置。找到存储桶的位置通常包括两步,伪代码如下:

hash = hashfunc(key)
index = hash % array_size

哈希碰撞

哈希函数在实际运用中最常见的问题是哈希碰撞(Hash Collision),即不同的键使用哈希算法可能产生相同的哈希值。如果将 2450 个键随机分配到一百万个桶中,根据概率计算,至少有两个键被分配到同一个桶中的可能性超过 95%。哈希碰撞导致同一个桶中可能存在多个元素,会减慢数据查找的速度。

避免哈希碰撞:

  • 拉链法1
  • 开放寻址法2

Go 语言中的哈希表采用的是优化的拉链法,它在桶中存储了 8 个元素用于加速访问。

拉链法

将同一个桶中的元素通过链表的形式进行链接,这是一种最简单、最常用的策略。随着桶中元素的增加,我们可以不断链接新的元素,而且不用预先为元素分配内存。

不足:

  • 需要存储额外的指针来链接元素,这就增加了整个哈希表的大小。
  • 由于链表存储的地址不连续,所以无法高效利用 CPU 缓存。

image

开放寻址法(Open Addressing)

这种方法是**将所有元素都存储在桶的数组中。**当必须插入新条目时,开放寻址法将按某种探测策略顺序查找,直到找到未使用的数组插槽为止。当搜索元素时,开放寻址法将按相同顺序扫描存储桶,直到查找到目标记录或找到未使用的插槽为止。

image

读取

  • 通过上面的伪代码找到桶的位置
  • 遍历桶中的 tophash 数组。
    tophash 数组存储了 8 个元素用于加速访问。
    如果在 tophash 数组中找到了相同的 hash 值,就可以通过指针的寻址操作找到对应的 Key 与 Value。
  • 此外,在 Go 语言中还有一个溢出桶的概念。在执行 hash[key] = value 的赋值操作时,当指定桶中的数据超过 8 个时,并不会直接开辟一个新桶,而是将数据放置到溢出桶中,每个桶的最后都存储了 overflow,即溢出桶的指针。

image

  • 在正常情况下,数据是很少会跑到溢出桶里面去的。同理,在 Map 执行查找操作时,如果 Key 的 hash 不在指定桶的 tophash 数组中,我们就需要遍历溢出桶中的数据。

image

​​

重建原理

不过,如果溢出桶的数量过多,或者 Map 超过了负载因子大小,Map 就要进行重建。负载因子是哈希表中的经典概念:

负载因子 = 哈希表中的元素数量 / 桶的数量

负载因子的增大意味着更多的元素会被分配到同一个桶中,此时效率会减慢。试想一下,如果桶的数量只有 1 个,这个时候负载因子到达最大,搜索就和遍历数组一样了,它的复杂度为 o(n)。

Go 语言中的负载因子为 6.5,当哈希表负载因子的大小超过 6.5 时,Map 就会扩容,变为旧表的两倍。当旧桶中的数据全部转移到新桶后,旧桶就会被清空。Map 的重建还存在第二种情况,那就是溢出桶的数量太多。这时 Map 只会新建和原来一样大的桶,目的是防止溢出桶的数量缓慢增长导致内存泄露。

image

​哈希表的重建过程提示我们,可以在初始化时评估并指定放入 Map 的数据大小,从而减少重建的性能消耗。

删除原理

当进行 Map 的 delete 函数时,delete 函数会根据 Key 找到指定的桶,如果存在指定的 Key,那么就释放掉 Key 与 Value 引用的内存。tophash 中的指定位置会存储 emptyOne,代表当前位置是空的。

同时,删除操作会探测当前要删除的元素之后是否都是空的。如果是, tophash 会存储为 emptyRest。这样做的好处是在进行查找时,遇到 emptyRest 可以直接退出,提高了查找效率。

image

思考题

Go 的哈希表为什么不是并发安全的?

在实践中,怎么才能够并发安全地操作哈希表?

写操作加锁,或者使用sync.Map;

「此文章为4月Day5学习笔记,内容来源于极客时间《Go分布式爬虫实战》,强烈推荐该课程!/推荐该课程」


  1. 拉链法

    ↩︎
  2. 开放寻址法(Open Addressing)

    ↩︎

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

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

相关文章

软件设计师笔记-----程序设计语言与语言处理程序基础

文章目录七、程序设计语言与语言处理程序基础7.1、编译与解释&#xff08;低频&#xff09;7.2、文法&#xff08;低频&#xff09;7.3、有限自动机与正规式&#xff08;几乎每次都会考到&#xff09;有限自动机正规式7.4、表达式&#xff08;偶尔考到&#xff09;7.5、传值和传…

2023-详解实时数仓建设

一、实时数仓建设背景 1. 实时需求日趋迫切 目前各大公司的产品需求和内部决策对于数据实时性的要求越来越迫切&#xff0c;需要实时数仓的能力来赋能。传统离线数仓的数据时效性是 T1&#xff0c;调度频率以天为单位&#xff0c;无法支撑实时场景的数据需求。即使能将调度频…

网狐大联盟增加账号登陆功能

1. UI设计 2. 发布CSB文件,并添加到前端工程资源目录下 打开已发布csb文件所有目录 复制到工程目录 如果有用到其它目录的资源也要同步复制到工程资源对应目录中: 2.脚本功能编写 增加前端结构: -- 帐号登录 login.CMD_MB_LogonAccounts= {{t = "word", k = &#

企业电子招标采购系统源码—企业战略布局下的采购寻源

​ 智慧寻源 多策略、多场景寻源&#xff0c;多种看板让寻源过程全程可监控&#xff0c;根据不同采购场景&#xff0c;采取不同寻源策略&#xff0c; 实现采购寻源线上化管控&#xff1b;同时支持公域和私域寻源。 询价比价 全程线上询比价&#xff0c;信息公开透明&#xff…

一站式智慧仓储物流方案,免费帮你一屏搞定,领导不重用你都难!

在江苏无锡&#xff0c;菜鸟已经通过柔性自动化技术搭建了亚洲规模最大的无人仓&#xff0c;超过1000台无人车可以快速组合、分拆作业&#xff0c;生产效率可提升一倍多&#xff0c;大大节省了人工成本。智慧仓储物流作为物流的重要一环&#xff0c;也吸引了广泛关注。2022年双…

如何使用 Jetpack Compose 创建翻转卡片效果

如何使用 Jetpack Compose 创建翻转卡片效果 介绍 在电子商务和银行应用程序中输入卡信息是很常见的情况。我认为让用户更轻松地处理这种情况并创建更吸引眼球的 UI 将很有用。大多数应用程序/网站都喜欢它。 执行 在开发阶段&#xff0c;您需要做的是打开一个 Android 项目…

vim命令模式指令一览

提示&#xff1a;本文介绍了linux下vim中的快捷指令。 文章目录注意&#xff1a;本文所有指令都只在命令行模式下有效&#xff01;&#xff01;&#xff01; vim指令图&#xff1a; 指令解析命令解析h光标向左移动j光标向下移动k光标向上移动l光标向下移动yy/nyy复制当前行/赋…

2023最新面试题-Java-1

知其然知其所以然 Java之父&#xff1a;詹姆斯高斯林 (James Gosling)。 什么是Java Java是一门面向对象编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念。意思Java不支持多继承、指针。Java语言具有功能强大和简单易用…

《花雕学AI》14:免费打开就可用,ChatGPT国内12个镜像站盘点与测试

引言 人工智能聊天机器人是能和人说话的智能系统&#xff0c;它可以帮人做很多事。现在&#xff0c;人工智能聊天机器人很厉害&#xff0c;很多人想试试。ChatGPT是一个很厉害的人工智能聊天机器人&#xff0c;是OpenAI做的。它可以和人一样说话&#xff0c;还可以回答问题、承…

无线耳机哪个音质比较好?四百内音质最好的无线耳机排行

蓝牙耳机常常作为手机的伴生产品而出现在人们的日常生活当中&#xff0c;其使用场景也越来越广泛。而随着蓝牙技术的发展&#xff0c;蓝牙耳机在音质上的表现也越来越好。下面&#xff0c;我来给大家推荐几款四百内音质最好的无线耳机&#xff0c;一起来看看吧。 一、南卡小音舱…

射频功率放大器在液体超声声强的光电测量中的应用

实验名称&#xff1a;液体中超声声强的光电测量 研究方向&#xff1a;光电测量 测试目的&#xff1a; 声强是描述声场的基本物理量口&#xff0c;超声效应直接与声强有关。例如在工程技术领域&#xff0c;液体中的声场分布直接影响流场分布口&#xff0c;声强的大小影响着超声波…

腾讯云GPU云服务器、CVM云服务器、轻量应用服务器配置价格表

这就是腾讯云GPU云服务器、CVM云服务器、轻量应用服务器配置价格表&#xff0c;最近整理的。目前腾讯云服务器分为轻量应用服务器、CVM云服务器和GPU云服务器&#xff0c;首先介绍一下这三种服务器。 1、GPU 云服务器&#xff08;Cloud GPU Service&#xff0c;GPU&#xff09;…

主从模式、哨兵模式、集群模式(cluster)

主从模式、哨兵模式、集群模式&#xff08;cluster&#xff09; redis 实现高可用的方式分为 主从模式、哨兵模式、集群模式&#xff08;cluster&#xff09; 1. 主从模式&#xff08;又称为主从复制&#xff09; 表现为1个主节点&#xff0c;多个从节点&#xff0c;主节点负…

2023年Python选择题及答案解析【35道】

2023年Python练习题及答案解析1、在Python3中&#xff0c;运行结果为&#xff1a;2、在Python3中&#xff0c;字符串的变换结果为&#xff1a;3、在Python3中&#xff0c;下列程序运行结果为&#xff1a;4、在Python3中&#xff0c;下列程序结果为&#xff1a;5、a与b定义如下&…

【C++】基础篇

C基础篇什么是C命名空间命名空间的三种使用方式C的输入和输出缺省参数缺省参数分类函数重载引用引用的使用场景常引用指针和引用的区别auto关键字auto使用细则auto不能推导的场景基于范围的for循环范围for的使用条件指针空值nullptr什么是C 1982年&#xff0c;Bjarne Stroustr…

AutoGPT保姆级使用教程

1. 介绍Auto-GPT是一个基于ChatGPT的工具&#xff0c;他能帮你自动完成各种任务&#xff0c;比如写代码、写报告、做调研等等。使用它时&#xff0c;你只需要告诉他要扮演的角色和要实现的目标&#xff0c;然后他就会利用ChatGPT和谷歌搜索等工具&#xff0c;不断“思考”如何接…

目标检测【Object Detection】

文章目录基本概念两阶段目标检测算法R-CNNFast R-CNNFaster R-CNNFPNMask R-CNN一阶段目标检测算法SSDYOLOv1YOLOv2YOLOv3目标检测的常用数据集目标检测的标注工具基本概念 目标检测是计算机视觉中的一个重要问题&#xff0c;它的目的是从图像或视频序列中识别出特定的目标&am…

在window上安装python

在Windows上安装python 1.进入python官网https://www.python.org/ 下载配置环境,点击上方downloads,根据系统选择python环境下载(选择windows) 往下拉查找需要的版本并下载 下载后双击就可以安装python了 如何检验是否安装成功 通过【winr】调出【运行】弹窗&#xff0c;输…

数据安全评估体系建设

数据安全评估是指对重要数据、个人信息等数据资产的价值与权益、合规性、威胁、脆弱性、防护等进行分析和判断&#xff0c;以评估数据安全事件发生的概率和可能造成的损失&#xff0c;并采取相应的措施和建议。 数据安全评估的重要性和背景 1.国家法律法规下的合规需要 目前数…

面试-基本计算器 II

题目 给你一个字符串表达式 s &#xff0c;请你实现一个基本计算器来计算并返回它的值。 整数除法仅保留整数部分。 你可以假设给定的表达式总是有效的。所有中间结果将在 [-231, 231 - 1] 的范围内。 注意&#xff1a;不允许使用任何将字符串作为数学表达式计算的内置函数&…