Golang實現set

来源:https://www.cnblogs.com/amos01/archive/2022/08/15/16586250.html
-Advertisement-
Play Games

Python爬蟲之xpath語法及案例使用 鋼鐵俠的知識庫 2022.08.15 我們在寫Python爬蟲時,經常需要對網頁提取信息,如果用傳統正則表達去寫會增加很多工作量,此時需要一種對數據解析的方法,也就是本章要介紹的Xpath表達式。 Xpath是什麼 XPath,全稱 XML Path La ...


背景

Golang語言本身未實現set,但是實現了map

golang的map是一種無序的鍵值對的集合,其中鍵是唯一的

而set是鍵的不重覆的集合,因此可以用map來實現set

Empty

由於map是key-value集合,如果使用map來實現set,則不需要關註value的具體類型和值

struct{}是具有零個元素的struct,struct{}的大小為0,不占用空間,因此十分適合作為value使用

type Empty struct{}

Int64HashSet

Golang是靜態強類型語言,對於int8、uint8、int64、uint64、 string基礎數據類型的set,均需要實現類似的代碼

定義

type Int8HashSet map[int8]Empty
type UintHashSet map[uint8]Empty 
type Int64HashSet map[int64]Empty 
type Uint64HashSet map[uint64]Empty 
type Int64HashSet map[string]Empty  

以int64為例,實現set的基本操作

初始化

func NewInt64HashSet(cap ...int) Int64HashSet {
	var set Int64HashSet
	if len(cap) == 0 {
		set = make(Int64HashSet)
	} else {
		set = make(Int64HashSet, cap[0])
	}
	return set
}

插入

func (set Int64HashSet) Insert(items ...int64) {
	for _, item := range items {
		set[item] = Empty{}
	}
}

刪除

func (set Int64HashSet) Delete(items ...int64) {
	for _, item := range items {
		delete(set, item)
	}
}

列表

func (set Int64HashSet) List() []int64 {
	list := make([]int64, 0, len(set))
	for item := range set {
		list = append(list, item)
	}
	return list
}

弊端

採用上面的方法實現,會充斥著大量重覆代碼,對於其它類型如int8,uint8,string等類型,需要單獨實現,儘管邏輯基本一致。

在Go 1.18版本之前,我們可以使用反射來避免這個問題,

使用反射在運行時推斷具體的類型,雖然有性能上的損耗,但是單次納秒級別的操作,基本可以忽略不計。

HashSet

interface{}是沒有方法的空介面,所有類型都實現了空介面

通過反射可以從interface獲取對象的值和類型

定義

type HashSet map[interface{}]Empty

初始化

func NewHashSet(cap ...int) HashSet {
	var set HashSet
	if len(cap) == 0 {
		set = make(HashSet)
	} else {
		set = make(HashSet, cap[0])
	}
	return set
}

插入

func (set HashSet) Insert(items ...interface{}) {
	for _, item := range items {
		set[item] = Empty{}
	}
}

刪除

func (set HashSet) Delete(items ...interface{}) {
	for _, item := range items {
		delete(set, item)
	}
}

列表

// 通過反射獲取到具體的類型
// 可以將int64替換為其它類型,如uint8, string等
func (set HashSet) ListInt64() []int64 {
	list := make([]int64, 0, len(set))
	for item := range set {
		if val, ok := item.(int64); ok {
			list = append(list, val)
		}
	}
	return list
}

func (set HashSet) ListString() []string {
	list := make([]string, 0, len(set))
	for item := range set {
		if val, ok := item.(string); ok {
			list = append(list, val)
		}
	}
	return list
}

GenericHashSet

反射在編譯時缺少類型檢查,比如對於同一個set,先後插入int類型和string類型數據,在編譯和運行階段均不會報錯。

hash := NewHashSet(8)
// 插入int類型
hash.Insert(111)
// 插入string類型
hash.Insert("string")

使用反射在一定程度上避免了大量的重覆代碼,但是將set轉換為slice還是會存在重覆的相似邏輯的代碼

並且需要在運行時獲取/判斷對象的類型和值,存在一定的性能損耗

在Go 1.18版本提供了範型(Generics)的支持,

範型可以在編譯期間進行類型檢查和類型推斷,相對於反射機制而言,性能有所提升

定義

type GenericHashSet[T comparable] map[T]Empty

初始化

func NewGenericHashSet[T comparable](cap ...int) *GenericHashSet[T] {
	var set GenericHashSet[T]
	if len(cap) == 0 {
		set = make(GenericHashSet[T])
	} else {
		set = make(GenericHashSet[T], cap[0])
	}
	return &set
}

插入

func (set *GenericHashSet[T]) Insert(items ...T) {
	for _, item := range items {
		(*set)[item] = Empty{}
	}
}

刪除

func (set *GenericHashSet[T]) Delete(items ...T) {
	for _, item := range items {
		delete(*set, item)
	}
}

列表

func (set *GenericHashSet[T]) List() []T {
	list := make([]T, 0, len(*set))
	for item := range *set {
		list = append(list, item)
	}
	return list
}

性能對比

插入操作測試代碼

func BenchmarkInt64HashSetInsert(b *testing.B) {
	intHashSet := NewInt64HashSet()
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < b.N; i++ {
		intHashSet.Insert(rand.Int63())
	}
}

func BenchmarkGenericHashSetInsert(b *testing.B) {
	gHashSet := NewGenericHashSet[int64]()
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < b.N; i++ {
		gHashSet.Insert(rand.Int63())
	}
}

func BenchmarkHashSetInsert(b *testing.B) {
	hashSet := NewHashSet()
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < b.N; i++ {
		hashSet.Insert(rand.Int63())
	}
}

 插入操作測試結果

zbwdeAir:set zbw$ go test -bench="BenchmarkInt64HashSetInsert|BenchmarkGenericHashSetInsert|BenchmarkHashSetInsert" -benchmem
goos: darwin
goarch: arm64
pkg: set/set
BenchmarkInt64HashSetInsert-8           10051916               119.2 ns/op            40 B/op          0 allocs/op
BenchmarkGenericHashSetInsert-8         13957741               123.7 ns/op            57 B/op          0 allocs/op
BenchmarkHashSetInsert-8                 6526810               188.9 ns/op            63 B/op          1 allocs/op
PASS
ok      set/set 4.897s

可以看出來,Int64HashSet性能最優,GenericHashSet次之,HashSet性能最差。

從實際使用角度看

對於Go < 1.18版本,使用HashSet即可。如果追求性能的極致,不介意大量重覆代碼,那還是使用Int64HashSet

對於單次操作的時間在ns級別,對於大部分業務場景,反射帶來的性能損耗基本可以忽略,性能的瓶頸並不在這裡。

對於Go >= 1.18版本,可以使用GenericHashSet

其它

如果需要實現有序set,則需要鏈表輔助實現

詳細代碼,github

如果你覺得還可以,點一下Star

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Android 知識體系 一、平臺架構 Google Android 平臺架構 Google Android 架構 Android 是一個針對多種不同設備類型打造的開放源代碼軟體堆棧。Android 的主要目的是為運營商、OEM 和開發者打造一個開放的軟體平臺,使他們能夠將創新理念變為現實,並推出能 ...
  • 原文:Kotlin學習快速入門(10)—— 重載運算符使用 - Stars-One的雜貨小窩 Kotlin中提供了基礎的運算符,但是只是針對基礎的數據類型,如Int,Double等 如果我們想讓兩個對象可以相加的功能,這個時候可以使用重載運算符的功能來實現 介紹 首先,先介紹下什麼是運算符,如以下代 ...
  • 區別一:存儲數據大小不同 1.cookie的存儲數據大小在不能超過4kb,每個頁面最多存儲20個cookie 2.localStorage能達到10mb,sessionStorage能達到5mb,雖然容量比cookie大,但是localStorage是同步執行,太大會影響渲染進度 區別二:相容性 1 ...
  • 本文是深入淺出 ahooks 源碼系列文章的第六篇,該系列已整理成文檔-地址。覺得還不錯,給個 star 支持一下哈,Thanks。 本文已收錄到個人博客中,歡迎關註~ 背景 大家在使用 useEffect 的時候,假如回調函數中使用 async...await... 的時候,會報錯如下。 看報錯, ...
  • 最近開發一款導航的項目需要行駛方向,這裡一般是gps會給我返回航向的,但是公司老系統的資料庫沒有這個數據,就只能自己計算咯 getAngle(lng_a,lat_a, lng_b, lat_b){ var a = (90 - lat_b) * Math.PI / 180; var b = (90 - ...
  • 一.簡介: 本文將完成一個真實業務中的設備上報數據的一個例子,完整的展示後臺服務接收到設備上報的數據後,將數據添加到時序資料庫,並且將數據查詢出來的一個例子。本文所有代碼已經上傳GitHub:https://github.com/Tom-shushu/work-study 下的 iotdb-demo ...
  • 文件的創建: package file; import java.io.File; import java.io.IOException; /* create:創建 new:新 file:文件 使用File新建一個文件 / public class CreateNewFileDemo { publi ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...