From 69855ce0568403fe1892b6bedee9959a0d1387bd Mon Sep 17 00:00:00 2001 From: xinghe903 Date: Sat, 28 Sep 2024 11:31:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0copy=20on=20write=20l?= =?UTF-8?q?ist=E5=AE=9E=E7=8E=B0=20(#260)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 添加copy on write list实现 * fix: 优化删除逻辑 --- list/copy_on_write_array_list.go | 148 ++++++++++ list/copy_on_write_array_list_test.go | 394 ++++++++++++++++++++++++++ 2 files changed, 542 insertions(+) create mode 100644 list/copy_on_write_array_list.go create mode 100644 list/copy_on_write_array_list_test.go diff --git a/list/copy_on_write_array_list.go b/list/copy_on_write_array_list.go new file mode 100644 index 0000000..c5f6e79 --- /dev/null +++ b/list/copy_on_write_array_list.go @@ -0,0 +1,148 @@ +// Copyright 2021 ecodeclub +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package list + +import ( + "sync" + + "github.com/ecodeclub/ekit/internal/errs" + "github.com/ecodeclub/ekit/internal/slice" +) + +var ( + _ List[any] = &CopyOnWriteArrayList[any]{} +) + +// CopyOnWriteArrayList 基于切片的简单封装 写时加锁,读不加锁,适合于读多写少场景 +type CopyOnWriteArrayList[T any] struct { + vals []T + mutex *sync.Mutex +} + +// NewCopyOnWriteArrayList +func NewCopyOnWriteArrayList[T any]() *CopyOnWriteArrayList[T] { + m := &sync.Mutex{} + return &CopyOnWriteArrayList[T]{ + vals: make([]T, 0), + mutex: m, + } +} + +// NewCopyOnWriteArrayListOf 直接使用 ts,会执行复制 +func NewCopyOnWriteArrayListOf[T any](ts []T) *CopyOnWriteArrayList[T] { + items := make([]T, len(ts)) + copy(items, ts) + m := &sync.Mutex{} + return &CopyOnWriteArrayList[T]{ + vals: items, + mutex: m, + } +} + +func (a *CopyOnWriteArrayList[T]) Get(index int) (t T, e error) { + l := a.Len() + if index < 0 || index >= l { + return t, errs.NewErrIndexOutOfRange(l, index) + } + return a.vals[index], e +} + +// Append 往CopyOnWriteArrayList里追加数据 +func (a *CopyOnWriteArrayList[T]) Append(ts ...T) error { + a.mutex.Lock() + defer a.mutex.Unlock() + n := len(a.vals) + newItems := make([]T, n, n+len(ts)) + copy(newItems, a.vals) + newItems = append(newItems, ts...) + a.vals = newItems + return nil +} + +// Add 在CopyOnWriteArrayList下标为index的位置插入一个元素 +// 当index等于CopyOnWriteArrayList长度等同于append +func (a *CopyOnWriteArrayList[T]) Add(index int, t T) (err error) { + a.mutex.Lock() + defer a.mutex.Unlock() + n := len(a.vals) + newItems := make([]T, n, n+1) + copy(newItems, a.vals) + newItems, err = slice.Add(newItems, t, index) + a.vals = newItems + return +} + +// Set 设置CopyOnWriteArrayList里index位置的值为t +func (a *CopyOnWriteArrayList[T]) Set(index int, t T) error { + a.mutex.Lock() + defer a.mutex.Unlock() + n := len(a.vals) + if index >= n || index < 0 { + return errs.NewErrIndexOutOfRange(n, index) + } + newItems := make([]T, n) + copy(newItems, a.vals) + newItems[index] = t + a.vals = newItems + return nil +} + +// 这里不涉及缩容,每次都是当前内容长度申请的数组容量 +func (a *CopyOnWriteArrayList[T]) Delete(index int) (T, error) { + a.mutex.Lock() + defer a.mutex.Unlock() + var ret T + n := len(a.vals) + if index >= n || index < 0 { + return ret, errs.NewErrIndexOutOfRange(n, index) + } + newItems := make([]T, len(a.vals)-1) + item := 0 + for i, v := range a.vals { + if i == index { + ret = v + continue + } + newItems[item] = v + item++ + } + a.vals = newItems + return ret, nil +} + +func (a *CopyOnWriteArrayList[T]) Len() int { + return len(a.vals) +} + +func (a *CopyOnWriteArrayList[T]) Cap() int { + return cap(a.vals) +} + +func (a *CopyOnWriteArrayList[T]) Range(fn func(index int, t T) error) error { + for key, value := range a.vals { + e := fn(key, value) + if e != nil { + return e + } + } + return nil +} + +func (a *CopyOnWriteArrayList[T]) AsSlice() []T { + a.mutex.Lock() + defer a.mutex.Unlock() + res := make([]T, len(a.vals)) + copy(res, a.vals) + return res +} diff --git a/list/copy_on_write_array_list_test.go b/list/copy_on_write_array_list_test.go new file mode 100644 index 0000000..0f99b23 --- /dev/null +++ b/list/copy_on_write_array_list_test.go @@ -0,0 +1,394 @@ +// Copyright 2021 ecodeclub +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package list + +import ( + "errors" + "fmt" + "testing" + + "github.com/ecodeclub/ekit/internal/errs" + + "github.com/stretchr/testify/assert" +) + +func TestCopyOnWriteArrayList_Add(t *testing.T) { + testCases := []struct { + name string + list *CopyOnWriteArrayList[int] + index int + newVal int + wantSlice []int + wantErr error + }{ + { + name: "add num to index left", + list: NewCopyOnWriteArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 0, + wantSlice: []int{100, 1, 2, 3}, + }, + { + name: "add num to index right", + list: NewCopyOnWriteArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 3, + wantSlice: []int{1, 2, 3, 100}, + }, + { + name: "add num to index mid", + list: NewCopyOnWriteArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 1, + wantSlice: []int{1, 100, 2, 3}, + }, + { + name: "add num to index -1", + list: NewCopyOnWriteArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: -1, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, -1), + }, + { + name: "add num to index OutOfRange", + list: NewCopyOnWriteArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 4, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 4), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Add(tc.index, tc.newVal) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + }) + } +} + +func TestCopyOnWriteArrayList_Cap(t *testing.T) { + testCases := []struct { + name string + expectCap int + list *CopyOnWriteArrayList[int] + }{ + { + name: "与实际容量相等", + expectCap: 5, + list: NewCopyOnWriteArrayListOf(make([]int, 5)), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := testCase.list.Cap() + assert.Equal(t, testCase.expectCap, actual) + }) + } +} + +func BenchmarkCopyOnWriteArrayList_Cap(b *testing.B) { + list := NewCopyOnWriteArrayListOf(make([]int, 0)) + + b.Run("Cap", func(b *testing.B) { + for i := 0; i < b.N; i++ { + list.Cap() + } + }) + + b.Run("Runtime cap", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = cap(list.vals) + } + }) +} + +func TestCopyOnWriteArrayList_Append(t *testing.T) { + testCases := []struct { + name string + list *CopyOnWriteArrayList[int] + newVal []int + wantSlice []int + }{ + { + name: "append non-empty values to non-empty list", + list: NewCopyOnWriteArrayListOf[int]([]int{123}), + newVal: []int{234, 456}, + wantSlice: []int{123, 234, 456}, + }, + { + name: "append empty values to non-empty list", + list: NewCopyOnWriteArrayListOf[int]([]int{123}), + newVal: []int{}, + wantSlice: []int{123}, + }, + { + name: "append nil to non-empty list", + list: NewCopyOnWriteArrayListOf[int]([]int{123}), + newVal: nil, + wantSlice: []int{123}, + }, + { + name: "append non-empty values to empty list", + list: NewCopyOnWriteArrayListOf[int]([]int{}), + newVal: []int{234, 456}, + wantSlice: []int{234, 456}, + }, + { + name: "append empty values to empty list", + list: NewCopyOnWriteArrayListOf[int]([]int{}), + newVal: []int{}, + wantSlice: []int{}, + }, + { + name: "append nil to empty list", + list: NewCopyOnWriteArrayListOf[int]([]int{}), + newVal: nil, + wantSlice: []int{}, + }, + { + name: "append non-empty values to nil list", + list: NewCopyOnWriteArrayListOf[int](nil), + newVal: []int{234, 456}, + wantSlice: []int{234, 456}, + }, + { + name: "append empty values to nil list", + list: NewCopyOnWriteArrayListOf[int](nil), + newVal: []int{}, + wantSlice: []int{}, + }, + { + name: "append nil to nil list", + list: NewCopyOnWriteArrayListOf[int](nil), + newVal: nil, + wantSlice: []int{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Append(tc.newVal...) + if err != nil { + return + } + + assert.Equal(t, tc.wantSlice, tc.list.AsSlice()) + }) + } +} + +func TestCopyOnWriteArrayList_Delete(t *testing.T) { + testCases := []struct { + name string + list *CopyOnWriteArrayList[int] + index int + wantSlice []int + wantVal int + wantErr error + }{ + { + name: "deleted", + list: NewCopyOnWriteArrayListOf([]int{123, 124, 125}), + index: 1, + wantSlice: []int{123, 125}, + wantVal: 124, + }, + { + name: "index out of range", + list: NewCopyOnWriteArrayListOf([]int{123, 100}), + index: 12, + wantErr: errs.NewErrIndexOutOfRange(2, 12), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Delete(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + assert.Equal(t, tc.wantVal, val) + }) + } +} + +func TestCopyOnWriteArrayList_Len(t *testing.T) { + testCases := []struct { + name string + expectLen int + list *CopyOnWriteArrayList[int] + }{ + { + name: "与实际元素数相等", + expectLen: 5, + list: NewCopyOnWriteArrayListOf(make([]int, 5)), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := testCase.list.Cap() + assert.Equal(t, testCase.expectLen, actual) + }) + } +} + +func TestCopyOnWriteArrayList_Get(t *testing.T) { + testCases := []struct { + name string + list *CopyOnWriteArrayList[int] + index int + wantVal int + wantErr error + }{ + { + name: "index 0", + list: NewCopyOnWriteArrayListOf[int]([]int{123, 100}), + index: 0, + wantVal: 123, + }, + { + name: "index 2", + list: NewCopyOnWriteArrayListOf[int]([]int{123, 100}), + index: 2, + wantVal: 0, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, 2), + }, + { + name: "index -1", + list: NewCopyOnWriteArrayListOf[int]([]int{123, 100}), + index: -1, + wantVal: 0, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, -1), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Get(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantVal, val) + }) + } +} +func TestCopyOnWriteArrayList_Range(t *testing.T) { + testCases := []struct { + name string + list *CopyOnWriteArrayList[int] + index int + wantVal int + wantErr error + }{ + { + name: "计算全部元素的和", + list: NewCopyOnWriteArrayListOf([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + wantVal: 55, + wantErr: nil, + }, + { + name: "测试中断", + list: NewCopyOnWriteArrayListOf([]int{1, 2, 3, 4, -5, 6, 7, 8, -9, 10}), + wantVal: 41, + wantErr: errors.New("index 4 is error"), + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := 0 + err := tc.list.Range(func(index int, num int) error { + if num < 0 { + return fmt.Errorf("index %d is error", index) + } + result += num + return nil + }) + + assert.Equal(t, tc.wantErr, err) + if err != nil { + return + } + assert.Equal(t, tc.wantVal, result) + }) + } +} + +func TestCopyOnWriteArrayList_AsSlice(t *testing.T) { + vals := []int{1, 2, 3} + a := NewCopyOnWriteArrayListOf[int](vals) + slice := a.AsSlice() + // 内容相同 + assert.Equal(t, slice, vals) + aAddr := fmt.Sprintf("%p", vals) + sliceAddr := fmt.Sprintf("%p", slice) + // 但是地址不同,也就是意味着 slice 必须是一个新创建的 + assert.NotEqual(t, aAddr, sliceAddr) +} + +func TestCopyOnWriteArrayList_Set(t *testing.T) { + testCases := []struct { + name string + list *CopyOnWriteArrayList[int] + index int + newVal int + wantSlice []int + wantErr error + }{ + { + name: "set 5 by index 1", + list: NewCopyOnWriteArrayListOf[int]([]int{0, 1, 2, 3, 4}), + index: 1, + newVal: 5, + wantSlice: []int{0, 5, 2, 3, 4}, + wantErr: nil, + }, + { + name: "index -1", + list: NewCopyOnWriteArrayListOf[int]([]int{0, 1, 2, 3, 4}), + index: -1, + newVal: 5, + wantSlice: []int{}, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, -1), + }, + { + name: "index 100", + list: NewCopyOnWriteArrayListOf[int]([]int{0, 1, 2, 3, 4}), + index: 100, + newVal: 5, + wantSlice: []int{}, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, 100), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Set(tc.index, tc.newVal) + if err != nil { + assert.Equal(t, tc.wantErr, err) + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + }) + } +}