Go slice切片使用教程,一次通關!

簡介

Go 中的 切片(slice) 是 Go 最強大、最常用的數據結構之一。它是對數組的輕量封裝,比數組更靈活,幾乎所有的集合處理都用切片來完成。

什么是切片(slice)

切片是一個擁有 長度(len)和容量(cap) 的 動態數組視圖。底層是一個數組,但可以動態擴容、共享數組。

var s []int // nil slice,len=0,cap=0
fmt.Println(s, len(s), cap(s)) // [] 0 0s := []int{} // 空切片,已初始化但無元素

切片的創建

使用字面量
s := []int{1, 2, 3}
從數組或切片切割而來
arr := [5]int{0, 1, 2, 3, 4}
s := arr[1:4] // 包含索引1~3:1, 2, 3
使用 make 創建
s := make([]int, 3)           // len=3, cap=3,默認值0
s := make([]int, 3, 5)        // len=3, cap=5
切片表達式
  • 簡單表達式:s[low:high],左閉右開區間。

  • 完整表達式:s[low:high:max],指定容量為 max-low,用于限制后續操作的容量。

切片的底層結構

type slice struct {ptr *T   // 底層數組指針len int  // 當前長度cap int  // 容量(底層數組的最大長度)
}

切片只是一個“視圖窗口”,多個切片可能共享同一數組。

切片的切割和操作

s := []int{10, 20, 30, 40, 50}
s1 := s[1:4]       // [20 30 40]
s2 := s[:3]        // [10 20 30]
s3 := s[2:]        // [30 40 50]
s4 := s[:]         // 全部復制

常見操作

刪除元素
s := []int{1, 2, 3, 4}
s = append(s[:2], s[3:]...) // 刪除索引2 → [1, 2, 4]
清空切片
s = s[:0]   // 長度置0,保留容量
s = nil     // 釋放底層數組
切片的遍歷

可以使用 for 循環或者 for...range 循環來遍歷切片。

package mainimport "fmt"func main() {slice := []int{1, 2, 3, 4, 5}// 使用 for 循環遍歷fmt.Println("Using for loop:")for i := 0; i < len(slice); i++ {fmt.Println(slice[i])}// 使用 for...range 循環遍歷fmt.Println("Using for...range loop:")for index, value := range slice {fmt.Printf("Index: %d, Value: %d\n", index, value)}
}

高級技巧

預分配容量:減少擴容次數,提升性能。
s := make([]int, 0, 1000) // 預分配容量
避免內存泄漏:截取大切片中的小部分后,復制到新切片以釋放原數組。
bigSlice := make([]int, 1000000)
smallPart := make([]int, 10)
copy(smallPart, bigSlice[:10])
字符串處理

字符串可視為 [] byte切片,但需注意中文字符需轉為 []rune

str := "你好"
runes := []rune(str)    // 正確處理中文字符

切片的容量擴展與底層數組共享

s := []int{1, 2, 3}
s1 := s[:2]   // [1 2]
s2 := append(s1, 99) // 可能修改 s 底層內容fmt.Println(s)

如果 append 后容量沒超出原數組,s1s 仍然共享底層數組。

append、copy 的用法

append 用于追加元素
s := []int{1, 2}
s = append(s, 3, 4)     // [1 2 3 4]
slice = append(slice, anotherSlice...) // 追加另一個切片(使用...展開)
copy 用于切片復制
a := []int{1, 2, 3}
b := make([]int, 2)
copy(b, a)   // 只復制前2個元素

函數傳參時的切片特性

切片本質是引用類型,傳參是復制切片結構體值,但底層數組是共享的。

func modify(s []int) {s[0] = 999
}
s := []int{1, 2, 3}
modify(s)
fmt.Println(s) // [999 2 3]

多維切片(二維數組)

matrix := [][]int{{1, 2, 3},{4, 5, 6},
}
matrix[1][2] = 9

多維切片是切片的切片,并不是嚴格的二維數組結構。

切片常見問題與陷阱

共享底層數組導致修改影響原切片
s := []int{1, 2, 3, 4}
s1 := s[:2]
s1[0] = 999
fmt.Println(s) // [999 2 3 4]
超過容量自動分配新數組
s := make([]int, 2, 3)
s = append(s, 4) // 使用原數組
s = append(s, 5) // 超過cap,創建新數組
判斷空切片

使用 len(s) == 0 而非 s == nil,因空切片可能非 nil

推薦使用方式總結

場景推薦寫法
初始化切片make([]T, len, cap) 或 []T{...}
安全擴容s = append(s, x...)
不修改原切片new := append([]T(nil), old...)
復制切片copy(dst, src)
清空切片s = s[:0] 或 var s []T

growslice 實現解析(簡化版)

func growslice(et *_type, old slice, cap int) slice {// 參數解釋:// et  : 元素類型指針// old : 原 slice 數據結構(包括 len, cap, ptr)// cap : 需要的新容量newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {if old.len < 1024 {newcap = doublecap} else {// 對于大 slice,增幅是 1.25 倍for newcap < cap {newcap += newcap / 4}}}// 分配新數組(unsafe.Pointer)newptr := mallocgc(et.size * uintptr(newcap), et, true)// 拷貝原數據到新內存memmove(newptr, old.array, et.size * uintptr(old.len))// 構造新的 slice 對象return slice{newptr, old.len, newcap}
}
slice 結構體
type slice struct {array unsafe.Pointerlen   intcap   int
}
分配和拷貝函數詳解
  • mallocgc:分配一塊 heap 內存(等價于 make([]T, cap))。

  • memmove:底層是匯編,類似 memcpy

  • et.size:每個元素的大小。

append 背后的操作
  • cap 足夠:原地追加,新 slice 與舊共享數組

  • cap 不夠:分配新數組、復制原數據、添加新元素

  • 多次 append:容量以倍數或 1.25 倍增長,最多增長到 2? 或更多

  • 返回值:一定是新 slice,即使底層沒變

  • 類型安全:編譯器會根據 T 類型生成對 growslice 的調用

growslice 的效率與陷阱
  • 復制開銷:每次擴容都需要拷貝舊數據(O(n) 復雜度);

  • 內存浪費:新數組會有空余空間(以倍數擴容);

  • 共享底層數組:沒擴容時,多個 slice 會共享底層數組,易出錯。

數組 vs 切片 GC 行為對比

特性數組([N]T)切片([]T)
是否固定大小是否固定大小
存儲位置值類型,復制時復制整個內容引用類型,復制的是頭結構
GC 行為如果被釋放,整體數組會被 GC如果原數組被引用,即使只用一部分,整個數組仍保留
是否共享底層內存不會共享可能多個切片共享底層數組
GC 是否會收回未使用部分若無引用,整體釋放若有切片引用,整個底層數組不會釋放
內存泄露風險
func getSlice() []byte {large := make([]byte, 1<<20) // 1MBreturn large[:100]           // 返回一個小切片,但引用了整個大數組
}

上面這段代碼雖然只返回了 100 個字節的切片,但實際上因為這個切片仍然引用了整個 1MB 的底層數組,GC 不會釋放整個數組,直到這個小切片本身不再被引用。

解決方法

func getCopiedSlice() []byte {large := make([]byte, 1<<20)result := make([]byte, 100)copy(result, large[:100])return result // 這樣只引用小數據
}
如何避免 GC 泄漏
場景建議做法
截取切片時只需部分數據使用 copy() 拷貝到新切片
數據生命周期短注意避免長生命周期切片引用大量數據
要釋放大數組確保沒有切片引用底層數組
想清空切片使用 s = nil 而不是 s = s[:0](后者不會釋放內存)

使用切片來模擬分頁

核心功能:

  • 模擬有很多行的表格數據

  • 支持分頁查看(指定頁碼、每頁條數)

  • 支持翻頁(上一頁、下一頁)

  • 支持跳轉頁數

示例效果(CLI)
$ go run main.go
[Page 1/10] Showing 10 of 100 rows:
1. Name: Alice       Age: 25
2. Name: Bob         Age: 30
...[n] Next Page | [p] Previous Page | [q] Quit | [g 5] Go to page 5
> 
項目結構
table-paginator/
├── main.go            // 啟動入口
├── paginator/
│   └── paginator.go   // 分頁邏輯
├── data/
│   └── mock.go        // 模擬表格數據
核心代碼框架預覽
  • data/mock.go
package datatype Person struct {Name stringAge  int
}func GenerateMockData(total int) []Person {people := make([]Person, total)for i := 0; i < total; i++ {people[i] = Person{Name: fmt.Sprintf("User%03d", i+1),Age:  20 + (i % 30),}}return people
}
  • paginator/paginator.go
package paginatorimport "fmt"type Page[T any] struct {Items      []TPageNo     intPageSize   intTotalItems int
}func Paginate[T any](data []T, pageNo, pageSize int) Page[T] {total := len(data)start := (pageNo - 1) * pageSizeend := start + pageSizeif start > total {start = total}if end > total {end = total}return Page[T]{Items:      data[start:end],PageNo:     pageNo,PageSize:   pageSize,TotalItems: total,}
}
  • main.go
package mainimport ("bufio""fmt""os""strings""table-paginator/data""table-paginator/paginator"
)func main() {people := data.GenerateMockData(100)pageSize := 10pageNo := 1reader := bufio.NewReader(os.Stdin)for {page := paginator.Paginate(people, pageNo, pageSize)fmt.Printf("\n[Page %d/%d] Showing %d of %d rows:\n",page.PageNo,(page.TotalItems+pageSize-1)/pageSize,len(page.Items),page.TotalItems)for i, p := range page.Items {fmt.Printf("%2d. Name: %-10s Age: %d\n", (page.PageNo-1)*pageSize+i+1, p.Name, p.Age)}fmt.Print("\n[n] Next Page | [p] Previous Page | [g 5] Go to page 5 | [q] Quit\n> ")input, _ := reader.ReadString('\n')input = strings.TrimSpace(input)switch {case input == "n":pageNo++case input == "p":if pageNo > 1 {pageNo--}case strings.HasPrefix(input, "g "):var newPage intfmt.Sscanf(input, "g %d", &newPage)if newPage >= 1 {pageNo = newPage}case input == "q":returndefault:fmt.Println("Invalid command")}}
}

Go slice切片跟Java的List和C#.NET的List異同

相同點(共性)
特性描述
動態增長都可以在運行時動態擴容,無需指定固定長度
支持下標訪問可以通過 [] 操作符訪問和修改元素
有長度和容量(或大小)有長度和容量(或大小)
內部基于數組實現內部基于數組實現
支持切片/子列表(部分)Go、Java(subList())、C#(GetRange())都可以實現
不同點(詳細對比)
特性Go sliceJava ListC# List<T>
所在語言GoJavaC# (.NET)
是否原生類型? 是語言內建類型? 是接口(通常用 ArrayList 等)? 是類
是否線程安全? 否? 否(Collections.synchronizedList 可封裝)? 否(要手動加鎖)
內部擴容機制默認 翻倍/1.25倍 擴容增長約 50%(ArrayList)每次翻倍擴容(默認實現)
內存管理自動垃圾回收自動垃圾回收自動垃圾回收
切片是否共享底層數組? 會共享? subList() 是新對象引用同數組? GetRange() 拷貝數據
底層可見性lencap 可查看只有 size()CountCapacity
可否使用 append()? 內置 append()? 使用 add()addAll()? 使用 Add()AddRange()
動態擴容機制

Go Slice

使用 append 追加元素時,若容量不足,按以下規則擴容:

  • 容量 < 1024:雙倍擴容
  • 容量 ≥ 1024:按 1.25 倍擴容,擴容后生成新底層數組,原數組可能被垃圾回收。

Java ArrayList

默認擴容為當前容量的 1.5 倍(如初始容量 10 → 15 → 22 → …)

C# List<T>

擴容時容量翻倍(如初始容量 4 → 8 → 16 → …)

常用操作對比
操作Go SliceJava ListC# List<T>
添加元素append(slice, element)add(element)Add(element)
刪除元素通過 append 拼接前后片段remove(index)remove(object)RemoveAt(index)Remove(obj)
截取子集s[start:end](共享底層數組)subList(from, to)GetRange(from, count)
容量預分配make([]T, len, cap)new ArrayList<>(initialCapacity)new List<T>(capacity)
拷貝copy(dst, src)(淺拷貝)new ArrayList<>(srcList)new List<T>(srcList)

切片典型的代碼示例

切片配合結構體(最常見)
  • 示例一
package mainimport "fmt"type User struct {ID   intName string
}func main() {users := []User{{ID: 1, Name: "Alice"},{ID: 2, Name: "Bob"},}for _, user := range users {fmt.Printf("ID: %d, Name: %s\n", user.ID, user.Name)}
}
  • 示例二
package mainimport ("fmt""sort"
)// 定義結構體
type Person struct {Name stringAge  int
}// 結構體方法(值接收者)
func (p Person) String() string {return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}// 結構體方法(指針接收者)
func (p *Person) Grow() {p.Age++
}func main() {// 創建結構體切片people := []Person{{"Alice", 25},{"Bob", 30},{"Charlie", 20},}// 遍歷切片(值傳遞)for _, p := range people {fmt.Println(p)}// 修改結構體(需使用指針切片)peoplePtr := []*Person{{"Alice", 25},{"Bob", 30},{"Charlie", 20},}for _, p := range peoplePtr {p.Grow() // 調用指針接收者方法}// 按年齡排序sort.Slice(people, func(i, j int) bool {return people[i].Age < people[j].Age})fmt.Println("Sorted:", people)// 動態添加元素people = append(people, Person{"David", 40})
}

關鍵點:

  • 使用指針切片([]*Person)避免結構體復制開銷

  • 結合 sort.Slice 實現自定義排序

  • 通過 append 動態添加元素

  • 示例三:模擬 ListAdd 方法

// 定義泛型結構體,包含切片字段
type Container[T any] struct {Elements []T
}// 添加元素方法(使用泛型接收器)
func (c *Container[T]) Add(element T) {c.Elements = append(c.Elements, element)
}// 使用示例
intContainer := Container[int]{Elements: []int{1, 2}}
intContainer.Add(3)  // Elements: [1,2,3]strContainer := Container[string]{Elements: []string{"a", "b"}}
strContainer.Add("c")  // Elements: [a,b,c]
切片配合接口(處理多種類型)
  • 示例一
package mainimport "fmt"type Shape interface {Area() float64
}type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }func (c Circle) Area() float64    { return 3.14 * c.Radius * c.Radius }
func (r Rectangle) Area() float64 { return r.Width * r.Height }func printAreas(shapes []Shape) {for _, s := range shapes {fmt.Printf("Area: %.2f\n", s.Area())}
}func main() {shapes := []Shape{Circle{Radius: 3},Rectangle{Width: 4, Height: 5},}printAreas(shapes)
}
  • 示例二
// 定義數值類型約束接口
type Numeric interface {int | float64
}// 泛型函數:計算切片元素和
func Sum[T Numeric](s []T) T {var total Tfor _, v := range s {total += v}return total
}// 使用示例
fmt.Println(Sum([]int{1, 2, 3}))       // 6
fmt.Println(Sum([]float64{1.1, 2.2}))  // 3.3

關鍵點:Numeric 接口通過類型集合約束切片元素類型,確保操作合法性

  • 示例三
// 定義數據訪問接口
type DataStore[T any] interface {GetAll() []TAdd(item T)
}// 實現接口的泛型結構體
type GenericSliceStore[T any] struct {data []T
}func (g *GenericSliceStore[T]) GetAll() []T {return g.data
}func (g *GenericSliceStore[T]) Add(item T) {g.data = append(g.data, item)
}// 使用示例
store := &GenericSliceStore[int]{data: []int{10}}
store.Add(20)
fmt.Println(store.GetAll())  // [10,20]

關鍵點:接口通過泛型類型參數定義方法,結構體實現接口時綁定具體類型

切片配合泛型(Go 1.18+)
  • 示例一
package mainimport "fmt"// 定義泛型函數,打印任何類型切片
func PrintSlice[T any](items []T) {for _, item := range items {fmt.Println(item)}
}func main() {ints := []int{1, 2, 3}strs := []string{"Go", "Rust", "Python"}PrintSlice[int](ints)PrintSlice[string](strs)
}
  • 示例二
package mainimport "fmt"// 泛型過濾函數(過濾滿足條件的元素)
func Filter[T any](slice []T, test func(T) bool) []T {result := make([]T, 0)for _, v := range slice {if test(v) {result = append(result, v)}}return result
}// 泛型映射函數(轉換元素類型)
func Map[T any, U any](slice []T, mapper func(T) U) []U {result := make([]U, len(slice))for i, v := range slice {result[i] = mapper(v)}return result
}// 泛型結構體(存儲切片)
type Repository[T any] struct {Data []T
}func (r *Repository[T]) Add(item T) {r.Data = append(r.Data, item)
}func main() {// 過濾整數切片numbers := []int{1, 2, 3, 4, 5}evenNumbers := Filter(numbers, func(n int) bool {return n%2 == 0})fmt.Println("Even numbers:", evenNumbers) // [2 4]// 轉換字符串切片為長度切片words := []string{"apple", "banana", "cherry"}lengths := Map(words, func(s string) int {return len(s)})fmt.Println("Word lengths:", lengths) // [5 6 6]// 使用泛型結構體repo := Repository[string]{Data: []string{"Go", "Rust"}}repo.Add("C++")fmt.Println("Repository:", repo.Data) // [Go Rust C++]
}

關鍵點:

  • 使用 T any 定義泛型類型參數

  • 泛型函數支持類型安全操作(Filter、Map

  • 泛型結構體(Repository[T])封裝切片操作

  • 示例三:模擬隊列

利用切片實現泛型隊列,支持動態類型存儲:

type Queue[T any] []T// 入隊方法
func (q *Queue[T]) Enqueue(item T) {*q = append(*q, item)
}// 出隊方法(返回泛型零值)
func (q *Queue[T]) Dequeue() T {if len(*q) == 0 {return *new(T) // 返回類型T的零值}item := (*q)[0]*q = (*q)[1:]return item
}// 使用示例
var q Queue[string]
q.Enqueue("first")
q.Enqueue("second")
fmt.Println(q.Dequeue())  // "first"

關鍵點:通過 any 類型約束實現多類型隊列,利用切片特性動態調整

  • 示例四
// 用戶訂單結構體
type UserOrder[T comparable] struct {UserID  TItems   []string  // 字符串切片字段
}// 泛型訂單管理器
type OrderManager[K comparable, V any] struct {Orders map[K][]V  // 鍵值對中值類型為切片
}// 添加訂單方法
func (m *OrderManager[K, V]) Add(key K, value V) {m.Orders[key] = append(m.Orders[key], value)
}// 使用示例
manager := OrderManager[int, string]{Orders: make(map[int][]string)}
manager.Add(1001, "itemA")
manager.Add(1001, "itemB")  // Orders[1001]: [itemA, itemB]

關鍵點:結合 map 和切片實現多層級泛型數據存儲,支持復雜業務場景

  • 示例五
// 切片去重(需comparable約束)
func Deduplicate[T comparable](s []T) []T {seen := make(map[T]bool)result := []T{}for _, v := range s {if !seen[v] {seen[v] = trueresult = append(result, v)}}return result
}// 使用示例
nums := []int{1, 2, 2, 3}
strs := []string{"a", "a", "b"}
fmt.Println(Deduplicate(nums)) // [1 2 3]
fmt.Println(Deduplicate(strs)) // [a b]
  • 示例六
func SliceToMap[T any, K comparable](s []T, keyFunc func(T) K) map[K]T {m := make(map[K]T)for _, item := range s {m[keyFunc(item)] = item}return m
}// 使用示例
userMap := SliceToMap(users, func(u User) int { return u.ID })
// 輸出map[1:{1 Alice} 2:{2 Bob} 3:{3 Charlie}]
切片 + 泛型 + 結構體(組合使用)
  • 示例一
package mainimport "fmt"type User struct {ID   intName string
}func FilterSlice[T any](items []T, filter func(T) bool) []T {var result []Tfor _, item := range items {if filter(item) {result = append(result, item)}}return result
}func main() {users := []User{{ID: 1, Name: "Alice"},{ID: 2, Name: "Bob"},{ID: 3, Name: "Eve"},}filtered := FilterSlice(users, func(u User) bool {return u.ID%2 == 1 // 只保留奇數 ID})for _, u := range filtered {fmt.Println(u)}
}
  • 示例二
package mainimport "fmt"// 定義接口
type IDisplay interface {Display() string
}// 實現接口的結構體
type Product struct {Name  stringPrice float64
}func (p Product) Display() string {return fmt.Sprintf("%s ($%.2f)", p.Name, p.Price)
}type User struct {Username stringEmail    string
}func (u User) Display() string {return fmt.Sprintf("%s <%s>", u.Username, u.Email)
}// 泛型容器(要求類型實現 IDisplay 接口)
type DisplayBox[T IDisplay] struct {Items []T
}func (b *DisplayBox[T]) Add(item T) {b.Items = append(b.Items, item)
}func (b *DisplayBox[T]) ShowAll() {for _, item := range b.Items {fmt.Println(item.Display())}
}func main() {// 創建容器并添加不同類型數據box := DisplayBox[IDisplay]{}box.Add(Product{"Laptop", 999.99})box.Add(User{"Alice", "alice@example.com"})box.ShowAll()// 輸出:// Laptop ($999.99)// Alice <alice@example.com>
}

關鍵點:

  • 結合接口約束(T IDisplay)實現類型安全

  • 泛型容器存儲接口類型切片

  • 統一調用接口方法(item.Display()


  • 示例三
type Identifiable interface {GetID() int
}type Product struct {ID    intName  stringPrice float64
}
func (p Product) GetID() int { return p.ID }// 通用查詢函數
func FindByID[T Identifiable](items []T, id int) (T, bool) {for _, item := range items {if item.GetID() == id {return item, true}}var zero Treturn zero, false
}// 使用示例
products := []Product{{1, "Laptop", 999.9}}
result, found := FindByID(products, 1)
  • 多級切片處理
type Matrix[T any] [][]Tfunc (m Matrix[T]) Flatten() []T {var result []Tfor _, row := range m {result = append(result, row...)}return result
}// 使用示例
intMatrix := Matrix[int]{{1,2}, {3,4}}
strMatrix := Matrix[string]{{"a","b"}, {"c"}}
fmt.Println(intMatrix.Flatten()) // [1 2 3 4]
fmt.Println(strMatrix.Flatten()) // [a b c]
切片 + 泛型接口 + 排序(高級組合)
package mainimport ("fmt""slices" // Go 1.21+ 官方 slices 工具包
)type Person struct {Name stringAge  int
}func main() {people := []Person{{"Alice", 25},{"Bob", 19},{"Eve", 31},}// 按 Age 排序slices.SortFunc(people, func(a, b Person) int {return a.Age - b.Age})fmt.Println(people)
}
總結
  • 切片 + 結構體:最常見的組合,適合表示表格、列表等數據結構

  • 切片 + 接口:支持多態,適合處理多種類型(如圖形、設備等)

  • 切片 + 泛型:類型安全、復用性強,適合通用算法/工具方法

  • 切片 + 泛型 + 結構體:結構化數據處理 + 高性能泛型,寫庫非常合適

通用分頁 + 泛型過濾 + 可視化表格

功能
  • 任意結構體類型的數據切片

  • 泛型過濾(支持傳入條件函數)

  • 分頁(頁碼 + 每頁大小)

  • Web UI 表格展示(用 Go Serve HTML)

目錄結構
slice-table-demo/
├── main.go
├── data.go       // 模擬數據和數據結構
├── pagination.go // 泛型分頁 + 過濾
├── templates/
│   └── index.html
代碼示例
  • data.go — 模擬數據定義
package maintype User struct {ID    intName  stringEmail stringAge   int
}func GetMockUsers() []User {users := make([]User, 100)for i := range users {users[i] = User{ID:    i + 1,Name:  "User_" + string('A'+(i%26)),Email: fmt.Sprintf("user%d@example.com", i+1),Age:   18 + (i % 30),}}return users
}
  • pagination.go — 泛型分頁與過濾
package mainfunc FilterSlice[T any](items []T, filter func(T) bool) []T {var result []Tfor _, item := range items {if filter(item) {result = append(result, item)}}return result
}func PaginateSlice[T any](items []T, page, pageSize int) []T {start := (page - 1) * pageSizeend := start + pageSizeif start >= len(items) {return []T{}}if end > len(items) {end = len(items)}return items[start:end]
}
  • templates/index.html — Web 表格模板(帶分頁)
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>User Table</title><style>table { border-collapse: collapse; width: 100%; }th, td { padding: 8px; border: 1px solid #ccc; text-align: left; }</style>
</head>
<body><h1>User List (Page {{.Page}})</h1><table><thead><tr><th>ID</th><th>Name</th><th>Email</th><th>Age</th></tr></thead><tbody>{{range .Users}}<tr><td>{{.ID}}</td><td>{{.Name}}</td><td>{{.Email}}</td><td>{{.Age}}</td></tr>{{end}}</tbody></table><p><a href="/?page={{.PrevPage}}">Prev</a> |<a href="/?page={{.NextPage}}">Next</a></p>
</body>
</html>
  • main.go — 啟動 HTTP Server
package mainimport ("fmt""html/template""net/http""strconv"
)type PageData struct {Users    []UserPage     intPrevPage intNextPage int
}func main() {tmpl := template.Must(template.ParseFiles("templates/index.html"))http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {pageSize := 10pageStr := r.URL.Query().Get("page")page, _ := strconv.Atoi(pageStr)if page <= 0 {page = 1}users := GetMockUsers()// 過濾條件:例如只顯示年齡 > 25 的用戶filtered := FilterSlice(users, func(u User) bool {return u.Age > 25})pagedUsers := PaginateSlice(filtered, page, pageSize)data := PageData{Users:    pagedUsers,Page:     page,PrevPage: max(1, page-1),NextPage: page + 1,}tmpl.Execute(w, data)})fmt.Println("Listening on http://localhost:8080")http.ListenAndServe(":8080", nil)
}func max(a, b int) int {if a > b {return a}return b
}
運行
go run main.go# 瀏覽器訪問:http://localhost:8080
總結與選型建議
場景推薦方案優勢
固定結構集合結構體切片直觀、類型安全
跨類型數據操作泛型切片+接口約束代碼復用率高
高性能批處理預分配切片+數組重用減少GC壓力
復雜數據結構嵌套泛型結構體靈活擴展多維數據
類型轉換優化泛型轉換工具函數避免冗余代碼

slices 包處理切片(slice)的常見操作

常用函數
  • slices.Sort:對切片排序(需要元素可比較)

  • slices.BinarySearch:二分查找(已排序切片)

  • slices.Index:查找第一個等于給定值的索引

  • slices.Contains:判斷切片中是否包含某值

  • slices.Equal:判斷兩個切片是否相等(順序和值都一樣)

  • slices.Clone:拷貝一個切片

  • slices.Compact:移除相鄰重復值(適合排好序的切片)

  • slices.Delete:刪除指定索引范圍的元素

  • slices.Insert:插入元素到切片中

  • slices.Reverse:反轉切片

使用 slices 包示例
package mainimport ("fmt""slices"
)func main() {ages := []int{32, 18, 45, 21, 18}// 排序slices.Sort(ages)fmt.Println("Sorted:", ages)// 查找idx := slices.Index(ages, 45)fmt.Println("Index of 45:", idx)// 是否包含fmt.Println("Contains 21:", slices.Contains(ages, 21))// 去重(只去相鄰重復)dedup := slices.Clone([]int{1, 1, 2, 2, 2, 3})slices.Compact(dedup)fmt.Println("Compact:", dedup)// 插入inserted := slices.Insert([]int{1, 2, 4}, 2, 3)fmt.Println("After insert:", inserted)// 刪除deleted := slices.Delete(inserted, 1, 3) // 刪除第1到3個元素fmt.Println("After delete:", deleted)
}

多個 goroutine 并發修改切片

使用 sync.Mutex 加鎖 來保證并發安全
package mainimport ("fmt""sync"
)func main() {var (slice []intmu    sync.Mutexwg    sync.WaitGroup)for i := 0; i < 10; i++ {wg.Add(1)go func(val int) {defer wg.Done()mu.Lock()slice = append(slice, val)mu.Unlock()}(i)}wg.Wait()fmt.Println("Final slice:", slice)
}
  • 每個 goroutineappend 前先加鎖,操作完成后釋放鎖。

  • sync.Mutex 保證了 append 的原子性,避免了競態。

使用 channel 做串行通信,避免共享內存
package mainimport ("fmt""sync"
)func main() {slice := []int{}ch := make(chan int)var wg sync.WaitGroup// 啟動一個 goroutine 專門負責寫入切片go func() {for val := range ch {slice = append(slice, val)}}()// 啟動多個生產者 goroutinefor i := 0; i < 10; i++ {wg.Add(1)go func(val int) {defer wg.Done()ch <- val}(i)}wg.Wait()close(ch) // 所有寫入完成后關閉通道fmt.Println("Final slice:", slice)
}
  • 所有寫入通過 channel 串行進行,避免了加鎖。

  • 這是 Go 推薦的方式:不要通過共享內存來通信,而應該通過通信來共享內存。

對比總結
方法優點缺點
sync.Mutex簡單高效,適合低沖突場景容易死鎖,復雜操作難管理
channel更符合 Go 哲學,邏輯清晰需要一個寫入協程,性能略低

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/78237.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78237.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/78237.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

nodejs的包管理工具介紹,npm的介紹和安裝,npm的初始化包 ,搜索包,下載安裝包

nodejs的包管理工具介紹&#xff0c;npm的介紹和安裝&#xff0c;npm的初始化包 &#xff0c;搜索包&#xff0c;下載安裝包 &#x1f9f0; 一、Node.js 的包管理工具有哪些&#xff1f; 工具簡介是否默認特點npmNode.js 官方的包管理工具&#xff08;Node Package Manager&am…

FPGA設計 時空變換

1、時空變換基本概念 1.1、時空概念簡介 時鐘速度決定完成任務需要的時間&#xff0c;規模的大小決定完成任務所需要的空間&#xff08;資源&#xff09;&#xff0c;因此速度和規模就是FPGA中時間和空間的體現。 如果要提高FPGA的時鐘&#xff0c;每個clk內組合邏輯所能做的事…

增加首屏圖片

增加首屏圖片&#xff08;bg.jpg&#xff09; web-mobile類型打包 //index.html腳本 <div id"myDiv_1111"style"background: url(./bg.jpg) 50% 50%/ 100% auto no-repeat ; width:100%;height:100%;position:absolute;"></div> //游戲內腳本…

貪心算法~~

目錄 一、理論基礎 二、題目練習 &#xff08;1&#xff09;455. 分發餅干 &#xff08;2&#xff09;53. 最大子數組和 - 力扣 &#xff08;3&#xff09;122. 買賣股票的最佳時機 II - 力扣&#xff08;LeetCode&#xff09; &#xff08;4&#xff09;860. 檸檬水找零…

形象解釋 HTTP 的四種常見請求方式及其中的區別聯系

HTTP 的常見請求方式常見的有四種&#xff1a;GET、POST、PUT、DELETE&#xff0c;它們各自的功能不一樣。 &#x1f35c; 場景比喻&#xff1a;HTTP 請求像“去餐廳點菜” 請求方式行為餐廳比喻說明GET獲取數據看菜單/問服務員&#xff1a;你們有什么菜&#xff1f;不帶食材、…

string的基本使用

string的模擬實現 string的基本用法string的遍歷&#xff08;三種方式&#xff09;&#xff1a;關于auto&#xff08;自動推導&#xff09;:范圍for: 迭代器普通迭代器(可讀可改&#xff09;const迭代器&#xff08;可讀不可改&#xff09; string細小知識點string的常見接口引…

kubernetes》》k8s》》證書有效期

cd /etc/kubernetes/pki openssl x509 -in apiserver.crt -text -noount通常&#xff0c;Kubernetes的證書是由kubeadm生成的&#xff0c;所以可能需要修改kubeadm的源碼或者配置 登錄Master節點 》》》默認延續1年 # 查看證書 檢查證書有效期 # 該命令顯示 /etc/kubernetes…

LangChain LCEL表達式語言簡介

LangChain表達式語言&#xff08;LCEL&#xff09;是專為構建AI應用鏈設計的聲明式編程框架&#xff0c;通過管道符|實現組件無縫銜接&#xff0c;支持流式處理、異步調用等生產級特性。其核心優勢在于零代碼改動實現原型到生產的過渡&#xff0c;同時保持代碼簡潔性和可維護性…

【計算機視覺】CV實踐項目- 基于PaddleSeg的遙感建筑變化檢測全解析:從U-Net 3+原理到工程實踐

基于PaddleSeg的遙感建筑變化檢測全解析&#xff1a;從U-Net 3原理到工程實踐 技術背景與項目意義傳統方法的局限性深度學習的優勢 核心技術與算法原理U-Net 3架構創新全尺度跳躍連接深度監督機制 變化檢測技術路線 實戰指南&#xff1a;從環境搭建到模型部署環境配置數據準備與…

萬字長文 | Apache SeaTunnel 分離集群模式部署 K8s 集群實踐

文章作者&#xff1a;雷寶鑫 整理排版&#xff1a;白鯨開源 曾輝 Apache SeaTunnel官網鏈接: https://seatunnel.apache.org/ Apache SeaTunnel(以下簡稱SeaTunnel&#xff09;是一款新一代高性能、分布式的數據集成同步工具&#xff0c;正受到業界廣泛關注和應用。SeaTunnel支…

深入解析YOLO v1:實時目標檢測的開山之作

目錄 YOLO v1 算法詳解? ?1. 核心思想? ?2. 算法優勢? ?3. 網絡結構&#xff08;Unified Detection&#xff09;?? ?4. 關鍵創新? ?5. 結構示意圖&#xff08;Fig1&#xff09;? Confidence Score 的計算? 類別概率與 Bounding Box 的關系? 后處理&…

信令與流程分析

WebRTC是h5支持的重要特征之一&#xff0c;有了它&#xff0c;不再需要借助音視頻相關的客戶端&#xff0c;直接通過瀏覽器的Web頁面就可以實現音視頻聊天功能。 WebRTC項目是開源的&#xff0c;我們可以借助WebRTC&#xff0c;構建自己的音視頻聊緹娜功能。無論是前端JS的Web…

BIOS主板(非UEFI)安裝fedora42的方法

BIOS主板(非UEFI)安裝fedora42的方法 現實困難&#xff1a;將Fedora-Workstation-Live-42-1.1.x86_64.iso寫入U盤制作成可啟動U盤啟動fedora42&#xff0c;按照向導將fedora42安裝到真機的sda7分區中得到報錯如下內容&#xff1a; /boot/efi 必需的 /boot/efi必須位于格式化為e…

安卓 Compose 相對傳統 View 的優勢

安卓 Compose 相對傳統 View 的優勢 文章目錄 安卓 Compose 相對傳統 View 的優勢1. 引言2. 核心概念&#xff1a;Compose的革新性設計2.1 Jetpack Compose2.2 傳統安卓View系統 3. 開發體驗&#xff1a;Compose大幅提升效率3.1 使用Jetpack Compose構建UI3.2 使用傳統View系統…

SIEMENS PLC 程序 GRAPH 程序解讀 車型入庫

1、程序載圖1 2、程序截圖2 3、程序解釋 這是一個基于西門子 GRAPH 編程的車型 1 入庫順序控制流程圖&#xff0c;通過狀態機結構&#xff08;狀態框 S 與轉移條件 T&#xff09;描述完整工作流程&#xff0c;具體如下&#xff1a; 整體流程概述 初始化&#xff1a;從 S1&am…

VuePress可以做什么?

VuePress 可以做什么 VuePress 是一個基于 Vue.js 的靜態站點生成器,專注于文檔和內容展示。它結合了 Markdown 的簡潔性和 Vue 的靈活性,適合多種場景的開發需求。以下是 VuePress 的主要用途和功能: 1. 技術文檔網站 VuePress 最初是為編寫 Vue.js 官方文檔而設計的,因…

架構-系統可靠性分析與設計

一、可靠性相關基本概念 1. 可靠性與可用性 可靠性&#xff1a;軟件系統在遇到錯誤、意外操作或系統故障時&#xff0c;仍能維持自身功能特性的能力。 舉例&#xff1a;手機銀行APP在用戶誤操作&#xff08;如快速點擊多次轉賬&#xff09;時&#xff0c;仍能正確處理交易并避…

再談String

1、字符串常量池 1.1 創建對象的思考 下面是兩種創建字符串對象的代碼 public static void main1(String[] args) {String s1 "hello";String s2 "hello";System.out.println(s1 s2);//trueString s3 new String("hello");String s4 new …

《深入淺出ProtoBuf:從環境搭建到高效數據序列化》?

ProtoBuf詳解 1、初識ProtoBuf2、安裝ProtoBuf2.1、ProtoBuf在Windows下的安裝2.2、ProtoBuf在Linux下的安裝 3、快速上手——通訊錄V1.03.1、步驟1&#xff1a;創建.proto文件3.2、步驟2&#xff1a;編譯contacts.proto文件&#xff0c;生成C文件3.3、步驟3&#xff1a;序列化…

基于PHP+Uniapp的互聯網醫院源碼:電子處方功能落地方案

隨著“互聯網醫療”政策紅利持續釋放&#xff0c;互聯網醫院已成為推動醫療數字化轉型的重要方向。在這一趨勢下&#xff0c;電子處方功能模塊作為核心環節&#xff0c;不僅直接關系到線上問診閉環的實現&#xff0c;也成為系統開發中技術難度較高、業務邏輯最為復雜的一部分。…