├── .imgs └── contact_me_qr.jpg ├── .licenserc.json ├── README.md ├── .devcontainer └── devcontainer.json ├── .github ├── ISSUE_TEMPLATE │ ├── refactor.md │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── pre-commit ├── workflows │ ├── license.yml │ ├── stale.yml │ ├── go.yml │ ├── go-fmt.yml │ └── golangci-lint.yml └── pre-push ├── .gitignore ├── Makefile ├── stringx ├── stringx_benchmark ├── string_example_test.go └── string.go ├── .script ├── goimports.sh └── setup.sh ├── ptr.go ├── internal ├── slice │ ├── doc.go │ ├── delete.go │ ├── add.go │ ├── shrink.go │ ├── shrink_test.go │ ├── delete_test.go │ └── add_test.go ├── errs │ └── error.go └── queue │ └── priority_queue_benchmark_test.go ├── go.mod ├── .golangci.yml ├── slice ├── types.go ├── add.go ├── reverse.go ├── find.go ├── delete.go ├── union.go ├── diff.go ├── intersect.go ├── aggregate.go ├── symmetric_diff.go ├── add_test.go ├── index.go ├── contains.go ├── union_test.go ├── diff_test.go └── map.go ├── queue ├── errs.go ├── priority_queue_example_test.go ├── priority_queue.go ├── types.go ├── concurrent_priority_queue.go ├── concurrent_linked_queue.go └── concurrent_linked_blocking_queue.go ├── retry ├── types.go ├── retry.go ├── fixed_internal.go ├── retry_test.go ├── exponential.go ├── adaptive.go └── adaptive_test.go ├── ptr_test.go ├── bean ├── copier │ ├── converter │ │ ├── time2string.go │ │ └── converter.go │ ├── reflect_copy_benchmark │ ├── errors.go │ └── copy.go └── option │ ├── option.go │ └── option_test.go ├── .deepsource.toml ├── spi ├── testdata │ ├── user_service │ │ └── a.go │ ├── user_service3 │ │ └── a.go │ └── user_service2 │ │ ├── b │ │ └── b.go │ │ └── a │ │ └── a.go └── spi.go ├── constrain.go ├── types.go ├── list ├── skip_list_example_test.go ├── types.go ├── skip_list.go ├── concurrent_list.go └── array_list.go ├── net └── httpx │ ├── response.go │ ├── httptestx │ ├── recorder_test.go │ └── recorder.go │ ├── response_test.go │ ├── log_round_trip_test.go │ ├── log_round_trip.go │ └── request.go ├── condition.go ├── iox ├── json_reader_example_test.go ├── json_reader.go ├── json_reader_test.go ├── concurrent_multiple_bytes.go ├── multiple_bytes.go └── concurrent_multiple_bytes_test.go ├── syncx ├── segment_key_lock_example_test.go ├── limit_pool_example_test.go ├── pool.go ├── limit_pool.go ├── atomicx │ ├── atomic.go │ └── example_test.go ├── segment_key_lock_test.go ├── limit_pool_test.go ├── segment_key_lock.go ├── pool_test.go └── map.go ├── mapx ├── types.go ├── linkedmap_example_test.go ├── treemap_example_test.go ├── hashmap_example_test.go ├── multi_map_example_test.go ├── builtin_map.go ├── map_example_test.go ├── map.go ├── treemap.go ├── multi_map.go └── linkedmap.go ├── sqlx ├── types.go ├── newnull.go ├── json.go └── scanner.go ├── .codecov.yml ├── reflectx └── reflect.go ├── set ├── set.go └── treeset.go ├── condition_test.go ├── pool ├── arena_pool.go ├── types.go └── arena_pool_test.go ├── tree └── red_black_tree.go └── go.sum /.imgs/contact_me_qr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecodeclub/ekit/HEAD/.imgs/contact_me_qr.jpg -------------------------------------------------------------------------------- /.licenserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.go": "// Copyright 2021 ecodeclub", 3 | "**/*.{yml,toml}": "# Copyright 2021 ecodeclub", 4 | "**/*.sh": "# Copyright 2021 ecodeclub" 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ekit 2 | 泛型工具库。 3 | 4 | - [文档](https://doc.meoying.com/) 5 | 6 | ## 交流 7 | 8 | 交流群。原本我是觉得有一个群会削弱 github 的社区氛围,但是比较多人还是习惯于用群交流,所以我也搞了一个。 9 | 10 | 但是希望你进群之前要先想好,这个群并不希望大家讨论任何的社会议题,包括政治、历史、男女、情感等。我们希望这个群承担的功能是讨论技术问题和技术互助。 11 | 12 | 技术互助的意思是,你进群是希望有人来帮你解答问题;那么同样地,看到别人提问,也希望你能帮助解答。 13 | 14 | ![入群](./.imgs/contact_me_qr.jpg) -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm", 3 | "customizations": { 4 | "vscode": { 5 | "extensions": [ 6 | "golang.go", 7 | "eamodio.gitlens", 8 | "ms-vscode.makefile-tools" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/refactor.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Refactor request 3 | about: Refactor existing code 4 | title: '' 5 | labels: refactor 6 | assignees: '' 7 | 8 | --- 9 | 10 | **仅限中文** 11 | 12 | ### 当前实现缺陷 13 | 14 | ### 重构方案 15 | > 描述可以如何重构,以及重构之后带来的效果,如可读性、性能等方面的提升 16 | 17 | ### 其它 18 | > 任何你觉得有利于解决问题的补充说明 19 | 20 | ### 你使用的是 ekit 哪个版本? 21 | 22 | ### 你设置的的 Go 环境? 23 | > 上传 `go env` 的结果 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea 18 | .vscode 19 | **/.DS_Store 20 | **/*.so -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Want to ask some questions 4 | title: '' 5 | labels: question 6 | --- 7 | 8 | **仅限中文** 9 | 10 | 在提之前请先查找[已有 issues](https://github.com/ecodeclub/eorm/issues),避免重复上报。 11 | 12 | 并且确保自己已经: 13 | - [ ] 阅读过文档 14 | - [ ] 阅读过注释 15 | - [ ] 阅读过例子 16 | 17 | ### 你的问题 18 | 19 | ### 你使用的是 ekit 哪个版本? 20 | 21 | ### 你设置的的 Go 环境? 22 | > 上传 `go env` 的结果 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **仅限中文** 11 | 12 | ### 使用场景 13 | 14 | ### 行业分析 15 | > 如果你知道有框架提供了类似功能,可以在这里描述,并且给出文档或者例子 16 | 17 | ### 可行方案 18 | > 如果你有设计思路或者解决方案,请在这里提供。你可以提供多个方案,并且给出自己的选择 19 | 20 | ### 其它 21 | > 任何你觉得有利于解决问题的补充说明 22 | 23 | ### 你使用的是 ekit 哪个版本? 24 | 25 | ### 你设置的的 Go 环境? 26 | > 上传 `go env` 的结果 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **仅限中文** 11 | 12 | 在提之前请先查找[已有 issues](https://github.com/ecodeclub/eorm/issues),避免重复上报。 13 | 14 | 并且确保自己已经: 15 | - [ ] 阅读过文档 16 | - [ ] 阅读过注释 17 | - [ ] 阅读过例子 18 | 19 | ### 问题简要描述 20 | 21 | ### 复现步骤 22 | > 请提供简单的复现代码 23 | 24 | ### 错误日志或者截图 25 | 26 | ### 你期望的结果 27 | 28 | ### 你排查的结果,或者你觉得可行的修复方案 29 | > 可选。我们希望你能够尽量先排查问题,帮助我们减轻维护负担。这对于你个人能力提升同样是有帮助的。 30 | 31 | ### 你使用的是 ekit 哪个版本? 32 | 33 | ### 你设置的的 Go 环境? 34 | > 上传 `go env` 的结果 -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: bench 2 | bench: 3 | @go test -bench=. -benchmem ./... 4 | 5 | .PHONY: ut 6 | ut: 7 | @go test -tags=goexperiment.arenas -race ./... 8 | 9 | .PHONY: ut-coverage 10 | ut-coverage: 11 | @go test -tags=goexperiment.arenas -race -coverprofile=cover.out ./... 12 | 13 | .PHONY: setup 14 | setup: 15 | @sh ./.script/setup.sh 16 | 17 | .PHONY: fmt 18 | fmt: 19 | @sh ./.script/goimports.sh 20 | 21 | .PHONY: lint 22 | lint: 23 | @golangci-lint run -c .golangci.yml 24 | 25 | .PHONY: tidy 26 | tidy: 27 | @go mod tidy -v 28 | 29 | .PHONY: check 30 | check: 31 | @$(MAKE) fmt 32 | @$(MAKE) tidy -------------------------------------------------------------------------------- /stringx/stringx_benchmark: -------------------------------------------------------------------------------- 1 | goos: darwin 2 | goarch: amd64 3 | pkg: github.com/ecodeclub/ekit/stringx 4 | cpu: Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz 5 | Benchmark_UnsafeToBytes/safe_to_bytes-8 39721614 29.60 ns/op 48 B/op 1 allocs/op 6 | Benchmark_UnsafeToBytes/unsafe_to_bytes-8 1000000000 0.2805 ns/op 0 B/op 0 allocs/op 7 | Benchmark_UnsafeToString/safe_to_string-8 45207981 26.77 ns/op 48 B/op 1 allocs/op 8 | Benchmark_UnsafeToString/unsafe_to_string-8 1000000000 0.2842 ns/op 0 B/op 0 allocs/op 9 | PASS 10 | ok github.com/ecodeclub/ekit/stringx 4.780s 11 | -------------------------------------------------------------------------------- /.script/goimports.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | goimports -l -w $(find . -type f -name '*.go' -not -path "./.idea/*") -------------------------------------------------------------------------------- /ptr.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ekit 16 | 17 | func ToPtr[T any](t T) *T { 18 | return &t 19 | } 20 | -------------------------------------------------------------------------------- /internal/slice/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package slice 后续逐步把切片会在不同部分使用的公共方法挪过来这个内部包 16 | package slice 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ecodeclub/ekit 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/DATA-DOG/go-sqlmock v1.5.0 7 | github.com/mattn/go-sqlite3 v1.14.15 8 | github.com/pkg/errors v0.9.1 9 | github.com/stretchr/testify v1.8.4 10 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d 11 | golang.org/x/sync v0.4.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 16 | github.com/kr/pretty v0.3.1 // indirect 17 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 18 | github.com/rogpeppe/go-internal v1.11.0 // indirect 19 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | run: 16 | go: '1.20' 17 | issues: 18 | exclude-dirs: 19 | - .idea 20 | linters-settings: 21 | errcheck: 22 | ignore: '' -------------------------------------------------------------------------------- /slice/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // equalFunc 比较两个元素是否相等 18 | type equalFunc[T any] func(src, dst T) bool 19 | 20 | type matchFunc[T any] func(src T) bool 21 | -------------------------------------------------------------------------------- /queue/errs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queue 16 | 17 | import "github.com/ecodeclub/ekit/internal/queue" 18 | 19 | // ErrOutOfCapacity 超过容量 20 | var ErrOutOfCapacity = queue.ErrOutOfCapacity 21 | -------------------------------------------------------------------------------- /retry/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retry 16 | 17 | import ( 18 | "time" 19 | ) 20 | 21 | type Strategy interface { 22 | // Next 返回下一次重试的间隔,如果不需要继续重试,那么第二参数返回 false 23 | Next() (time.Duration, bool) 24 | Report(err error) Strategy 25 | } 26 | -------------------------------------------------------------------------------- /ptr_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ekit 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestToPtr(t *testing.T) { 24 | i := 12 25 | res := ToPtr[int](i) 26 | assert.Equal(t, &i, res) 27 | } 28 | -------------------------------------------------------------------------------- /bean/copier/converter/time2string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package converter 16 | 17 | import "time" 18 | 19 | type Time2String struct { 20 | Pattern string 21 | } 22 | 23 | func (t Time2String) Convert(src time.Time) (string, error) { 24 | return src.Format(t.Pattern), nil 25 | } 26 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version = 1 16 | 17 | [[analyzers]] 18 | name = "go" 19 | enabled = true 20 | 21 | [analyzers.meta] 22 | import_root = "github.com/ecodeclub/ekit" 23 | dependencies_vendored = false 24 | 25 | [[analyzers]] 26 | name = "test-coverage" 27 | enabled = true 28 | 29 | -------------------------------------------------------------------------------- /bean/copier/reflect_copy_benchmark: -------------------------------------------------------------------------------- 1 | cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz 2 | BenchmarkReflectCopier_Copy_PureRunTime-12 1332534 910.4 ns/op 240 B/op 17 allocs/op 3 | BenchmarkReflectCopier_CopyComplexStruct_WithPureRuntime-12 239366 4562 ns/op 1056 B/op 80 allocs/op 4 | BenchmarkReflectCopier_Copy/reused-12 2169706 534.5 ns/op 192 B/op 11 allocs/op 5 | BenchmarkReflectCopier_Copy/create-12 733861 1684 ns/op 1064 B/op 29 allocs/op 6 | BenchmarkReflectCopier_CopyComplexStruct/reused-12 470026 2359 ns/op 784 B/op 46 allocs/op 7 | BenchmarkReflectCopier_CopyComplexStruct/create-12 149652 8172 ns/op 4968 B/op 134 allocs/op -------------------------------------------------------------------------------- /spi/testdata/user_service/a.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package main 15 | 16 | // 测试用 17 | //go:generate go build -race -tags=goexperiment.arenas --buildmode=plugin -o a.so ./a.go 18 | 19 | type UserService struct{} 20 | 21 | func (u UserService) Get() string { 22 | return "Get" 23 | } 24 | 25 | var UserSvc UserService 26 | -------------------------------------------------------------------------------- /spi/testdata/user_service3/a.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | // 测试用 18 | 19 | //go:generate go build -race -tags=goexperiment.arenas --buildmode=plugin -o a.so ./a.go 20 | 21 | type UserService struct{} 22 | 23 | func (u UserService) GetV1() string { 24 | return "Get" 25 | } 26 | 27 | var UserSvc UserService 28 | -------------------------------------------------------------------------------- /bean/copier/converter/converter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package converter 16 | 17 | type Converter[Src any, Dst any] interface { 18 | Convert(src Src) (Dst, error) 19 | } 20 | 21 | type ConverterFunc[Src any, Dst any] func(src Src) (Dst, error) 22 | 23 | func (cf ConverterFunc[Src, Dst]) Convert(src Src) (Dst, error) { 24 | return cf(src) 25 | } 26 | -------------------------------------------------------------------------------- /constrain.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ekit 16 | 17 | // RealNumber 实数 18 | // 绝大多数情况下,你都应该用这个来表达数字的含义 19 | type RealNumber interface { 20 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | 21 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | 22 | ~float32 | ~float64 23 | } 24 | 25 | type Number interface { 26 | RealNumber | ~complex64 | ~complex128 27 | } 28 | -------------------------------------------------------------------------------- /slice/add.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import "github.com/ecodeclub/ekit/internal/slice" 18 | 19 | // Add 在index处添加元素 20 | // index 范围应为[0, len(src)] 21 | // 如果index == len(src) 则表示往末尾添加元素 22 | func Add[Src any](src []Src, element Src, index int) ([]Src, error) { 23 | res, err := slice.Add[Src](src, element, index) 24 | return res, err 25 | } 26 | -------------------------------------------------------------------------------- /spi/testdata/user_service2/b/b.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate go build -race -tags=goexperiment.arenas --buildmode=plugin -o ../b.so ./b.go 16 | package main 17 | 18 | // 测试用 19 | 20 | type UserService struct{} 21 | 22 | // GetName returns the name of the service 23 | func (u UserService) Get() string { 24 | return "B" 25 | } 26 | 27 | // 导出对象 28 | var UserSvc UserService 29 | -------------------------------------------------------------------------------- /spi/testdata/user_service2/a/a.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate go build -race -tags=goexperiment.arenas --buildmode=plugin -o ../a.so ./a.go 16 | 17 | package main 18 | 19 | // 测试用 20 | 21 | type UserService struct{} 22 | 23 | // GetName returns the name of the service 24 | func (u UserService) Get() string { 25 | return "A" 26 | } 27 | 28 | // 导出对象 29 | var UserSvc UserService 30 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ekit 16 | 17 | // Comparator 用于比较两个对象的大小 src < dst, 返回-1,src = dst, 返回0,src > dst, 返回1 18 | // 不要返回任何其它值! 19 | type Comparator[T any] func(src T, dst T) int 20 | 21 | func ComparatorRealNumber[T RealNumber](src T, dst T) int { 22 | if src < dst { 23 | return -1 24 | } else if src == dst { 25 | return 0 26 | } else { 27 | return 1 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /list/skip_list_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package list_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ecodeclub/ekit" 21 | "github.com/ecodeclub/ekit/internal/list" 22 | ) 23 | 24 | func ExampleNewSkipList() { 25 | l := list.NewSkipList[int](ekit.ComparatorRealNumber[int]) 26 | l.Insert(123) 27 | val, _ := l.Get(0) 28 | fmt.Println(val) 29 | // Output: 30 | // 123 31 | } 32 | -------------------------------------------------------------------------------- /net/httpx/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpx 16 | 17 | import ( 18 | "encoding/json" 19 | "net/http" 20 | ) 21 | 22 | type Response struct { 23 | *http.Response 24 | err error 25 | } 26 | 27 | // JSONScan 将 Body 按照 JSON 反序列化为结构体 28 | func (r *Response) JSONScan(val any) error { 29 | if r.err != nil { 30 | return r.err 31 | } 32 | err := json.NewDecoder(r.Body).Decode(val) 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /.github/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2021 ecodeclub 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # To use, store as .git/hooks/pre-commit inside your repository and make sure 17 | # it has execute permissions. 18 | # 19 | # This script does not handle file names that contain spaces. 20 | 21 | # Pre-commit configuration 22 | 23 | make check 24 | printf "执行检查中...\n" 25 | 26 | if [ $? -ne 0 ]; then 27 | echo >&2 "[ERROR]: 有文件发生变更,请将变更文件添加到本次提交中" 28 | exit 1 29 | fi 30 | 31 | exit 0 -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Check License Lines 16 | on: 17 | pull_request: 18 | types: [opened, synchronize, reopened, labeled, unlabeled] 19 | branches: 20 | - develop 21 | - main 22 | - dev 23 | jobs: 24 | check-license-lines: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@master 28 | - name: Check License Lines 29 | uses: kt3k/license_checker@v1.0.6 -------------------------------------------------------------------------------- /queue/priority_queue_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queue_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ecodeclub/ekit" 21 | "github.com/ecodeclub/ekit/internal/queue" 22 | ) 23 | 24 | func ExampleNewPriorityQueue() { 25 | // 容量,并且队列里面放的是 int 26 | pq := queue.NewPriorityQueue(10, ekit.ComparatorRealNumber[int]) 27 | _ = pq.Enqueue(10) 28 | _ = pq.Enqueue(9) 29 | val, _ := pq.Dequeue() 30 | fmt.Println(val) 31 | // Output: 32 | // 9 33 | } 34 | -------------------------------------------------------------------------------- /slice/reverse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // Reverse 将会完全创建一个新的切片,而不是直接在 src 上进行翻转。 18 | func Reverse[T any](src []T) []T { 19 | var ret = make([]T, 0, len(src)) 20 | for i := len(src) - 1; i >= 0; i-- { 21 | ret = append(ret, src[i]) 22 | } 23 | return ret 24 | } 25 | 26 | // ReverseSelf 會直接在 src 上进行翻转。 27 | func ReverseSelf[T any](src []T) { 28 | for i, j := 0, len(src)-1; i < j; i, j = i+1, j-1 { 29 | src[i], src[j] = src[j], src[i] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /stringx/string_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stringx_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ecodeclub/ekit/stringx" 21 | ) 22 | 23 | func ExampleUnsafeToBytes() { 24 | str := "hello" 25 | val := stringx.UnsafeToBytes(str) 26 | fmt.Println(len(val)) 27 | // Output: 28 | // 5 29 | } 30 | 31 | func ExampleUnsafeToString() { 32 | val := stringx.UnsafeToString([]byte("hello")) 33 | fmt.Println(val) 34 | // Output: 35 | // hello 36 | } 37 | -------------------------------------------------------------------------------- /condition.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ekit 16 | 17 | // IfThenElse 根据条件返回对应的泛型结果 18 | // 注意避免结果的空指针问题 19 | func IfThenElse[T any](condition bool, trueValue, falseValue T) T { 20 | if condition { 21 | return trueValue 22 | } 23 | return falseValue 24 | } 25 | 26 | // IfThenElseFunc 根据条件执行对应的函数并返回泛型结果 27 | func IfThenElseFunc[T any](condition bool, trueFunc, falseFunc func() (T, error)) (T, error) { 28 | if condition { 29 | return trueFunc() 30 | } 31 | return falseFunc() 32 | } 33 | -------------------------------------------------------------------------------- /iox/json_reader_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iox_test 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | 21 | "github.com/ecodeclub/ekit/iox" 22 | ) 23 | 24 | func ExampleNewJSONReader() { 25 | val := iox.NewJSONReader(User{Name: "Tom"}) 26 | _, err := http.NewRequest(http.MethodPost, "/hello", val) 27 | if err != nil { 28 | fmt.Println(err.Error()) 29 | return 30 | } 31 | fmt.Println("OK") 32 | } 33 | 34 | type User struct { 35 | Name string `json:"name"` 36 | } 37 | -------------------------------------------------------------------------------- /syncx/segment_key_lock_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ecodeclub/ekit/syncx" 21 | ) 22 | 23 | func ExampleNewSegmentKeysLock() { 24 | // 参数就是分多少段,你也可以理解为总共有多少锁 25 | // 锁越多,并发竞争越低,但是消耗内存; 26 | // 锁越少,并发竞争越高,但是内存消耗少; 27 | lock := syncx.NewSegmentKeysLock(100) 28 | // 对应的还有 TryLock 29 | // RLock 和 RUnlock 30 | lock.Lock("key1") 31 | defer lock.Unlock("key1") 32 | fmt.Println("OK") 33 | // Output: 34 | // OK 35 | } 36 | -------------------------------------------------------------------------------- /syncx/limit_pool_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ecodeclub/ekit/syncx" 21 | ) 22 | 23 | func ExampleNewLimitPool() { 24 | p := syncx.NewLimitPool(1, func() int { 25 | return 123 26 | }) 27 | val, ok := p.Get() 28 | fmt.Println("第一次", val, ok) 29 | val, ok = p.Get() 30 | fmt.Println("第二次", val, ok) 31 | p.Put(123) 32 | val, ok = p.Get() 33 | fmt.Println("第三次", val, ok) 34 | // Output: 35 | // 第一次 123 true 36 | // 第二次 0 false 37 | // 第三次 123 true 38 | } 39 | -------------------------------------------------------------------------------- /internal/slice/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import "github.com/ecodeclub/ekit/internal/errs" 18 | 19 | func Delete[T any](src []T, index int) ([]T, T, error) { 20 | length := len(src) 21 | if index < 0 || index >= length { 22 | var zero T 23 | return nil, zero, errs.NewErrIndexOutOfRange(length, index) 24 | } 25 | res := src[index] 26 | //从index位置开始,后面的元素依次往前挪1个位置 27 | for i := index; i+1 < length; i++ { 28 | src[i] = src[i+1] 29 | } 30 | //去掉最后一个重复元素 31 | src = src[:length-1] 32 | return src, res, nil 33 | } 34 | -------------------------------------------------------------------------------- /mapx/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | type mapi[K any, V any] interface { 18 | Put(key K, val V) error 19 | Get(key K) (V, bool) 20 | // Delete 删除 21 | // 第一个返回值是被删除的 key 对应的值 22 | // 第二个返回值是代表是否真的删除了 23 | Delete(k K) (V, bool) 24 | // Keys 返回所有的键 25 | // 注意,当你调用多次拿到的结果不一定相等 26 | // 取决于具体实现 27 | Keys() []K 28 | // Values 返回所有的值 29 | // 注意,当你调用多次拿到的结果不一定相等 30 | // 取决于具体实现 31 | Values() []V 32 | // 返回键值对数量 33 | Len() int64 34 | // cb 返回值为 true 就是继续遍历,为 false 就是中断,直接返回 35 | Iterate(cb func(key K, val V) bool) 36 | } 37 | -------------------------------------------------------------------------------- /internal/slice/add.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import "github.com/ecodeclub/ekit/internal/errs" 18 | 19 | func Add[T any](src []T, element T, index int) ([]T, error) { 20 | length := len(src) 21 | if index < 0 || index > length { 22 | return nil, errs.NewErrIndexOutOfRange(length, index) 23 | } 24 | 25 | //先将src扩展一个元素 26 | var zeroValue T 27 | src = append(src, zeroValue) 28 | for i := len(src) - 1; i > index; i-- { 29 | if i-1 >= 0 { 30 | src[i] = src[i-1] 31 | } 32 | } 33 | src[index] = element 34 | return src, nil 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Mark stale issues and pull requests 16 | 17 | on: 18 | schedule: 19 | - cron: "30 1 * * *" 20 | 21 | jobs: 22 | stale: 23 | 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/stale@v4 28 | with: 29 | repo-token: ${{ secrets.GITHUB_TOKEN }} 30 | stale-issue-message: 'This issue is inactive for a long time.' 31 | stale-pr-message: 'This PR is inactive for a long time' 32 | stale-issue-label: 'inactive-issue' 33 | stale-pr-label: 'inactive-pr' 34 | -------------------------------------------------------------------------------- /sqlx/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sqlx 16 | 17 | import "database/sql" 18 | 19 | // 因为 sql 包里面缺乏顶级接口定义,而在研发一些中间件的时候,又必须用到不同的实现 20 | // 因此在这里提前定义一些顶级接口 21 | // 一般来说,如果你不是设计一些和数据库有关的中间件,你是用不上这些接口的 22 | 23 | var _ Rows = (*sql.Rows)(nil) 24 | 25 | type Rows interface { 26 | Next() bool 27 | NextResultSet() bool 28 | Err() error 29 | Columns() ([]string, error) 30 | // ColumnTypes 还是返回了原本的 sql.ColumnType 31 | // 因为 ColumnType 同样不是一个接口,所以为了兼容 sql.Rows, 32 | // 就只有保持这个设计 33 | ColumnTypes() ([]*sql.ColumnType, error) 34 | Scan(dest ...any) error 35 | Close() error 36 | } 37 | -------------------------------------------------------------------------------- /internal/slice/shrink.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | func calCapacity(c, l int) (int, bool) { 18 | if c <= 64 { 19 | return c, false 20 | } 21 | if c > 2048 && (c/l >= 2) { 22 | factor := 0.625 23 | return int(float32(c) * float32(factor)), true 24 | } 25 | if c <= 2048 && (c/l >= 4) { 26 | return c / 2, true 27 | } 28 | return c, false 29 | } 30 | 31 | func Shrink[T any](src []T) []T { 32 | c, l := cap(src), len(src) 33 | n, changed := calCapacity(c, l) 34 | if !changed { 35 | return src 36 | } 37 | s := make([]T, 0, n) 38 | s = append(s, src...) 39 | return s 40 | } 41 | -------------------------------------------------------------------------------- /syncx/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import "sync" 18 | 19 | // Pool 是对 sync.Pool 的简单封装 20 | // 会有一些性能损耗,但是基本可以忽略不计。担忧性能问题的可以参考 21 | type Pool[T any] struct { 22 | p sync.Pool 23 | } 24 | 25 | // NewPool 创建一个 Pool 实例 26 | // factory 必须返回 T 类型的值,并且不能返回 nil 27 | func NewPool[T any](factory func() T) *Pool[T] { 28 | return &Pool[T]{ 29 | p: sync.Pool{ 30 | New: func() any { 31 | return factory() 32 | }, 33 | }, 34 | } 35 | } 36 | 37 | // Get 取出一个元素 38 | func (p *Pool[T]) Get() T { 39 | return p.p.Get().(T) 40 | } 41 | 42 | // Put 放回去一个元素 43 | func (p *Pool[T]) Put(t T) { 44 | p.p.Put(t) 45 | } 46 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | coverage: 16 | status: 17 | project: 18 | default: 19 | # basic 20 | target: 95% 21 | threshold: 0.5% 22 | # advanced settings 23 | branches: 24 | - main 25 | - dev 26 | if_ci_failed: error #success, failure, error, ignore 27 | informational: false 28 | only_pulls: false 29 | patch: 30 | default: 31 | # basic 32 | target: 95% 33 | threshold: 0.5% 34 | branches: 35 | - main 36 | - dev 37 | if_ci_failed: error #success, failure, error, ignore 38 | informational: false 39 | only_pulls: false -------------------------------------------------------------------------------- /slice/find.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // Find 查找元素 18 | // 如果没有找到,第二个返回值返回 false 19 | func Find[T any](src []T, match matchFunc[T]) (T, bool) { 20 | for _, val := range src { 21 | if match(val) { 22 | return val, true 23 | } 24 | } 25 | var t T 26 | return t, false 27 | } 28 | 29 | // FindAll 查找所有符合条件的元素 30 | // 永远不会返回 nil 31 | func FindAll[T any](src []T, match matchFunc[T]) []T { 32 | // 我们认为符合条件元素应该是少数 33 | // 所以会除以 8 34 | // 也就是触发扩容的情况下,最多三次就会和原本的容量一样 35 | // +1 是为了保证,至少有一个元素 36 | res := make([]T, 0, len(src)>>3+1) 37 | for _, val := range src { 38 | if match(val) { 39 | res = append(res, val) 40 | } 41 | } 42 | return res 43 | } 44 | -------------------------------------------------------------------------------- /net/httpx/httptestx/recorder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httptestx 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestJSONResponseRecorder_MustScan(t *testing.T) { 25 | // 成功案例 26 | recorder := NewJSONResponseRecorder[User]() 27 | _, err := recorder.WriteString(`{"name": "Tom"}`) 28 | require.NoError(t, err) 29 | u := recorder.MustScan() 30 | assert.Equal(t, User{Name: "Tom"}, u) 31 | 32 | // panic 案例 33 | recorder = NewJSONResponseRecorder[User]() 34 | assert.Panics(t, func() { 35 | recorder.MustScan() 36 | }) 37 | } 38 | 39 | type User struct { 40 | Name string `json:"name"` 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Go 16 | 17 | on: 18 | push: 19 | branches: [ dev ] 20 | pull_request: 21 | branches: [ dev ] 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Set up Go 29 | uses: actions/setup-go@v2 30 | with: 31 | go-version: 1.20.0 32 | 33 | 34 | - name: Build 35 | run: go build -v ./... 36 | 37 | - name: Test 38 | run: make ut-coverage 39 | 40 | - name: Upload results to Codecov 41 | uses: codecov/codecov-action@v5 42 | with: 43 | fail_ci_if_error: true 44 | token: ${{ secrets.CODECOV_TOKEN }} 45 | -------------------------------------------------------------------------------- /net/httpx/httptestx/recorder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httptestx 16 | 17 | import ( 18 | "encoding/json" 19 | "net/http/httptest" 20 | ) 21 | 22 | type JSONResponseRecorder[T any] struct { 23 | *httptest.ResponseRecorder 24 | } 25 | 26 | func NewJSONResponseRecorder[T any]() *JSONResponseRecorder[T] { 27 | return &JSONResponseRecorder[T]{ 28 | ResponseRecorder: httptest.NewRecorder(), 29 | } 30 | } 31 | 32 | func (r JSONResponseRecorder[T]) Scan() (T, error) { 33 | var t T 34 | err := json.NewDecoder(r.Body).Decode(&t) 35 | return t, err 36 | } 37 | 38 | func (r JSONResponseRecorder[T]) MustScan() T { 39 | t, err := r.Scan() 40 | if err != nil { 41 | panic(err) 42 | } 43 | return t 44 | } 45 | -------------------------------------------------------------------------------- /slice/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import "github.com/ecodeclub/ekit/internal/slice" 18 | 19 | // Delete 删除 index 处的元素 20 | func Delete[Src any](src []Src, index int) ([]Src, error) { 21 | res, _, err := slice.Delete[Src](src, index) 22 | return res, err 23 | } 24 | 25 | // FilterDelete 删除符合条件的元素 26 | // 考虑到性能问题,所有操作都会在原切片上进行 27 | // 被删除元素之后的元素会往前移动,有且只会移动一次 28 | func FilterDelete[Src any](src []Src, m func(idx int, src Src) bool) []Src { 29 | // 记录被删除的元素位置,也称空缺的位置 30 | emptyPos := 0 31 | for idx := range src { 32 | // 判断是否满足删除的条件 33 | if m(idx, src[idx]) { 34 | continue 35 | } 36 | // 移动元素 37 | src[emptyPos] = src[idx] 38 | emptyPos++ 39 | } 40 | return src[:emptyPos] 41 | } 42 | -------------------------------------------------------------------------------- /stringx/string.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stringx 16 | 17 | import ( 18 | "unsafe" 19 | ) 20 | 21 | // 确保传入的字符串和字节切片的生命周期足够长,不会在转换后被释放或修改。 22 | // 确保传入的字符串和字节切片的长度和容量是一致的,否则可能导致访问越界。 23 | // 不要对转换后的字节切片或字符串进行修改,因为它们可能与原始的字符串或字节切片共享底层的内存。 24 | 25 | // UnsafeToBytes 非安全 string 转 []byte 他必须遵守上述规则 26 | func UnsafeToBytes(val string) []byte { 27 | sh := (*[2]uintptr)(unsafe.Pointer(&val)) 28 | bh := [3]uintptr{sh[0], sh[1], sh[1]} 29 | return *(*[]byte)(unsafe.Pointer(&bh)) 30 | } 31 | 32 | // UnsafeToString 非安全 []byte 转 string 他必须遵守上述规则 33 | func UnsafeToString(val []byte) string { 34 | bh := (*[3]uintptr)(unsafe.Pointer(&val)) 35 | sh := [2]uintptr{bh[0], bh[1]} 36 | return *(*string)(unsafe.Pointer(&sh)) 37 | } 38 | -------------------------------------------------------------------------------- /mapx/linkedmap_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | import ( 18 | "fmt" 19 | "sort" 20 | ) 21 | 22 | func ExampleLinkedMap_Iterate() { 23 | linkedMap := NewLinkedHashMap[testStringData, int](0) 24 | strArr := make([]string, 0) 25 | _ = linkedMap.Put(testStringData{data: "hello"}, 1) 26 | _ = linkedMap.Put(testStringData{data: "world"}, 2) 27 | _ = linkedMap.Put(testStringData{data: "ekit"}, 3) 28 | 29 | linkedMap.Iterate( 30 | func(key testStringData, val int) bool { 31 | strArr = append(strArr, key.data) 32 | return true 33 | }) 34 | 35 | sort.Strings(strArr) 36 | for _, s := range strArr { 37 | fmt.Println(s) 38 | } 39 | 40 | // Output: 41 | // ekit 42 | // hello 43 | // world 44 | } 45 | -------------------------------------------------------------------------------- /reflectx/reflect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package reflectx 16 | 17 | import ( 18 | "reflect" 19 | ) 20 | 21 | // IsNilValue 对 IsNil 方法进一步封装 22 | // 如果 val 的类型 为 map、chan、slice、interface 和 ptr 以及 func,可以执行 IsNil 方法 23 | // 否则直接返回 false,避免执行 IsNil 方法时发生 panic 24 | // 特别注意的是,如果 val 本身为非法的值时(例如 nil),需要先通过 IsValid 方法进行判断,避免后续操作发生 panic 25 | func IsNilValue(val reflect.Value) bool { 26 | // 先判断 reflect.Value 本身是否为非法的值,例如 nil。避免后续获取 val.Type() 时 panic。 27 | if !val.IsValid() { 28 | return true 29 | } 30 | // 根据类型判断是否可以执行 IsNil 方法 31 | switch val.Type().Kind() { 32 | case reflect.Map, reflect.Chan, reflect.Slice, reflect.Interface, reflect.Ptr, reflect.Func, reflect.UnsafePointer: 33 | return val.IsNil() 34 | } 35 | return false 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/go-fmt.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Format Go code 16 | 17 | on: 18 | push: 19 | branches: [ main,dev ] 20 | pull_request: 21 | branches: [ main,dev ] 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Set up Go 29 | uses: actions/setup-go@v3 30 | with: 31 | go-version: ">=1.20.0" 32 | 33 | - name: Install goimports 34 | run: go install golang.org/x/tools/cmd/goimports@latest 35 | 36 | - name: Check 37 | run: | 38 | make check 39 | if [ -n "$(git status --porcelain)" ]; then 40 | echo >&2 "错误: 请在本地运行命令'make check'后再提交." 41 | exit 1 42 | fi -------------------------------------------------------------------------------- /slice/union.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // UnionSet 并集,只支持 comparable 18 | // 已去重 19 | // 返回值的元素顺序是不定的 20 | func UnionSet[T comparable](src, dst []T) []T { 21 | srcMap, dstMap := toMap[T](src), toMap[T](dst) 22 | for key := range srcMap { 23 | dstMap[key] = struct{}{} 24 | } 25 | 26 | var ret = make([]T, 0, len(dstMap)) 27 | for key := range dstMap { 28 | ret = append(ret, key) 29 | } 30 | 31 | return ret 32 | } 33 | 34 | // UnionSetFunc 并集,支持任意类型 35 | // 你应该优先使用 UnionSet 36 | // 已去重 37 | func UnionSetFunc[T any](src, dst []T, equal equalFunc[T]) []T { 38 | var ret = make([]T, 0, len(src)+len(dst)) 39 | ret = append(ret, dst...) 40 | ret = append(ret, src...) 41 | 42 | return deduplicateFunc[T](ret, equal) 43 | } 44 | -------------------------------------------------------------------------------- /bean/option/option.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package option 16 | 17 | // Option 是用于 Option 模式的泛型设计, 18 | // 避免在代码中定义很多类似这样的结构体 19 | // 一般情况下 T 应该是一个结构体 20 | type Option[T any] func(t *T) 21 | 22 | // Apply 将 opts 应用在 t 之上 23 | func Apply[T any](t *T, opts ...Option[T]) { 24 | for _, opt := range opts { 25 | opt(t) 26 | } 27 | } 28 | 29 | // OptionErr 形如 Option,但是会返回一个 error 30 | // 你应该优先使用 Option,除非你在设计 option 模式的时候需要进行一些校验 31 | type OptionErr[T any] func(t *T) error 32 | 33 | // ApplyErr 形如 Apply,它将 opts 应用在 t 之上, 34 | // 如果 opts 中任何一个返回 error,那么它会中断并且返回 error 35 | func ApplyErr[T any](t *T, opts ...OptionErr[T]) error { 36 | for _, opt := range opts { 37 | if err := opt(t); err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /.script/setup.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | SOURCE_COMMIT=.github/pre-commit 16 | TARGET_COMMIT=.git/hooks/pre-commit 17 | SOURCE_PUSH=.github/pre-push 18 | TARGET_PUSH=.git/hooks/pre-push 19 | 20 | # copy pre-commit file if not exist. 21 | echo "设置 git pre-commit hooks..." 22 | cp $SOURCE_COMMIT $TARGET_COMMIT 23 | 24 | # copy pre-push file if not exist. 25 | echo "设置 git pre-push hooks..." 26 | cp $SOURCE_PUSH $TARGET_PUSH 27 | 28 | # add permission to TARGET_PUSH and TARGET_COMMIT file. 29 | test -x $TARGET_PUSH || chmod +x $TARGET_PUSH 30 | test -x $TARGET_COMMIT || chmod +x $TARGET_COMMIT 31 | 32 | echo "安装 golangci-lint..." 33 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 34 | 35 | echo "安装 goimports..." 36 | go install golang.org/x/tools/cmd/goimports@latest -------------------------------------------------------------------------------- /iox/json_reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iox 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | ) 21 | 22 | type JSONReader struct { 23 | val any 24 | bf *bytes.Reader 25 | } 26 | 27 | func (j *JSONReader) Read(p []byte) (n int, err error) { 28 | if j.bf == nil { 29 | var data []byte 30 | data, err = json.Marshal(j.val) 31 | if err == nil { 32 | j.bf = bytes.NewReader(data) 33 | } 34 | } 35 | if err != nil { 36 | return 37 | } 38 | return j.bf.Read(p) 39 | } 40 | 41 | // NewJSONReader 用于解决将一个结构体序列化为 JSON 之后,再封装为 io.Reader 的场景。 42 | // 该实现没有做任何输入检查。 43 | // 也就是你需要自己确保 val 是一个可以被 json 正确处理的东西。 44 | // 非线程安全。 45 | // 如果你传入的是 nil,那么读到的结果应该是 null。务必小心。 46 | func NewJSONReader(val any) *JSONReader { 47 | return &JSONReader{ 48 | val: val, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /slice/diff.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // DiffSet 差集,只支持 comparable 类型 18 | // 已去重 19 | // 并且返回值的顺序是不确定的 20 | func DiffSet[T comparable](src, dst []T) []T { 21 | srcMap := toMap[T](src) 22 | for _, val := range dst { 23 | delete(srcMap, val) 24 | } 25 | 26 | var ret = make([]T, 0, len(srcMap)) 27 | for key := range srcMap { 28 | ret = append(ret, key) 29 | } 30 | 31 | return ret 32 | } 33 | 34 | // DiffSetFunc 差集,已去重 35 | // 你应该优先使用 DiffSet 36 | func DiffSetFunc[T any](src, dst []T, equal equalFunc[T]) []T { 37 | var ret = make([]T, 0, len(src)) 38 | for _, val := range src { 39 | if !ContainsFunc[T](dst, func(src T) bool { 40 | return equal(src, val) 41 | }) { 42 | ret = append(ret, val) 43 | } 44 | } 45 | return deduplicateFunc[T](ret, equal) 46 | } 47 | -------------------------------------------------------------------------------- /slice/intersect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // IntersectSet 取交集,只支持 comparable 类型 18 | // 已去重 19 | func IntersectSet[T comparable](src []T, dst []T) []T { 20 | srcMap := toMap(src) 21 | var ret = make([]T, 0, len(src)) 22 | // 交集小于等于两个集合中的任意一个 23 | for _, val := range dst { 24 | if _, exist := srcMap[val]; exist { 25 | ret = append(ret, val) 26 | } 27 | } 28 | return deduplicate[T](ret) 29 | } 30 | 31 | // IntersectSetFunc 支持任意类型 32 | // 你应该优先使用 Intersect 33 | // 已去重 34 | func IntersectSetFunc[T any](src []T, dst []T, equal equalFunc[T]) []T { 35 | var ret = make([]T, 0, len(src)) 36 | for _, v := range dst { 37 | if ContainsFunc[T](src, func(t T) bool { 38 | return equal(t, v) 39 | }) { 40 | ret = append(ret, v) 41 | } 42 | } 43 | return deduplicateFunc[T](ret, equal) 44 | } 45 | -------------------------------------------------------------------------------- /set/set.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package set 16 | 17 | type Set[T comparable] interface { 18 | Add(key T) 19 | Delete(key T) 20 | // Exist 返回是否存在这个元素 21 | Exist(key T) bool 22 | Keys() []T 23 | } 24 | 25 | type MapSet[T comparable] struct { 26 | m map[T]struct{} 27 | } 28 | 29 | func NewMapSet[T comparable](size int) *MapSet[T] { 30 | return &MapSet[T]{ 31 | m: make(map[T]struct{}, size), 32 | } 33 | } 34 | func (s *MapSet[T]) Add(val T) { 35 | s.m[val] = struct{}{} 36 | } 37 | 38 | func (s *MapSet[T]) Delete(key T) { 39 | delete(s.m, key) 40 | } 41 | 42 | func (s *MapSet[T]) Exist(key T) bool { 43 | _, ok := s.m[key] 44 | return ok 45 | } 46 | 47 | // Keys 方法返回的元素顺序不固定 48 | func (s *MapSet[T]) Keys() []T { 49 | ans := make([]T, 0, len(s.m)) 50 | for key := range s.m { 51 | ans = append(ans, key) 52 | } 53 | return ans 54 | } 55 | -------------------------------------------------------------------------------- /slice/aggregate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import ( 18 | "github.com/ecodeclub/ekit" 19 | ) 20 | 21 | // Max 返回最大值。 22 | // 该方法假设你至少会传入一个值。 23 | // 在使用 float32 或者 float64 的时候要小心精度问题 24 | func Max[T ekit.RealNumber](ts []T) T { 25 | res := ts[0] 26 | for i := 1; i < len(ts); i++ { 27 | if ts[i] > res { 28 | res = ts[i] 29 | } 30 | } 31 | return res 32 | } 33 | 34 | // Min 返回最小值 35 | // 该方法会假设你至少会传入一个值 36 | // 在使用 float32 或者 float64 的时候要小心精度问题 37 | func Min[T ekit.RealNumber](ts []T) T { 38 | res := ts[0] 39 | for i := 1; i < len(ts); i++ { 40 | if ts[i] < res { 41 | res = ts[i] 42 | } 43 | } 44 | return res 45 | } 46 | 47 | // Sum 求和 48 | // 在使用 float32 或者 float64 的时候要小心精度问题 49 | func Sum[T ekit.Number](ts []T) T { 50 | var res T 51 | for _, n := range ts { 52 | res += n 53 | } 54 | return res 55 | } 56 | -------------------------------------------------------------------------------- /list/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package list 16 | 17 | // List 接口 18 | // 该接口只定义清楚各个方法的行为和表现 19 | type List[T any] interface { 20 | // Get 返回对应下标的元素, 21 | // 在下标超出范围的情况下,返回错误 22 | Get(index int) (T, error) 23 | // Append 在末尾追加元素 24 | Append(ts ...T) error 25 | // Add 在特定下标处增加一个新元素 26 | // 如果下标不在[0, Len()]范围之内 27 | // 应该返回错误 28 | // 如果index == Len()则表示往List末端增加一个值 29 | Add(index int, t T) error 30 | // Set 重置 index 位置的值 31 | // 如果下标超出范围,应该返回错误 32 | Set(index int, t T) error 33 | // Delete 删除目标元素的位置,并且返回该位置的值 34 | // 如果 index 超出下标,应该返回错误 35 | Delete(index int) (T, error) 36 | // Len 返回长度 37 | Len() int 38 | // Cap 返回容量 39 | Cap() int 40 | // Range 遍历 List 的所有元素 41 | Range(fn func(index int, t T) error) error 42 | // AsSlice 将 List 转化为一个切片 43 | // 不允许返回nil,在没有元素的情况下, 44 | // 必须返回一个长度和容量都为 0 的切片 45 | // AsSlice 每次调用都必须返回一个全新的切片 46 | AsSlice() []T 47 | } 48 | -------------------------------------------------------------------------------- /mapx/treemap_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ecodeclub/ekit" 21 | "github.com/ecodeclub/ekit/mapx" 22 | ) 23 | 24 | func ExampleNewTreeMap() { 25 | m, _ := mapx.NewTreeMap[int, int](ekit.ComparatorRealNumber[int]) 26 | _ = m.Put(1, 11) 27 | val, _ := m.Get(1) 28 | fmt.Println(val) 29 | // Output: 30 | // 11 31 | } 32 | 33 | func ExampleTreeMap_Iterate() { 34 | m, _ := mapx.NewTreeMap[int, int](ekit.ComparatorRealNumber[int]) 35 | _ = m.Put(1, 11) 36 | _ = m.Put(-1, 12) 37 | _ = m.Put(100, 13) 38 | _ = m.Put(-100, 14) 39 | _ = m.Put(-101, 15) 40 | 41 | m.Iterate(func(key, value int) bool { 42 | if key > 1 { 43 | return false 44 | } 45 | fmt.Println(key, value) 46 | return true 47 | }) 48 | 49 | // Output: 50 | // -101 15 51 | // -100 14 52 | // -1 12 53 | // 1 11 54 | } 55 | -------------------------------------------------------------------------------- /net/httpx/response_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpx 16 | 17 | import ( 18 | "io" 19 | "net/http" 20 | "testing" 21 | 22 | "github.com/ecodeclub/ekit/iox" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestResponse_JSONScan(t *testing.T) { 27 | testCases := []struct { 28 | name string 29 | resp *Response 30 | wantVal User 31 | wantErr error 32 | }{ 33 | { 34 | name: "scan成功", 35 | resp: &Response{ 36 | Response: &http.Response{ 37 | Body: io.NopCloser(iox.NewJSONReader(User{Name: "Tom"})), 38 | }, 39 | }, 40 | wantVal: User{ 41 | Name: "Tom", 42 | }, 43 | }, 44 | } 45 | 46 | for _, tc := range testCases { 47 | t.Run(tc.name, func(t *testing.T) { 48 | var u User 49 | err := tc.resp.JSONScan(&u) 50 | assert.Equal(t, tc.wantErr, err) 51 | assert.Equal(t, tc.wantVal, u) 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.github/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2021 ecodeclub 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # git test pre-push hook 16 | # 17 | # To use, store as .git/hooks/pre-push inside your repository and make sure 18 | # it has execute permissions. 19 | # 20 | # This script does not handle file names that contain spaces. 21 | 22 | # Pre-push configuration 23 | remote=$1 24 | url=$2 25 | echo >&2 "Try pushing $2 to $1" 26 | 27 | TEST="go test ./... -race -cover -failfast" 28 | LINTER="golangci-lint run" 29 | 30 | # Run test and return if failed 31 | printf "Running go test..." 32 | $TEST 33 | RESULT=$? 34 | if [ $RESULT -ne 0 ]; then 35 | echo >&2 "$TEST" 36 | echo >&2 "Check code to pass test." 37 | exit 1 38 | fi 39 | 40 | # Run linter and return if failed 41 | printf "Running go linter..." 42 | $LINTER 43 | RESULT=$? 44 | if [ $RESULT -ne 0 ]; then 45 | echo >&2 "$LINTER" 46 | echo >&2 "Check code to pass linter." 47 | exit 1 48 | fi 49 | 50 | exit 0 -------------------------------------------------------------------------------- /queue/priority_queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queue 16 | 17 | import ( 18 | "github.com/ecodeclub/ekit" 19 | "github.com/ecodeclub/ekit/internal/queue" 20 | ) 21 | 22 | type PriorityQueue[T any] struct { 23 | priorityQueue *queue.PriorityQueue[T] 24 | } 25 | 26 | func NewPriorityQueue[T any](capacity int, compare ekit.Comparator[T]) *PriorityQueue[T] { 27 | pq := &PriorityQueue[T]{} 28 | pq.priorityQueue = queue.NewPriorityQueue[T](capacity, compare) 29 | return pq 30 | } 31 | 32 | func (pq *PriorityQueue[T]) Len() int { 33 | return pq.priorityQueue.Len() 34 | } 35 | 36 | func (pq *PriorityQueue[T]) Peek() (T, error) { 37 | return pq.priorityQueue.Peek() 38 | } 39 | 40 | func (pq *PriorityQueue[T]) Enqueue(t T) error { 41 | return pq.priorityQueue.Enqueue(t) 42 | } 43 | 44 | func (pq *PriorityQueue[T]) Dequeue() (T, error) { 45 | return pq.priorityQueue.Dequeue() 46 | } 47 | -------------------------------------------------------------------------------- /sqlx/newnull.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sqlx 16 | 17 | import ( 18 | "database/sql" 19 | "time" 20 | ) 21 | 22 | //这一个系列的方法,会在数据为零值时,将valid设置为false;否则设置为true; 23 | 24 | func NewNullString(val string) sql.NullString { 25 | return sql.NullString{String: val, Valid: val != ""} 26 | } 27 | 28 | func NewNullInt64(val int64) sql.NullInt64 { 29 | return sql.NullInt64{Int64: val, Valid: val != 0} 30 | } 31 | 32 | func NewNullFloat64(val float64) sql.NullFloat64 { 33 | return sql.NullFloat64{Float64: val, Valid: val != 0} 34 | } 35 | 36 | func NewNullBool(val bool) sql.NullBool { 37 | return sql.NullBool{Bool: val, Valid: val} 38 | } 39 | 40 | func NewNullTime(val time.Time) sql.NullTime { 41 | return sql.NullTime{Time: val, Valid: !val.IsZero()} 42 | } 43 | 44 | func NewNullBytes(val []byte) sql.NullString { 45 | return sql.NullString{String: string(val), Valid: len(val) > 0} 46 | } 47 | -------------------------------------------------------------------------------- /set/treeset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package set 16 | 17 | import ( 18 | "github.com/ecodeclub/ekit" 19 | "github.com/ecodeclub/ekit/mapx" 20 | ) 21 | 22 | type TreeSet[T any] struct { 23 | treeMap *mapx.TreeMap[T, any] 24 | } 25 | 26 | func NewTreeSet[T any](compare ekit.Comparator[T]) (*TreeSet[T], error) { 27 | treeMap, err := mapx.NewTreeMap[T, any](compare) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &TreeSet[T]{ 32 | treeMap: treeMap, 33 | }, nil 34 | } 35 | 36 | func (s *TreeSet[T]) Add(key T) { 37 | _ = s.treeMap.Put(key, nil) 38 | } 39 | 40 | func (s *TreeSet[T]) Delete(key T) { 41 | s.treeMap.Delete(key) 42 | } 43 | 44 | func (s *TreeSet[T]) Exist(key T) bool { 45 | _, isExist := s.treeMap.Get(key) 46 | return isExist 47 | } 48 | 49 | // Keys 方法返回的元素顺序不固定 50 | func (s *TreeSet[T]) Keys() []T { 51 | return s.treeMap.Keys() 52 | } 53 | 54 | var _ Set[int] = (*TreeSet[int])(nil) 55 | -------------------------------------------------------------------------------- /internal/errs/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errs 16 | 17 | import ( 18 | "fmt" 19 | "time" 20 | ) 21 | 22 | // NewErrIndexOutOfRange 创建一个代表下标超出范围的错误 23 | func NewErrIndexOutOfRange(length int, index int) error { 24 | return fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", length, index) 25 | } 26 | 27 | // NewErrInvalidType 创建一个代表类型转换失败的错误 28 | func NewErrInvalidType(want string, got any) error { 29 | return fmt.Errorf("ekit: 类型转换失败,预期类型:%s, 实际值:%#v", want, got) 30 | } 31 | 32 | func NewErrInvalidIntervalValue(interval time.Duration) error { 33 | return fmt.Errorf("ekit: 无效的间隔时间 %d, 预期值应大于 0", interval) 34 | } 35 | 36 | func NewErrInvalidMaxIntervalValue(maxInterval, initialInterval time.Duration) error { 37 | return fmt.Errorf("ekit: 最大重试间隔的时间 [%d] 应大于等于初始重试的间隔时间 [%d] ", maxInterval, initialInterval) 38 | } 39 | 40 | func NewErrRetryExhausted(lastErr error) error { 41 | return fmt.Errorf("ekit: 超过最大重试次数,业务返回的最后一个 error %w", lastErr) 42 | } 43 | -------------------------------------------------------------------------------- /list/skip_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package list 16 | 17 | import ( 18 | "github.com/ecodeclub/ekit" 19 | "github.com/ecodeclub/ekit/internal/list" 20 | ) 21 | 22 | func NewSkipList[T any](compare ekit.Comparator[T]) *SkipList[T] { 23 | pq := &SkipList[T]{} 24 | pq.skiplist = list.NewSkipList[T](compare) 25 | return pq 26 | } 27 | 28 | type SkipList[T any] struct { 29 | skiplist *list.SkipList[T] 30 | } 31 | 32 | func (sl *SkipList[T]) Search(target T) bool { 33 | return sl.skiplist.Search(target) 34 | } 35 | 36 | func (sl *SkipList[T]) AsSlice() []T { 37 | return sl.skiplist.AsSlice() 38 | } 39 | 40 | func (sl *SkipList[T]) Len() int { 41 | return sl.skiplist.Len() 42 | } 43 | 44 | func (sl *SkipList[T]) Cap() int { 45 | return sl.Len() 46 | } 47 | 48 | func (sl *SkipList[T]) Insert(Val T) { 49 | sl.skiplist.Insert(Val) 50 | } 51 | 52 | func (sl *SkipList[T]) DeleteElement(target T) bool { 53 | return sl.skiplist.DeleteElement(target) 54 | } 55 | -------------------------------------------------------------------------------- /bean/copier/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package copier 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "reflect" 21 | ) 22 | 23 | var ( 24 | errConvertFieldTypeNotMatch = errors.New("ekit: 转化字段类型不匹配") 25 | ) 26 | 27 | // newErrTypeError copier 不支持的类型 28 | func newErrTypeError(typ reflect.Type) error { 29 | return fmt.Errorf("ekit: copier 入口只支持 Struct 不支持类型 %v, 种类 %v", typ, typ.Kind()) 30 | } 31 | 32 | // newErrKindNotMatchError 字段类型不匹配 33 | func newErrKindNotMatchError(src, dst reflect.Kind, field string) error { 34 | return fmt.Errorf("ekit: 字段 %s 的 Kind 不匹配, src: %v, dst: %v", field, src, dst) 35 | } 36 | 37 | // newErrTypeNotMatchError 字段不匹配 38 | func newErrTypeNotMatchError(src, dst reflect.Type, field string) error { 39 | return fmt.Errorf("ekit: 字段 %s 的 Type 不匹配, src: %v, dst: %v", field, src, dst) 40 | } 41 | 42 | // newErrMultiPointer 43 | func newErrMultiPointer(field string) error { 44 | return fmt.Errorf("ekit: 字段 %s 是多级指针", field) 45 | } 46 | -------------------------------------------------------------------------------- /retry/retry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retry 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "github.com/ecodeclub/ekit/internal/errs" 22 | ) 23 | 24 | // Retry 会在以下条件满足的情况下返回: 25 | // 1. 重试达到了最大次数,而后返回重试耗尽的错误 26 | // 2. ctx 被取消或者超时 27 | // 3. bizFunc 没有返回 error 28 | // 而只要 bizFunc 返回 error,就会尝试重试 29 | func Retry(ctx context.Context, 30 | s Strategy, 31 | bizFunc func() error) error { 32 | var ticker *time.Ticker 33 | defer func() { 34 | if ticker != nil { 35 | ticker.Stop() 36 | } 37 | }() 38 | for { 39 | err := bizFunc() 40 | // 直接退出 41 | if err == nil { 42 | return nil 43 | } 44 | duration, ok := s.Next() 45 | if !ok { 46 | return errs.NewErrRetryExhausted(err) 47 | } 48 | if ticker == nil { 49 | ticker = time.NewTicker(duration) 50 | } else { 51 | ticker.Reset(duration) 52 | } 53 | select { 54 | case <-ctx.Done(): 55 | // 超时或者被取消了,直接返回 56 | return ctx.Err() 57 | case <-ticker.C: 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /syncx/limit_pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import ( 18 | "sync/atomic" 19 | ) 20 | 21 | // LimitPool 是对 Pool 的简单封装允许用户通过控制一段时间内对Pool的令牌申请次数来间接控制Pool中对象的内存总占用量 22 | type LimitPool[T any] struct { 23 | pool *Pool[T] 24 | tokens *atomic.Int32 25 | } 26 | 27 | // NewLimitPool 创建一个 LimitPool 实例 28 | // maxTokens 表示一段时间内的允许发放的最大令牌数 29 | // factory 必须返回 T 类型的值,并且不能返回 nil 30 | func NewLimitPool[T any](maxTokens int, factory func() T) *LimitPool[T] { 31 | var tokens atomic.Int32 32 | tokens.Add(int32(maxTokens)) 33 | return &LimitPool[T]{ 34 | pool: NewPool[T](factory), 35 | tokens: &tokens, 36 | } 37 | } 38 | 39 | // Get 取出一个元素 40 | // 如果返回值是 true,则代表确实从 Pool 里面取出来了一个 41 | // 否则是新建了一个 42 | func (l *LimitPool[T]) Get() (T, bool) { 43 | if l.tokens.Add(-1) < 0 { 44 | l.tokens.Add(1) 45 | var zero T 46 | return zero, false 47 | } 48 | return l.pool.Get(), true 49 | } 50 | 51 | // Put 放回去一个元素 52 | func (l *LimitPool[T]) Put(t T) { 53 | l.pool.Put(t) 54 | l.tokens.Add(1) 55 | } 56 | -------------------------------------------------------------------------------- /iox/json_reader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iox 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestJSONReader(t *testing.T) { 24 | testCases := []struct { 25 | name string 26 | input []byte 27 | val any 28 | 29 | wantRes []byte 30 | wantN int 31 | wantErr error 32 | }{ 33 | { 34 | name: "正常读取", 35 | input: make([]byte, 10), 36 | wantN: 10, 37 | val: User{Name: "Tom"}, 38 | wantRes: []byte(`{"name":"T`), 39 | }, 40 | { 41 | name: "输入 nil", 42 | input: make([]byte, 7), 43 | wantN: 4, 44 | wantRes: append([]byte(`null`), 0, 0, 0), 45 | }, 46 | } 47 | 48 | for _, tc := range testCases { 49 | t.Run(tc.name, func(t *testing.T) { 50 | reader := NewJSONReader(tc.val) 51 | n, err := reader.Read(tc.input) 52 | assert.Equal(t, tc.wantErr, err) 53 | assert.Equal(t, tc.wantN, n) 54 | assert.Equal(t, tc.wantRes, tc.input) 55 | }) 56 | } 57 | } 58 | 59 | type User struct { 60 | Name string `json:"name"` 61 | } 62 | -------------------------------------------------------------------------------- /mapx/hashmap_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | import ( 18 | "fmt" 19 | "hash/crc32" 20 | "sort" 21 | ) 22 | 23 | type testStringData struct { 24 | data string 25 | } 26 | 27 | func (ts testStringData) Code() uint64 { 28 | return uint64(crc32.ChecksumIEEE([]byte(ts.data))) 29 | } 30 | 31 | func (ts testStringData) Equals(other any) bool { 32 | otherv, ok := other.(testStringData) 33 | if !ok { 34 | return false 35 | } 36 | return ts.data == otherv.data 37 | } 38 | 39 | func ExampleHashMap_Iterate() { 40 | hashMap := NewHashMap[testStringData, int](0) 41 | strArr := make([]string, 0) 42 | _ = hashMap.Put(testStringData{data: "hello"}, 1) 43 | _ = hashMap.Put(testStringData{data: "world"}, 2) 44 | _ = hashMap.Put(testStringData{data: "ekit"}, 3) 45 | 46 | hashMap.Iterate( 47 | func(key testStringData, val int) bool { 48 | strArr = append(strArr, key.data) 49 | return true 50 | }) 51 | 52 | sort.Strings(strArr) 53 | for _, s := range strArr { 54 | fmt.Println(s) 55 | } 56 | 57 | // Output: 58 | // ekit 59 | // hello 60 | // world 61 | } 62 | -------------------------------------------------------------------------------- /queue/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queue 16 | 17 | import ( 18 | "context" 19 | ) 20 | 21 | // BlockingQueue 阻塞队列 22 | // 参考 Queue 普通队列 23 | // 一个阻塞队列是否遵循 FIFO 取决于具体实现 24 | type BlockingQueue[T any] interface { 25 | // Enqueue 将元素放入队列。如果在 ctx 超时之前,队列有空闲位置,那么元素会被放入队列; 26 | // 否则返回 error。 27 | // 在超时或者调用者主动 cancel 的情况下,所有的实现都必须返回 ctx。 28 | // 调用者可以通过检查 error 是否为 context.DeadlineExceeded 29 | // 或者 context.Canceled 来判断入队失败的原因 30 | // 注意,调用者必须使用 errors.Is 来判断,而不能直接使用 == 31 | Enqueue(ctx context.Context, t T) error 32 | // Dequeue 从队首获得一个元素 33 | // 如果在 ctx 超时之前,队列中有元素,那么会返回队首的元素,否则返回 error。 34 | // 在超时或者调用者主动 cancel 的情况下,所有的实现都必须返回 ctx。 35 | // 调用者可以通过检查 error 是否为 context.DeadlineExceeded 36 | // 或者 context.Canceled 来判断入队失败的原因 37 | // 注意,调用者必须使用 errors.Is 来判断,而不能直接使用 == 38 | Dequeue(ctx context.Context) (T, error) 39 | } 40 | 41 | // Queue 普通队列 42 | // 参考 BlockingQueue 阻塞队列 43 | // 一个队列是否遵循 FIFO 取决于具体实现 44 | type Queue[T any] interface { 45 | // Enqueue 将元素放入队列,如果此时队列已经满了,那么返回错误 46 | Enqueue(t T) error 47 | // Dequeue 从队首获得一个元素 48 | // 如果此时队列里面没有元素,那么返回错误 49 | Dequeue() (T, error) 50 | } 51 | -------------------------------------------------------------------------------- /mapx/multi_map_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | import ( 18 | "fmt" 19 | "sort" 20 | 21 | "github.com/ecodeclub/ekit/tuple/pair" 22 | ) 23 | 24 | func ExampleMultiMap_Iterate() { 25 | multiMap := NewMultiHashMap[testStringData, int](0) 26 | arr := make([]pair.Pair[string, int], 0) 27 | _ = multiMap.Put(testStringData{data: "hello"}, 1) 28 | _ = multiMap.Put(testStringData{data: "world"}, 2) 29 | _ = multiMap.Put(testStringData{data: "world"}, 3) 30 | _ = multiMap.Put(testStringData{data: "world"}, 4) 31 | _ = multiMap.Put(testStringData{data: "ekit"}, 3) 32 | 33 | multiMap.Iterate( 34 | func(key testStringData, val int) bool { 35 | arr = append(arr, pair.NewPair(key.data, val)) 36 | return true 37 | }) 38 | 39 | sort.Slice(arr, func(i, j int) bool { 40 | if arr[i].Key == arr[j].Key { 41 | return arr[i].Value < arr[j].Value 42 | } 43 | return arr[i].Key < arr[j].Key 44 | }) 45 | 46 | for _, pa := range arr { 47 | fmt.Println(pa.Key, pa.Value) 48 | } 49 | // Output: 50 | // ekit 3 51 | // hello 1 52 | // world 2 53 | // world 3 54 | // world 4 55 | } 56 | -------------------------------------------------------------------------------- /iox/concurrent_multiple_bytes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iox 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | // ConcurrentMultipleBytes 是 MultipleBytes 的线程安全装饰器 22 | type ConcurrentMultipleBytes struct { 23 | mb *MultipleBytes 24 | lock sync.Mutex 25 | } 26 | 27 | // NewConcurrentMultipleBytes 创建一个新的线程安全的 MultipleBytes 实例 28 | // sliceCount 参数用于预分配内部切片数组的容量 29 | func NewConcurrentMultipleBytes(sliceCount int) *ConcurrentMultipleBytes { 30 | return &ConcurrentMultipleBytes{ 31 | mb: NewMultipleBytes(sliceCount), 32 | } 33 | } 34 | 35 | // Read 实现 io.Reader 接口 36 | // 从当前位置读取数据到 p 中,如果没有数据可读返回 io.EOF 37 | func (c *ConcurrentMultipleBytes) Read(p []byte) (n int, err error) { 38 | c.lock.Lock() 39 | defer c.lock.Unlock() 40 | return c.mb.Read(p) 41 | } 42 | 43 | // Write 实现 io.Writer 接口 44 | // 将 p 中的数据写入到内部缓冲区 45 | func (c *ConcurrentMultipleBytes) Write(p []byte) (n int, err error) { 46 | c.lock.Lock() 47 | defer c.lock.Unlock() 48 | return c.mb.Write(p) 49 | } 50 | 51 | // Reset 重置读取位置到开始处 52 | func (c *ConcurrentMultipleBytes) Reset() { 53 | c.lock.Lock() 54 | defer c.lock.Unlock() 55 | c.mb.Reset() 56 | } 57 | -------------------------------------------------------------------------------- /slice/symmetric_diff.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // SymmetricDiffSet 对称差集 18 | // 已去重 19 | // 返回值的元素顺序是不定的 20 | func SymmetricDiffSet[T comparable](src, dst []T) []T { 21 | srcMap, dstMap := toMap[T](src), toMap[T](dst) 22 | for k := range dstMap { 23 | if _, ok := srcMap[k]; ok { 24 | delete(srcMap, k) 25 | } else { 26 | srcMap[k] = struct{}{} 27 | } 28 | } 29 | 30 | res := make([]T, 0, len(srcMap)) 31 | for k := range srcMap { 32 | res = append(res, k) 33 | } 34 | 35 | return res 36 | } 37 | 38 | // SymmetricDiffSetFunc 对称差集 39 | // 你应该优先使用 SymmetricDiffSet 40 | // 已去重 41 | func SymmetricDiffSetFunc[T any](src, dst []T, equal equalFunc[T]) []T { 42 | res := []T{} 43 | 44 | //找出在src不在dst的元素 45 | for _, v := range src { 46 | if !ContainsFunc[T](dst, func(t T) bool { 47 | return equal(t, v) 48 | }) { 49 | res = append(res, v) 50 | } 51 | } 52 | 53 | //找出在dst不在src的元素 54 | for _, v := range dst { 55 | if !ContainsFunc[T](src, func(t T) bool { 56 | return equal(t, v) 57 | }) { 58 | res = append(res, v) 59 | } 60 | } 61 | 62 | return deduplicateFunc[T](res, equal) 63 | } 64 | -------------------------------------------------------------------------------- /retry/fixed_internal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retry 16 | 17 | import ( 18 | "sync/atomic" 19 | "time" 20 | 21 | "github.com/ecodeclub/ekit/internal/errs" 22 | ) 23 | 24 | var _ Strategy = (*FixedIntervalRetryStrategy)(nil) 25 | 26 | // FixedIntervalRetryStrategy 等间隔重试 27 | type FixedIntervalRetryStrategy struct { 28 | maxRetries int32 // 最大重试次数,如果是 0 或负数,表示无限重试 29 | interval time.Duration // 重试间隔时间 30 | retries int32 // 当前重试次数 31 | } 32 | 33 | func NewFixedIntervalRetryStrategy(interval time.Duration, maxRetries int32) (*FixedIntervalRetryStrategy, error) { 34 | if interval <= 0 { 35 | return nil, errs.NewErrInvalidIntervalValue(interval) 36 | } 37 | return &FixedIntervalRetryStrategy{ 38 | maxRetries: maxRetries, 39 | interval: interval, 40 | }, nil 41 | } 42 | 43 | func (s *FixedIntervalRetryStrategy) Next() (time.Duration, bool) { 44 | retries := atomic.AddInt32(&s.retries, 1) 45 | if s.maxRetries <= 0 || retries <= s.maxRetries { 46 | return s.interval, true 47 | } 48 | return 0, false 49 | } 50 | 51 | func (s *FixedIntervalRetryStrategy) Report(err error) Strategy { 52 | return s 53 | } 54 | -------------------------------------------------------------------------------- /net/httpx/log_round_trip_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpx 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "io" 21 | "net/http" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestLogRoundTrip(t *testing.T) { 28 | client := http.DefaultClient 29 | var acceptLog Log 30 | var acceptError error 31 | client.Transport = NewLogRoundTrip(&doNothingRoundTrip{}, func(l Log, err error) { 32 | acceptLog = l 33 | acceptError = err 34 | }) 35 | NewRequest(context.Background(), 36 | http.MethodGet, "http://localhost/test"). 37 | JSONBody(User{Name: "Tom"}). 38 | Client(client). 39 | Do() 40 | assert.Equal(t, nil, acceptError) 41 | assert.Equal(t, Log{ 42 | URL: "http://localhost/test", 43 | ReqBody: `{"Name":"Tom"}`, 44 | RespBody: "resp body", 45 | RespStatus: "200 OK", 46 | }, acceptLog) 47 | } 48 | 49 | type doNothingRoundTrip struct { 50 | } 51 | 52 | func (d *doNothingRoundTrip) RoundTrip(request *http.Request) (*http.Response, error) { 53 | return &http.Response{ 54 | Status: "200 OK", 55 | Body: io.NopCloser(bytes.NewBuffer([]byte("resp body"))), 56 | }, nil 57 | } 58 | -------------------------------------------------------------------------------- /sqlx/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sqlx 16 | 17 | import ( 18 | "database/sql/driver" 19 | "encoding/json" 20 | "fmt" 21 | ) 22 | 23 | // JsonColumn 代表存储字段的 json 类型 24 | // 主要用于没有提供默认 json 类型的数据库 25 | // T 可以是结构体,也可以是切片或者 map 26 | // 理论上来说一切可以被 json 库所处理的类型都能被用作 T 27 | // 不建议使用指针作为 T 的类型 28 | // 如果 T 是指针,那么在 Val 为 nil 的情况下,一定要把 Valid 设置为 false 29 | type JsonColumn[T any] struct { 30 | Val T 31 | Valid bool 32 | } 33 | 34 | // Value 返回一个 json 串。类型是 []byte 35 | func (j JsonColumn[T]) Value() (driver.Value, error) { 36 | if !j.Valid { 37 | return nil, nil 38 | } 39 | res, err := json.Marshal(j.Val) 40 | return res, err 41 | } 42 | 43 | // Scan 将 src 转化为对象 44 | // src 的类型必须是 []byte, string 或者 nil 45 | // 如果是 nil,我们不会做任何处理 46 | func (j *JsonColumn[T]) Scan(src any) error { 47 | var bs []byte 48 | switch val := src.(type) { 49 | case nil: 50 | return nil 51 | case []byte: 52 | bs = val 53 | case string: 54 | bs = []byte(val) 55 | default: 56 | return fmt.Errorf("ekit:JsonColumn.Scan 不支持 src 类型 %v", src) 57 | } 58 | 59 | if err := json.Unmarshal(bs, &j.Val); err != nil { 60 | return err 61 | } 62 | j.Valid = true 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /syncx/atomicx/atomic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package atomicx 16 | 17 | import "sync/atomic" 18 | 19 | // Value 是对 atomic.Value 的泛型封装 20 | // 相比直接使用 atomic.Value, 21 | // - Load 方法大概开销多了 0.5 ns 22 | // - Store 方法多了不到 2 ns 23 | // - Swap 方法多了 14 ns 24 | // - CompareAndSwap 在失败的情况下,会多 2 ns,成功的时候多了 0.3 ns 25 | // 使用 NewValue 或者 NewValueOf 来创建实例 26 | type Value[T any] struct { 27 | val atomic.Value 28 | } 29 | 30 | // NewValue 会创建一个 Value 对象,里面存放着 T 的零值 31 | // 注意,这个零值是带了类型的零值 32 | func NewValue[T any]() *Value[T] { 33 | var t T 34 | return NewValueOf[T](t) 35 | } 36 | 37 | // NewValueOf 会使用传入的值来创建一个 Value 对象 38 | func NewValueOf[T any](t T) *Value[T] { 39 | val := atomic.Value{} 40 | val.Store(t) 41 | return &Value[T]{ 42 | val: val, 43 | } 44 | } 45 | 46 | func (v *Value[T]) Load() (val T) { 47 | data := v.val.Load() 48 | val = data.(T) 49 | return 50 | } 51 | 52 | func (v *Value[T]) Store(val T) { 53 | v.val.Store(val) 54 | } 55 | 56 | func (v *Value[T]) Swap(new T) (old T) { 57 | data := v.val.Swap(new) 58 | old = data.(T) 59 | return 60 | } 61 | 62 | func (v *Value[T]) CompareAndSwap(old, new T) (swapped bool) { 63 | return v.val.CompareAndSwap(old, new) 64 | } 65 | -------------------------------------------------------------------------------- /condition_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ekit 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestIfThenElse(t *testing.T) { 26 | i := 7 27 | i = IfThenElse(false, i, 0) 28 | assert.Equal(t, i, 0) 29 | } 30 | 31 | func ExampleIfThenElse() { 32 | result := IfThenElse(true, "yes", "no") 33 | fmt.Println(result) 34 | 35 | // Output: 36 | // yes 37 | } 38 | 39 | func TestIfThenElseFunc(t *testing.T) { 40 | resp, err := IfThenElseFunc(true, func() (int, error) { 41 | return 0, nil 42 | }, func() (int, error) { 43 | return 1, errors.New("some error") 44 | }) 45 | assert.NoError(t, err) 46 | assert.Equal(t, resp, 0) 47 | } 48 | 49 | func ExampleIfThenElseFunc() { 50 | code, err := IfThenElseFunc(false, func() (code int, err error) { 51 | // do something when condition is true 52 | // ... 53 | return 0, nil 54 | }, func() (code int, err error) { 55 | // do something when condition is false 56 | // ... 57 | return 1, errors.New("some error when execute func2") 58 | }) 59 | fmt.Println(code) 60 | fmt.Println(err) 61 | 62 | // Output: 63 | // 1 64 | // some error when execute func2 65 | } 66 | -------------------------------------------------------------------------------- /pool/arena_pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build goexperiment.arenas 16 | 17 | package pool 18 | 19 | import ( 20 | "arena" 21 | "sync" 22 | ) 23 | 24 | type ArenaPool[T any] struct { 25 | chain []*Arena[T] 26 | mutex sync.RWMutex 27 | } 28 | 29 | func NewArenaPool[T any]() *ArenaPool[T] { 30 | return &ArenaPool[T]{} 31 | } 32 | 33 | func (a *ArenaPool[T]) Get() (*Arena[T], error) { 34 | a.mutex.RLock() 35 | l := len(a.chain) 36 | a.mutex.RUnlock() 37 | if l == 0 { 38 | mem := arena.NewArena() 39 | obj := arena.New[T](mem) 40 | return &Arena[T]{arena: mem, obj: obj}, nil 41 | } 42 | a.mutex.Lock() 43 | defer a.mutex.Unlock() 44 | l = len(a.chain) 45 | if l == 0 { 46 | mem := arena.NewArena() 47 | obj := arena.New[T](mem) 48 | return &Arena[T]{arena: mem, obj: obj}, nil 49 | } 50 | ret := a.chain[l-1] 51 | a.chain = a.chain[:l-1] 52 | return ret, nil 53 | } 54 | 55 | func (a *ArenaPool[T]) Put(X *Arena[T]) error { 56 | a.mutex.Lock() 57 | defer a.mutex.Unlock() 58 | a.chain = append(a.chain, X) 59 | return nil 60 | } 61 | 62 | // Arena 二次封装 63 | // 将来支持缩容,或者淘汰空闲很久的 Arena 64 | type Arena[T any] struct { 65 | arena *arena.Arena 66 | obj *T 67 | } 68 | 69 | // Obj 返回已有的对象 70 | func (b *Arena[T]) Obj() *T { 71 | return b.obj 72 | } 73 | -------------------------------------------------------------------------------- /mapx/builtin_map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | // builtinMap 是对 map 的二次封装 18 | // 主要用于各种装饰器模式中被装饰的那个 19 | type builtinMap[K comparable, V any] struct { 20 | data map[K]V 21 | } 22 | 23 | func (b *builtinMap[K, V]) Put(key K, val V) error { 24 | b.data[key] = val 25 | return nil 26 | } 27 | 28 | func (b *builtinMap[K, V]) Get(key K) (V, bool) { 29 | val, ok := b.data[key] 30 | return val, ok 31 | } 32 | 33 | func (b *builtinMap[K, V]) Delete(k K) (V, bool) { 34 | v, ok := b.data[k] 35 | delete(b.data, k) 36 | return v, ok 37 | } 38 | 39 | // Keys 返回的 key 是随机的。即便对于同一个实例,调用两次,得到的结果都可能不同。 40 | func (b *builtinMap[K, V]) Keys() []K { 41 | return Keys[K, V](b.data) 42 | } 43 | 44 | func (b *builtinMap[K, V]) Values() []V { 45 | return Values[K, V](b.data) 46 | } 47 | 48 | // Iterate 按照随机顺序遍历, 并对每个键值对执行cb(k, v) 49 | // 如果cb的返回值为 true 则继续遍历,否则遍历结束 50 | func (b *builtinMap[K, V]) Iterate(cb func(key K, val V) bool) { 51 | for k, v := range b.data { 52 | if !cb(k, v) { 53 | break 54 | } 55 | } 56 | } 57 | 58 | func newBuiltinMap[K comparable, V any](capacity int) *builtinMap[K, V] { 59 | return &builtinMap[K, V]{ 60 | data: make(map[K]V, capacity), 61 | } 62 | } 63 | 64 | func (b *builtinMap[K, V]) Len() int64 { 65 | return int64(len(b.data)) 66 | } 67 | -------------------------------------------------------------------------------- /internal/slice/shrink_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestShrink(t *testing.T) { 24 | testCases := []struct { 25 | name string 26 | originCap int 27 | enqueueLoop int 28 | expectCap int 29 | }{ 30 | { 31 | name: "小于64", 32 | originCap: 32, 33 | enqueueLoop: 6, 34 | expectCap: 32, 35 | }, 36 | { 37 | name: "小于2048, 不足1/4", 38 | originCap: 1000, 39 | enqueueLoop: 20, 40 | expectCap: 500, 41 | }, 42 | { 43 | name: "小于2048, 超过1/4", 44 | originCap: 1000, 45 | enqueueLoop: 400, 46 | expectCap: 1000, 47 | }, 48 | { 49 | name: "大于2048,不足一半", 50 | originCap: 3000, 51 | enqueueLoop: 60, 52 | expectCap: 1875, 53 | }, 54 | { 55 | name: "大于2048,大于一半", 56 | originCap: 3000, 57 | enqueueLoop: 2000, 58 | expectCap: 3000, 59 | }, 60 | } 61 | for _, tc := range testCases { 62 | t.Run(tc.name, func(t *testing.T) { 63 | l := make([]int, 0, tc.originCap) 64 | 65 | for i := 0; i < tc.enqueueLoop; i++ { 66 | l = append(l, i) 67 | } 68 | l = Shrink[int](l) 69 | assert.Equal(t, tc.expectCap, cap(l)) 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /syncx/atomicx/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package atomicx 16 | 17 | import "fmt" 18 | 19 | func ExampleNewValue() { 20 | val := NewValue[int]() 21 | data := val.Load() 22 | fmt.Println(data) 23 | // Output: 24 | // 0 25 | } 26 | 27 | func ExampleNewValueOf() { 28 | val := NewValueOf[int](123) 29 | data := val.Load() 30 | fmt.Println(data) 31 | // Output: 32 | // 123 33 | } 34 | 35 | func ExampleValue_Load() { 36 | val := NewValueOf[int](123) 37 | data := val.Load() 38 | fmt.Println(data) 39 | // Output: 40 | // 123 41 | } 42 | 43 | func ExampleValue_Store() { 44 | val := NewValueOf[int](123) 45 | data := val.Load() 46 | fmt.Println(data) 47 | val.Store(456) 48 | data = val.Load() 49 | fmt.Println(data) 50 | // Output: 51 | // 123 52 | // 456 53 | } 54 | 55 | func ExampleValue_Swap() { 56 | val := NewValueOf[int](123) 57 | oldVal := val.Swap(456) 58 | newVal := val.Load() 59 | fmt.Printf("old: %d, new: %d", oldVal, newVal) 60 | // Output: 61 | // old: 123, new: 456 62 | } 63 | 64 | func ExampleValue_CompareAndSwap() { 65 | val := NewValueOf[int](123) 66 | swapped := val.CompareAndSwap(123, 456) 67 | fmt.Println(swapped) 68 | 69 | swapped = val.CompareAndSwap(455, 459) 70 | fmt.Println(swapped) 71 | // Output: 72 | // true 73 | // false 74 | } 75 | -------------------------------------------------------------------------------- /syncx/segment_key_lock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | // 通过 TryLock 和 TryRLock 来判定加锁问题 24 | // 也就是只判定我们拿到了正确的锁,但是没有判定并发与互斥 25 | 26 | // TestNewSegmentKeysLock_Lock 测试 Lock, UnLock 和 TryLock 27 | func TestNewSegmentKeysLock_Lock(t *testing.T) { 28 | l := NewSegmentKeysLock(8) 29 | key1 := "key1" 30 | l.Lock(key1) 31 | // 必然加锁失败 32 | assert.False(t, l.TryLock(key1)) 33 | // 读锁也失败 34 | assert.False(t, l.TryRLock(key1)) 35 | key2 := "key2" 36 | // 加锁成功 37 | assert.True(t, l.TryLock(key2)) 38 | // 解锁不会触发 panic 39 | defer l.Unlock(key2) 40 | 41 | // 释放锁 42 | l.Unlock(key1) 43 | // 此时应该预期自己可以再次加锁 44 | assert.True(t, l.TryLock(key1)) 45 | } 46 | 47 | func TestNewSegmentKeysLock_RLock(t *testing.T) { 48 | l := NewSegmentKeysLock(8) 49 | key1, key2 := "key1", "key2" 50 | l.RLock(key1) 51 | // 必然加锁失败 52 | assert.False(t, l.TryLock(key1)) 53 | // 读锁可以成功 54 | assert.True(t, l.TryRLock(key1)) 55 | // 加锁成功 56 | assert.True(t, l.TryRLock(key2)) 57 | // 解锁不会触发 panic 58 | defer l.RUnlock(key2) 59 | 60 | // 释放读锁 61 | l.RUnlock(key1) 62 | // 此时还有一个读锁没有释放 63 | assert.False(t, l.TryLock(key1)) 64 | // 再次释放读锁 65 | l.RUnlock(key1) 66 | assert.True(t, l.TryLock(key1)) 67 | } 68 | -------------------------------------------------------------------------------- /syncx/limit_pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import ( 18 | "bytes" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestLimitPool(t *testing.T) { 26 | 27 | expectedMaxAttempts := 3 28 | expectedVal := []byte("A") 29 | 30 | pool := NewLimitPool(expectedMaxAttempts, func() []byte { 31 | var buffer bytes.Buffer 32 | buffer.Write(expectedVal) 33 | return buffer.Bytes() 34 | }) 35 | 36 | var wg sync.WaitGroup 37 | bufChan := make(chan []byte, expectedMaxAttempts) 38 | 39 | // 从Pool中并发获取缓冲区 40 | for i := 0; i < expectedMaxAttempts; i++ { 41 | wg.Add(1) 42 | go func() { 43 | defer wg.Done() 44 | 45 | buf, ok := pool.Get() 46 | assert.True(t, ok) 47 | assert.NotZero(t, buf) 48 | assert.Equal(t, string(expectedVal), string(buf)) 49 | 50 | bufChan <- buf 51 | }() 52 | } 53 | 54 | wg.Wait() 55 | close(bufChan) 56 | 57 | // 超过最大申请次数返回零值 58 | val, ok := pool.Get() 59 | assert.False(t, ok) 60 | assert.Zero(t, val) 61 | 62 | // 归还一个 63 | pool.Put(<-bufChan) 64 | 65 | // 再次申请仍可以拿到非零值缓冲区 66 | val, ok = pool.Get() 67 | assert.True(t, ok) 68 | assert.NotZero(t, string(expectedVal), string(val)) 69 | 70 | // 超过最大申请次数返回零值 71 | val, ok = pool.Get() 72 | assert.False(t, ok) 73 | assert.Zero(t, val) 74 | } 75 | -------------------------------------------------------------------------------- /net/httpx/log_round_trip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpx 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "net/http" 21 | ) 22 | 23 | type LogRoundTrip struct { 24 | delegate http.RoundTripper 25 | // l 绝对不会为 nil 26 | log func(l Log, err error) 27 | } 28 | 29 | func NewLogRoundTrip(rp http.RoundTripper, log func(l Log, err error)) *LogRoundTrip { 30 | return &LogRoundTrip{ 31 | delegate: rp, 32 | log: log, 33 | } 34 | } 35 | 36 | func (l *LogRoundTrip) RoundTrip(request *http.Request) (resp *http.Response, err error) { 37 | log := Log{ 38 | URL: request.URL.String(), 39 | } 40 | defer func() { 41 | if resp != nil { 42 | log.RespStatus = resp.Status 43 | if resp.Body != nil { 44 | // 出现 error 了这里也不知道怎么处理,暂时忽略 45 | body, _ := io.ReadAll(resp.Body) 46 | resp.Body = io.NopCloser(bytes.NewReader(body)) 47 | log.RespBody = string(body) 48 | } 49 | } 50 | l.log(log, err) 51 | }() 52 | if request.Body != nil { 53 | // 出现 error 了这里也不知道怎么处理,暂时忽略 54 | body, _ := io.ReadAll(request.Body) 55 | request.Body = io.NopCloser(bytes.NewReader(body)) 56 | log.ReqBody = string(body) 57 | } 58 | resp, err = l.delegate.RoundTrip(request) 59 | return 60 | } 61 | 62 | type Log struct { 63 | URL string 64 | ReqBody string 65 | RespBody string 66 | RespStatus string 67 | } 68 | -------------------------------------------------------------------------------- /queue/concurrent_priority_queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queue 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/ecodeclub/ekit" 21 | "github.com/ecodeclub/ekit/internal/queue" 22 | ) 23 | 24 | type ConcurrentPriorityQueue[T any] struct { 25 | pq queue.PriorityQueue[T] 26 | m sync.RWMutex 27 | } 28 | 29 | func (c *ConcurrentPriorityQueue[T]) Len() int { 30 | c.m.RLock() 31 | defer c.m.RUnlock() 32 | return c.pq.Len() 33 | } 34 | 35 | // Cap 无界队列返回0,有界队列返回创建队列时设置的值 36 | func (c *ConcurrentPriorityQueue[T]) Cap() int { 37 | c.m.RLock() 38 | defer c.m.RUnlock() 39 | return c.pq.Cap() 40 | } 41 | 42 | func (c *ConcurrentPriorityQueue[T]) Peek() (T, error) { 43 | c.m.RLock() 44 | defer c.m.RUnlock() 45 | return c.pq.Peek() 46 | } 47 | 48 | func (c *ConcurrentPriorityQueue[T]) Enqueue(t T) error { 49 | c.m.Lock() 50 | defer c.m.Unlock() 51 | return c.pq.Enqueue(t) 52 | } 53 | 54 | func (c *ConcurrentPriorityQueue[T]) Dequeue() (T, error) { 55 | c.m.Lock() 56 | defer c.m.Unlock() 57 | return c.pq.Dequeue() 58 | } 59 | 60 | // NewConcurrentPriorityQueue 创建优先队列 capacity <= 0 时,为无界队列 61 | func NewConcurrentPriorityQueue[T any](capacity int, compare ekit.Comparator[T]) *ConcurrentPriorityQueue[T] { 62 | return &ConcurrentPriorityQueue[T]{ 63 | pq: *queue.NewPriorityQueue[T](capacity, compare), 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /slice/add_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | 21 | "github.com/ecodeclub/ekit/internal/errs" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestAdd(t *testing.T) { 27 | // Add 主要依赖于 internal/slice.Add 来保证正确性 28 | testCases := []struct { 29 | name string 30 | slice []int 31 | addVal int 32 | index int 33 | wantSlice []int 34 | wantErr error 35 | }{ 36 | { 37 | name: "index 0", 38 | slice: []int{123, 100}, 39 | addVal: 233, 40 | index: 0, 41 | wantSlice: []int{233, 123, 100}, 42 | }, 43 | { 44 | name: "index -1", 45 | slice: []int{123, 100}, 46 | index: -1, 47 | wantErr: errs.NewErrIndexOutOfRange(2, -1), 48 | }, 49 | } 50 | 51 | for _, tc := range testCases { 52 | t.Run(tc.name, func(t *testing.T) { 53 | res, err := Add(tc.slice, tc.addVal, tc.index) 54 | assert.Equal(t, tc.wantErr, err) 55 | if err != nil { 56 | return 57 | } 58 | assert.Equal(t, tc.wantSlice, res) 59 | }) 60 | } 61 | } 62 | 63 | func ExampleAdd() { 64 | res, _ := Add[int]([]int{1, 2, 3, 4}, 233, 2) 65 | fmt.Println(res) 66 | _, err := Add[int]([]int{1, 2, 3, 4}, 233, -1) 67 | fmt.Println(err) 68 | // Output: 69 | // [1 2 233 3 4] 70 | // ekit: 下标超出范围,长度 4, 下标 -1 71 | } 72 | -------------------------------------------------------------------------------- /internal/queue/priority_queue_benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queue 16 | 17 | import "testing" 18 | 19 | func BenchmarkPriorityQueue_NoCapacity_Enqueue(b *testing.B) { 20 | n := 300000 21 | pq := priorityQueueOf(-1, []int{}, compare()) 22 | b.ResetTimer() 23 | for i := n; i > 0; i-- { 24 | if pq.Enqueue(i) != nil { 25 | b.Fail() 26 | } 27 | } 28 | } 29 | 30 | func BenchmarkPriorityQueue_NoCapacity_Dequeue(b *testing.B) { 31 | n := 300000 32 | pq := priorityQueueOf(-1, []int{}, compare()) 33 | for i := n; i > 0; i-- { 34 | if pq.Enqueue(i) != nil { 35 | b.Fail() 36 | } 37 | } 38 | b.ResetTimer() 39 | for i := 0; i < n; i++ { 40 | _, err := pq.Dequeue() 41 | if err != nil { 42 | b.Fail() 43 | } 44 | } 45 | } 46 | 47 | func BenchmarkPriorityQueue_Capacity_Enqueue(b *testing.B) { 48 | n := 300000 49 | pq := priorityQueueOf(n, []int{}, compare()) 50 | b.ResetTimer() 51 | for i := n; i > 0; i-- { 52 | if pq.Enqueue(i) != nil { 53 | b.Fail() 54 | } 55 | } 56 | } 57 | 58 | func BenchmarkPriorityQueue_Capacity_Dequeue(b *testing.B) { 59 | n := 300000 60 | pq := priorityQueueOf(n, []int{}, compare()) 61 | for i := n; i > 0; i-- { 62 | if pq.Enqueue(i) != nil { 63 | b.Fail() 64 | } 65 | } 66 | b.ResetTimer() 67 | for i := 0; i < n; i++ { 68 | _, err := pq.Dequeue() 69 | if err != nil { 70 | b.Fail() 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tree/red_black_tree.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tree 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/ecodeclub/ekit" 21 | "github.com/ecodeclub/ekit/internal/tree" 22 | ) 23 | 24 | var ( 25 | errRBTreeComparatorIsNull = errors.New("ekit: RBTree 的 Comparator 不能为 nil") 26 | ) 27 | 28 | // RBTree 简单的封装一下红黑树 29 | type RBTree[K any, V any] struct { 30 | rbTree *tree.RBTree[K, V] //红黑树本体 31 | } 32 | 33 | func NewRBTree[K any, V any](compare ekit.Comparator[K]) (*RBTree[K, V], error) { 34 | if nil == compare { 35 | return nil, errRBTreeComparatorIsNull 36 | } 37 | 38 | return &RBTree[K, V]{ 39 | rbTree: tree.NewRBTree[K, V](compare), 40 | }, nil 41 | } 42 | 43 | // Add 增加节点 44 | func (rb *RBTree[K, V]) Add(key K, value V) error { 45 | return rb.rbTree.Add(key, value) 46 | } 47 | 48 | // Delete 删除节点 49 | func (rb *RBTree[K, V]) Delete(key K) (V, bool) { 50 | return rb.rbTree.Delete(key) 51 | } 52 | 53 | // Set 修改节点 54 | func (rb *RBTree[K, V]) Set(key K, value V) error { 55 | return rb.rbTree.Set(key, value) 56 | } 57 | 58 | // Find 查找节点 59 | func (rb *RBTree[K, V]) Find(key K) (V, error) { 60 | return rb.rbTree.Find(key) 61 | } 62 | 63 | // Size 返回红黑树结点个数 64 | func (rb *RBTree[K, V]) Size() int { 65 | return rb.rbTree.Size() 66 | } 67 | 68 | // KeyValues 获取红黑树所有节点K,V 69 | func (rb *RBTree[K, V]) KeyValues() ([]K, []V) { 70 | return rb.rbTree.KeyValues() 71 | } 72 | -------------------------------------------------------------------------------- /slice/index.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // Index 返回和 dst 相等的第一个元素下标 18 | // -1 表示没找到 19 | func Index[T comparable](src []T, dst T) int { 20 | return IndexFunc[T](src, func(src T) bool { 21 | return src == dst 22 | }) 23 | } 24 | 25 | // IndexFunc 返回 match 返回 true 的第一个下标 26 | // -1 表示没找到 27 | // 你应该优先使用 Index 28 | func IndexFunc[T any](src []T, match matchFunc[T]) int { 29 | for k, v := range src { 30 | if match(v) { 31 | return k 32 | } 33 | } 34 | return -1 35 | } 36 | 37 | // LastIndex 返回和 dst 相等的最后一个元素下标 38 | // -1 表示没找到 39 | func LastIndex[T comparable](src []T, dst T) int { 40 | return LastIndexFunc[T](src, func(src T) bool { 41 | return src == dst 42 | }) 43 | } 44 | 45 | // LastIndexFunc 返回和 dst 相等的最后一个元素下标 46 | // -1 表示没找到 47 | // 你应该优先使用 LastIndex 48 | func LastIndexFunc[T any](src []T, match matchFunc[T]) int { 49 | for i := len(src) - 1; i >= 0; i-- { 50 | if match(src[i]) { 51 | return i 52 | } 53 | } 54 | return -1 55 | } 56 | 57 | // IndexAll 返回和 dst 相等的所有元素的下标 58 | func IndexAll[T comparable](src []T, dst T) []int { 59 | return IndexAllFunc[T](src, func(src T) bool { 60 | return src == dst 61 | }) 62 | } 63 | 64 | // IndexAllFunc 返回和 match 返回 true 的所有元素的下标 65 | // 你应该优先使用 IndexAll 66 | func IndexAllFunc[T any](src []T, match matchFunc[T]) []int { 67 | var indexes = make([]int, 0, len(src)) 68 | for k, v := range src { 69 | if match(v) { 70 | indexes = append(indexes, k) 71 | } 72 | } 73 | return indexes 74 | } 75 | -------------------------------------------------------------------------------- /pool/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pool 16 | 17 | import ( 18 | "context" 19 | "time" 20 | ) 21 | 22 | // TaskPool 任务池 23 | type TaskPool interface { 24 | // Submit 执行一个任务 25 | // 如果任务池提供了阻塞的功能,那么如果在 ctx 过期都没有提交成功,那么应该返回错误 26 | // 调用 Start 之后能否继续提交任务,则取决于具体的实现 27 | // 调用 Shutdown 或者 ShutdownNow 之后提交任务都会返回错误 28 | Submit(ctx context.Context, task Task) error 29 | 30 | // Start 开始调度任务执行。在调用 Start 之前,所有的任务都不会被调度执行。 31 | // Start 之后,能否继续调用 Submit 提交任务,取决于具体的实现 32 | Start() error 33 | 34 | // Shutdown 关闭任务池。如果此时尚未调用 Start 方法,那么将会立刻返回。 35 | // 任务池将会停止接收新的任务,但是会继续执行剩下的任务, 36 | // 在所有任务执行完毕之后,用户可以从返回的 chan 中得到通知 37 | // 任务池在发出通知之后会关闭 chan struct{} 38 | Shutdown() (<-chan struct{}, error) 39 | 40 | // ShutdownNow 立刻关闭线程池 41 | // 任务池能否中断当前正在执行的任务,取决于 TaskPool 的具体实现,以及 Task 的具体实现 42 | // 该方法会返回所有剩下的任务,剩下的任务是否包含正在执行的任务,也取决于具体的实现 43 | ShutdownNow() ([]Task, error) 44 | 45 | // States 暴露 TaskPool 生命周期内的运行状态 46 | // ctx 是让用户来控制什么时候退出采样。那么最基本的两个退出机制:一个是 ctx 被 cancel 了或者超时了,一个是TaskPool 被关闭了 47 | // error 仅仅表示创建 chan state 是否成功 48 | // interval 表示获取TaskPool运行期间内部状态的周期/时间间隔 49 | States(ctx context.Context, interval time.Duration) (<-chan State, error) 50 | } 51 | 52 | // Task 代表一个任务 53 | type Task interface { 54 | // Run 执行任务 55 | // 如果 ctx 设置了超时时间,那么实现者需要自己决定是否进行超时控制 56 | Run(ctx context.Context) error 57 | } 58 | 59 | type State struct { 60 | PoolState int32 61 | GoCnt int32 62 | WaitingTasksCnt int 63 | QueueSize int 64 | RunningTasksCnt int32 65 | Timestamp int64 66 | } 67 | -------------------------------------------------------------------------------- /syncx/segment_key_lock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import ( 18 | "hash/fnv" 19 | "sync" 20 | ) 21 | 22 | // SegmentKeysLock 部分key lock结构定义 23 | type SegmentKeysLock struct { 24 | locks []*sync.RWMutex 25 | size uint32 26 | } 27 | 28 | // NewSegmentKeysLock 创建 SegmentKeysLock 示例 29 | func NewSegmentKeysLock(size uint32) *SegmentKeysLock { 30 | locks := make([]*sync.RWMutex, size) 31 | for i := range locks { 32 | locks[i] = &sync.RWMutex{} 33 | } 34 | return &SegmentKeysLock{ 35 | locks: locks, 36 | size: size, 37 | } 38 | } 39 | 40 | // hash 索引锁的hash函数 41 | func (s *SegmentKeysLock) hash(key string) uint32 { 42 | h := fnv.New32a() 43 | _, _ = h.Write([]byte(key)) 44 | return h.Sum32() 45 | } 46 | 47 | // RLock 读锁加锁 48 | func (s *SegmentKeysLock) RLock(key string) { 49 | s.getLock(key).RLock() 50 | } 51 | 52 | // TryRLock 试着加读锁,加锁成功会返回 53 | func (s *SegmentKeysLock) TryRLock(key string) bool { 54 | return s.getLock(key).TryRLock() 55 | } 56 | 57 | // RUnlock 读锁解锁 58 | func (s *SegmentKeysLock) RUnlock(key string) { 59 | s.getLock(key).RUnlock() 60 | } 61 | 62 | // Lock 写锁加锁 63 | func (s *SegmentKeysLock) Lock(key string) { 64 | s.getLock(key).Lock() 65 | } 66 | 67 | // TryLock 试着加锁,加锁成功会返回 true 68 | func (s *SegmentKeysLock) TryLock(key string) bool { 69 | return s.getLock(key).TryLock() 70 | } 71 | 72 | // Unlock 写锁解锁 73 | func (s *SegmentKeysLock) Unlock(key string) { 74 | s.getLock(key).Unlock() 75 | } 76 | 77 | func (s *SegmentKeysLock) getLock(key string) *sync.RWMutex { 78 | hash := s.hash(key) 79 | return s.locks[hash%s.size] 80 | } 81 | -------------------------------------------------------------------------------- /internal/slice/delete_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/ecodeclub/ekit/internal/errs" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestDelete(t *testing.T) { 25 | testCases := []struct { 26 | name string 27 | slice []int 28 | index int 29 | wantSlice []int 30 | wantVal int 31 | wantErr error 32 | }{ 33 | { 34 | name: "index 0", 35 | slice: []int{123, 100}, 36 | index: 0, 37 | wantSlice: []int{100}, 38 | wantVal: 123, 39 | }, 40 | { 41 | name: "index middle", 42 | slice: []int{123, 124, 125}, 43 | index: 1, 44 | wantSlice: []int{123, 125}, 45 | wantVal: 124, 46 | }, 47 | { 48 | name: "index out of range", 49 | slice: []int{123, 100}, 50 | index: 12, 51 | wantErr: errs.NewErrIndexOutOfRange(2, 12), 52 | }, 53 | { 54 | name: "index less than 0", 55 | slice: []int{123, 100}, 56 | index: -1, 57 | wantErr: errs.NewErrIndexOutOfRange(2, -1), 58 | }, 59 | { 60 | name: "index last", 61 | slice: []int{123, 100, 101, 102, 102, 102}, 62 | index: 5, 63 | wantSlice: []int{123, 100, 101, 102, 102}, 64 | wantVal: 102, 65 | }, 66 | } 67 | 68 | for _, tc := range testCases { 69 | t.Run(tc.name, func(t *testing.T) { 70 | res, val, err := Delete(tc.slice, tc.index) 71 | assert.Equal(t, tc.wantErr, err) 72 | if err != nil { 73 | return 74 | } 75 | assert.Equal(t, tc.wantSlice, res) 76 | assert.Equal(t, tc.wantVal, val) 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /list/concurrent_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package list 16 | 17 | import "sync" 18 | 19 | var ( 20 | _ List[any] = &ConcurrentList[any]{} 21 | ) 22 | 23 | // ConcurrentList 用读写锁封装了对 List 的操作 24 | // 达到线程安全的目标 25 | type ConcurrentList[T any] struct { 26 | List[T] 27 | lock sync.RWMutex 28 | } 29 | 30 | func (c *ConcurrentList[T]) Get(index int) (T, error) { 31 | c.lock.RLock() 32 | defer c.lock.RUnlock() 33 | return c.List.Get(index) 34 | } 35 | 36 | func (c *ConcurrentList[T]) Append(ts ...T) error { 37 | c.lock.Lock() 38 | defer c.lock.Unlock() 39 | return c.List.Append(ts...) 40 | } 41 | 42 | func (c *ConcurrentList[T]) Add(index int, t T) error { 43 | c.lock.Lock() 44 | defer c.lock.Unlock() 45 | return c.List.Add(index, t) 46 | } 47 | 48 | func (c *ConcurrentList[T]) Set(index int, t T) error { 49 | c.lock.Lock() 50 | defer c.lock.Unlock() 51 | return c.List.Set(index, t) 52 | } 53 | 54 | func (c *ConcurrentList[T]) Delete(index int) (T, error) { 55 | c.lock.Lock() 56 | defer c.lock.Unlock() 57 | return c.List.Delete(index) 58 | } 59 | 60 | func (c *ConcurrentList[T]) Len() int { 61 | c.lock.RLock() 62 | defer c.lock.RUnlock() 63 | return c.List.Len() 64 | } 65 | 66 | func (c *ConcurrentList[T]) Cap() int { 67 | c.lock.RLock() 68 | defer c.lock.RUnlock() 69 | return c.List.Cap() 70 | } 71 | 72 | func (c *ConcurrentList[T]) Range(fn func(index int, t T) error) error { 73 | c.lock.RLock() 74 | defer c.lock.RUnlock() 75 | return c.List.Range(fn) 76 | } 77 | 78 | func (c *ConcurrentList[T]) AsSlice() []T { 79 | c.lock.RLock() 80 | defer c.lock.RUnlock() 81 | return c.List.AsSlice() 82 | } 83 | -------------------------------------------------------------------------------- /net/httpx/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpx 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "net/http" 21 | 22 | "github.com/ecodeclub/ekit/iox" 23 | ) 24 | 25 | type Request struct { 26 | req *http.Request 27 | err error 28 | client *http.Client 29 | } 30 | 31 | func NewRequest(ctx context.Context, method, url string) *Request { 32 | req, err := http.NewRequestWithContext(ctx, method, url, nil) 33 | return &Request{ 34 | req: req, 35 | err: err, 36 | client: http.DefaultClient, 37 | } 38 | } 39 | 40 | // JSONBody 使用 JSON body 41 | func (req *Request) JSONBody(val any) *Request { 42 | if req.err != nil { 43 | return req 44 | } 45 | req.req.Body = io.NopCloser(iox.NewJSONReader(val)) 46 | req.req.Header.Set("Content-Type", "application/json") 47 | return req 48 | } 49 | 50 | func (req *Request) Client(cli *http.Client) *Request { 51 | req.client = cli 52 | return req 53 | } 54 | 55 | func (req *Request) AddHeader(key string, value string) *Request { 56 | if req.err != nil { 57 | return req 58 | } 59 | req.req.Header.Add(key, value) 60 | return req 61 | } 62 | 63 | // AddParam 添加查询参数 64 | // 这个方法性能不好,但是好用 65 | func (req *Request) AddParam(key string, value string) *Request { 66 | if req.err != nil { 67 | return req 68 | } 69 | q := req.req.URL.Query() 70 | q.Add(key, value) 71 | req.req.URL.RawQuery = q.Encode() 72 | return req 73 | } 74 | 75 | func (req *Request) Do() *Response { 76 | if req.err != nil { 77 | return &Response{ 78 | err: req.err, 79 | } 80 | } 81 | resp, err := req.client.Do(req.req) 82 | return &Response{ 83 | Response: resp, 84 | err: err, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /syncx/pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestPool(t *testing.T) { 26 | cnt := 0 27 | p := NewPool[[]byte](func() []byte { 28 | cnt += 1 29 | res := make([]byte, 1, 12) 30 | res[0] = 'A' 31 | return res 32 | }) 33 | 34 | res := p.Get() 35 | assert.Equal(t, "A", string(res)) 36 | res = append(res, 'B') 37 | p.Put(res) 38 | res = p.Get() 39 | if cnt == 1 { 40 | assert.Equal(t, "AB", string(res)) 41 | } else { 42 | assert.Equal(t, "A", string(res)) 43 | } 44 | 45 | } 46 | 47 | func ExampleNew() { 48 | p := NewPool[[]byte](func() []byte { 49 | res := make([]byte, 1, 12) 50 | res[0] = 'A' 51 | return res 52 | }) 53 | 54 | res := p.Get() 55 | fmt.Print(string(res)) 56 | // Output: 57 | // A 58 | } 59 | 60 | // goos: linux 61 | // goarch: amd64 62 | // pkg: github.com/gotomicro/ekit/pkg/pool 63 | // cpu: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz 64 | // BenchmarkPool_Get/Pool-12 9190246 130.0 ns/op 0 B/op 0 allocs/op 65 | // BenchmarkPool_Get/sync.Pool-12 9102818 128.6 ns/op 0 B/op 0 allocs/op 66 | func BenchmarkPool_Get(b *testing.B) { 67 | p := NewPool[string](func() string { 68 | return "" 69 | }) 70 | 71 | sp := &sync.Pool{ 72 | New: func() any { 73 | return "" 74 | }, 75 | } 76 | b.Run("Pool", func(b *testing.B) { 77 | for i := 0; i < b.N; i++ { 78 | p.Get() 79 | } 80 | }) 81 | b.Run("sync.Pool", func(b *testing.B) { 82 | for i := 0; i < b.N; i++ { 83 | sp.Get() 84 | } 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 ecodeclub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: golangci-lint 16 | on: 17 | push: 18 | branches: 19 | - dev 20 | - main 21 | pull_request: 22 | branches: 23 | - dev 24 | - main 25 | permissions: 26 | contents: read 27 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 28 | pull-requests: read 29 | jobs: 30 | golangci: 31 | name: lint 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/setup-go@v3 35 | with: 36 | go-version: 1.20.0 37 | - uses: actions/checkout@v3 38 | - name: golangci-lint 39 | uses: golangci/golangci-lint-action@v3 40 | with: 41 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 42 | version: latest 43 | 44 | # Optional: working directory, useful for monorepos 45 | # working-directory: somedir 46 | 47 | # Optional: golangci-lint command line arguments. 48 | args: -c .golangci.yml 49 | 50 | # Optional: show only new issues if it's a pull request. The default value is `false`. 51 | only-new-issues: true 52 | 53 | # Optional: if set to true then the all caching functionality will be complete disabled, 54 | # takes precedence over all other caching options. 55 | # skip-cache: true 56 | 57 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 58 | # skip-pkg-cache: true 59 | 60 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 61 | # skip-build-cache: true -------------------------------------------------------------------------------- /retry/retry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retry 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "testing" 22 | "time" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestRetry(t *testing.T) { 28 | bizErr := errors.New("biz error") 29 | testCases := []struct { 30 | name string 31 | biz func() error 32 | strategy Strategy 33 | wantError error 34 | }{ 35 | { 36 | name: "第一次就成功", 37 | biz: func() error { 38 | t.Log("模拟业务") 39 | return nil 40 | }, 41 | strategy: func() Strategy { 42 | res, _ := NewFixedIntervalRetryStrategy(time.Second, 3) 43 | return res 44 | }(), 45 | }, 46 | { 47 | name: "重试最终失败", 48 | biz: func() error { 49 | return bizErr 50 | }, 51 | strategy: func() Strategy { 52 | res, _ := NewFixedIntervalRetryStrategy(time.Second, 3) 53 | return res 54 | }(), 55 | wantError: bizErr, 56 | }, 57 | } 58 | 59 | for _, tc := range testCases { 60 | t.Run(tc.name, func(t *testing.T) { 61 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 62 | defer cancel() 63 | err := Retry(ctx, tc.strategy, tc.biz) 64 | assert.ErrorIs(t, err, tc.wantError) 65 | }) 66 | } 67 | } 68 | 69 | func ExampleRetry() { 70 | // 这是你的业务 71 | bizFunc := func() error { 72 | fmt.Print("hello, world") 73 | return nil 74 | } 75 | strategy, _ := NewFixedIntervalRetryStrategy(time.Millisecond*100, 3) 76 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 77 | defer cancel() 78 | err := Retry(ctx, strategy, bizFunc) 79 | if err != nil { 80 | fmt.Println("error:", err) 81 | } 82 | // Output: 83 | // hello, world 84 | } 85 | -------------------------------------------------------------------------------- /slice/contains.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // Contains 判断 src 里面是否存在 dst 18 | func Contains[T comparable](src []T, dst T) bool { 19 | return ContainsFunc[T](src, func(src T) bool { 20 | return src == dst 21 | }) 22 | } 23 | 24 | // ContainsFunc 判断 src 里面是否存在 dst 25 | // 你应该优先使用 Contains 26 | func ContainsFunc[T any](src []T, equal func(src T) bool) bool { 27 | // 遍历调用equal函数进行判断 28 | for _, v := range src { 29 | if equal(v) { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | // ContainsAny 判断 src 里面是否存在 dst 中的任何一个元素 37 | func ContainsAny[T comparable](src, dst []T) bool { 38 | srcMap := toMap[T](src) 39 | for _, v := range dst { 40 | if _, exist := srcMap[v]; exist { 41 | return true 42 | } 43 | } 44 | return false 45 | } 46 | 47 | // ContainsAnyFunc 判断 src 里面是否存在 dst 中的任何一个元素 48 | // 你应该优先使用 ContainsAny 49 | func ContainsAnyFunc[T any](src, dst []T, equal equalFunc[T]) bool { 50 | for _, valDst := range dst { 51 | for _, valSrc := range src { 52 | if equal(valSrc, valDst) { 53 | return true 54 | } 55 | } 56 | } 57 | return false 58 | } 59 | 60 | // ContainsAll 判断 src 里面是否存在 dst 中的所有元素 61 | func ContainsAll[T comparable](src, dst []T) bool { 62 | srcMap := toMap[T](src) 63 | for _, v := range dst { 64 | if _, exist := srcMap[v]; !exist { 65 | return false 66 | } 67 | } 68 | return true 69 | } 70 | 71 | // ContainsAllFunc 判断 src 里面是否存在 dst 中的所有元素 72 | // 你应该优先使用 ContainsAll 73 | func ContainsAllFunc[T any](src, dst []T, equal equalFunc[T]) bool { 74 | for _, valDst := range dst { 75 | if !ContainsFunc[T](src, func(src T) bool { 76 | return equal(src, valDst) 77 | }) { 78 | return false 79 | } 80 | } 81 | return true 82 | } 83 | -------------------------------------------------------------------------------- /iox/multiple_bytes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iox 16 | 17 | import ( 18 | "io" 19 | ) 20 | 21 | // MultipleBytes 是一个实现了 io.Reader 和 io.Writer 接口的结构体 22 | // 它可以安全地在多个 goroutine 之间共享 23 | type MultipleBytes struct { 24 | data [][]byte 25 | idx1 int // 第几个切片 26 | idx2 int // data[idx1] 中的下标 27 | } 28 | 29 | // NewMultipleBytes 创建一个新的 MultipleBytes 实例 30 | // sliceCount 参数用于预分配内部切片数组的容量 31 | func NewMultipleBytes(sliceCount int) *MultipleBytes { 32 | return &MultipleBytes{ 33 | data: make([][]byte, 0, sliceCount), 34 | } 35 | } 36 | 37 | // Read 实现 io.Reader 接口 38 | // 从当前位置读取数据到 p 中,如果没有数据可读返回 io.EOF 39 | func (m *MultipleBytes) Read(p []byte) (n int, err error) { 40 | // 如果没有数据或者已经读完了所有数据 41 | if len(m.data) == 0 || (m.idx1 >= len(m.data)) { 42 | return 0, io.EOF 43 | } 44 | 45 | totalRead := 0 46 | for m.idx1 < len(m.data) { 47 | currentSlice := m.data[m.idx1] 48 | remaining := len(currentSlice) - m.idx2 49 | if remaining <= 0 { 50 | m.idx1++ 51 | m.idx2 = 0 52 | continue 53 | } 54 | 55 | toRead := len(p) - totalRead 56 | if toRead <= 0 { 57 | break 58 | } 59 | 60 | if remaining > toRead { 61 | n = copy(p[totalRead:], currentSlice[m.idx2:m.idx2+toRead]) 62 | m.idx2 += n 63 | } else { 64 | n = copy(p[totalRead:], currentSlice[m.idx2:]) 65 | m.idx1++ 66 | m.idx2 = 0 67 | } 68 | totalRead += n 69 | } 70 | 71 | return totalRead, nil 72 | } 73 | 74 | // Write 实现 io.Writer 接口 75 | // 将 p 中的数据写入到内部缓冲区 76 | func (m *MultipleBytes) Write(p []byte) (n int, err error) { 77 | if len(p) == 0 { 78 | return 0, nil 79 | } 80 | 81 | // 直接将输入切片追加到内部存储 82 | m.data = append(m.data, p) 83 | 84 | return len(p), nil 85 | } 86 | 87 | // Reset 重置读取位置到开始处 88 | func (m *MultipleBytes) Reset() { 89 | m.idx1 = 0 90 | m.idx2 = 0 91 | } 92 | -------------------------------------------------------------------------------- /spi/spi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package spi 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | "plugin" 22 | 23 | "github.com/pkg/errors" 24 | ) 25 | 26 | // LoadService 加载 dir 下面的所有的实现了 T 接口的类型 27 | // 举个例子来说,如果你有一个叫做 UserService 的接口 28 | // 而后你将所有的实现都放到了 /ext/user_service 目录下 29 | // 并且所有的实现,虽然在不同的包,但是都叫做 UserService 30 | // 那么我可以执行 LoadService("/ext/user_service", "UserService") 31 | // 加载到所有的实现 32 | // LoadService 加载 dir 下面的所有的实现了 T 接口的类型 33 | 34 | var ( 35 | ErrDirNotFound = errors.New("ekit: 目录不存在") 36 | ErrSymbolNameIsEmpty = errors.New("ekit: 结构体名不能为空") 37 | ErrOpenPluginFailed = errors.New("ekit: 打开插件失败") 38 | ErrSymbolNameNotFound = errors.New("ekit: 从插件中查找对象失败") 39 | ErrInvalidSo = errors.New("ekit: 插件非该接口类型") 40 | ) 41 | 42 | func LoadService[T any](dir string, symName string) ([]T, error) { 43 | var services []T 44 | // 检查目录是否存在 45 | if _, err := os.Stat(dir); os.IsNotExist(err) { 46 | return nil, fmt.Errorf("%w", ErrDirNotFound) 47 | } 48 | if symName == "" { 49 | return nil, fmt.Errorf("%w", ErrSymbolNameIsEmpty) 50 | } 51 | // 遍历目录下的所有 .so 文件 52 | err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 53 | if !info.IsDir() && filepath.Ext(path) == ".so" { 54 | // 打开插件 55 | p, err := plugin.Open(path) 56 | if err != nil { 57 | return fmt.Errorf("%w: %w", ErrOpenPluginFailed, err) 58 | } 59 | // 查找变量 60 | sym, err := p.Lookup(symName) 61 | if err != nil { 62 | return fmt.Errorf("%w: %w", ErrSymbolNameNotFound, err) 63 | } 64 | 65 | // 尝试将符号断言为接口类型 66 | service, ok := sym.(T) 67 | if !ok { 68 | return fmt.Errorf("%w", ErrInvalidSo) 69 | } 70 | // 收集服务 71 | services = append(services, service) 72 | } 73 | return nil 74 | }) 75 | return services, err 76 | } 77 | -------------------------------------------------------------------------------- /mapx/map_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx_test 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/ecodeclub/ekit/mapx" 21 | ) 22 | 23 | func ExampleNewHashMap() { 24 | m := mapx.NewHashMap[MockKey, int](10) 25 | _ = m.Put(MockKey{}, 123) 26 | val, _ := m.Get(MockKey{}) 27 | fmt.Println(val) 28 | // Output: 29 | // 123 30 | } 31 | 32 | type MockKey struct { 33 | values []int 34 | } 35 | 36 | func (m MockKey) Code() uint64 { 37 | res := 3 38 | for _, v := range m.values { 39 | res += v * 7 40 | } 41 | return uint64(res) 42 | } 43 | 44 | func (m MockKey) Equals(key any) bool { 45 | k, ok := key.(MockKey) 46 | if !ok { 47 | return false 48 | } 49 | if len(k.values) != len(m.values) { 50 | return false 51 | } 52 | if k.values == nil && m.values != nil { 53 | return false 54 | } 55 | if k.values != nil && m.values == nil { 56 | return false 57 | } 58 | for i, v := range m.values { 59 | if v != k.values[i] { 60 | return false 61 | } 62 | } 63 | return true 64 | } 65 | 66 | func ExampleMerge() { 67 | m1 := map[int]int{1: 1, 2: 2, 3: 3} 68 | m2 := map[int]int{4: 4, 5: 5, 6: 6} 69 | got := mapx.Merge(m1, m2) 70 | fmt.Println(got) 71 | 72 | m3 := map[int]int{1: 1, 2: 2, 3: 3} 73 | m4 := map[int]int{1: 5, 2: 6, 3: 7} 74 | got = mapx.Merge(m3, m4) 75 | fmt.Println(got) 76 | 77 | var m map[int]int 78 | got = mapx.Merge(m) 79 | fmt.Println(got == nil) // 不会返回 nil map 80 | 81 | // Output: 82 | // map[1:1 2:2 3:3 4:4 5:5 6:6] 83 | // map[1:5 2:6 3:7] 84 | // false 85 | } 86 | 87 | func ExampleMergeFunc() { 88 | m1 := map[int]int{1: 1, 2: 2, 3: 3} 89 | m2 := map[int]int{1: 2, 2: 3, 3: 4} 90 | got := mapx.MergeFunc(func(val1, val2 int) int { 91 | return val1 + val2 92 | }, m1, m2) 93 | fmt.Println(got) 94 | 95 | // Output: 96 | // map[1:3 2:5 3:7] 97 | } 98 | -------------------------------------------------------------------------------- /retry/exponential.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retry 16 | 17 | import ( 18 | "math" 19 | "sync/atomic" 20 | "time" 21 | 22 | "github.com/ecodeclub/ekit/internal/errs" 23 | ) 24 | 25 | var _ Strategy = (*ExponentialBackoffRetryStrategy)(nil) 26 | 27 | // ExponentialBackoffRetryStrategy 指数退避重试 28 | type ExponentialBackoffRetryStrategy struct { 29 | // 初始重试间隔 30 | initialInterval time.Duration 31 | // 最大重试间隔 32 | maxInterval time.Duration 33 | // 最大重试次数 34 | maxRetries int32 35 | // 当前重试次数 36 | retries int32 37 | // 是否已经达到最大重试间隔 38 | maxIntervalReached atomic.Value 39 | } 40 | 41 | func NewExponentialBackoffRetryStrategy(initialInterval, maxInterval time.Duration, maxRetries int32) (*ExponentialBackoffRetryStrategy, error) { 42 | if initialInterval <= 0 { 43 | return nil, errs.NewErrInvalidIntervalValue(initialInterval) 44 | } else if initialInterval > maxInterval { 45 | return nil, errs.NewErrInvalidMaxIntervalValue(maxInterval, initialInterval) 46 | } 47 | return &ExponentialBackoffRetryStrategy{ 48 | initialInterval: initialInterval, 49 | maxInterval: maxInterval, 50 | maxRetries: maxRetries, 51 | }, nil 52 | } 53 | 54 | func (s *ExponentialBackoffRetryStrategy) Report(err error) Strategy { 55 | return s 56 | } 57 | 58 | func (s *ExponentialBackoffRetryStrategy) Next() (time.Duration, bool) { 59 | retries := atomic.AddInt32(&s.retries, 1) 60 | if s.maxRetries <= 0 || retries <= s.maxRetries { 61 | if reached, ok := s.maxIntervalReached.Load().(bool); ok && reached { 62 | return s.maxInterval, true 63 | } 64 | interval := s.initialInterval * time.Duration(math.Pow(2, float64(retries-1))) 65 | // 溢出或当前重试间隔大于最大重试间隔 66 | if interval <= 0 || interval > s.maxInterval { 67 | s.maxIntervalReached.Store(true) 68 | return s.maxInterval, true 69 | } 70 | return interval, true 71 | } 72 | return 0, false 73 | } 74 | -------------------------------------------------------------------------------- /queue/concurrent_linked_queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package queue 16 | 17 | import ( 18 | "sync/atomic" 19 | "unsafe" 20 | 21 | "github.com/ecodeclub/ekit/internal/queue" 22 | ) 23 | 24 | // ConcurrentLinkedQueue 无界并发安全队列 25 | type ConcurrentLinkedQueue[T any] struct { 26 | // *node[T] 27 | head unsafe.Pointer 28 | // *node[T] 29 | tail unsafe.Pointer 30 | } 31 | 32 | func NewConcurrentLinkedQueue[T any]() *ConcurrentLinkedQueue[T] { 33 | head := &node[T]{} 34 | ptr := unsafe.Pointer(head) 35 | return &ConcurrentLinkedQueue[T]{ 36 | head: ptr, 37 | tail: ptr, 38 | } 39 | } 40 | 41 | func (c *ConcurrentLinkedQueue[T]) Enqueue(t T) error { 42 | newNode := &node[T]{val: t} 43 | newPtr := unsafe.Pointer(newNode) 44 | for { 45 | tailPtr := atomic.LoadPointer(&c.tail) 46 | tail := (*node[T])(tailPtr) 47 | tailNext := atomic.LoadPointer(&tail.next) 48 | if tailNext != nil { 49 | // 已经被人修改了,我们不需要修复,因为预期中修改的那个人会把 c.tail 指过去 50 | continue 51 | } 52 | if atomic.CompareAndSwapPointer(&tail.next, tailNext, newPtr) { 53 | // 如果失败也不用担心,说明有人抢先一步了 54 | atomic.CompareAndSwapPointer(&c.tail, tailPtr, newPtr) 55 | return nil 56 | } 57 | } 58 | } 59 | 60 | func (c *ConcurrentLinkedQueue[T]) Dequeue() (T, error) { 61 | for { 62 | headPtr := atomic.LoadPointer(&c.head) 63 | head := (*node[T])(headPtr) 64 | tailPtr := atomic.LoadPointer(&c.tail) 65 | tail := (*node[T])(tailPtr) 66 | if head == tail { 67 | // 不需要做更多检测,在当下这一刻,我们就认为没有元素,即便这时候正好有人入队 68 | // 但是并不妨碍我们在它彻底入队完成——即所有的指针都调整好——之前, 69 | // 认为其实还是没有元素 70 | var t T 71 | return t, queue.ErrEmptyQueue 72 | } 73 | headNextPtr := atomic.LoadPointer(&head.next) 74 | if atomic.CompareAndSwapPointer(&c.head, headPtr, headNextPtr) { 75 | headNext := (*node[T])(headNextPtr) 76 | return headNext.val, nil 77 | } 78 | } 79 | } 80 | 81 | type node[T any] struct { 82 | val T 83 | // *node[T] 84 | next unsafe.Pointer 85 | } 86 | -------------------------------------------------------------------------------- /internal/slice/add_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/ecodeclub/ekit/internal/errs" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestAdd(t *testing.T) { 25 | testCases := []struct { 26 | name string 27 | slice []int 28 | addVal int 29 | index int 30 | wantSlice []int 31 | wantErr error 32 | }{ 33 | { 34 | name: "index 0", 35 | slice: []int{123, 100}, 36 | addVal: 233, 37 | index: 0, 38 | wantSlice: []int{233, 123, 100}, 39 | }, 40 | { 41 | name: "index middle", 42 | slice: []int{123, 124, 125}, 43 | addVal: 233, 44 | index: 1, 45 | wantSlice: []int{123, 233, 124, 125}, 46 | }, 47 | { 48 | name: "index out of range", 49 | slice: []int{123, 100}, 50 | index: 12, 51 | wantErr: errs.NewErrIndexOutOfRange(2, 12), 52 | }, 53 | { 54 | name: "index less than 0", 55 | slice: []int{123, 100}, 56 | index: -1, 57 | wantErr: errs.NewErrIndexOutOfRange(2, -1), 58 | }, 59 | { 60 | name: "index last", 61 | slice: []int{123, 100, 101, 102, 102, 102}, 62 | addVal: 233, 63 | index: 5, 64 | wantSlice: []int{123, 100, 101, 102, 102, 233, 102}, 65 | }, 66 | { 67 | name: "append on last", 68 | slice: []int{123, 100, 101, 102, 102, 102}, 69 | addVal: 233, 70 | index: 6, 71 | wantSlice: []int{123, 100, 101, 102, 102, 102, 233}, 72 | }, 73 | { 74 | name: "index out of range", 75 | slice: []int{123, 100, 101, 102, 102, 102}, 76 | addVal: 233, 77 | index: 7, 78 | wantErr: errs.NewErrIndexOutOfRange(6, 7), 79 | }, 80 | } 81 | 82 | for _, tc := range testCases { 83 | t.Run(tc.name, func(t *testing.T) { 84 | res, err := Add(tc.slice, tc.addVal, tc.index) 85 | assert.Equal(t, tc.wantErr, err) 86 | if err != nil { 87 | return 88 | } 89 | assert.Equal(t, tc.wantSlice, res) 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /bean/option/option_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package option 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func TestApply(t *testing.T) { 27 | u := &User{} 28 | Apply[User](u, WithName("Tom"), WithAge(18)) 29 | assert.Equal(t, u, &User{name: "Tom", age: 18}) 30 | } 31 | 32 | func TestApplyErr(t *testing.T) { 33 | u := &User{} 34 | err := ApplyErr[User](u, WithNameErr("Tom"), WithAgeErr(18)) 35 | require.NoError(t, err) 36 | assert.Equal(t, u, &User{name: "Tom", age: 18}) 37 | 38 | err = ApplyErr[User](u, WithNameErr(""), WithAgeErr(18)) 39 | assert.Equal(t, errors.New("name 不能为空"), err) 40 | } 41 | 42 | func ExampleApplyErr() { 43 | u := &User{} 44 | err := ApplyErr[User](u, WithNameErr("Tom"), WithAgeErr(18)) 45 | fmt.Println(err) 46 | fmt.Println(u) 47 | 48 | err = ApplyErr[User](u, WithNameErr(""), WithAgeErr(18)) 49 | fmt.Println(err) 50 | // Output: 51 | // 52 | // &{Tom 18} 53 | // name 不能为空 54 | } 55 | 56 | func ExampleApply() { 57 | u := &User{} 58 | Apply[User](u, WithName("Tom"), WithAge(18)) 59 | fmt.Println(u) 60 | // Output: 61 | // &{Tom 18} 62 | } 63 | 64 | func WithNameErr(name string) OptionErr[User] { 65 | return func(u *User) error { 66 | if name == "" { 67 | return errors.New("name 不能为空") 68 | } 69 | u.name = name 70 | return nil 71 | } 72 | } 73 | 74 | func WithName(name string) Option[User] { 75 | return func(u *User) { 76 | u.name = name 77 | } 78 | } 79 | 80 | func WithAgeErr(age int) OptionErr[User] { 81 | return func(u *User) error { 82 | if age <= 0 { 83 | return errors.New("age 应该是正数") 84 | } 85 | u.age = age 86 | return nil 87 | } 88 | } 89 | 90 | func WithAge(age int) Option[User] { 91 | return func(u *User) { 92 | u.age = age 93 | } 94 | } 95 | 96 | type User struct { 97 | name string 98 | age int 99 | } 100 | -------------------------------------------------------------------------------- /pool/arena_pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build goexperiment.arenas 16 | 17 | package pool 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func TestArenaPool(t *testing.T) { 27 | testCases := []struct { 28 | name string 29 | p func() *ArenaPool[TestStruct] 30 | wantObj *TestStruct 31 | wantErr error 32 | }{ 33 | { 34 | name: "no obj", 35 | p: func() *ArenaPool[TestStruct] { 36 | return NewArenaPool[TestStruct]() 37 | }, 38 | wantObj: &TestStruct{}, 39 | }, 40 | { 41 | name: "reuse", 42 | p: func() *ArenaPool[TestStruct] { 43 | p := NewArenaPool[TestStruct]() 44 | obj, err := p.Get() 45 | require.NoError(t, err) 46 | obj.Obj().Age = 123 47 | err = p.Put(obj) 48 | require.NoError(t, err) 49 | return p 50 | }, 51 | wantObj: &TestStruct{ 52 | Age: 123, 53 | }, 54 | }, 55 | { 56 | name: "multiple", 57 | p: func() *ArenaPool[TestStruct] { 58 | p := NewArenaPool[TestStruct]() 59 | obj1, err := p.Get() 60 | require.NoError(t, err) 61 | 62 | obj2, err := p.Get() 63 | require.NoError(t, err) 64 | 65 | obj3, err := p.Get() 66 | require.NoError(t, err) 67 | 68 | err = p.Put(obj1) 69 | require.NoError(t, err) 70 | err = p.Put(obj2) 71 | require.NoError(t, err) 72 | err = p.Put(obj3) 73 | require.NoError(t, err) 74 | 75 | newObj3, err := p.Get() 76 | require.NoError(t, err) 77 | assert.Equal(t, obj3, newObj3) 78 | return p 79 | }, 80 | wantObj: &TestStruct{}, 81 | }, 82 | } 83 | 84 | for _, tc := range testCases { 85 | t.Run(tc.name, func(t *testing.T) { 86 | obj, err := tc.p().Get() 87 | assert.Equal(t, tc.wantErr, err) 88 | if err != nil { 89 | return 90 | } 91 | assert.Equal(t, tc.wantObj, obj.Obj()) 92 | }) 93 | } 94 | } 95 | 96 | type TestStruct struct { 97 | Age int 98 | AgePtr *int 99 | } 100 | -------------------------------------------------------------------------------- /syncx/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import "sync" 18 | 19 | // Map 是对 sync.Map 的一个泛型封装 20 | // 要注意,K 必须是 comparable 的,并且谨慎使用指针作为 K。 21 | // 使用指针的情况下,两个 key 是否相等,仅仅取决于它们的地址 22 | // 而不是地址指向的值。可以参考 Load 测试。 23 | // 注意,key 不存在和 key 存在但是值恰好为零值(如 nil),是两码事 24 | type Map[K comparable, V any] struct { 25 | m sync.Map 26 | } 27 | 28 | // Load 加载键值对 29 | func (m *Map[K, V]) Load(key K) (value V, ok bool) { 30 | var anyVal any 31 | anyVal, ok = m.m.Load(key) 32 | if anyVal != nil { 33 | value = anyVal.(V) 34 | } 35 | return 36 | } 37 | 38 | // Store 存储键值对 39 | func (m *Map[K, V]) Store(key K, value V) { 40 | m.m.Store(key, value) 41 | } 42 | 43 | // LoadOrStore 加载或者存储一个键值对 44 | // true 代表是加载的,false 代表执行了 store 45 | func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { 46 | var anyVal any 47 | anyVal, loaded = m.m.LoadOrStore(key, value) 48 | if anyVal != nil { 49 | actual = anyVal.(V) 50 | } 51 | return 52 | } 53 | 54 | // LoadOrStoreFunc 是一个优化,也就是使用该方法能够避免无意义的创建实例。 55 | // 如果你的初始化过程非常消耗资源,那么使用这个方法是有价值的。 56 | // 它的代价就是 Key 不存在的时候会多一次 Load 调用。 57 | // 当 fn 返回 error 的时候,LoadOrStoreFunc 也会返回 error。 58 | func (m *Map[K, V]) LoadOrStoreFunc(key K, fn func() (V, error)) (actual V, loaded bool, err error) { 59 | val, ok := m.Load(key) 60 | if ok { 61 | return val, true, nil 62 | } 63 | val, err = fn() 64 | if err != nil { 65 | return 66 | } 67 | actual, loaded = m.LoadOrStore(key, val) 68 | return 69 | } 70 | 71 | // LoadAndDelete 加载并且删除一个键值对 72 | func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) { 73 | var anyVal any 74 | anyVal, loaded = m.m.LoadAndDelete(key) 75 | if anyVal != nil { 76 | value = anyVal.(V) 77 | } 78 | return 79 | } 80 | 81 | // Delete 删除键值对 82 | func (m *Map[K, V]) Delete(key K) { 83 | m.m.Delete(key) 84 | } 85 | 86 | // Range 遍历, f 不能为 nil 87 | // 传入 f 的时候,K 和 V 直接使用对应的类型,如果 f 返回 false,那么就会中断遍历 88 | func (m *Map[K, V]) Range(f func(key K, value V) bool) { 89 | m.m.Range(func(key, value any) bool { 90 | var ( 91 | k K 92 | v V 93 | ) 94 | if value != nil { 95 | v = value.(V) 96 | } 97 | if key != nil { 98 | k = key.(K) 99 | } 100 | return f(k, v) 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /retry/adaptive.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retry 16 | 17 | import ( 18 | "math/bits" 19 | "sync/atomic" 20 | "time" 21 | ) 22 | 23 | var _ Strategy = (*AdaptiveTimeoutRetryStrategy)(nil) 24 | 25 | type AdaptiveTimeoutRetryStrategy struct { 26 | strategy Strategy // 基础重试策略 27 | threshold int // 超时比率阈值 (单位:比特数量) 28 | ringBuffer []uint64 // 比特环(滑动窗口存储超时信息) 29 | reqCount uint64 // 请求数量 30 | bufferLen int // 滑动窗口长度 31 | bitCnt uint64 // 比特位总数 32 | } 33 | 34 | func (s *AdaptiveTimeoutRetryStrategy) Next() (time.Duration, bool) { 35 | failCount := s.getFailed() 36 | if failCount >= s.threshold { 37 | return 0, false 38 | } 39 | return s.strategy.Next() 40 | } 41 | 42 | func (s *AdaptiveTimeoutRetryStrategy) Report(err error) Strategy { 43 | if err == nil { 44 | s.markSuccess() 45 | } else { 46 | s.markFail() 47 | } 48 | return s 49 | } 50 | 51 | func (s *AdaptiveTimeoutRetryStrategy) markSuccess() { 52 | count := atomic.AddUint64(&s.reqCount, 1) 53 | count = count % s.bitCnt 54 | // 对2^x进行取模或者整除运算时可以用位运算代替除法和取模 55 | // count / 64 可以转换成 count >> 6。 位运算会更高效。 56 | idx := count >> 6 57 | // count % 64 可以转换成 count & 63 58 | bitPos := count & 63 59 | old := atomic.LoadUint64(&s.ringBuffer[idx]) 60 | atomic.StoreUint64(&s.ringBuffer[idx], old&^(uint64(1)<> 6 67 | bitPos := count & 63 68 | old := atomic.LoadUint64(&s.ringBuffer[idx]) 69 | // (uint64(1)< 0 && c.linkedlist.Len() == c.maxSize { 60 | signal := c.notFull.signalCh() 61 | select { 62 | case <-ctx.Done(): 63 | return ctx.Err() 64 | case <-signal: 65 | // 收到信号要重新加锁 66 | c.mutex.Lock() 67 | } 68 | } 69 | 70 | err := c.linkedlist.Append(t) 71 | 72 | // 这里会释放锁 73 | c.notEmpty.broadcast() 74 | return err 75 | } 76 | 77 | // Dequeue 出队 78 | // 注意:目前我们已经通过broadcast实现了超时控制 79 | func (c *ConcurrentLinkedBlockingQueue[T]) Dequeue(ctx context.Context) (T, error) { 80 | if ctx.Err() != nil { 81 | var t T 82 | return t, ctx.Err() 83 | } 84 | c.mutex.Lock() 85 | for c.linkedlist.Len() == 0 { 86 | signal := c.notEmpty.signalCh() 87 | select { 88 | case <-ctx.Done(): 89 | var t T 90 | return t, ctx.Err() 91 | case <-signal: 92 | c.mutex.Lock() 93 | } 94 | } 95 | 96 | val, err := c.linkedlist.Delete(0) 97 | c.notFull.broadcast() 98 | return val, err 99 | } 100 | 101 | func (c *ConcurrentLinkedBlockingQueue[T]) Len() int { 102 | c.mutex.RLock() 103 | defer c.mutex.RUnlock() 104 | return c.linkedlist.Len() 105 | } 106 | 107 | func (c *ConcurrentLinkedBlockingQueue[T]) AsSlice() []T { 108 | c.mutex.RLock() 109 | defer c.mutex.RUnlock() 110 | res := c.linkedlist.AsSlice() 111 | return res 112 | } 113 | -------------------------------------------------------------------------------- /list/array_list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package list 16 | 17 | import ( 18 | "github.com/ecodeclub/ekit/internal/errs" 19 | "github.com/ecodeclub/ekit/internal/slice" 20 | ) 21 | 22 | var ( 23 | _ List[any] = &ArrayList[any]{} 24 | ) 25 | 26 | // ArrayList 基于切片的简单封装 27 | type ArrayList[T any] struct { 28 | vals []T 29 | } 30 | 31 | // NewArrayList 初始化一个len为0,cap为cap的ArrayList 32 | func NewArrayList[T any](cap int) *ArrayList[T] { 33 | return &ArrayList[T]{vals: make([]T, 0, cap)} 34 | } 35 | 36 | // NewArrayListOf 直接使用 ts,而不会执行复制 37 | func NewArrayListOf[T any](ts []T) *ArrayList[T] { 38 | return &ArrayList[T]{ 39 | vals: ts, 40 | } 41 | } 42 | 43 | func (a *ArrayList[T]) Get(index int) (t T, e error) { 44 | l := a.Len() 45 | if index < 0 || index >= l { 46 | return t, errs.NewErrIndexOutOfRange(l, index) 47 | } 48 | return a.vals[index], e 49 | } 50 | 51 | // Append 往ArrayList里追加数据 52 | func (a *ArrayList[T]) Append(ts ...T) error { 53 | a.vals = append(a.vals, ts...) 54 | return nil 55 | } 56 | 57 | // Add 在ArrayList下标为index的位置插入一个元素 58 | // 当index等于ArrayList长度等同于append 59 | func (a *ArrayList[T]) Add(index int, t T) (err error) { 60 | a.vals, err = slice.Add(a.vals, t, index) 61 | return 62 | } 63 | 64 | // Set 设置ArrayList里index位置的值为t 65 | func (a *ArrayList[T]) Set(index int, t T) error { 66 | length := len(a.vals) 67 | if index >= length || index < 0 { 68 | return errs.NewErrIndexOutOfRange(length, index) 69 | } 70 | a.vals[index] = t 71 | return nil 72 | } 73 | 74 | // Delete 方法会在必要的时候引起缩容,其缩容规则是: 75 | // - 如果容量 > 2048,并且长度小于容量一半,那么就会缩容为原本的 5/8 76 | // - 如果容量 (64, 2048],如果长度是容量的 1/4,那么就会缩容为原本的一半 77 | // - 如果此时容量 <= 64,那么我们将不会执行缩容。在容量很小的情况下,浪费的内存很少,所以没必要消耗 CPU去执行缩容 78 | func (a *ArrayList[T]) Delete(index int) (T, error) { 79 | res, t, err := slice.Delete(a.vals, index) 80 | if err != nil { 81 | return t, err 82 | } 83 | a.vals = res 84 | a.shrink() 85 | return t, nil 86 | } 87 | 88 | // shrink 数组缩容 89 | func (a *ArrayList[T]) shrink() { 90 | a.vals = slice.Shrink(a.vals) 91 | } 92 | 93 | func (a *ArrayList[T]) Len() int { 94 | return len(a.vals) 95 | } 96 | 97 | func (a *ArrayList[T]) Cap() int { 98 | return cap(a.vals) 99 | } 100 | 101 | func (a *ArrayList[T]) Range(fn func(index int, t T) error) error { 102 | for key, value := range a.vals { 103 | e := fn(key, value) 104 | if e != nil { 105 | return e 106 | } 107 | } 108 | return nil 109 | } 110 | 111 | func (a *ArrayList[T]) AsSlice() []T { 112 | res := make([]T, len(a.vals)) 113 | copy(res, a.vals) 114 | return res 115 | } 116 | -------------------------------------------------------------------------------- /iox/concurrent_multiple_bytes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package iox 16 | 17 | import ( 18 | "io" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestConcurrentMultipleBytes(t *testing.T) { 26 | t.Run("基本读写功能", func(t *testing.T) { 27 | cmb := NewConcurrentMultipleBytes(2) 28 | data := []byte{1, 2, 3, 4} 29 | 30 | // 写入数据 31 | n, err := cmb.Write(data) 32 | assert.Equal(t, len(data), n) 33 | assert.Nil(t, err) 34 | 35 | // 读取数据 36 | read := make([]byte, 4) 37 | n, err = cmb.Read(read) 38 | assert.Equal(t, len(data), n) 39 | assert.Nil(t, err) 40 | assert.Equal(t, data, read[:n]) 41 | }) 42 | 43 | t.Run("并发读写", func(t *testing.T) { 44 | cmb := NewConcurrentMultipleBytes(3) 45 | var wg sync.WaitGroup 46 | 47 | // 并发写入 48 | for i := 0; i < 3; i++ { 49 | wg.Add(1) 50 | go func(val byte) { 51 | defer wg.Done() 52 | n, err := cmb.Write([]byte{val}) 53 | assert.Equal(t, 1, n) 54 | assert.Nil(t, err) 55 | }(byte(i + 1)) 56 | } 57 | wg.Wait() 58 | 59 | // 并发读取 60 | results := make([][]byte, 3) 61 | for i := 0; i < 3; i++ { 62 | wg.Add(1) 63 | go func(idx int) { 64 | defer wg.Done() 65 | read := make([]byte, 1) 66 | n, err := cmb.Read(read) 67 | if err != nil && err != io.EOF { 68 | assert.Nil(t, err) 69 | return 70 | } 71 | results[idx] = read[:n] 72 | }(i) 73 | } 74 | wg.Wait() 75 | 76 | // 验证总读取字节数 77 | total := 0 78 | for _, res := range results { 79 | total += len(res) 80 | } 81 | assert.Equal(t, 3, total) 82 | }) 83 | 84 | t.Run("边界场景", func(t *testing.T) { 85 | cmb := NewConcurrentMultipleBytes(1) 86 | 87 | // 空切片写入 88 | n, err := cmb.Write([]byte{}) 89 | assert.Equal(t, 0, n) 90 | assert.Nil(t, err) 91 | 92 | // 空切片读取 93 | read := make([]byte, 1) 94 | n, err = cmb.Read(read) 95 | assert.Equal(t, 0, n) 96 | assert.Equal(t, io.EOF, err) 97 | }) 98 | 99 | t.Run("Reset功能", func(t *testing.T) { 100 | cmb := NewConcurrentMultipleBytes(1) 101 | data := []byte{1, 2} 102 | 103 | // 写入数据 104 | n, err := cmb.Write(data) 105 | assert.Equal(t, len(data), n) 106 | assert.Nil(t, err) 107 | 108 | // 读取一部分 109 | read := make([]byte, 1) 110 | n, err = cmb.Read(read) 111 | assert.Equal(t, 1, n) 112 | assert.Nil(t, err) 113 | 114 | // 重置 115 | cmb.Reset() 116 | 117 | // 重新读取 118 | read = make([]byte, 2) 119 | n, err = cmb.Read(read) 120 | assert.Equal(t, 2, n) 121 | assert.Nil(t, err) 122 | assert.Equal(t, data, read[:n]) 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /retry/adaptive_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retry 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestNewAdaptiveTimeoutRetryStrategy_New(t *testing.T) { 26 | testCases := []struct { 27 | name string 28 | threshold int 29 | ringBufferLen int 30 | strategy Strategy 31 | want *AdaptiveTimeoutRetryStrategy 32 | wantErr error 33 | }{ 34 | { 35 | name: "valid strategy and threshold", 36 | strategy: &MockStrategy{}, // 假设有一个 MockStrategy 用于测试 37 | threshold: 50, 38 | ringBufferLen: 16, 39 | want: NewAdaptiveTimeoutRetryStrategy(&MockStrategy{}, 16, 50), 40 | wantErr: nil, 41 | }, 42 | } 43 | 44 | for _, tt := range testCases { 45 | t.Run(tt.name, func(t *testing.T) { 46 | s := NewAdaptiveTimeoutRetryStrategy(tt.strategy, tt.ringBufferLen, tt.threshold) 47 | assert.Equal(t, tt.want, s) 48 | }) 49 | } 50 | } 51 | 52 | func TestAdaptiveTimeoutRetryStrategy_Next(t *testing.T) { 53 | baseStrategy := &MockStrategy{} 54 | strategy := NewAdaptiveTimeoutRetryStrategy(baseStrategy, 16, 50) 55 | 56 | tests := []struct { 57 | name string 58 | wantDelay time.Duration 59 | wantOk bool 60 | }{ 61 | { 62 | name: "error below threshold", 63 | wantDelay: 1 * time.Second, 64 | wantOk: true, 65 | }, 66 | { 67 | name: "error above threshold", 68 | wantDelay: 1 * time.Second, 69 | wantOk: true, 70 | }, 71 | } 72 | 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | delay, ok := strategy.Next() 76 | assert.Equal(t, tt.wantDelay, delay) 77 | assert.Equal(t, tt.wantOk, ok) 78 | }) 79 | } 80 | } 81 | 82 | func ExampleAdaptiveTimeoutRetryStrategy_Next() { 83 | baseStrategy, err := NewExponentialBackoffRetryStrategy(time.Second, time.Second*5, 10) 84 | if err != nil { 85 | fmt.Println(err) 86 | return 87 | } 88 | strategy := NewAdaptiveTimeoutRetryStrategy(baseStrategy, 16, 50) 89 | interval, ok := strategy.Next() 90 | for ok { 91 | fmt.Println(interval) 92 | interval, ok = strategy.Next() 93 | } 94 | // Output: 95 | // 1s 96 | // 2s 97 | // 4s 98 | // 5s 99 | // 5s 100 | // 5s 101 | // 5s 102 | // 5s 103 | // 5s 104 | // 5s 105 | } 106 | 107 | type MockStrategy struct { 108 | } 109 | 110 | func (m MockStrategy) Next() (time.Duration, bool) { 111 | return 1 * time.Second, true 112 | } 113 | 114 | func (m MockStrategy) Report(err error) Strategy { 115 | return m 116 | } 117 | -------------------------------------------------------------------------------- /mapx/treemap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/ecodeclub/ekit" 21 | "github.com/ecodeclub/ekit/internal/tree" 22 | ) 23 | 24 | var ( 25 | errTreeMapComparatorIsNull = errors.New("ekit: Comparator不能为nil") 26 | ) 27 | 28 | // TreeMap 是基于红黑树实现的Map 29 | type TreeMap[K any, V any] struct { 30 | tree *tree.RBTree[K, V] 31 | } 32 | 33 | // NewTreeMapWithMap TreeMap构造方法 34 | // 支持通过传入的map构造生成TreeMap 35 | func NewTreeMapWithMap[K comparable, V any](compare ekit.Comparator[K], m map[K]V) (*TreeMap[K, V], error) { 36 | treeMap, err := NewTreeMap[K, V](compare) 37 | if err != nil { 38 | return treeMap, err 39 | } 40 | putAll(treeMap, m) 41 | return treeMap, nil 42 | } 43 | 44 | // NewTreeMap TreeMap构造方法,创建一个的TreeMap 45 | // 需注意比较器compare不能为nil 46 | func NewTreeMap[K any, V any](compare ekit.Comparator[K]) (*TreeMap[K, V], error) { 47 | if compare == nil { 48 | return nil, errTreeMapComparatorIsNull 49 | } 50 | return &TreeMap[K, V]{ 51 | tree: tree.NewRBTree[K, V](compare), 52 | }, nil 53 | } 54 | 55 | // putAll 将map传入TreeMap 56 | // 需注意如果map中的key已存在,value将被替换 57 | func putAll[K comparable, V any](treeMap *TreeMap[K, V], m map[K]V) { 58 | for k, v := range m { 59 | _ = treeMap.Put(k, v) 60 | } 61 | } 62 | 63 | // Put 在TreeMap插入指定值 64 | // 需注意如果TreeMap已存在该Key那么原值会被替换 65 | func (treeMap *TreeMap[K, V]) Put(key K, value V) error { 66 | err := treeMap.tree.Add(key, value) 67 | if err == tree.ErrRBTreeSameRBNode { 68 | return treeMap.tree.Set(key, value) 69 | } 70 | return nil 71 | } 72 | 73 | // Get 在TreeMap找到指定Key的节点,返回Val 74 | // TreeMap未找到指定节点将会返回false 75 | func (treeMap *TreeMap[K, V]) Get(key K) (V, bool) { 76 | v, err := treeMap.tree.Find(key) 77 | return v, err == nil 78 | } 79 | 80 | // Delete TreeMap中删除指定key的节点 81 | func (treeMap *TreeMap[T, V]) Delete(k T) (V, bool) { 82 | return treeMap.tree.Delete(k) 83 | } 84 | 85 | // Keys 返回了全部的键 86 | // 目前我们是按照中序遍历来返回的数据,但是你不能依赖于这个特性 87 | func (treeMap *TreeMap[T, V]) Keys() []T { 88 | keys, _ := treeMap.tree.KeyValues() 89 | return keys 90 | } 91 | 92 | // Values 返回了全部的值 93 | // 目前我们是按照中序遍历来返回的数据,但是你不能依赖于这个特性 94 | func (treeMap *TreeMap[T, V]) Values() []V { 95 | _, vals := treeMap.tree.KeyValues() 96 | return vals 97 | } 98 | 99 | // Len 返回了键值对的数量 100 | func (treeMap *TreeMap[T, V]) Len() int64 { 101 | return int64(treeMap.tree.Size()) 102 | } 103 | 104 | // Iterate 按照key的顺序遍历并执行cb,如果cb返回值为false则结束遍历,否则继续遍历 105 | func (treeMap *TreeMap[K, V]) Iterate(cb func(key K, value V) bool) { 106 | treeMap.tree.Iterate(cb) 107 | } 108 | 109 | var _ mapi[any, any] = (*TreeMap[any, any])(nil) 110 | -------------------------------------------------------------------------------- /mapx/multi_map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | import ( 18 | "github.com/ecodeclub/ekit" 19 | ) 20 | 21 | // MultiMap 多映射的 Map 22 | // 它可以将一个键映射到多个值上 23 | type MultiMap[K any, V any] struct { 24 | m mapi[K, []V] 25 | } 26 | 27 | // NewMultiTreeMap 创建一个基于 TreeMap 的 MultiMap 28 | // 注意: 29 | // - comparator 不能为 nil 30 | func NewMultiTreeMap[K any, V any](comparator ekit.Comparator[K]) (*MultiMap[K, V], error) { 31 | treeMap, err := NewTreeMap[K, []V](comparator) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &MultiMap[K, V]{ 36 | m: treeMap, 37 | }, nil 38 | } 39 | 40 | // NewMultiHashMap 创建一个基于 HashMap 的 MultiMap 41 | func NewMultiHashMap[K Hashable, V any](size int) *MultiMap[K, V] { 42 | var m mapi[K, []V] = NewHashMap[K, []V](size) 43 | return &MultiMap[K, V]{ 44 | m: m, 45 | } 46 | } 47 | 48 | func NewMultiBuiltinMap[K comparable, V any](size int) *MultiMap[K, V] { 49 | var m mapi[K, []V] = newBuiltinMap[K, []V](size) 50 | return &MultiMap[K, V]{ 51 | m: m, 52 | } 53 | } 54 | 55 | // Put 在 MultiMap 中添加键值对或向已有键 k 的值追加数据 56 | func (m *MultiMap[K, V]) Put(k K, v V) error { 57 | return m.PutMany(k, v) 58 | } 59 | 60 | // PutMany 在 MultiMap 中添加键值对或向已有键 k 的值追加多个数据 61 | func (m *MultiMap[K, V]) PutMany(k K, v ...V) error { 62 | val, _ := m.Get(k) 63 | val = append(val, v...) 64 | return m.m.Put(k, val) 65 | } 66 | 67 | // Get 从 MultiMap 中获取已有键 k 的值 68 | // 如果键 k 不存在,则返回的 bool 值为 false 69 | // 返回的切片是一个副本,你对该切片的修改不会影响原本的数据。 70 | func (m *MultiMap[K, V]) Get(k K) ([]V, bool) { 71 | if v, ok := m.m.Get(k); ok { 72 | return append([]V{}, v...), ok 73 | } 74 | return nil, false 75 | } 76 | 77 | // Delete 从 MultiMap 中删除指定的键 k 78 | func (m *MultiMap[K, V]) Delete(k K) ([]V, bool) { 79 | return m.m.Delete(k) 80 | } 81 | 82 | // Keys 返回 MultiMap 所有的键 83 | func (m *MultiMap[K, V]) Keys() []K { 84 | return m.m.Keys() 85 | } 86 | 87 | // Values 返回 MultiMap 所有的值 88 | func (m *MultiMap[K, V]) Values() [][]V { 89 | values := m.m.Values() 90 | copyValues := make([][]V, 0, len(values)) 91 | for i := range values { 92 | copyValues = append(copyValues, append([]V{}, values[i]...)) 93 | } 94 | return copyValues 95 | } 96 | 97 | // Len 返回 MultiMap 键值对的数量 98 | func (m *MultiMap[K, V]) Len() int64 { 99 | return m.m.Len() 100 | } 101 | 102 | // Iterate 遍历整个map, 对每个键值对执行cb(k, v), 如果cb的返回值为 true 则继续遍历,否则遍历结束 103 | // 具体遍历顺序取决于MultiMap包装的mapi的实现 104 | func (m *MultiMap[K, V]) Iterate(cb func(K, V) bool) { 105 | m.m.Iterate(func(key K, val []V) bool { 106 | for _, v := range val { 107 | if !cb(key, v) { 108 | return false 109 | } 110 | } 111 | return true 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /slice/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slice 16 | 17 | // FilterMap 执行过滤并且转化 18 | // 如果 m 的第二个返回值是 false,那么我们会忽略第一个返回值 19 | // 即便第二个返回值是 false,后续的元素依旧会被遍历 20 | func FilterMap[Src any, Dst any](src []Src, m func(idx int, src Src) (Dst, bool)) []Dst { 21 | res := make([]Dst, 0, len(src)) 22 | for i, s := range src { 23 | dst, ok := m(i, s) 24 | if ok { 25 | res = append(res, dst) 26 | } 27 | } 28 | return res 29 | } 30 | 31 | func Map[Src any, Dst any](src []Src, m func(idx int, src Src) Dst) []Dst { 32 | dst := make([]Dst, len(src)) 33 | for i, s := range src { 34 | dst[i] = m(i, s) 35 | } 36 | return dst 37 | } 38 | 39 | // 将[]Ele映射到map[Key]Ele 40 | // 从Ele中提取Key的函数fn由使用者提供 41 | // 42 | // 注意: 43 | // 如果出现 i < j 44 | // 设: 45 | // 46 | // key_i := fn(elements[i]) 47 | // key_j := fn(elements[j]) 48 | // 49 | // 满足key_i == key_j 的情况,则在返回结果的resultMap中 50 | // resultMap[key_i] = val_j 51 | // 52 | // 即使传入的字符串为nil,也保证返回的map是一个空map而不是nil 53 | func ToMap[Ele any, Key comparable]( 54 | elements []Ele, 55 | fn func(element Ele) Key, 56 | ) map[Key]Ele { 57 | return ToMapV( 58 | elements, 59 | func(element Ele) (Key, Ele) { 60 | return fn(element), element 61 | }) 62 | } 63 | 64 | // 将[]Ele映射到map[Key]Val 65 | // 从Ele中提取Key和Val的函数fn由使用者提供 66 | // 67 | // 注意: 68 | // 如果出现 i < j 69 | // 设: 70 | // 71 | // key_i, val_i := fn(elements[i]) 72 | // key_j, val_j := fn(elements[j]) 73 | // 74 | // 满足key_i == key_j 的情况,则在返回结果的resultMap中 75 | // resultMap[key_i] = val_j 76 | // 77 | // 即使传入的字符串为nil,也保证返回的map是一个空map而不是nil 78 | func ToMapV[Ele any, Key comparable, Val any]( 79 | elements []Ele, 80 | fn func(element Ele) (Key, Val), 81 | ) (resultMap map[Key]Val) { 82 | resultMap = make(map[Key]Val, len(elements)) 83 | for _, element := range elements { 84 | k, v := fn(element) 85 | resultMap[k] = v 86 | } 87 | return 88 | } 89 | 90 | // 构造map 91 | func toMap[T comparable](src []T) map[T]struct{} { 92 | var dataMap = make(map[T]struct{}, len(src)) 93 | for _, v := range src { 94 | // 使用空结构体,减少内存消耗 95 | dataMap[v] = struct{}{} 96 | } 97 | return dataMap 98 | } 99 | 100 | func deduplicateFunc[T any](data []T, equal equalFunc[T]) []T { 101 | var newData = make([]T, 0, len(data)) 102 | for k, v := range data { 103 | if !ContainsFunc[T](data[k+1:], func(src T) bool { 104 | return equal(src, v) 105 | }) { 106 | newData = append(newData, v) 107 | } 108 | } 109 | return newData 110 | } 111 | 112 | func deduplicate[T comparable](data []T) []T { 113 | dataMap := toMap[T](data) 114 | var newData = make([]T, 0, len(dataMap)) 115 | for key := range dataMap { 116 | newData = append(newData, key) 117 | } 118 | return newData 119 | } 120 | -------------------------------------------------------------------------------- /sqlx/scanner.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sqlx 16 | 17 | import ( 18 | "bytes" 19 | "database/sql" 20 | "errors" 21 | "fmt" 22 | "reflect" 23 | ) 24 | 25 | var ( 26 | ErrNoMoreRows = errors.New("ekit: 已读取完") 27 | errInvalidArgument = errors.New("ekit: 参数非法") 28 | _ Scanner = &sqlRowsScanner{} 29 | ) 30 | 31 | // Scanner 用于简化sql.Rows包中的Scan操作 32 | // Scanner 不会关闭sql.Rows,用户需要对此负责 33 | type Scanner interface { 34 | Scan() (values []any, err error) 35 | // ScanAll 扫描当前结果集的全部数据 36 | ScanAll() (allValues [][]any, err error) 37 | // NextResultSet 移动到下一个结果集 38 | NextResultSet() bool 39 | } 40 | 41 | type sqlRowsScanner struct { 42 | sqlRows Rows 43 | columnValuePointers []any 44 | } 45 | 46 | // NewSQLRowsScanner 返回一个Scanner 47 | func NewSQLRowsScanner(r Rows) (Scanner, error) { 48 | if r == nil { 49 | return nil, fmt.Errorf("%w *sql.Rows不能为nil", errInvalidArgument) 50 | } 51 | columnTypes, err := r.ColumnTypes() 52 | if err != nil || len(columnTypes) < 1 { 53 | return nil, fmt.Errorf("%w 无法获取*sql.Rows列类型信息: %v", errInvalidArgument, err) 54 | } 55 | columnValuePointers := make([]any, len(columnTypes)) 56 | for i, columnType := range columnTypes { 57 | typ := columnType.ScanType() 58 | for typ.Kind() == reflect.Pointer { 59 | // 兼容 sqlite,理论上来说其他 driver 不应该命中这个分支 60 | typ = typ.Elem() 61 | } 62 | columnValuePointers[i] = reflect.New(typ).Interface() 63 | } 64 | return &sqlRowsScanner{sqlRows: r, columnValuePointers: columnValuePointers}, nil 65 | } 66 | 67 | func (s *sqlRowsScanner) NextResultSet() bool { 68 | return s.sqlRows.NextResultSet() 69 | } 70 | 71 | // Scan 返回一行 72 | func (s *sqlRowsScanner) Scan() ([]any, error) { 73 | if !s.sqlRows.Next() { 74 | if err := s.sqlRows.Err(); err != nil { 75 | return nil, err 76 | } 77 | 78 | return nil, fmt.Errorf("%w", ErrNoMoreRows) 79 | } 80 | err := s.sqlRows.Scan(s.columnValuePointers...) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return s.columnValues(), nil 85 | } 86 | 87 | func (s *sqlRowsScanner) columnValues() []any { 88 | values := make([]any, len(s.columnValuePointers)) 89 | for i := 0; i < len(s.columnValuePointers); i++ { 90 | val := reflect.ValueOf(s.columnValuePointers[i]).Elem().Interface() 91 | // sql.RawBytes 存在内存共享的问题,所以需要执行复制 92 | if rawBytes, ok := val.(sql.RawBytes); ok { 93 | val = sql.RawBytes(bytes.Clone(rawBytes)) 94 | } 95 | values[i] = val 96 | } 97 | return values 98 | } 99 | 100 | // ScanAll 返回所有行 101 | func (s *sqlRowsScanner) ScanAll() ([][]any, error) { 102 | all := make([][]any, 0, 32) 103 | for { 104 | columnValues, err := s.Scan() 105 | if err != nil { 106 | if errors.Is(err, ErrNoMoreRows) { 107 | break 108 | } 109 | return nil, err 110 | } 111 | all = append(all, columnValues) 112 | } 113 | return all, nil 114 | } 115 | -------------------------------------------------------------------------------- /mapx/linkedmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ecodeclub 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mapx 16 | 17 | import "github.com/ecodeclub/ekit" 18 | 19 | type LinkedMap[K any, V any] struct { 20 | m mapi[K, *linkedKV[K, V]] 21 | head, tail *linkedKV[K, V] 22 | length int 23 | } 24 | 25 | type linkedKV[K any, V any] struct { 26 | key K 27 | value V 28 | prev, next *linkedKV[K, V] 29 | } 30 | 31 | func NewLinkedHashMap[K Hashable, V any](size int) *LinkedMap[K, V] { 32 | hashmap := NewHashMap[K, *linkedKV[K, V]](size) 33 | head := &linkedKV[K, V]{} 34 | tail := &linkedKV[K, V]{next: head, prev: head} 35 | head.prev, head.next = tail, tail 36 | return &LinkedMap[K, V]{ 37 | m: hashmap, 38 | head: head, 39 | tail: tail, 40 | } 41 | } 42 | 43 | func NewLinkedTreeMap[K any, V any](comparator ekit.Comparator[K]) (*LinkedMap[K, V], error) { 44 | treeMap, err := NewTreeMap[K, *linkedKV[K, V]](comparator) 45 | if err != nil { 46 | return nil, err 47 | } 48 | head := &linkedKV[K, V]{} 49 | tail := &linkedKV[K, V]{next: head, prev: head} 50 | head.prev, head.next = tail, tail 51 | return &LinkedMap[K, V]{ 52 | m: treeMap, 53 | head: head, 54 | tail: tail, 55 | }, nil 56 | } 57 | 58 | func (l *LinkedMap[K, V]) Put(key K, val V) error { 59 | if lk, ok := l.m.Get(key); ok { 60 | lk.value = val 61 | return nil 62 | } 63 | lk := &linkedKV[K, V]{ 64 | key: key, 65 | value: val, 66 | prev: l.tail.prev, 67 | next: l.tail, 68 | } 69 | if err := l.m.Put(key, lk); err != nil { 70 | return err 71 | } 72 | lk.prev.next, lk.next.prev = lk, lk 73 | l.length++ 74 | return nil 75 | } 76 | 77 | func (l *LinkedMap[K, V]) Get(key K) (V, bool) { 78 | if lk, ok := l.m.Get(key); ok { 79 | return lk.value, ok 80 | } 81 | var v V 82 | return v, false 83 | } 84 | 85 | func (l *LinkedMap[K, V]) Delete(key K) (V, bool) { 86 | if lk, ok := l.m.Delete(key); ok { 87 | lk.prev.next = lk.next 88 | lk.next.prev = lk.prev 89 | l.length-- 90 | return lk.value, ok 91 | } 92 | var v V 93 | return v, false 94 | } 95 | 96 | func (l *LinkedMap[K, V]) Keys() []K { 97 | keys := make([]K, 0, l.length) 98 | for cur := l.head.next; cur != l.tail; { 99 | keys = append(keys, cur.key) 100 | cur = cur.next 101 | } 102 | return keys 103 | } 104 | 105 | func (l *LinkedMap[K, V]) Values() []V { 106 | values := make([]V, 0, l.length) 107 | for cur := l.head.next; cur != l.tail; { 108 | values = append(values, cur.value) 109 | cur = cur.next 110 | } 111 | return values 112 | } 113 | 114 | func (l *LinkedMap[K, V]) Len() int64 { 115 | return int64(l.length) 116 | } 117 | 118 | // Iterate 按照随机顺序遍历, 并对每个键值对执行cb(k, v) 119 | // 如果cb的返回值为 true 则继续遍历,否则遍历结束 120 | func (l *LinkedMap[K, V]) Iterate(cb func(K, V) bool) { 121 | for cur := l.head.next; cur != l.tail; cur = cur.next { 122 | if !cb(cur.key, cur.value) { 123 | return 124 | } 125 | } 126 | } 127 | --------------------------------------------------------------------------------