Compare commits

...

665 Commits

Author SHA1 Message Date
75725db6fb remove gmlock for print of glog; add time.Time support for gdb 2019-05-15 23:53:48 +08:00
5cd8475143 add time.Time support for convertParam function of gdb 2019-05-15 16:47:39 +08:00
5629f37939 version updates 2019-05-14 22:38:03 +08:00
08ec04d8b6 fix issue in unit test case of gredis 2019-05-14 22:37:13 +08:00
c0b46f364a version updates 2019-05-14 22:02:09 +08:00
8c5f74e8bb add DoVar/ReceiveVar function for gredis 2019-05-14 21:34:38 +08:00
94832262e3 version updates 2019-05-13 22:37:31 +08:00
aefbfd52e9 add more example for gtree 2019-05-13 22:37:05 +08:00
f3f0689bd4 rename LinkMap to ListMap for gmap 2019-05-13 22:26:39 +08:00
5198d4c5fc add unit test cases for gtree.AVLTree/BTree 2019-05-12 22:56:13 +08:00
123f2d3e4e remove return value from RLockFunc/LockFunc for gset 2019-05-12 21:26:01 +08:00
3c750c3c92 copy treemap logics to new file 2019-05-12 21:22:07 +08:00
17b29cd19f improve performance for Map/Keys/Values functions for gmap; add unit test cases for gtree.RedBlackTree 2019-05-12 21:11:26 +08:00
cf1077bec4 add LinkMap for gmap package 2019-05-12 20:43:52 +08:00
4e2e4e95e0 add gmap.HashMap/TreeMap/AnyAnyMap for gmap; add unit test cases for TreeMap 2019-05-11 20:47:25 +08:00
61d64e7ae4 remove gvar.VarRead 2019-05-11 18:47:35 +08:00
883797c495 fix issue in gconv 2019-05-11 18:03:49 +08:00
0113971877 version updates 2019-05-11 17:59:56 +08:00
664b0c06a6 gjson updates 2019-05-11 17:56:14 +08:00
bd4c75a98e add AVLTree container for gtee 2019-05-10 22:29:06 +08:00
d35840409b add BTree container for gtree package 2019-05-10 21:31:35 +08:00
abaef9ba87 add gtree.RedBlackTree container 2019-05-10 13:38:06 +08:00
b15d8bdd2e adding gtree.RedBlackTree 2019-05-09 22:53:42 +08:00
718997327a add default value for gcfg.Get* functions; rename gconv.TimeDuration to gconv.Duration, and do corresponding changes to caller packages 2019-05-09 17:14:24 +08:00
fdfefbb94d add default value feature for gjson/gparser; update default value type for ghttp.Request 2019-05-09 14:19:27 +08:00
2b865a55ac update gjson/gparser 2019-05-08 22:04:36 +08:00
8138215597 comments update 2019-05-08 21:03:04 +08:00
7cc0c7a1cc rename map type of gmap; rename BatchSet/BatchRemove to Sets/Removes for gmap/gcache 2019-05-08 17:21:18 +08:00
4c647aaa19 refacting gmap 2019-05-07 22:28:34 +08:00
48b1d616c5 update code format for gtime 2019-05-07 10:22:50 +08:00
693c37d6d6 Merge pull request #116 from jroam/master
add some var flags gtime.format
2019-05-07 10:18:22 +08:00
d525c04826 公开几个常用功能方法 2019-05-06 15:03:14 +08:00
c170edbdfc add ghttp.Request.GetUrl to get current request URL 2019-05-06 13:52:34 +08:00
66e40155a9 add http/https scheme for log of ghttp.Server; add transport setting to ignore tls cert for ghttp.Client; version updates 2019-05-06 09:35:39 +08:00
59ad1a9b00 fix issue in incorrect every running logics of gcron.AddOnce 2019-05-05 22:57:13 +08:00
a5b536e218 去掉不常用参数e 2019-05-05 22:49:52 +08:00
0e6c2e790d copy mysql driver to third folder 2019-05-04 23:02:07 +08:00
5761e73061 优化一些参数性能 2019-05-04 18:14:05 +08:00
34c761e9db rename mysql driver from 'mysql' to 'gf-mysql' to avoid multiple imports error 2019-05-04 00:25:02 +08:00
87e3813636 remove go-sql-driver-mysql from third, add Register function to manually register 'mysql' driver 2019-05-04 00:10:02 +08:00
361ff0315c version updates 2019-05-03 17:04:42 +08:00
2bb227d058 update gtcp exxamples 2019-05-03 15:47:25 +08:00
99dc69e839 update gtcp exxamples 2019-05-03 13:28:27 +08:00
d78fde8099 edit a func's name 2019-04-30 22:31:52 +08:00
5d0c8956d6 edit some errs and fmt some codes 2019-04-30 11:45:26 +08:00
c9537af062 add package feature for gudp; gtcp updates 2019-04-29 23:54:47 +08:00
dfb5b3a8ce Merge pull request #15 from gogf/master
日常更新
2019-04-29 14:30:24 +08:00
a177e44583 edit some infos 2019-04-29 14:29:33 +08:00
7ae03729f3 add package support for gtcp 2019-04-28 23:55:23 +08:00
898ec21a25 del mod contents of 'go1.12' 2019-04-28 23:00:07 +08:00
6d7d8dec02 add var 'e' of gtime.format 2019-04-28 22:53:03 +08:00
ea7e2ec5ec remove go version limit in go.mod 2019-04-28 21:10:22 +08:00
a5b8e2aa2f add var 'w' of gtime.format 2019-04-28 18:12:43 +08:00
123333d9c2 edit some info 2019-04-27 22:59:06 +08:00
0c4fa1d96a fmt files 2019-04-27 22:50:44 +08:00
e5805e8c69 Merge branch 'master' of https://github.com/jroam/gf 2019-04-27 22:44:59 +08:00
bf2d45a012 add gtime.format var 't' 2019-04-27 22:37:36 +08:00
a7122788b1 add 'W' to gtime.format func(doing) 2019-04-26 17:52:45 +08:00
237c58f2b0 Merge branch 'master' of https://github.com/gogf/gf 2019-04-26 13:46:47 +08:00
efa23e4a1d README updates 2019-04-26 13:46:42 +08:00
c109cee7ef Merge pull request #115 from touzijiao/master
Merge pull request #115 from touzijiao/master
2019-04-26 13:44:51 +08:00
e111d39c54 测试文件 2019-04-26 10:38:27 +08:00
66306464e1 add Pop/Pops functions for gset 2019-04-26 08:57:48 +08:00
dd34ac1722 add build-in function 'eq/ne/lt/le/gt/ge' for gview to replace the same functions in stdlib 2019-04-25 23:23:24 +08:00
34cb222b33 remove temprary function map parameter for gview when parsing template file and content 2019-04-25 22:14:20 +08:00
a0276f7e81 add param 'z' to gtime.format func 2019-04-24 23:12:48 +08:00
d39ef156de garray updates 2019-04-24 22:23:32 +08:00
f464dc7fb8 add NewFrom/NewIntSetFrom/NewStringSetFrom functions for gset; add more example for gset 2019-04-24 18:52:24 +08:00
d29b27a5df add Merge/Sum functions for gset 2019-04-24 18:15:50 +08:00
5346ca9046 use golint checked 2019-04-24 18:08:10 +08:00
6d5b552bb7 add param of 'N','s' to gtime.format 2019-04-24 18:03:29 +08:00
aadc6aa504 Merge pull request #114 from proptypes/master
fix: #112
2019-04-24 14:07:37 +08:00
e8c3dfa13e fix: #112
closes #112
2019-04-24 11:31:13 +08:00
836d62f4aa Merge pull request #14 from gogf/master
日常更新
2019-04-24 09:34:50 +08:00
9eea93cc6e change param type from string to interface{} for ghttp.ClientRequest 2019-04-23 20:12:44 +08:00
308cb55b6b version updates 2019-04-23 19:44:28 +08:00
75ada78f8f remove parameter bind from ghttp.RouterGroup.Bind 2019-04-23 19:39:35 +08:00
ecd86e3a12 add layout example for package gview 2019-04-23 19:11:38 +08:00
c1aa5eb717 Merge pull request #98 from qq976739120/gmap-test
新增测试方法
2019-04-23 18:56:58 +08:00
d2fed1198b Merge pull request #110 from jroam/master
增加gtime包下,format方法能直接格式化星期的数字型的值
2019-04-23 18:54:43 +08:00
a9f9261dbd add gregex.ReplaceFuncMatch/ReplaceStringFuncMatch functions for package gregex 2019-04-23 14:15:12 +08:00
161e0d6e97 edit var name to "weekMap" 2019-04-23 09:59:13 +08:00
3efe511f42 优化星期英文值和数字值的格式化功能 2019-04-22 23:13:35 +08:00
5d04c2e50a fix issue in gfile.MainPkgPath 2019-04-22 22:33:11 +08:00
7b26b7ea4c fix issue in gstr.Chr 2019-04-22 21:37:11 +08:00
9d1063c6b2 Merge branch 'master' of https://github.com/gogf/gf 2019-04-22 15:48:16 +08:00
5ff7632d32 add support of layout feature for gview; fix issue in gstr.Chr 2019-04-22 15:47:59 +08:00
e6fb41504c 简写"w"参数的注释,增加周六值测试 2019-04-22 14:10:46 +08:00
a800f731dd 优化格式化星期值性能 2019-04-22 13:55:53 +08:00
f1a9fbb74e Merge branch 'master' of https://github.com/jroam/gf 2019-04-22 10:51:40 +08:00
cf81a73526 增加gtime包下,format能直接格式化星期的数字型的值 2019-04-22 10:51:24 +08:00
65036fffe8 Merge pull request #13 from gogf/master
日常更新
2019-04-22 10:49:28 +08:00
a69934a7e3 添加星期值的int形式 2019-04-21 19:36:06 +08:00
9400457bf2 Merge pull request #107 from wenzi1/master
add encoding package unit test
2019-04-19 18:29:11 +08:00
5060329721 Merge branch 'master' of https://github.com/wenzi1/gf 2019-04-19 17:11:10 +08:00
07ab1d60e8 add encoding package unit test 2019-04-19 17:04:43 +08:00
7377a82e19 Merge pull request #8 from gogf/master
Merge pull request #80 from wenzi1/master
2019-04-19 12:06:00 +08:00
f82e3ac808 Merge pull request #80 from wenzi1/master
增加gbinary单元测试
2019-04-18 22:50:24 +08:00
1a6cd1de04 Merge pull request #7 from gogf/master
update latest code
2019-04-18 20:22:17 +08:00
90e6f685b7 Merge pull request #12 from gogf/master
日常更新
2019-04-18 17:55:19 +08:00
0fc825dac1 add gcompress packge unit test 2019-04-18 12:34:01 +08:00
dbb27efe3e rename io to writer for glog.Logger 2019-04-18 09:11:14 +08:00
2d3d2e783e add default writer for glog to be integrated with other package; comments update for glog 2019-04-17 23:50:37 +08:00
6ae1defa35 Merge pull request #6 from gogf/master
pull
2019-04-17 16:56:18 +08:00
d55e77fb90 README updates 2019-04-17 09:50:57 +08:00
7fb9ae71e7 Merge pull request #103 from jroam/master
增加gfile模块的测试代码
2019-04-17 09:47:51 +08:00
9419555149 add BindFuncMap funtion for gview; README updates 2019-04-17 09:46:31 +08:00
ab634f8beb del some test code 2019-04-16 23:15:22 +08:00
9503b80d57 edit tempdir 2019-04-16 23:07:53 +08:00
bc870226e5 Merge pull request #11 from gogf/master
日常更新
2019-04-16 22:52:03 +08:00
7e106ae011 Merge pull request #81 from pibigstar/master
add the crypto test
2019-04-16 22:38:26 +08:00
958e00e231 fmt test code file 2019-04-16 22:14:42 +08:00
ab187d225d edit yml 2019-04-16 22:12:29 +08:00
9f5711d41d edit testpackage's name 2019-04-16 22:11:44 +08:00
cdb2773127 Merge branch 'master' into test_gfile 2019-04-16 18:35:25 +08:00
d12532ccc1 set a definite value for an assertion #66 2019-04-16 15:49:42 +08:00
53e9f05a10 make the details perfect #66 2019-04-16 15:49:42 +08:00
60e7ab95bc increase coverage for crypto #66 2019-04-16 15:49:42 +08:00
ae552e2b46 increase coverage for crypto #66 2019-04-16 15:49:42 +08:00
145b52f343 increase coverage for crypto #66 2019-04-16 15:49:42 +08:00
57b8fac0d5 add the crypto test 2019-04-16 15:49:42 +08:00
Jay
16a4a5ba46 Gmap测试修改 2019-04-16 14:28:25 +08:00
3d37c83532 Update TODO.MD 2019-04-15 23:49:47 +08:00
b02fa701b8 add test_gfile test of ci 2019-04-15 23:31:17 +08:00
37b7c82c64 Merge branch 'master' into test_gfile 2019-04-15 23:30:21 +08:00
1deb27df06 Merge branch 'master' of https://github.com/gogf/gf into gogf-master 2019-04-15 23:24:38 +08:00
84ed3d5767 add move and rename testfun 2019-04-15 23:14:48 +08:00
fc909a3db2 travis ci updates 2019-04-15 23:06:42 +08:00
2f7d4cd80d fix issue in Add function for gtype.Float32/Float64 2019-04-15 22:55:12 +08:00
03ccbf3613 edit TestStat fun 2019-04-15 22:52:19 +08:00
1868465319 edit TestTruncate fun 2019-04-15 22:47:09 +08:00
ff473e2fcc edit ci yml 2019-04-15 22:17:58 +08:00
a3c38eec86 edit ci yml 2019-04-15 22:12:28 +08:00
2015c847e8 fmt test file 2019-04-15 18:14:38 +08:00
1b583ed984 pull ci yml 2019-04-15 15:41:51 +08:00
11191c746a edit test 2019-04-15 15:34:35 +08:00
abedd3c5bf edit test 2019-04-15 15:30:44 +08:00
501ba5135b edit test 2019-04-15 15:27:49 +08:00
a76c98c348 暂时去掉search测试 2019-04-15 14:01:09 +08:00
9dcdc1a339 edit ci yml 2019-04-15 13:47:25 +08:00
6d1f386203 edit travis.yml 2019-04-15 11:49:07 +08:00
ff9bbf0a49 edit test 2019-04-15 11:41:05 +08:00
66e24c8d40 Merge branch 'master' into test_gfile 2019-04-15 11:26:11 +08:00
34f117c631 Merge pull request #9 from gogf/master
日常更新
2019-04-15 11:22:16 +08:00
21f2f16889 go test 2019-04-14 23:21:03 +08:00
f6fa7c422d 测试小调整 2019-04-14 22:41:11 +08:00
6903b84bb6 调整测试文件生成目录 2019-04-13 23:00:57 +08:00
0978b8fb4f 初步完成测试代码重构 2019-04-13 18:27:01 +08:00
d014583e88 重构测试代码中 2019-04-13 13:14:30 +08:00
a747f51b9d 重构测试代码 2019-04-12 18:15:31 +08:00
8300885ab6 Merge pull request #5 from gogf/master
update gtest
2019-04-12 17:59:02 +08:00
Jay
b489eed4ef 新增测试方法 2019-04-12 10:59:05 +08:00
0b57771d76 用fmt格式化代码 2019-04-12 09:47:55 +08:00
8a32a8271c fix issue in empty router group error of ghttp.Server 2019-04-12 00:19:15 +08:00
c3e716dafd Merge branch 'master' of https://github.com/gogf/gf 2019-04-11 23:07:28 +08:00
119a11eb8d README updates 2019-04-11 23:07:22 +08:00
e464d14842 Merge pull request #85 from qq976739120/gmap-test
Gmap 测试添加
2019-04-11 21:59:00 +08:00
Jay
905f46359a Gmap string-string 类型测试提交 2019-04-11 17:50:26 +08:00
Jay
8e7bf1f908 bug 修复 2019-04-11 17:46:19 +08:00
Jay
3a1524ae6d Gmap 初测完成 2019-04-11 17:33:52 +08:00
5c04befea3 edit ci yml file 2019-04-11 16:39:20 +08:00
ae3584cdff edit ci 2019-04-11 16:27:47 +08:00
a60578c82c edit ci 2019-04-11 16:23:29 +08:00
74558a500c edit c1 2019-04-11 16:17:44 +08:00
d963d8c8c1 testci 2019-04-11 16:13:56 +08:00
853892c24f config ci 2019-04-11 16:05:33 +08:00
641f939e3d edit test 2019-04-11 15:57:48 +08:00
af77504eaf 修改测试 2019-04-11 15:49:00 +08:00
62649d6468 config ci 2019-04-11 15:22:29 +08:00
bb914e605e 配置ci 2019-04-11 15:08:17 +08:00
62ee88bbfa Update .travis.yml 2019-04-11 14:45:47 +08:00
99c964bb4a 添加ci配置文 2019-04-11 11:02:43 +08:00
09ceaef3e9 配置ci 2019-04-11 10:59:53 +08:00
a82900af55 对分支添加ci检查 2019-04-11 10:03:47 +08:00
5ef589f31a Merge pull request #8 from gogf/master
合并前更新。
2019-04-11 09:56:01 +08:00
cd719f134d 测试率达到82.9% 2019-04-11 09:47:51 +08:00
f69eb219b5 version updates 2019-04-11 09:43:17 +08:00
0ffe17ee3d revert SearchArray/InArray functions for gstr 2019-04-11 09:26:52 +08:00
4ac647a215 gcmd updates 2019-04-11 09:05:27 +08:00
872536c035 Merge branch 'master' of https://github.com/gogf/gf 2019-04-10 23:09:59 +08:00
c7a6a6fff0 improve grand.Intn; README updates 2019-04-10 23:09:38 +08:00
2534655bc8 测试覆盖率达到76.3% 2019-04-10 22:16:16 +08:00
0cb82d70fd Merge pull request #96 from aloncn/master
update unit test for gring
2019-04-10 21:42:25 +08:00
acac5a2ad6 version updates 2019-04-10 18:33:51 +08:00
9ec15ad2ca fix issue in repeated rand value of grand.Intn 2019-04-10 18:33:12 +08:00
a9b7d56d0b 测试覆盖率达到63.1% 2019-04-10 17:54:28 +08:00
e9ca1eb538 完成第一遍功能测试 2019-04-10 16:45:33 +08:00
0532800895 添加测试代码 2019-04-10 16:22:29 +08:00
ad50ca6e60 update unit test for gring 2019-04-10 14:26:21 +08:00
c1ad999c25 添加测试 2019-04-10 13:45:42 +08:00
4accd1264d 添加内容测试 2019-04-10 13:39:36 +08:00
e1f3da3aa8 去掉mod设置的版本信息 2019-04-10 11:31:24 +08:00
4bf9a7950b Merge branch 'master' into test_gfile 2019-04-10 11:28:57 +08:00
8285c31bf1 Update unit test for gring 2019-04-10 10:44:40 +08:00
230be66fa9 update unit test for gring 2019-04-10 10:13:56 +08:00
2c53f934c9 Merge branch 'master' of https://github.com/gogf/gf 2019-04-10 09:50:29 +08:00
08785cb272 update unit test cases of gjson 2019-04-10 08:54:49 +08:00
bd0207c938 update unit test for gring 2019-04-10 01:16:45 +08:00
6fad737617 update unit test cases of gjson/gparser 2019-04-10 01:00:51 +08:00
af4148d985 Merge branch 'master' of github.com:aloncn/gf 2019-04-10 00:37:22 +08:00
922eaf4d42 Update unit test for gring 2019-04-10 00:37:06 +08:00
4b5153950f Add unit test for gring 2019-04-10 00:37:06 +08:00
78010d2bd7 Update unit test for gring 2019-04-10 00:22:10 +08:00
6cc0017826 测试空的断言。 2019-04-09 23:25:20 +08:00
8a9131c3dc Merge pull request #7 from gogf/master
日常更新
2019-04-09 23:16:18 +08:00
85c2ed1bf2 improve nil checks for gtest.Assert* 2019-04-09 23:12:22 +08:00
aca1df634d 添加测试文件内容相关函数。 2019-04-09 23:11:04 +08:00
053a3c1a53 add unit test 2019-04-09 19:12:48 +08:00
429aa90e0d Merge branch 'master' of github.com:aloncn/gf 2019-04-09 18:42:16 +08:00
4f792b347d Add unit test for gring 2019-04-09 18:40:48 +08:00
468c315087 Merge pull request #4 from gogf/master
update 1.6
2019-04-09 17:36:40 +08:00
6d8ced21b9 Add unit test for gring 2019-04-09 17:27:58 +08:00
b3d5fc149e add unit test 2019-04-09 17:27:11 +08:00
Jay
277b7a4536 测试修改 2019-04-09 15:06:12 +08:00
43886511b9 add unit test 2019-04-09 12:28:21 +08:00
8b3eb5b02d version&release updates 2019-04-09 00:15:48 +08:00
054ef87886 继续添加文件测试. 2019-04-08 23:13:28 +08:00
Jay
39c65d9e9a 测试添加 2019-04-08 17:49:15 +08:00
Jay
b97bbbfa3d 测试添加 2019-04-08 17:32:07 +08:00
Jay
ace6ba8096 Gmap 测试添加 2019-04-08 17:02:57 +08:00
a2b87d84e9 添加gfile测试代码,10% 2019-04-07 22:46:14 +08:00
b90d61b27c README updates 2019-04-07 22:03:04 +08:00
85606e3e7e README updates 2019-04-07 21:59:28 +08:00
1fc85d49bd gtime updates; README updates 2019-04-07 21:49:24 +08:00
a5cfb4e638 Merge pull request #82 from hailaz/master
Add gtime unit test
2019-04-07 21:27:29 +08:00
38754bf062 Merge pull request #78 from youyixiao/youyixiao_gogf
gregex_unit_test
2019-04-07 21:23:03 +08:00
f1818ed2ff 补充gtime_format的覆盖测试。 2019-04-06 23:53:06 +08:00
352ad17715 避免pr时造成gtime文件的更改。 2019-04-06 22:56:47 +08:00
e50b8d9632 移除gtime unit test中注释掉的函数。 2019-04-06 22:50:37 +08:00
2107061a46 Add gtime unit test 2019-04-06 22:48:47 +08:00
61f57b4895 update comments of gstr 2019-04-06 21:31:01 +08:00
d34273abff 同步master 2019-04-05 23:53:43 +08:00
0aff0f0362 同步master 2019-04-05 23:49:20 +08:00
68949b69bc update example of gtime 2019-04-05 23:31:14 +08:00
c56c77d3a1 fix issue in G&j char format 2019-04-05 23:13:47 +08:00
dc82ce395a comment updates for gcfg 2019-04-05 00:23:59 +08:00
fd63a2209b 增加单元测试 2019-04-04 23:31:17 +08:00
2a29483456 comment fix 2019-04-04 23:24:27 +08:00
4f10562980 remove limit for gdb.Model.One 2019-04-04 23:22:09 +08:00
a26ec37f59 Merge pull request #3 from gogf/master
更新
2019-04-04 23:17:09 +08:00
779ad93bcb 参数为nil时的特殊处理 2019-04-04 23:02:00 +08:00
1ec0219473 add gbinary unit tests 2019-04-04 23:00:21 +08:00
6863928b06 !18 限制下查询单条记录查询条数
Merge pull request !18 from 一墨染尽青衣颜/master
2019-04-04 22:55:13 +08:00
aa4dca11f0 fix issue in gtime; update comment for g/gtest 2019-04-04 22:52:56 +08:00
dc6ab820ce 修复解析日期函数(parseDateStr)对"02.jan.2006"格式日期解析异常的问题。
更正函数(isNumeric)的注释。
2019-04-04 17:57:17 +08:00
be0fa4d60b gregex_unit_test 2019-04-04 17:45:04 +08:00
a86d2272af gregex_unit_test 2019-04-04 16:15:50 +08:00
61a67892ac Merge pull request #6 from gogf/master
同步最新代码
2019-04-04 11:37:58 +08:00
388d5954cb Merge pull request #2 from gogf/master
update unit test
2019-04-04 10:56:01 +08:00
08550d413e update unit test cases for gcfg 2019-04-04 09:18:43 +08:00
b89294561b add gmap.SetIfNotExistFunc/SetIfNotExistFuncLock for gmap; update comment of gmap; update instance feature of gcfg 2019-04-03 23:39:31 +08:00
20977558cc Merge pull request #1 from gogf/master
更新代码
2019-04-03 16:47:16 +08:00
630d8fdb43 add Instance function for gcfg 2019-04-03 09:59:15 +08:00
c4c7e6caf4 gofmt 2019-04-03 00:03:46 +08:00
8a8fea1257 v1.5.23 2019-04-02 23:40:20 +08:00
0e0f297a3f add consurrent safety parameter for gjson.Load/LoadContent; fulfil data type auto-check logics for gjson.LoadConetnt; update comment of gjson/gparser 2019-04-02 23:33:27 +08:00
fd2c0f2b24 Merge pull request #59 from wenzi1/master
修复gxml字符集转换的并发安全问题
2019-04-02 17:43:19 +08:00
ecc6e3888d up 2019-04-02 17:10:21 +08:00
47c073aaf3 add Clear function for gcfg; mark Reload function of gcfg as deprecated; update unit test for gins 2019-04-02 16:08:46 +08:00
07476a4349 add instance management feature for gdb/gredis; add customized configuration content management feature for gcfg; update gjson for data content type check 2019-04-02 14:37:46 +08:00
817148f3a1 增加单元测试 2019-04-01 18:32:37 +08:00
bd4271cd8c 增加gxml的单元测试 2019-03-31 23:36:11 +08:00
b1804fc346 TODO updates; version updates 2019-03-31 21:13:11 +08:00
a3886c2179 fix issue in RemoteAddr of gudp.Conn 2019-03-31 20:58:31 +08:00
f258b5bf1c change logging level from error to debug in route regitsry for struct for ghttp package; handle possible overrange usage of r.URL.Path in ghttp.Server 2019-03-31 20:52:30 +08:00
a05361011f 修复并发安全问题,改为如果非UTF8字符集则先做字符集转换 2019-03-30 23:53:42 +08:00
6b34a77251 修复并发安全问题,改为如果非UTF8字符集则先做字符集转换 2019-03-30 23:39:07 +08:00
afb1adee3d version updates 2019-03-28 17:58:33 +08:00
22fa7a37f3 add UseNumber support for gjson; add more unit test cases for gjson 2019-03-28 17:51:05 +08:00
6a58bfc574 add stdout printing support for ghttp.Server logging 2019-03-28 09:34:16 +08:00
64124c60fc add fatal error when no router set or statis feature enabled for ghttp.Server 2019-03-27 16:23:35 +08:00
9a0066de62 add file/folder search support for gcfg/gview in order of envpath/pwdpath/binpath/mainpath; add gfile.Search function 2019-03-27 11:48:53 +08:00
22c7c7403b fix issue in basic http auth check for server side 2019-03-27 09:16:23 +08:00
83db8e4b15 add wss example for websocket 2019-03-22 17:26:39 +08:00
25f2e121e7 version updates 2019-03-22 15:09:22 +08:00
1325a145d8 fix issue in config auto reload of gcfg 2019-03-22 15:08:43 +08:00
9bc49c0b29 add logging for error in ghttp.Request 2019-03-22 14:31:02 +08:00
8e84e5b0f3 update comment of gredis 2019-03-22 11:22:03 +08:00
a42e6b0c45 ignore private attribute of struct in gconv/gvalid 2019-03-22 10:12:43 +08:00
51bb7a9854 ignore private attribute of struct in gconv/gvalid 2019-03-22 10:12:15 +08:00
62580b5719 add examples for gredis 2019-03-21 23:51:57 +08:00
15cfd5ce5c add g.Export 2019-03-21 18:21:53 +08:00
11c89c4090 fix issue in GetOrSetFuncLock of gmap/gcache; add gutil.IsEmpty/Export functions 2019-03-21 18:20:20 +08:00
4a12cb9f27 version updates 2019-03-21 10:52:12 +08:00
32f575eddd update unit test of gins 2019-03-21 10:36:24 +08:00
fbb4cb3b1c add more unit test cases for gredis 2019-03-21 10:04:53 +08:00
c9d2d5e8ab add configuration support for maxIdle/maxActive/idleTimeout/maxConnLifetime of gredis; update gjson.Append/Len functions; add more unit test cases for gredis/gins/gstr/gjson 2019-03-21 00:14:23 +08:00
93763192f2 version updates 2019-03-19 17:55:02 +08:00
80e0eae6b0 add TLSConfig support for ghttp.Server 2019-03-19 17:48:37 +08:00
4e3d735b90 add logging for gcron 2019-03-19 13:58:18 +08:00
60e5a7da28 add more unit test cases for grand/gstr 2019-03-18 23:52:25 +08:00
997b5ba889 README updates 2019-03-18 14:10:30 +08:00
b3e7ca1963 travis updates 2019-03-18 14:05:46 +08:00
5a82d695c1 mv greuseport to a new repo 2019-03-18 13:56:16 +08:00
64a0427150 travis updates 2019-03-18 13:34:51 +08:00
f6aafc1d6b Merge pull request #5 from gogf/master
更新数据库操作
2019-03-18 10:00:23 +08:00
bca5532df8 version updates 2019-03-17 22:36:58 +08:00
72eeadd9aa Merge branch 'master' into develop 2019-03-17 22:31:15 +08:00
0af55794f6 add more unit test cases for gdb 2019-03-17 22:26:41 +08:00
25a6c53533 add unit test cases for gfsnotify 2019-03-15 14:54:01 +08:00
9f9172c775 update unit test cases of package gconv 2019-03-15 09:05:56 +08:00
320e0db417 fix int-overflow issue in gconv.String when converting int64 to string; add more unit test cases for package gconv 2019-03-15 00:22:39 +08:00
cb8362d447 update unit test cases of package gins 2019-03-14 23:28:56 +08:00
45a83fc53c unit test cases update for package gins 2019-03-14 00:23:46 +08:00
3411bd1c1d merge master 2019-03-13 22:41:00 +08:00
6ab0a77364 add more defaulr searching paths for g.Config() 2019-03-13 22:12:59 +08:00
281bae4116 unit test cases update for package gins 2019-03-13 09:11:50 +08:00
218c692fe0 update unit test cases 2019-03-12 23:56:09 +08:00
fa69b581e1 update unit test cases 2019-03-12 23:50:30 +08:00
bd0baceeca update unit test cases 2019-03-12 23:47:27 +08:00
e71c837472 update unit test cases 2019-03-12 23:45:44 +08:00
782aaabd07 add more unit test cases for gins; update text/template for gview 2019-03-12 23:26:10 +08:00
9014325a7c test 2019-03-12 10:54:38 +08:00
8ae9276732 add disable cache feature option for package gspath 2019-03-12 00:24:31 +08:00
79a3aa5916 add more unit test cases for package gvalid 2019-03-11 22:58:21 +08:00
733c5db228 version updates 2019-03-11 22:33:43 +08:00
6171c621a7 fix issue in missing customed error messages in package gvalid 2019-03-11 22:33:20 +08:00
fc11856a28 Merge pull request #4 from gogf/master
日常更新
2019-03-11 17:43:06 +08:00
802568856c version updates 2019-03-11 16:35:44 +08:00
127fb67185 add session id check in session object initialzing 2019-03-11 16:33:51 +08:00
5db039bbce gdb.Model updates, rename Alterable function to Safe, change gdb.Model alterable in default, update Where function to support more cases when using map as its param, add more unit test cases for gdb.Model 2019-03-11 16:14:55 +08:00
b0726b9733 Merge pull request #3 from gogf/master
日常更新
2019-03-10 22:38:15 +08:00
8a3365d18e ghttp comment updates 2019-03-10 00:39:34 +08:00
2ae5b1a4f8 add more unit test cases for ghttp.Server 2019-03-10 00:35:03 +08:00
f9515d7126 version updates 2019-03-09 10:20:11 +08:00
b56679a97c revert g.Map to map[string]interface{}, add g.MapAnyAny 2019-03-09 10:17:21 +08:00
d1b123964a README updates 2019-03-08 21:07:18 +08:00
991f7c4958 ghttp.Response updates 2019-03-08 19:03:22 +08:00
770619c39e update unit test cases og gcron 2019-03-08 18:27:11 +08:00
4ad5450b80 gtest updates; remove one unit test case of gconv.Struct 2019-03-08 18:18:29 +08:00
49ce7fe885 fix data race issue of gqueue 2019-03-08 17:54:50 +08:00
e66f63262b add more unit test cases for ghttp.Server 2019-03-08 17:31:30 +08:00
94bd5da68a add map key operator support in Where function for gdb 2019-03-08 11:12:52 +08:00
3eee95caf2 VERSION updates 2019-03-08 10:35:35 +08:00
5c638c630a add select in support for slice type of arguments in Where function of gdb 2019-03-08 10:33:36 +08:00
a6ec9d7a1c update gdb.Model.Where, orverwrite args if multiple calls Where function 2019-03-08 10:18:38 +08:00
374c70c0e3 README updates 2019-03-08 09:03:32 +08:00
40771066d4 travis updates 2019-03-08 08:48:53 +08:00
22a7ef43ce Merge branch 'master' into develop 2019-03-08 00:21:52 +08:00
05f22d1cee README updates 2019-03-08 00:15:57 +08:00
ebf56a86ab add Alterable function for gdb.Model 2019-03-07 23:53:56 +08:00
2ba59e8943 fix issue in gtime convert in package gconv; add Structs/Scan functions for gdb.Model; add GetStructs/GetScan functions for gdb.Base and gdb.TX 2019-03-07 23:36:45 +08:00
4e4ea25e7f Merge pull request #2 from gogf/master
第一次同步
2019-03-07 17:35:22 +08:00
83be1de04c remove error returns from router registry functions of WebServer; add more unit test cases for WebServer 2019-03-06 15:21:00 +08:00
c8251ed82f g.Map updates 2019-03-06 10:38:50 +08:00
2335ea0c4d merge master 2019-03-05 21:07:54 +08:00
5d874e9063 add example code for gconv.Map; comment updates of gdb 2019-03-05 17:52:34 +08:00
f2c080d25f update ghttp.Response 2019-03-05 17:16:21 +08:00
975da97b4a fix issue in empty check for nil attribute of struct in package 'empty';add set-cookie support for 302 status in ghttp.Server 2019-03-05 17:06:37 +08:00
37617589a6 add more unit test cases for ghttp.Server 2019-03-04 23:51:44 +08:00
5c9f0db903 version updates 2019-03-04 23:37:01 +08:00
28abf0c175 add ReplaceI/ReplaceIByArray/ReplaceIByMap case-insensetive replacing functions for gstr 2019-03-04 23:35:06 +08:00
13749feab4 add json features '-' and 'omitempty' for gconv.Map, like package 'json' from stdlib; add internal package 'empty', to check empty variable 2019-03-04 22:59:29 +08:00
c1e77b7e09 update example of gtcp 2019-03-03 20:40:36 +08:00
962a5e93f7 update example of gtcp 2019-03-03 20:20:31 +08:00
c3b9b8d5ae gconv updates 2019-03-03 00:53:35 +08:00
1ad076c522 travis updates 2019-03-03 00:31:16 +08:00
b01777fcd1 travis updates 2019-03-03 00:28:32 +08:00
55a5532c2e gdb updates 2019-03-03 00:14:20 +08:00
adb928941a issue template updates 2019-03-02 00:02:05 +08:00
f92c1fc527 update CORS feature of ghttp.Response; add more unit cases for ghttp.Server 2019-03-01 23:45:55 +08:00
3ae7279ebc issue template updates 2019-03-01 14:01:05 +08:00
66287c2d0e update the admin feature and unit test cases of ghttp 2019-02-28 23:57:20 +08:00
5d37626981 hot fix issue in router registry 2019-02-28 14:07:00 +08:00
d0ed3b979d release updates 2019-02-28 10:28:09 +08:00
fa256aec9f add example for layout using template engine; fix issue with config error output in gview when no config used 2019-02-27 22:53:39 +08:00
86834c5a15 version and comment updates 2019-02-27 22:17:09 +08:00
c2046157d6 add ghttp.Request.GetRawString function 2019-02-27 21:17:56 +08:00
cdb2cc89c0 remove password for unit test of gdb 2019-02-27 12:51:48 +08:00
4964c09a77 add more unit test cases for gdb 2019-02-27 12:38:12 +08:00
ef34b2c9ce gdb updates, add batch operation support for Insert/Save/Replace, change list param type from List to interface{} for Batch* functions 2019-02-27 09:38:10 +08:00
9afe242293 Merge branch 'master' into qiangg_gdb_map 2019-02-27 08:52:50 +08:00
136d93d373 add donate for gitee 2019-02-26 23:39:09 +08:00
3102cec5b8 issue template updates 2019-02-26 23:06:14 +08:00
e28eb9da04 add issue template 2019-02-26 22:58:52 +08:00
754ed86dfb add issue template 2019-02-26 22:56:52 +08:00
e352b07055 add issue_template 2019-02-26 22:51:30 +08:00
fdea242b50 Merge branch 'master' into develop 2019-02-26 22:23:24 +08:00
7058e4f2c4 fix issue of router group in auto-adding 'index' router for controller and object 2019-02-26 22:21:57 +08:00
704a5dbd73 fix issue of "memory leaks" in gfpool 2019-02-26 17:52:50 +08:00
fbd4ce8c2e fulfil unit cases of ghttp.Cookie/Session 2019-02-26 17:17:11 +08:00
cb3ce71cdc fix issue in ghttp.Session 2019-02-26 14:33:01 +08:00
7f44f2f5e4 gdb updates 2019-02-26 14:23:29 +08:00
66efbe63f0 add struct support for *Insert/*Save/*Replace/*Update/Where/Data functions 2019-02-26 01:19:01 +08:00
49a1308875 ghttp.Session updates 2019-02-25 23:08:26 +08:00
4332580c01 disable build-in session variable in template parsing, when it's not necessary 2019-02-25 15:20:57 +08:00
72ecf2d2af TODO++ 2019-02-25 12:39:07 +08:00
0f854e46d8 refact Merge function for garray; add more frequently-used type alias for gf 2019-02-22 09:08:46 +08:00
4564f38e1a add PopRands/Rands functions for garray 2019-02-20 19:06:08 +08:00
7e06bf6705 Merge branch 'master' into qiangg_garray2 2019-02-20 16:28:24 +08:00
3dd8b6ad33 version updates 2019-02-20 16:24:44 +08:00
3e0a975a88 fix issue in gcron, allow special char '?' for day and week pattern 2019-02-20 16:24:05 +08:00
6aa1c5b1eb ghttp updates 2019-02-20 16:07:11 +08:00
d780cf64c2 garray updates 2019-02-20 14:18:11 +08:00
1fb5a8cd6f comments for glog update 2019-02-20 11:16:10 +08:00
8925460718 comment updates of gregex package 2019-02-19 11:19:23 +08:00
9797701881 remove Search/InArray functions from gstr package; update custom http status handling logics of web server 2019-02-18 16:12:59 +08:00
8a50b180c0 README updates 2019-02-18 09:25:05 +08:00
159190d187 version updates 2019-02-17 20:49:58 +08:00
989d543a1f fix issue with error response status code 200 of web server; ghttp request client updates 2019-02-17 20:45:35 +08:00
fcc3a1b2f6 README updates 2019-02-15 23:21:46 +08:00
cfdeb87093 gfile updates 2019-02-15 21:30:35 +08:00
74eef34ec2 gtest updates; TODO++ 2019-02-15 08:55:29 +08:00
2e87d5322f remove sarama-cluster 2019-02-14 19:30:58 +08:00
e89a49f39a remove gkafka 2019-02-14 19:24:45 +08:00
5d3fd91f0b remove sarama 2019-02-14 19:21:38 +08:00
f455d22893 move gkafka to new repo 2019-02-14 17:04:20 +08:00
c00f528098 remove reuseport in ghttp 2019-02-14 15:43:31 +08:00
6f02ad60eb hot fix issue in tpl auto update 2019-02-14 13:38:52 +08:00
ca6c0791ae fix issue with greuseport in windows 2019-02-03 21:41:32 +08:00
ee485dbfd8 README updates 2019-02-02 17:55:22 +08:00
2ecaa12647 travis updates 2019-02-02 16:54:04 +08:00
580e099cb7 update imports 2019-02-02 16:42:26 +08:00
83e50f0d38 update imports 2019-02-02 16:35:26 +08:00
48c770b475 update dependences 2019-02-02 16:31:34 +08:00
ba67101942 updates 2019-02-02 16:22:41 +08:00
72efde09b3 travis updates 2019-02-02 16:20:36 +08:00
76c49170bd change gitee.com/johng/gf -> github.com/gogf/gf 2019-02-02 16:18:25 +08:00
150aa7e2c2 comments update 2019-02-02 15:21:48 +08:00
0762fec696 comments update 2019-02-02 15:16:45 +08:00
1447496efa comments update 2019-02-02 14:22:32 +08:00
6fb9eafef0 garray updates 2019-02-02 09:07:40 +08:00
16bde5e9fc gmap updates 2019-02-01 22:25:58 +08:00
7b85c44444 add Clone for garray; comments of garray update 2019-02-01 22:00:58 +08:00
87d553fca2 greuseport updates 2019-02-01 21:05:56 +08:00
ba050d4c86 merge qiangg_reuseport 2019-02-01 20:51:02 +08:00
90cd7f49fd greuseport updates 2019-02-01 20:49:02 +08:00
29b42290c7 merge master 2019-02-01 20:46:53 +08:00
6c6c64bb3b rename category string to text 2019-02-01 20:45:11 +08:00
ae7db2cf9f gmap updates 2019-02-01 18:40:48 +08:00
1afab62dec fulfil unit test cases for garray 2019-02-01 18:33:53 +08:00
4171ca992e add reverse feature for function Sort of normal array; fulfil unit test cases for StringArray 2019-02-01 18:14:12 +08:00
e1166a8a80 Merge branch 'master' into develop 2019-02-01 17:55:06 +08:00
1dc6c799e1 version updates 2019-02-01 17:48:48 +08:00
ae1e075696 fix issue in binSearch of garray 2019-02-01 17:44:58 +08:00
74a7f71894 gset/gmap update 2019-02-01 17:30:23 +08:00
f2e149d3b3 fulfil unit test cases of gstr 2019-02-01 14:38:45 +08:00
92c0bf9cdc fix unit test cases of ghttp 2019-02-01 11:53:10 +08:00
020b050fb1 fix unit test cases for gstr/gconv/ghttp 2019-02-01 11:42:25 +08:00
08d71cede3 merge qiangg_php 2019-02-01 00:04:34 +08:00
be1f6cfbae Merge branch 'master' of https://gitee.com/johng/gf into develop 2019-02-01 00:02:17 +08:00
9aa5e139cf fulfil unit test cases of gstr 2019-02-01 00:02:01 +08:00
1d5c3d62dd fullfil unit test cases of gset 2019-01-31 21:15:39 +08:00
0e611ae94b merge master 2019-01-31 18:14:02 +08:00
104613b056 VERSION up 2019-01-31 13:58:35 +08:00
99577ad874 hot fix gfcache issue 2019-01-31 13:55:53 +08:00
908a46d27d gtest updates 2019-01-31 13:00:17 +08:00
85677b952f fulfil funcs of gstr/gset/garray; move gstr/gregex to the string category; move gtest to the test category 2019-01-30 21:27:03 +08:00
f4773ef1e4 add func ghttp.Group.Common for common http methods(GET/PUT/POST/DELETE) binding; fix issue #IRHB3,#IRHSJ; add more unit test cases for gvalid 2019-01-29 23:01:14 +08:00
5eaa6183b5 update benchmark test cases of gregex 2019-01-28 12:03:47 +08:00
4036d40c58 add batchSqlResult for gform; TODO++ 2019-01-27 19:44:05 +08:00
0b80cbb0dc merge master 2019-01-27 19:18:24 +08:00
79cb386d82 reuseport updates 2019-01-27 16:40:16 +08:00
09be68831b reuseport updates 2019-01-27 14:25:31 +08:00
0ac13c2b81 reuseport updates 2019-01-27 14:14:59 +08:00
94ef38e3cc reuseport updates 2019-01-27 03:28:55 +08:00
76882ac01c update golang.org/x/sys, add reuseport package 2019-01-27 03:13:20 +08:00
0d315218dd README updates 2019-01-25 12:48:22 +08:00
59945fbe91 README updates 2019-01-25 12:47:53 +08:00
c4962ec017 gfpool updates 2019-01-24 20:15:11 +08:00
58d60bc899 add json tag support for struct converting of gconv 2019-01-24 15:37:53 +08:00
5dd0a78423 ghttp updates 2019-01-24 14:03:48 +08:00
141ea7cc2d ghttp updates 2019-01-24 13:50:26 +08:00
a488d1dbf7 ghttp updates 2019-01-24 13:40:48 +08:00
ec130d0763 RELEASE updates 2019-01-23 21:30:02 +08:00
30729e3f93 README updates 2019-01-23 18:34:52 +08:00
241d7402cc README updates 2019-01-23 18:30:04 +08:00
3b14aba1a2 README updates 2019-01-23 17:33:51 +08:00
b02205f7cd gpool updates 2019-01-23 17:27:30 +08:00
c27bc0023f Merge branch 'master' of https://gitee.com/johng/gf 2019-01-23 15:01:29 +08:00
9698a7c5be gpool updates 2019-01-23 15:01:21 +08:00
071e2f8bb4 !17 修复where为空时不是1=1的bug
Merge pull request !17 from 张金富/master
2019-01-23 13:46:41 +08:00
726d3f7024 update unit tests of gtimer 2019-01-23 13:36:39 +08:00
3503aa43b4 gtimer, gmlock updates 2019-01-23 13:30:46 +08:00
e865b46304 add console and env values to change the default values of gtimer 2019-01-23 13:01:58 +08:00
494f96495e fulfil unit test cases of gtimer 2019-01-23 11:28:57 +08:00
7ed2081513 gcron, gtimer updates 2019-01-22 22:07:46 +08:00
5110313657 gcron, gtimer updates 2019-01-22 13:50:10 +08:00
24990e26c8 gcron, gtimer updates 2019-01-21 22:09:51 +08:00
3ca086bcec 修复where为空时不是1=1的bug 2019-01-21 16:26:47 +08:00
7d103c4ee8 gcron updates 2019-01-18 22:02:17 +08:00
5fed6f5681 update unit test case of gmlock 2019-01-18 15:14:05 +08:00
616539ecb0 update unit test cases of gmlock, concurrent safe reason 2019-01-18 15:03:45 +08:00
9e99e88d27 gmlock updates, add more unit test cases for gmlock 2019-01-18 11:30:52 +08:00
0e39400dd0 johng-cn/gf -> gogf/gf 2019-01-17 22:20:37 +08:00
2ba796de01 gconv examples update 2019-01-17 20:32:02 +08:00
efe2535977 README updates 2019-01-17 20:04:17 +08:00
c17352b8af travis updates 2019-01-17 16:37:26 +08:00
b1fc3ff17a travis updates 2019-01-17 16:06:56 +08:00
485dafb616 update grand.MeetProb, change param type from float64 to float32 2019-01-17 14:20:18 +08:00
bf25a3a601 gtimer updates; grand updates, change buffer slice type from uint64 to uint32 2019-01-17 14:15:23 +08:00
14fcd0b2f9 move gtime.SetTimeout&gtime.SetInterval to gtimer 2019-01-16 23:23:53 +08:00
cb24714faa gcron updates 2019-01-16 22:59:26 +08:00
36199334f0 gcron updates 2019-01-16 22:34:22 +08:00
72c7e65dfa gcron updates 2019-01-16 21:06:35 +08:00
a4ad301b44 fulfil logics of Exit* funcs of ghttp 2019-01-16 20:27:58 +08:00
72569321fa package comments update 2019-01-16 13:35:16 +08:00
1600a80124 comment updates 2019-01-16 13:02:59 +08:00
80c1a02377 pakage comments update 2019-01-16 09:00:23 +08:00
0c41909454 pakage comments updates 2019-01-15 23:27:47 +08:00
2b5d889bb9 fulfil unit test cases of gconv 2019-01-15 21:54:34 +08:00
2c2a71d429 fix issue in concurrent safe handling of gregex 2019-01-15 20:20:34 +08:00
f900414e38 add surport for field type auto detection in gform 2019-01-14 22:55:43 +08:00
1c72766c34 add new func Meet/MeetProb and alias N/Str/Digits/Letters for grand 2019-01-14 20:12:44 +08:00
302f3c1467 optimized package gconv, supported struct attr with ptr 2019-01-14 13:55:07 +08:00
9415419324 fix issue in gconv.Struct when given atrr of struct is nil time.Time 2019-01-14 09:00:31 +08:00
602592a354 TODO-- 2019-01-13 17:02:32 +08:00
1a4cba5fa5 VERSION updates 2019-01-13 00:47:23 +08:00
018853e976 optimize WebServer in hook priority handler and request exit mechanism 2019-01-13 00:43:36 +08:00
651bd33b73 add new 16x and 19x phone number validation for gvalid 2019-01-12 23:45:45 +08:00
f4644ce685 change func(xxx, safe...bool) to func(xxx, unsafe...bool); add new internal package mutex 2019-01-12 23:36:22 +08:00
0a422e9a89 rename go test funcs for gtimer 2019-01-12 22:47:07 +08:00
432c16c89f complete pckage gtimer development, a levelled timing wheel for interval/delayed jobs running and management 2019-01-12 22:41:12 +08:00
241706cbbf up 2019-01-12 20:20:30 +08:00
33dd0f9922 timing wheel up 2019-01-11 13:46:40 +08:00
ed8bb354e5 up 2019-01-09 18:47:11 +08:00
e373392f64 up 2019-01-09 13:26:59 +08:00
292fd2f39e up 2019-01-09 13:06:15 +08:00
1c9cb8286f up 2019-01-09 12:54:37 +08:00
8296061b64 up 2019-01-06 16:43:42 +08:00
bb5d84c29c up 2019-01-06 11:09:50 +08:00
eae857bcf7 merge master 2019-01-04 18:39:40 +08:00
d604d198ab hot fix 2019-01-04 15:43:56 +08:00
40b5162fdf up 2019-01-04 15:32:16 +08:00
7934ad6904 Merge branch 'develop' of https://gitee.com/johng/gf into develop 2019-01-03 19:12:08 +08:00
858b010caa up 2019-01-03 19:11:54 +08:00
36791d2f48 README updates 2019-01-02 21:43:17 +08:00
08f9cffed9 TODO++ 2019-01-02 21:38:26 +08:00
783c0ba846 updates kafka dependences 2019-01-02 21:35:48 +08:00
37cd2351e2 !16 增加ORACLE,SQL SERVER中获取指定表的所有字段名的方法
Merge pull request !16 from 蚊子/master
2019-01-02 21:11:30 +08:00
be1e250a6b 增加ORACLE,SQL SERVER中获取指定表的所有字段名的方法 2019-01-02 18:17:01 +08:00
f86896e5af Merge branch 'master' into develop 2019-01-02 12:42:36 +08:00
7ad4f61564 revert hot fix codes, waiting for next release to fix 2019-01-02 11:41:22 +08:00
adf06a2b0d thirdparty package kafka updated to date 2019-01-02 11:02:03 +08:00
d6aa2b2512 hot fix for gcache 2019-01-02 10:30:27 +08:00
a95b1f0dae add more unit test cases 2019-01-02 10:18:00 +08:00
0a8af94610 !15 打开文件没关闭
Merge pull request !15 from hello/master
2019-01-01 19:45:46 +08:00
f1c7b95b33 up 2019-01-01 19:43:31 +08:00
wgb
2c27c0f58a close file 2019-01-01 15:35:25 +08:00
e4a7e23c46 VERSION updates 2018-12-31 17:56:06 +08:00
6f15adf57f unit test cases++ 2018-12-31 17:46:04 +08:00
1966b40d01 ghttp.Client updates; add more unit test cases for web server 2018-12-31 00:50:55 +08:00
24ce4d098e ghttp.Client updates; add more unit test cases for web server 2018-12-31 00:22:18 +08:00
98619f9bc9 add more unit test cases for web server 2018-12-30 22:02:46 +08:00
1efeb2515d refract package 'gcron'; add package 'gtimew' for Time Wheel style job management 2018-12-30 18:56:21 +08:00
ccf837b2bf up 2018-12-30 14:53:16 +08:00
e558863743 up 2018-12-30 11:08:07 +08:00
43f21dfe92 Merge branch 'master' into qiangg_unit_test1 2018-12-29 13:07:32 +08:00
4172eae87e update default ConnMaxLifeTime to 30 seconds in gdb package 2018-12-28 22:02:21 +08:00
26f2c61068 update default ConnMaxLifeTime to 30 seconds in gdb package 2018-12-28 22:00:49 +08:00
f97bed2607 update default ConnMaxLifeTime to 10 seconds in gdb package 2018-12-28 21:56:27 +08:00
8ef7155c70 hot fix 2018-12-28 21:46:01 +08:00
f5b2556b70 up 2018-12-28 21:44:36 +08:00
2c6e8f88fb README updates, TODO++ 2018-12-27 20:47:13 +08:00
25068b1e83 README updates 2018-12-27 13:27:57 +08:00
1f36eb3a9a README updates 2018-12-27 13:27:04 +08:00
a9ed577d05 README updates 2018-12-27 09:59:56 +08:00
782d614082 README updates 2018-12-27 09:57:54 +08:00
0629c00b07 README updates 2018-12-27 09:49:50 +08:00
b90d5bb205 README updates 2018-12-27 09:46:53 +08:00
cbc824c80a README updates 2018-12-27 09:46:35 +08:00
0c9be40b86 README updates 2018-12-27 09:46:18 +08:00
c96abd706d README updates 2018-12-27 09:45:04 +08:00
0ae5872783 README updates 2018-12-27 09:22:07 +08:00
2cff10e0d2 fix issue in controller interface definition 2018-12-26 10:17:24 +08:00
cab78f557d fix issue in controller detection for object parameter, in router group of web server 2018-12-25 23:20:43 +08:00
04353aa1a5 RELEASE updates 2018-12-25 13:54:36 +08:00
35121a66e9 README updates 2018-12-22 21:50:47 +08:00
e726ed2c19 gdb.Model updates 2018-12-22 21:03:03 +08:00
503446afc7 fix issue of ghttp.Request.GetVar 2018-12-22 11:52:12 +08:00
2063f662d3 fix silly issue in binary search of garray package, and add unit-test file for garray 2018-12-20 21:55:05 +08:00
d7381399aa fix issue of grand.intn in x86 arch; add router group feature for WebServer 2018-12-20 21:04:43 +08:00
d05b497cdb Merge branch 'master' into qiangg_router_group 2018-12-19 18:58:47 +08:00
ef919be587 g.DB can use gdb's configurations, not to force using config.toml 2018-12-19 18:35:44 +08:00
fff31e0f4f add Charset support for mysql of gdb package; fix issue for glog for log writing failed when the folder path wa deleted 2018-12-19 18:15:22 +08:00
cdd6fc7c1e extend pid length from 16bit to 24bit in process communication of gproc package 2018-12-19 16:17:54 +08:00
74bc36a2dc remove gfile.MainPkgPath check in gcfg/gview default path 2018-12-19 14:51:09 +08:00
48328ae52c router group developing 2018-12-19 14:45:39 +08:00
a86f4f8e23 disable auto adding temp directory to gview/gcfg search path; disable backtrace feature in normal log print with glog; fix issue caused by fmt.Fprintf in gfsnotify 2018-12-18 20:03:23 +08:00
0a1e048268 add Model.Clone support for gdb package 2018-12-18 10:10:14 +08:00
6fc5efd6ba README updates 2018-12-17 20:51:49 +08:00
2d795b593d README updates 2018-12-17 20:44:38 +08:00
20628ec75c README updates 2018-12-17 19:50:35 +08:00
10d1ccb009 README updates 2018-12-17 19:41:08 +08:00
fcc37c9581 CI updates 2018-12-17 19:36:34 +08:00
43cd391543 CI updates 2018-12-17 19:29:03 +08:00
18d2df33f7 CI updates 2018-12-17 19:26:59 +08:00
a85daa5617 CI updates 2018-12-17 18:35:29 +08:00
48dc4ce3e2 travis updates 2018-12-17 14:01:43 +08:00
d07bac89a0 travis updates 2018-12-17 13:59:00 +08:00
5d32ad6bc4 travis updates 2018-12-17 13:57:15 +08:00
397b0a3e7e travis updates 2018-12-17 13:50:17 +08:00
259961632d travis updates 2018-12-17 13:46:06 +08:00
cb1d6382ec travis updates 2018-12-17 13:38:35 +08:00
8714a69a13 travis updates 2018-12-17 13:36:38 +08:00
3ae0ea2de7 travis updates 2018-12-17 13:32:35 +08:00
1879a9f4c7 README updates 2018-12-17 13:28:19 +08:00
3938717b04 travis updates 2018-12-17 13:24:53 +08:00
1208b688f1 add code helper 2018-12-17 13:08:32 +08:00
0ad7ee5a32 add code helper 2018-12-17 13:07:01 +08:00
7a4e68e6b9 add code helper 2018-12-17 13:06:58 +08:00
71222b247f add travis/goreport/golint/govet 2018-12-17 13:02:55 +08:00
95db811943 add travis/goreport/golint/govet 2018-12-17 13:02:18 +08:00
2dbc817132 VERSION updates 2018-12-17 11:24:58 +08:00
7a8bd96edc gdb: add support for slice argument in where statement 2018-12-17 10:52:44 +08:00
c5e9686a95 gdb updates, make priority=1 when no priority set 2018-12-16 23:11:15 +08:00
c914edf616 gdb comment updates 2018-12-16 22:27:04 +08:00
656bfcb6bd Merge branch 'qiangg_db2' into develop 2018-12-16 22:22:33 +08:00
7434dfe6fa done refacting gdb package 2018-12-16 22:22:07 +08:00
e67aa63a50 refract gdb package, add complete unit test cases, almost there 2018-12-15 15:50:39 +08:00
06fc786416 fix issue of gcache 2018-12-14 18:38:29 +08:00
d5e46f2b42 refracting gdb package 2018-12-14 18:35:51 +08:00
c003a92408 remove HANGUP signal handle of Web Server admin 2018-12-14 10:11:05 +08:00
09e6f10b60 new version of gdb developing 2018-12-14 10:09:45 +08:00
c961c22cd7 update gview: show empty string when the variable does not exist 2018-12-13 18:24:22 +08:00
105a821069 fix issue: database connection pool does not work expectly 2018-12-12 20:01:10 +08:00
670993f769 remove auto-adding main package path to search path of WebServer in develop environment 2018-12-11 19:30:38 +08:00
60a571f291 Fix issue: add mutex lock for method Remove of garray. 2018-12-11 09:56:58 +08:00
20a0cb2cd9 Merge branch 'master' of https://gitee.com/johng/gf 2018-12-10 20:24:24 +08:00
51e70be04d Add rewrite feature to WebServer; Add Unique for un sorted array of garray; Fix map conversion issue of gjson. 2018-12-10 20:24:20 +08:00
ac65b808c6 !14 修复mssql的OPEN方法BUG
Merge pull request !14 from 蚊子/master
2018-12-10 18:43:56 +08:00
eb9ddf3c47 修改mssql的open方法连接串 2018-12-10 18:14:51 +08:00
80993e9f77 Fix issue of gjson package in map conversion 2018-12-09 22:30:10 +08:00
b7a6d257d5 Fix binary decoding issue of gbinary package. 2018-12-08 18:18:05 +08:00
7022486e93 Package gview: build-in function 'date' supports printing current datetime. 2018-12-05 15:52:38 +08:00
83f5a9d34e Package gview: build-in function 'date' supports printing current datetime. 2018-12-05 15:48:15 +08:00
431e1051b8 Fix gdb.Model.Count method: Count with no fields checking 2018-12-05 13:52:34 +08:00
f8ab5c3842 VERSION updates 2018-12-04 20:29:32 +08:00
101d095f45 GF greets you. 2018-12-04 20:28:06 +08:00
8481de2b47 Add method for gcache package, to show all map datas of the cache. 2018-12-04 20:23:48 +08:00
c973f133de hot fix 2018-12-04 19:50:24 +08:00
d23cdcbe57 改进gcache,完善功能及基准测试;改进gconv.Map对struct转换时默认使用json tag作为键名 2018-12-04 19:26:46 +08:00
9a52175bd6 gredis增加GetConn方法获取原生redis连接对象 2018-12-01 11:28:47 +08:00
4275218841 README updates 2018-11-30 20:43:08 +08:00
663a2c2a16 README updates 2018-11-30 20:40:23 +08:00
da58a60ad5 README updates 2018-11-30 20:38:53 +08:00
2c26063f4b README updates 2018-11-30 20:37:28 +08:00
b19e47783b ghttp增加静态文件目录映射功能;改进gspath目录管理功能;改进gconv的slice转换功能,并增加gconv.Map方法 2018-11-30 09:48:57 +08:00
aee266eea0 WebServer改进 2018-11-28 20:19:28 +08:00
8304769953 代码修正 2018-11-27 20:37:57 +08:00
914a74bca9 完善代码示例 2018-11-26 09:33:45 +08:00
b965dbff70 gvar调用端改进,去掉不必要的并发安全参数;错误提示细节改进 2018-11-25 22:18:36 +08:00
9f9bcd2467 RELEASE updates 2018-11-24 17:21:30 +08:00
b3353afe3c WebServer添加RouterCacheExpire配置参数 2018-11-24 11:55:57 +08:00
4e3081afee ORM增加mysql datetime参数写入示例 2018-11-24 09:42:21 +08:00
578a6a2df3 改进随机数生成缓冲区 2018-11-24 08:43:39 +08:00
aa42ddd3f1 改进随机数生成缓冲区 2018-11-23 21:39:05 +08:00
69738c337f VERSION updates 2018-11-23 16:50:58 +08:00
12f099fd54 VERSION updates, TODO++ 2018-11-23 16:47:03 +08:00
38932f306d 改进gspath缓存数据结构 2018-11-23 16:45:30 +08:00
5e7e1077a1 性能改进 2018-11-23 09:20:45 +08:00
8f85311332 完善获取数据库配置失败时的错误提示 2018-11-21 00:02:29 +08:00
6eb2887a5a README updates 2018-11-20 23:26:58 +08:00
54f4fd3101 README updates 2018-11-20 10:15:23 +08:00
64a22acf84 WebServer允许同一HOOK事件被多次绑定注册,先注册的回调函数优先级更高 2018-11-19 23:13:12 +08:00
4e5877923d dev 2018-11-19 21:49:43 +08:00
ceaa1a4dd1 Merge branch 'develop' of https://gitee.com/johng/gf into develop 2018-11-19 21:12:21 +08:00
9e1ad46c90 改进ghttp.Request,增加SetParam/GetParam请求流程自定义变量方法;gvar模块增加VarRead只读接口 2018-11-19 21:11:43 +08:00
d5a3fefd8b !13 ORM新增对MSSQL的支持
Merge pull request !13 from 蚊子/master
2018-11-19 11:48:18 +08:00
d85332aca1 ORM新增对MSSQL的支持 2018-11-19 11:38:57 +08:00
10c3f6d85a 完善程序细节和测试 2018-11-18 22:22:44 +08:00
ea4764f1f9 当前工作目录为系统临时目录时,gcfg/gview/ghttp模块默认不添加工作目录到搜索路径 2018-11-18 19:45:04 +08:00
fe753b0bc8 当前工作目录为系统临时目录时,gcfg/gview/ghttp模块默认不添加工作目录到搜索路径 2018-11-18 19:37:42 +08:00
04608269fe 修复gspath模块是windows下搜索失效问题 2018-11-18 19:14:17 +08:00
6addd64cf0 glog模块日志前缀输出改进 2018-11-17 22:12:41 +08:00
2106 changed files with 131562 additions and 93307 deletions

34
.gitee/ISSUE_TEMPLATE.MD Normal file
View File

@ -0,0 +1,34 @@
<!-- 为更高效率地交流并解决问题请按照以下模板提交issue感谢 -->
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?
<!-- 使用 `go version` 命令查看,期望的结果如:`go 1.12, linux/amd64` -->
### 2. 您当前使用的`GoFrame`框架版本?
<!-- 框架版本可以查看自己项目下的 `go.mod`,或者框架文件 `version.go` -->
### 3. 更新到最新的框架版本是否能够解决问题?
<!-- 务必检查是否相同问题已在新版本中已修复 -->
### 4. 问题描述?
<!--
请您尽可能地提供一份最短的,可复现问题的代码。
代码尽可能地完整,最好是可以直接编译运行。
-->
### 5. 您期望得到的结果?
### 6. 您实际得到的结果?

36
.github/ISSUE_TEMPLATE.MD vendored Normal file
View File

@ -0,0 +1,36 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
### 1. What version of `Go` and system type/arch are you using?
<!--
Please paste the output of command `go version` from your terminal.
What expect to see is like: `go 1.12, linux/amd64`
-->
### 2. What version of `GoFrame` are you using?
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
### 3. Can this issue be reproduced with the latest release?
### 4. What did you do?
<!--
If possible, provide a copy of shortest codes for reproducing the error.
A complete runnable program is best.
-->
### 5. What did you expect to see?
### 6. What did you see instead?

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ cbuild
**/.DS_Store
.vscode/
go.sum

38
.travis.yml Normal file
View File

@ -0,0 +1,38 @@
language: go
go:
- "1.11.x"
- "1.12.x"
branches:
only:
- master
- develop
env:
- GO111MODULE=on
services:
- mysql
- redis-server
addons:
hosts:
- local
before_install:
- pwd
install:
- cat /etc/hosts
script:
- cd g
- GOARCH=386 go test -v ./...
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 john@johng.cn http://johng.cn
Copyright (c) 2017 john@goframe.org https://goframe.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

239
README.MD
View File

@ -1,55 +1,51 @@
# GoFrame
<img align="right" height="150px" src="https://goframe.org/cover.png">
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
<!--
GoFrame is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: cache, logging, containers, timer, validator, database orm, etc. Supporting web server integrated with router, cookie, session, logger, configure, template, https, hooks, rewrites and many more features.
-->
`GF(GoFrame)` is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: memcache, configure, validator, logging, array/queue/set/map containers, timer/timing tasks, file/memory lock, object pool, database ORM, etc. Supporting web server integrated with router, cookie, session, logger, template, https, hooks, rewrites and many more features.
# Installation
```
go get -u github.com/gogf/gf
```
or use `go.mod`:
```
require github.com/gogf/gf latest
```
# Limitation
```
golang version >= 1.9.2
```
# Documentation
* [APIDoc](https://godoc.org/github.com/gogf/gf)
* [中文文档](https://goframe.org)
# Architecture
<div align=center>
<img src="http://cover.kancloud.cn/johng/gf" width="150"/>
<img src="https://goframe.org/images/arch.png"/>
</div>
# Quick Start
GF(Go Frame)是一款模块化、松耦合、轻量级、高性能的Go语言Web开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等
并且提供了数十个实用开发模块集缓存、日志、时间、命令行、二进制、文件锁、对象池、连接池、数据编码、进程管理、进程通信、TCP/UDP组件、
并发安全容器、Goroutine池等等等等等等。
开源项目地址(仓库保持实时同步)
[Gitee](https://gitee.com/johng/gf)[Github](https://github.com/johng-cn/gf)。
使用中有任何问题/建议欢迎加入技术QQ群交流**116707870**。
如有优秀的框架使用案例,欢迎联系作者将地址展示到项目库中,您的牛逼将被世人所瞻仰。
# 安装
```html
go get -u gitee.com/johng/gf
```
# 限制
```shell
golang版本 >= 1.9.2
```
# 特点
1. 轻量级、高性能,模块化、松耦合设计,丰富的开发模块;
1. 热重启、热更新特性并支持Web界面及命令行管理接口
1. 专业的技术交流群,完善的开发文档及示例代码,良好的中文化支持;
1. 支持多种形式的服务注册特性,灵活高效的路由控制管理;
1. 支持服务事件回调注册功能可供选择的pprof性能分析模块
1. 支持配置文件及模板文件的自动检测更新机制,即修改即生效;
1. 支持自定义日期时间格式的时间模块类似PHP日期时间格式化
1. 强大的数据/表单校验模块支持常用的40种及自定义校验规则
1. 强大的网络通信TCP/UDP组件并提供TCP连接池特性简便高效
1. 提供了对基本数据类型的并发安全封装,提供了常用的数据结构容器;
1. 支持Go变量/Json/Xml/Yml/Toml任意数据格式之间的相互转换及创建
1. 强大的数据库ORM支持应用层级的集群管理、读写分离、负载均衡查询缓存、方法及链式ORM操作
1. 更多特点请查阅框架[手册](https://gfer.me)和[源码](https://godoc.org/github.com/johng-cn/gf)
# 文档
GoFrame开发文档[gfer.me](https://gfer.me)
# 使用
## Hello World
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
@ -60,122 +56,49 @@ func main() {
s.Run()
}
```
## 多域名支持
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.Domain("localhost1,localhost2").BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("localhostx")
})
s.Run()
}
```
## 多端口支持
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("go frame!")
})
s.SetPort(8080, 8081, 8082)
s.Run()
}
```
## 路由控制
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/order/:action/{page}.html", func(r *ghttp.Request){
r.Response.Writef("action:%s, page:%s", r.Get("action"), r.Get("page"))
})
s.SetPort(8199)
s.Run()
}
```
## 数据库ORM
### ORM创建/关闭
```go
// 获取默认配置的单例数据库对象(配置名称为"default")
db, err := gdb.DB()
// 获取配置分组名称为"user-center"的单例数据库对象
db, err := gdb.DB("user-center")
// 无须显示Close数据库引擎底层采用了链接池设计当链接不再使用时会自动关闭
```
### 单表/联表查询
```go
// 查询多条记录并使用Limit分页
r, err := db.Table("user").Where("u.uid > ?", 1).Limit(0, 10).Select()
// 查询符合条件的单条记录(第一条)
r, err := db.Table("user u").LeftJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.site").Where("u.uid=?", 1).One()
// 查询指定字段值
r, err := db.Table("user u").RightJoin("user_detail ud", "u.uid=ud.uid").Fields("ud.site").Where("u.uid=?", 1).Value()
// 分组及排序
r, err := db.Table("user u").InnerJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.city").GroupBy("city").OrderBy("register_time asc").Select()
// 不使用john的联表查询
r, err := db.Table("user u,user_detail ud").Where("u.uid=ud.uid").Fields("u.*,ud.city").All()
// 不使用Fields方法指定查询字段时默认查询为"*"
r, err := db.Table("user").Where("u.uid=1",).One()
```
### 更新/删除
```go
// 更新
r, err := db.Table("user").Data(gdb.Map{"name" : "john2"}).Where("name=?", "john").Update()
r, err := db.Table("user").Data("name='john3'").Where("name=?", "john2").Update()
// 删除
r, err := db.Table("user").Where("uid=?", 10).Delete()
// Data数值方法的参数形式比较灵活
r, err := db.Table("user").Data(`name="john"`).Update()
r, err := db.Table("user").Data("name", "john").Update()
r, err := db.Table("user").Data(g.Map{"name" : "john"}).Update()
```
### 写入/保存
```go
r, err := db.Table("user").Data(gdb.Map{"name": "john"}).Insert()
r, err := db.Table("user").Data(gdb.Map{"uid": 10000, "name": "john"}).Replace()
r, err := db.Table("user").Data(gdb.Map{"uid": 10001, "name": "john"}).Save()
```
### 事务操作
```go
if tx, err := db.Begin(); err == nil {
if r, err := tx.Table("user").Data(gdb.Map{"uid":1, "name": "john"}).Save(); err == nil {
tx.Commit()
} else {
tx.Rollback()
}
fmt.Println(r, err)
}
```
[View More..](https://goframe.org/start/index)
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
# Contributors
- [aloncn](https://github.com/aloncn)
- [chenyang351](https://github.com/chenyang351)
- [garfieldkwong](https://gitee.com/garfieldkwong)
- [hailaz](https://gitee.com/hailaz)
- [johng](https://johng.cn)
- [jroam](https://github.com/jroam)
- [pibigstar](https://github.com/pibigstar)
- [qq1054000800](https://gitee.com/qq1054000800)
- [qq976739120](https://github.com/qq976739120)
- [touzijiao](https://github.com/touzijiao)
- [wenzi1](https://gitee.com/wenzi1)
- [wxkj001](https://github.com/wxkj001)
- [ymrjqyy](https://gitee.com/ymrjqyy)
- [youyixiao](https://github.com/youyixiao)
- [zhangjinfu](https://gitee.com/zhangjinfu)
- [zhaopengme](https://github.com/zhaopengme)
- [zseeker](https://gitee.com/zseeker)
# Donators
- [flyke-xu](https://gitee.com/flyke-xu)
- [hailaz](https://gitee.com/hailaz)
- [ireadx](https://github.com/ireadx)
- [mg91](https://gitee.com/mg91)
- [pibigstar](https://github.com/pibigstar)
- [tiangenglan](https://gitee.com/tiangenglan)
- [wxkj](https://gitee.com/wxkj)
- [zhuhuan12](https://gitee.com/zhuhuan12)
- [zfan_codes](https://gitee.com/zfan_codes)
...
更多特性及示例请查看官方开发文档:[gfer.me](https://gfer.me)

117
README_ZH.MD Normal file
View File

@ -0,0 +1,117 @@
# GoFrame
<img align="right" height="150px" src="https://goframe.org/cover.png">
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
`GF(Go Frame)`是一款模块化、松耦合、生产级Go应用开发框架。提供了常用的核心开发组件缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、
并发安全容器等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎等等支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
# 特点
* 模块化、松耦合设计;
* 丰富实用的开发模块;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 致力于项目的通用方案;
* 更适合企业及团队使用;
* 更多请查阅文档及源码;
# 安装
```html
go get -u github.com/gogf/gf
```
或者
`go.mod`:
```
require github.com/gogf/gf latest
```
# 限制
```shell
golang版本 >= 1.9.2
```
# 架构
<div align=center>
<img src="https://goframe.org/images/arch.png"/>
</div>
# 文档
开发文档:[https://goframe.org](https://goframe.org)
接口文档:[https://godoc.org/github.com/gogf/gf](https://godoc.org/github.com/gogf/gf)
# 使用
```go
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("Hello World")
})
s.Run()
}
```
[更多..](https://goframe.org/start/index)
# 协议
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
# 捐赠
捐赠支持`GF`框架的研发,
请在捐赠时备注您的`github`/`gitee`账号名称。
<a href="https://goframe.org/images/donate.png" target="_blank">
<img src="https://goframe.org/images/donate.png" width="300"/>
</a>
# 贡献者
- [aloncn](https://github.com/aloncn)
- [chenyang351](https://github.com/chenyang351)
- [garfieldkwong](https://gitee.com/garfieldkwong)
- [hailaz](https://gitee.com/hailaz)
- [johng](https://johng.cn)
- [jroam](https://github.com/jroam)
- [pibigstar](https://github.com/pibigstar)
- [qq1054000800](https://gitee.com/qq1054000800)
- [qq976739120](https://github.com/qq976739120)
- [touzijiao](https://github.com/touzijiao)
- [wenzi1](https://gitee.com/wenzi1)
- [wxkj001](https://github.com/wxkj001)
- [ymrjqyy](https://gitee.com/ymrjqyy)
- [youyixiao](https://github.com/youyixiao)
- [zhangjinfu](https://gitee.com/zhangjinfu)
- [zhaopengme](https://github.com/zhaopengme)
- [zseeker](https://gitee.com/zseeker)
# 捐赠者
- [flyke-xu](https://gitee.com/flyke-xu)
- [hailaz](https://gitee.com/hailaz)
- [ireadx](https://github.com/ireadx)
- [mg91](https://gitee.com/mg91)
- [pibigstar](https://github.com/pibigstar)
- [tiangenglan](https://gitee.com/tiangenglan)
- [wxkj](https://gitee.com/wxkj)
- [zhuhuan12](https://gitee.com/zhuhuan12)
- [zfan_codes](https://gitee.com/zfan_codes)

View File

@ -1,136 +1,199 @@
# `v0.97.399 beta` (2018-04-23)
1、 增加gfsnotify文件监控模块
2、 配置管理模块增加配置文件自动检测更新机制;
3、 模板引擎增加对模板文件的自动检测更新机制;
4、 改进gconv包基本类型转换功能提高转换性能
5、 增加gpage分页管理包支持动态分页、静态分页以及自定义分页样式特性
6、 ghttp.Request增加Exit方法用以标记服务退出当在服务执行前调用后服务将不再执行
7、 ghttp.Response去掉WriteString方法统一使用Write方法返回数据流是使用灵活的参数形式
8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc以便支持开发者灵活处理模板变量
9、 ghttp.Server增加access & error log功能并支持开发者自定义日志处理回调函数注册
10、增加gredis包支持对redis的客户端操作封装并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护
11、gins单例管理器增加对单例对象配置文件的自动检测更新机制当配置文件在外部发生变更时自动刷新单例管理器中的单例对象
12、gdb数据库ORM包增加And/Or条件链式方法并改进Where/Data方法参数灵活性
13、对于新增加的模块同时也增加了对应的开发文档并梳理完善了现有的其他模块开发文档
14、修复ISSUE:
#IISWI gitee.com/johng/gf/issues/IISWI,
#IISMY gitee.com/johng/gf/issues/IISMY,
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48)
# `v1.6.0` (2019-04-09)
## 新功能/改进
1. `gcron`定时任务模块增加运行日志记录功能https://goframe.org/os/gcron/index
1. `gredis`增加全局分组配置功能,并增加更多的配置选项`maxIdle/maxActive/idleTimeout/maxConnLifetime`https://goframe.org/database/gredis/index
1. `gcfg`模块增加更多的默认配置文件检索路径,并且增加全局分组配置特性,增加`Instance`单例方法https://goframe.org/os/gcfg/index
1. `gview`模块增加更多的默认配置文件检索路径,并且增加`Instance`单例方法https://goframe.org/os/gview/index
1. `ghttp`模块新功能及改进:
- 新增`CORS`HTTP(S)跨域请求特性: https://goframe.org/net/ghttp/cors
- 增加`TLSConfig`配置功能
- 去掉路由注册方法的`error`返回值,当产生注册错误时直接终端打印错误/输出到日志文件
- 增加在`HTTP Code 302`跳转时的`Set-Cookie`支持
- 增加对`SESSION ID`的安全性检查
- 增加对基于`HTTPS`的`WebSocket`支持(`WSS`https://goframe.org/net/ghttp/websocket/index
- `Request`对象增加`Error`方法,用于输出自定义错误信息到`WebServer`错误日志中;
- 其他一些改进;
1. `gdb`模块新功能及改进:
- 新增`Instance`单例管理方法
- 新增`Structs/Scan`链式操作方法,`gdb.DB/TX`新增`GetStructs/GetScan`方法,用于结果集`struct`/`slice`映射转换https://goframe.org/database/gdb/chaining
- 新增`Safe`链式操作方法默认非并发安全用于链式安全控制https://goframe.org/database/gdb/chaining
- `Where`链式操作方法改进:
- 方法支持任意的`string/map/slice/struct/*struct`类型;
- 逻辑调整,当链式操作中存在多个`Where`方法调用时,自动转换为`And`条件;
- 支持`slice`条件参数,常用在`SELECT IN`查询中,例如:`Where("uid IN(?)", g.Slice{1,2,3})`
- 支持在`map`类型条件参数的`key`中传递条件,例如:`Where(g.Map{"uid>?", uid})`
1. `gconv`及`gvalid`模块改进并去掉对私有`struct`方法属性的转换/校验;
1. `gconv.Map`转换方法新增对`json tag`: `-`, `omitempty`的支持: https://goframe.org/util/gconv/map
1. `gstr`模块新增 `ReplaceI/ReplaceIByArray/ReplaceIByMap`大小写非敏感替换方法;
1. `gutil`模块增加`IsEmpty`方法用于判断给定变量是否为空整型0, 布尔false, slice/map长度为0, 其他为nil的情况判断为空并增加快捷方法`g.IsEmpty`
1. `gutil`模块增加`Export`方法,用于导出返回格式化打印的变量内容字符串,并增加快捷方法`g.Export`
1. `gspath`增加缓存及非缓存检索检索方法`Search`/`SearchWithCache`
1. `gjson`模块增加默认的`UseNumber`功能支持;
1. `gmap`增加`SetIfNotExistFunc/SetIfNotExistFuncLock`方法;
1. 迁移`greuseport`模块到新的仓库https://github.com/gogf/greuseport
1. 大量的单元测试完善;
## Bug Fix
1. 修复`gqueue`模块的资源竞争问题;
1. 修复`gconv.GTime`转换失败问题;
1. 修复`gconv.String`在转换`int`参数时字节溢出问题;
1. 修复`ghttp.Request`的`HTTP Basic Auth`校验问题;
1. 修复`gxml`针对于非`UTF-8`编码内容转换的并发安全问题;
1. 修复`gtime`部分`Format``G`&`j`)格式失效问题;
1. 修复`gudp.Conn`对象的`RemoteAddr`获取客户端连接地址方法问题;
1. 修复`gmap/gcache`模块的`GetOrSetFuncLock`方法,增加对回调方法返回值的`nil`判断只有非nil返回值才会被保存
# `v0.98.503 beta` (2018-05-21)
# `v1.5.8` (2019-02-28)
## 新特性
1、平滑重启特性( http://gf.johng.cn/625833 )
2、gflock文件锁模块( http://gf.johng.cn/626062 )
3、gproc进程管理及通信模块( http://gf.johng.cn/626063 )
4、gpage分页管理模块强大的动态分页及静态分页功能并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 )
5、ghttp.Server增加多端口监听特性并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 )
6、增加gspath目录检索包管理工具支持对多目录下的文件检索特性
7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性路由规则增加{method}变量支持
1. 主库从`gitee`迁移到了`github`( https://github.com/gogf/gf )`gitee`作为镜像站用于国内的代码贡献及ISSUE提交迁移说明详见https://goframe.org/upgradeto150
1. 对常用的`container`数组模块: `garray`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/garray
1. 对常用的`container`集合模块: `gset`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/gset
1. 对常用的`container`MAP模块: `gmap`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/gmap
1. 对常用的字符串模块: `gstr`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/text/gstr
1. 改进`gform`中对`struct`/`*struct`参数的支持,`*Insert/*Save/*Replace/*Update/Where/Data`方法的参数调整为`interface{}`类型,并支持任意类型的: `string/map/slice/struct/*struct`参数传递具体请参考https://goframe.org/database/orm/chaining
1. 新增/完善若干模块的单元测试用例, 包括:`gvalid`/`gregex`/`garray`/`gset`/`gmap`/`gstr`/`gconv`/`ghttp`/`gdb`
1. 由于`gkafka`模块比较重且不是框架核心模块因此将该模块迁移到新的仓库中独立管理并去掉相关依赖包https://github.com/gogf/gkafka
1. 新增`greuseport`模块用以实现TCP的`REUSEPORT`特性https://godoc.org/github.com/gogf/gf/g/net/greuseport
## 新功能/改进
1. 去掉模板引擎内置变量中自动初始化`session`对象带来的内存占用问题;
1. `ghttp.Client`改进增加若干方法详见https://goframe.org/net/ghttp/client
1. `ghttp`分组路由增加`COMMON`方法,用以注册常用的`HTTP METHOD`(`GET/PUT/POST/DELETE`)路由;
1. 更新框架依赖的`golang.org/x/sys`模块;
1. 改进`gform`的批量操作(`Batch*`操作)返回结果对象,可以通过该结果对象获得批量操作准确的受影响记录行数;
1. 将`gstr`/`gregex`模块从`util`分类迁移到了`text`分类目录下;
1. 将`gtest`模块从`util`分类迁移到了`test`分类目录下;
1. 完善`glog`方法注释;
## Bug Fix
1. 修复带点的邮件格式,用`gvalid.Check`的"`email`"规则不能匹配成功;
1. 修复`gvalid.Check`在`regex`规则下的检查失败问题;
1. 修复`gcron`模块定时规则中天和周不允许`?`符号的问题;
1. 修复`ghttp.Server`在部分异常情况下仍然返回`200`状态码的问题;
1. 修复`gfpool`模块中由于原子操作问题造成的高并发"内存泄露"问题;
1. 修复分组路由注册对象/控制时,方法`Index`的路由仅能通过`/xxx/index`访问的问题;
1. 修复模板引擎使用中,当不存在`config.toml`(即使没使用)配置文件时的报错问题;
1. 其他一些修复;
# `v1.4.6` (2019-01-24)
## 新特性
1. 新增并发安全的高性能任务定时器模块`gtimer`, 类似于Java的`Timer`但是比较于Java的`Timer`更加强大,内部实现采用灵活高效的`分层时间轮`设计,被设计为可管理维护百万级别以上数量的定时任务。`gtimer`为`GF`框架的核心模块之一,单元测试覆盖率达到`93.6%`[https://goframe.org/os/gtimer/index](https://goframe.org/os/gtimer/index)
1. 采用任务定时器`gtimer`重构`gcron`定时任务模块,去掉第三方`github.com/robfig/cron`包的使用。`gcron`增加单例模式的定时任务:[https://goframe.org/os/gcron/index#](https://goframe.org/os/gcron/index#)
1. `gconv`类型转换模块支持对`struct`结构体中的**指针属性**转换:[https://goframe.org/util/gconv/struct](https://goframe.org/util/gconv/struct)
1. `gform`增加对数据库类型的自动识别特性,这一特性在需要将查询结果`json`编码返回时非常有用: [https://goframe.org/database/orm/index](https://goframe.org/database/orm/index)
1. `Travis CI`增加对`386`架构的自动化测试支持(目前已支持`386`和`amd64`)
## 新功能
1、gutil包增加MapToStruct方法支持将map数据类型映射为struct对象
2、gconv
1)、gconv包增加按照类型名称字符串进行类型转换
2)、gconv包新增Time/TimeDuration类型转换方法
3、ghttp
1)、增加Web Server目录安全访问控制机制
2)、ghttp.Server增加自定义状态码回调函数注册处理
4、gdb
1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法获取查询结果记录自动转换为指定对象
2)、gdb增加Value/Record/Result类型增加对Value类型的系列类型转换方法
3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法
1. `ghttp`模块新增`Exit`、`ExitAll`、`ExitHook`方法用于HTTP请求处理流程控制: [https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object)
1. `grand`模块增加`Meet/MeetProb`方法,用于给定概率的随机满足判断,增加别名方法`N/Str/Digits/Letters`
1. `gvalid`数据/表单校验模块增加`16X`及`19X`手机号的校验支持
## 功能改进
1、改进gredis客户端功能封装
2、改进grand包随机数生成性能
3、grand/gdb/gredis包增加benchmark性能测试脚本
4、改进gjson/gparser包的ToStruct方法实现
5、gdb 改进gdb.New获取ORM操作对象性能
6、gcfg :改进配置文件检索功能
7、gview模板引擎增加多目录检索功能
8、gfile增加源码main包目录获取方法MainPkgPath
9、ghttp
1)、ghttp.Request增加请求进入和完成时间记录并增加到默认日志内容中
2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参
10、gdb
1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换便于业务层数据编码处理(如json/xml)
2)、改进gdb.Tx.GetValue返回值类型
3)、gdb.Model.Data参数支持更加灵活的map参数
1. `gform`设置默认的数据库连接池`CONN_MAX_LIFE`参数值为`30`秒
1. 改进`glist`模块,提高约`20%`左右性能,并增加若干链表操作方法
1. 改进`gqueue`模块,提高约`50`左右性能,并增加模块对`select`语法的支持(使用`Queue.C`): [https://goframe.org/container/gqueue/index](https://goframe.org/container/gqueue/index)
1. 改进`gmlock`内存锁模块,并完善单元测试用例:[https://goframe.org/os/gmlock/index](https://goframe.org/os/gmlock/index)
1. 改进并发安全容器所有的模块,调整并发安全控制非必需参数`safe...bool`为`unsafe...bool`
1. 改进`gpool`对象复用模块,支持并发安全
1. 更新`gkafka`模块的第三方依赖包
1. 完善`ghttp`模块的单元测试用例
## 问题修复
1、ghttp
1)、修复ghttp包路由缓存问题
2)、修复服务注册时的控制器及执行对象方法丢失问题;
2、gconv
1)、修正gconv.Float64方法位大小设置问题
2)、修复gconv.Int64(float64(xxx))问题;
2、gdb
1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题
2)、修复gdb.Delete方法错误
3)、修复gdb.Model.And/Or方法
4)、修复gdb.Model.Where方法参数处理问题
3、garray修复garray包Remove方法锁机制问题
4、gtype 修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误
5、gfsnotify修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题
6、修复gvalid包验证问题如果值为nil并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。
# `v0.99.682 beta` (2018-08-07)
## Bug Fix
1. 修复`gmd5`模块操作文件时的文件指针未关闭问题;
1. 修复`gcache`缓存项过期删除失效问题;
1. 其他修复;
# `v1.3.8` (2018-12-26)
## 新特性
1、新增gdes包用于DES加密/加密算法处理
2、新增gkafka包kafka的golang客户端
3、新增gpool对象复用池比较于标准库的sync.Pool更加灵活强大可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654)
4、完成网络通信gtcp/gudp包的重构并进行了大量的改进工作新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382)
5、增加gring并发安全环标准库container/ring包的并发安全版本并做了易用性的封装(http://gf.johng.cn/686655)
6、gtime包新增了自定义日期格式话的支持格式化语法类似PHP的date语法(http://gf.johng.cn/494387)
7、gdb增加调试模式特性使用SetDebug方法实现在调试模式下可以获取详细的SQL执行记录增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
8、gdb增加查询缓存特性使用Cache方法实现增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
9、ghttp.Server路由功能增加字段匹配规则特性支持如/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766)
10、gpage分页包增加分页URL规则生成模板特性内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438)
11、增加gmap.Map对象这是gmap.InterfaceInterfaceMap的别名
1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://goframe.org/database/orm/index](https://goframe.org/database/orm/index))
1. `WebServer`路由注册新增分组路由特性([https://goframe.org/net/ghttp/group](https://goframe.org/net/ghttp/group));
1. `WebServer`新增`Rewrite`路由重写特性([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static));
1. 增加框架运行时对开发环境的自动识别
1. 增加了`Travis CI`自动化构建/测试
## 新功能
1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置并增加SetMaxConnLifetime方法
2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth)
3、glog新增对系统换行符号的自适配调整(\n|\r\n)
4、增加glog控制台调试模式打印开关(SetDebug)
5、gcfg增加SetFileName方法设置默认读取的配置文件名称
6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法
7、增加gzip方法的封装(Zip/Unzip)
8、gview增加模板变量分隔符设置方法SetDelimiters
9、ghttp.Response增加Writef、Writefln方法
1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static))
1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop)
1. `gcache`新增`Data`方法,用以获取所有的缓存数据项
1. `gredis`增加`GetConn`方法获取原生redis连接对象
## 功能改进
1、改进gfilepool文件指针池设计改进gfile文本内容写入增加指针池使用
2、gdb包增加调试模式特性并支持在调试模式下获得已执行的SQL列表结果
3、改进gproc进程间通信机制增加进程消息分组特性并限定队列大小
4、gdb结果方法处理增加ToXml/ToJson方法
5、gregx包名修改为gregex
6、改进gtime.StrToTime方法新增对常见标准时间日期的自动转换以及对时区的自动识别支持并调整gconv,gvalid对该包的引用
7、增加对字符集转换的封装gxml包中使用新增的字符集转换包来做处理
8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持
9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新)
10、改进ghttp.Server静态文件检索设计增加开发环境时的main包源码目录查找机制改进gcfg/gview的main包源码目录查找机制
11、优化gcache设计LRU特性非默认开启优化gtype/gcache基准测试脚本新增gregx基准测试脚本改进设计提升性能
12、gfile包增加GoRootOfBuild方法用于获取编译时的GOROOT数值并改进glog包中backtrace的GOROOT路径过滤处理
13、改进grpool代码质量并改进对池化goroutine数量的限制设计
14、改进gdb.Map/List及g.Map/List的类型定义改用别名特性以便支持原生类型输入(map/slice)并修复gdb.Model.Update方法参数处理问题
15、调整ghttp包示例代码目录结构增加ghttp.Client自定义Header方法ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值构造成map返回
16、删除gcharset中的getcharset方法
17、去掉gmap中常用的基本数据类型转换获取方法
18、改进gconv.String方法当无法使用基本类型进行字符串转换时使用json.Marshal进行转换
19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct
1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop)
1. 改进`gproc`进程间通信数据结构,将`pid`字段从`16bit`扩展为`24bit`
1. 改进`gconv`/`gmap`/`garray`,增加若干操作方法;
1. 改进`gview`模板引擎中的`date`内置函数,当给定的时间戳为空时打印当前的系统时间;
1. 改进`gview`模板引擎中,当打印的变量不存在时,显示为空(标准库默认显示为`<no value>`
1. 改进`WebServer`,去掉`HANGUP`的信号监听,避免程序通过`nohup`运行时产生异常退出问题;
1. 改进`gcache`性能,并完善基准测试;
## Bug Fix
1. 修复`gcache`在非LRU特性开启时的缓存关闭资源竞争问题并修复`doSetWithLockCheck`内部方法的返回值问题;
1. 修复`grand.intn`内部方法在`x86`架构下的随机数位溢出问题;
1. 修复`gbinary`中`Int`方法针对`[]byte`参数长度自动匹配造成的字节长度溢出问题;
1. 修复`gjson`由于官方标准库`json`不支持`map[interface{}]*`类型造成的Go变量编码问题
1. 修复`garray`中部分方法的数据竞争问题,修复二分查找排序问题;
1. 修复`ghttp.Request.GetVar`方法获取参数问题;
1. 修复`gform`的数据库连接池不起作用的问题;
# `v1.2.11` (2018-11-26)
## 新特性
1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://goframe.org/database/orm/database](https://goframe.org/database/orm/database))
1. 完成`gvalid`模块校验结果的顺序特性([https://goframe.org/util/gvalid/checkmap](https://goframe.org/util/gvalid/checkmap));
1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object))
1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://goframe.org/os/gview/funcs](https://goframe.org/os/gview/funcs));
1. 模板引擎新增内置变量`Config` ([https://goframe.org/os/gview/vars](https://goframe.org/os/gview/vars));
1. 改进`gconv.Struct`转换默认规则,支持不区分大小写的键名与属性名称匹配;
1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://goframe.org/database/orm/config](https://goframe.org/database/orm/config))
1. `gfsnotify`模块增加对特定回调的取消注册功能([https://goframe.org/os/gfsnotify/index](https://goframe.org/os/gfsnotify/index)
## 新功能
1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://goframe.org/net/ghttp/request](https://goframe.org/net/ghttp/request);
1. 改进`ghttp.Response`,增加`ServeFileDownload`方法用于WebServer引导客户端下载文件([https://goframe.org/net/ghttp/response](https://goframe.org/net/ghttp/response));
1. `gvar`模块新增`gvar.VarRead`只读接口,用于控制对外只暴露数据读取功能;
1. 增加`g.Throw`抛异常方法,`g.TryCatch`异常捕获方法封装;
1. 改进`gcron`模块增加自定义的Cron管理对象增加`New/Start/Stop`方法;
## 功能改进
1. WebServer添加`RouterCacheExpire`配置参数,用于设置路由检索缓存过期时间;
1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://goframe.org/net/ghttp/service/hook](https://goframe.org/net/ghttp/service/hook));
1. 当前工作目录为系统临时目录时,`gcfg`/`gview`/`ghttp`模块默认不添加工作目录到搜索路径;
1. 改进`WebSocket`默认支持跨域请求([https://goframe.org/net/ghttp/websocket](https://goframe.org/net/ghttp/websocket));
1. 改进`gtime.Format`支持中文;
1. 改进`gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题;
1. 改进`gtype.Set`方法增加Set原子操作返回旧的变量值;
1. `gfile.ScanDir`增加支持`pattern`多个文件模式匹配,使用'`,`'符号分隔多个匹配模式;
1. `gcfg`模块增加获取配置变量为`*gvar.Var`;
1. `gstr`模块增加对中文截取方法;
1. 改进`gtime.StrToTime`对常用时间格式匹配模式,新增`gtime.ParseTimeFromContent`方法;
1. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式;
1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://goframe.org/util/grand/index](https://goframe.org/util/grand/index));
## 问题修复
1、修正gstr.IsNumeric错误
2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题
3、修正gconv包float32->float64精度问题
4、修复gpage包分页计数问题
5、修复gdb批量数据Save错误
6、去掉gpool中math.MAXINT64常量的使用以修复int64到int类型的转换错误兼容32位系统
7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题
1. 修复`gspath`模块在`windows`下搜索失效问题;
1. 修复`gspath`模块Search时带有indexFiles的检索问题;
1. bug fix INZS1([https://github.com/gogf/gf/issues/INZS1](https://github.com/gogf/gf/issues/INZS1));
1. 修复`gproc.ShellRun`在windows下的执行问题;
# `v1.0.898 stable` (2018-10-24)
@ -227,3 +290,145 @@
1. 其他一些改动;
# `v0.99.682 beta` (2018-08-07)
## 新特性
1、新增gdes包用于DES加密/加密算法处理;
2、新增gkafka包kafka的golang客户端
3、新增gpool对象复用池比较于标准库的sync.Pool更加灵活强大可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654)
4、完成网络通信gtcp/gudp包的重构并进行了大量的改进工作新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382)
5、增加gring并发安全环标准库container/ring包的并发安全版本并做了易用性的封装(http://gf.johng.cn/686655)
6、gtime包新增了自定义日期格式话的支持格式化语法类似PHP的date语法(http://gf.johng.cn/494387)
7、gdb增加调试模式特性使用SetDebug方法实现在调试模式下可以获取详细的SQL执行记录增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
8、gdb增加查询缓存特性使用Cache方法实现增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
9、ghttp.Server路由功能增加字段匹配规则特性支持如/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766)
10、gpage分页包增加分页URL规则生成模板特性内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438)
11、增加gmap.Map对象这是gmap.InterfaceInterfaceMap的别名
## 新功能
1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置并增加SetMaxConnLifetime方法
2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth)
3、glog新增对系统换行符号的自适配调整(\n|\r\n)
4、增加glog控制台调试模式打印开关(SetDebug)
5、gcfg增加SetFileName方法设置默认读取的配置文件名称
6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法
7、增加gzip方法的封装(Zip/Unzip)
8、gview增加模板变量分隔符设置方法SetDelimiters
9、ghttp.Response增加Writef、Writefln方法
## 功能改进
1、改进gfilepool文件指针池设计改进gfile文本内容写入增加指针池使用
2、gdb包增加调试模式特性并支持在调试模式下获得已执行的SQL列表结果
3、改进gproc进程间通信机制增加进程消息分组特性并限定队列大小
4、gdb结果方法处理增加ToXml/ToJson方法
5、gregx包名修改为gregex
6、改进gtime.StrToTime方法新增对常见标准时间日期的自动转换以及对时区的自动识别支持并调整gconv,gvalid对该包的引用
7、增加对字符集转换的封装gxml包中使用新增的字符集转换包来做处理
8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持
9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新)
10、改进ghttp.Server静态文件检索设计增加开发环境时的main包源码目录查找机制改进gcfg/gview的main包源码目录查找机制
11、优化gcache设计LRU特性非默认开启优化gtype/gcache基准测试脚本新增gregx基准测试脚本改进设计提升性能
12、gfile包增加GoRootOfBuild方法用于获取编译时的GOROOT数值并改进glog包中backtrace的GOROOT路径过滤处理
13、改进grpool代码质量并改进对池化goroutine数量的限制设计
14、改进gdb.Map/List及g.Map/List的类型定义改用别名特性以便支持原生类型输入(map/slice)并修复gdb.Model.Update方法参数处理问题
15、调整ghttp包示例代码目录结构增加ghttp.Client自定义Header方法ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值构造成map返回
16、删除gcharset中的getcharset方法
17、去掉gmap中常用的基本数据类型转换获取方法
18、改进gconv.String方法当无法使用基本类型进行字符串转换时使用json.Marshal进行转换
19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct
## 问题修复
1、修正gstr.IsNumeric错误
2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题
3、修正gconv包float32->float64精度问题
4、修复gpage包分页计数问题
5、修复gdb批量数据Save错误
6、去掉gpool中math.MAXINT64常量的使用以修复int64到int类型的转换错误兼容32位系统
7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题
# `v0.98.503 beta` (2018-05-21)
## 新特性
1、平滑重启特性( http://gf.johng.cn/625833 )
2、gflock文件锁模块( http://gf.johng.cn/626062 )
3、gproc进程管理及通信模块( http://gf.johng.cn/626063 )
4、gpage分页管理模块强大的动态分页及静态分页功能并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 )
5、ghttp.Server增加多端口监听特性并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 )
6、增加gspath目录检索包管理工具支持对多目录下的文件检索特性
7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性路由规则增加{method}变量支持;
## 新功能
1、gutil包增加MapToStruct方法支持将map数据类型映射为struct对象
2、gconv
1)、gconv包增加按照类型名称字符串进行类型转换
2)、gconv包新增Time/TimeDuration类型转换方法
3、ghttp
1)、增加Web Server目录安全访问控制机制
2)、ghttp.Server增加自定义状态码回调函数注册处理
4、gdb
1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法获取查询结果记录自动转换为指定对象
2)、gdb增加Value/Record/Result类型增加对Value类型的系列类型转换方法
3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法
## 功能改进
1、改进gredis客户端功能封装
2、改进grand包随机数生成性能
3、grand/gdb/gredis包增加benchmark性能测试脚本
4、改进gjson/gparser包的ToStruct方法实现
5、gdb 改进gdb.New获取ORM操作对象性能
6、gcfg :改进配置文件检索功能;
7、gview模板引擎增加多目录检索功能
8、gfile增加源码main包目录获取方法MainPkgPath
9、ghttp
1)、ghttp.Request增加请求进入和完成时间记录并增加到默认日志内容中
2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参
10、gdb
1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换便于业务层数据编码处理(如json/xml)
2)、改进gdb.Tx.GetValue返回值类型
3)、gdb.Model.Data参数支持更加灵活的map参数
## 问题修复
1、ghttp
1)、修复ghttp包路由缓存问题
2)、修复服务注册时的控制器及执行对象方法丢失问题;
2、gconv
1)、修正gconv.Float64方法位大小设置问题
2)、修复gconv.Int64(float64(xxx))问题;
2、gdb
1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题
2)、修复gdb.Delete方法错误
3)、修复gdb.Model.And/Or方法
4)、修复gdb.Model.Where方法参数处理问题
3、garray修复garray包Remove方法锁机制问题
4、gtype 修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误
5、gfsnotify修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题
6、修复gvalid包验证问题如果值为nil并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。
# `v0.97.399 beta` (2018-04-23)
1、 增加gfsnotify文件监控模块
2、 配置管理模块增加配置文件自动检测更新机制;
3、 模板引擎增加对模板文件的自动检测更新机制;
4、 改进gconv包基本类型转换功能提高转换性能
5、 增加gpage分页管理包支持动态分页、静态分页以及自定义分页样式特性
6、 ghttp.Request增加Exit方法用以标记服务退出当在服务执行前调用后服务将不再执行
7、 ghttp.Response去掉WriteString方法统一使用Write方法返回数据流是使用灵活的参数形式
8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc以便支持开发者灵活处理模板变量
9、 ghttp.Server增加access & error log功能并支持开发者自定义日志处理回调函数注册
10、增加gredis包支持对redis的客户端操作封装并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护
11、gins单例管理器增加对单例对象配置文件的自动检测更新机制当配置文件在外部发生变更时自动刷新单例管理器中的单例对象
12、gdb数据库ORM包增加And/Or条件链式方法并改进Where/Data方法参数灵活性
13、对于新增加的模块同时也增加了对应的开发文档并梳理完善了现有的其他模块开发文档
14、修复ISSUE:
#IISWI github.com/gogf/gf/issues/IISWI,
#IISMY github.com/gogf/gf/issues/IISMY,
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48)

59
TODO.MD
View File

@ -1,27 +1,16 @@
# ON THE WAY
1. orm增加更多数据库支持
1. 增加对于数据表Model的封装
1. 更多数据库的ORM功能支持
1. 考虑gdb对象管理增加二级连接池特性提高New&Close性能
1. 增加图形验证码支持,至少支持数字和英文字母;
1. 增加热编译工具,提高开发环境的开发/测试效率媲美PHP开发效率
1. 增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
1. ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
1. Cookie&Session数据池化处理
1. ghttp.Client增加proxy特性
1. gtime增加对时区转换的封装并简化失去转换时对类似+80500时区的支持
1. orm增加sqlite对Save方法的支持(去掉触发器语句);
1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps)
1. ghttp路由功能增加分组路由特性
1. ghttp增加返回数据压缩机制
1. gview中的template标签失效问题
1. gfile文件stat信息使用gfsnotify进行缓存更新改进
1. ghttp.Server增加proxy功能特性本地proxy和远程proxy本地即将路由规则映射远程即反向代理
1. gjson对大json数据的解析效率问题
1. ghttp增加route name特性并同时支持backend和template(提供内置函数)引用可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上
1. ghttp.Client自动Close机制
1. gvalid校验支持当第一个规则失败后便不再校验后续的规则最好做成链式操作
1. 检查ghttp.Server超时问题
1. gvalid增加支持对[]rune的长度校验(一个中文占3个字节)
1. ghttp.Request增加对输入参数的自动HtmlEncode机制
1. 常量命名风格根据golint进行修改
@ -34,17 +23,29 @@
- glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、
1. 服务注册域名增加对泛域名的支持;
1. Cookie设置中文失效问题
1. ghttp hook回调使用方式在注册路由比较多的时候优先级可能使得开发者混乱考虑方式便于管理
1. 使用gconv将slice映射到struct属性上例如redis hscan的结果集
1. 项目参考:
- https://github.com/namreg/godown
- https://github.com/Masterminds/sprig
1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
1. 模板引擎增加对对象的支持(参考https://segmentfault.com/q/1010000016829214)
1. 改进gfpool在文件指针变化时的更新
1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题并完善文档参考https://www.cnblogs.com/kex1n/p/6502002.html
1. gfile对于文件的读写强行使用了gfpool在某些场景下不合适需要考虑剥离开并为开发者提供单独的指针池文件操作特性
1. 路由增加不区分大小写得匹配方式
1. 改进WebServer获取POST参数处理逻辑当提交非form数据时例如json数据针对某些方法可以直接解析
1. WebServer增加可选择的路由覆盖配置默认情况下不覆盖
1. grpool性能压测结果变慢的问题
1. 增加jumplist的数据结构容器
1. DelayQueue/PriorityQueue
1. 权限管理模块;
1. 从ghttp中剥离SESSION功能构成单独的模块gsession
1. 改进gproc进程间通信处理逻辑提高稳定性以应对进程间大批量的数据发送/接收;
1. ghttp的热重启的本地进程端口监听在不使用该特性时默认关闭掉
1. gtcp增加对TLS加密通信的支持
1. 添加Save/Replace/BatchSave/BatchReplace方法对sqlite数据库的支持
1. 添加sqlite数据库的单元测试用例
1. gredis增加cluster支持
1. gset.Add/Remove/Contains方法增加批量操作支持
1. gmlock增加手动清理机制当内存锁不再使用时由调用端决定是否清理内存锁
1. gtimer增加DelayAdd*方法返回Entry对象以便DelayAdd*的定时任务也能进行状态控制gcron同理需要改进
# DONE
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换
@ -70,7 +71,7 @@
21. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name现有的控制器及执行对象注册很难友好支持这种动态形式
22. 当前gpage分页包的输出标签不支持li大多数CSS框架都是li+a标签模式需要提供可更加灵活的定制化功能实现
23. 平滑重启机制改进,以便于开发阶段调试;
24. 对grpool进行优化改进包括属性原子操作封装采用gtype实现修正设计BUGhttps://github.com/johng-cn/gf/issues/6
24. 对grpool进行优化改进包括属性原子操作封装采用gtype实现修正设计BUGhttps://github.com/gogf/gf/issues/6
25. gredis增加redis密码支持
26. 改进ghttp.Server平滑重启机制当新进程接管服务后再使用进程间通信方式通知父进程销毁
27. gproc进程间通信增加分组特性不同的进程间可以通过进程ID以及分组名称发送/获取进程消息;
@ -96,4 +97,26 @@
1. `gfsnotify`增加添加监听文件时的监听ID返回以便调用端删除监听时只删除自己添加的监听而不影响其他对该同一文件的监听回调
1. `gfsnotify`针对添加目录监听时无法使用多个`Watcher`,考虑改进,并考虑动态扩容全局`Watcher`方案;
1. 由于系统对inotify实例数量(`fs.inotify.max_user_instances`)以及队列大小(`fs.inotify.max_user_watches`)有限制,需要改进`gfsnotify`
1. WebServer事件回调允许对同一个路由规则绑定多个事件回调
1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断基本是开发环境下特别是windows环境去掉临时文件的监听避免临时文件过大引起的运行缓慢占用内存问题
1. 改进gfpool在文件指针变化时的更新
1. ghttp hook回调使用方式在注册路由比较多的时候优先级可能使得开发者混乱考虑方式便于管理
1. gform对于MySQL字段类型为datetime类型的时区问题分析
1. 改进证书打开失败时的WebServer错误提示前置HOOK校验后关闭后续的HOOK逻辑执行
1. 目前WebServer的HOOK是按照优先级执行的需要增加覆盖特性
1. 更新跨域请求CORS相关功能文档
1. ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
1. gcfg包目前允许添加重复的目录路径需要在SetPath/AddPath时判断重复性不能添加重复的路径
1. gdb执行数据写入时如果参数为struct/[]struct自动映射与表字段对应关系不再使用gconv标签标识
1. gdb的Data方法支持struct参数传入
1. gfcache依旧使用gcache作为缓存控制对象不要使用gmap
1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
1. gconv针对struct的转换增加json tag支持gconv.Map默认也支持json tag, 完善开发文档;
1. 增加SO_REUSEPORT的支持
1. gkafka这个包比较重未来从框架中剥离出来
1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php
1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php
1. gfile对于文件的读写强行使用了gfpool在某些场景下不合适需要考虑剥离开并为开发者提供单独的指针池文件操作特性
1. ghttp.Client自动Close机制
1. ghttp路由功能增加分组路由特性
1. 增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射

View File

@ -1,7 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package container

View File

@ -1,12 +1,9 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// 并发安全的数组.
// Package garray provides concurrent-safe/unsafe arrays.
package garray
func New(size int, cap int, safe...bool) *Array {
return NewArray(size, cap, safe...)
}

View File

@ -0,0 +1,20 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package garray
type apiSliceInterface interface {
Slice() []interface{}
}
type apiSliceInt interface {
Slice() []int
}
type apiSliceString interface {
Slice() []string
}

View File

@ -1,151 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package garray
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type IntArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
size int // 初始化设置的数组大小
array []int // 底层数组
}
func NewIntArray(size int, cap int, safe...bool) *IntArray {
a := &IntArray{
mu : rwmutex.New(safe...),
}
a.size = size
if cap > 0 {
a.cap = cap
a.array = make([]int, size, cap)
} else {
a.array = make([]int, size)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Get(index int) int {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Set(index int, value int) {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *IntArray) InsertBefore(index int, value int) {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]int{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界
func (a *IntArray) InsertAfter(index int, value int) {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]int{}, a.array[index + 1:]...)
a.array = append(a.array[0 : index + 1], value)
a.array = append(a.array, rear...)
}
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 追加数据项
func (a *IntArray) Append(value...int) {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
}
// 数组长度
func (a *IntArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
func (a *IntArray) Slice() []int {
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]int, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
}
// 清空数据数组
func (a *IntArray) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]int, a.size, a.cap)
} else {
a.array = make([]int, a.size)
}
}
a.mu.Unlock()
}
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
func (a *IntArray) Search(value int) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// 使用自定义方法执行加锁修改操作
func (a *IntArray) LockFunc(f func(array []int)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
}
// 使用自定义方法执行加锁读取操作
func (a *IntArray) RLockFunc(f func(array []int)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}

View File

@ -1,150 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package garray
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type Array struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
size int // 初始化设置的数组大小
array []interface{} // 底层数组
}
func NewArray(size int, cap int, safe...bool) *Array {
a := &Array{
mu : rwmutex.New(safe...),
}
a.size = size
if cap > 0 {
a.cap = cap
a.array = make([]interface{}, size, cap)
} else {
a.array = make([]interface{}, size)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Get(index int) interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Set(index int, value interface{}) {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *Array) InsertBefore(index int, value interface{}) {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]interface{}{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *Array) InsertAfter(index int, value interface{}) {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]interface{}{}, a.array[index + 1 : ]...)
a.array = append(a.array[0 : index + 1], value)
a.array = append(a.array, rear...)
}
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 追加数据项
func (a *Array) Append(value...interface{}) {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
}
// 数组长度
func (a *Array) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
func (a *Array) Slice() []interface{} {
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
}
// 清空数据数组
func (a *Array) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]interface{}, a.size, a.cap)
} else {
a.array = make([]interface{}, a.size)
}
}
a.mu.Unlock()
}
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
func (a *Array) Search(value interface{}) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// 使用自定义方法执行加锁修改操作
func (a *Array) LockFunc(f func(array []interface{})) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
}
// 使用自定义方法执行加锁读取操作
func (a *Array) RLockFunc(f func(array []interface{})) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}

View File

@ -0,0 +1,580 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"bytes"
"fmt"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
)
type IntArray struct {
mu *rwmutex.RWMutex
array []int
}
// NewIntArray creates and returns an empty array.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArray(unsafe...bool) *IntArray {
return NewIntArraySize(0, 0, unsafe...)
}
// NewIntArraySize create and returns an array with given size and cap.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArraySize(size int, cap int, unsafe...bool) *IntArray {
return &IntArray{
mu : rwmutex.New(unsafe...),
array : make([]int, size, cap),
}
}
// NewIntArrayFrom creates and returns an array with given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArrayFrom(array []int, unsafe...bool) *IntArray {
return &IntArray{
mu : rwmutex.New(unsafe...),
array : array,
}
}
// NewIntArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewIntArrayFromCopy(array []int, unsafe...bool) *IntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &IntArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *IntArray) Get(index int) int {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set sets value to specified index.
func (a *IntArray) Set(index int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// SetArray sets the underlying slice array with the given <array>.
func (a *IntArray) SetArray(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
func (a *IntArray) Replace(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
return a
}
// Sum returns the sum of values in an array.
func (a *IntArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += v
}
return
}
// Sort sorts the array in increasing order.
// The param <reverse> controls whether sort
// in increasing order(default) or decreasing order
func (a *IntArray) Sort(reverse...bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if len(reverse) > 0 && reverse[0] {
sort.Slice(a.array, func(i, j int) bool {
if a.array[i] < a.array[j] {
return false
}
return true
})
} else {
sort.Ints(a.array)
}
return a
}
// SortFunc sorts the array by custom function <less>.
func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
return a
}
// InsertBefore inserts the <value> to the front of <index>.
func (a *IntArray) InsertBefore(index int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]int{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
return a
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *IntArray) InsertAfter(index int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]int{}, a.array[index + 1:]...)
a.array = append(a.array[0 : index + 1], value)
a.array = append(a.array, rear...)
return a
}
// Remove removes an item by index.
func (a *IntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *IntArray) PushLeft(value...int) *IntArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *IntArray) PushRight(value...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// PopLeft pops and returns an item from the beginning of array.
func (a *IntArray) PopLeft() int {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// PopRight pops and returns an item from the end of array.
func (a *IntArray) PopRight() int {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
// PopRand randomly pops and return an item out of array.
func (a *IntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *IntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]int, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *IntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0 : size]
a.array = a.array[size : ]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *IntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index :]
a.array = a.array[ : index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *IntArray) Range(start, end int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
if start > length || start > end {
return nil
}
if start < 0 {
start = 0
}
if end > length {
end = length
}
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]int, end - start)
copy(array, a.array[start : end])
} else {
array = a.array[start : end]
}
return array
}
// See PushRight.
func (a *IntArray) Append(value...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Len returns the length of array.
func (a *IntArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *IntArray) Slice() []int {
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]int, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *IntArray) Clone() (newArray *IntArray) {
a.mu.RLock()
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewIntArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *IntArray) Clear() *IntArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]int, 0)
}
a.mu.Unlock()
return a
}
// Contains checks whether a value exists in the array.
func (a *IntArray) Contains(value int) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *IntArray) Search(value int) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
func (a *IntArray) Unique() *IntArray {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// LockFunc locks writing by callback function <f>.
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *IntArray) Merge(array interface{}) *IntArray {
switch v := array.(type) {
case *Array: a.Append(gconv.Ints(v.Slice())...)
case *IntArray: a.Append(gconv.Ints(v.Slice())...)
case *StringArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Ints(v.Slice())...)
default:
a.Append(gconv.Ints(array)...)
}
return a
}
// Fill fills an array with num entries of the value <value>,
// keys starting at the <startIndex> parameter.
func (a *IntArray) Fill(startIndex int, num int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
}
for i := startIndex; i < startIndex + num; i++ {
if i > len(a.array) - 1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *IntArray) Chunk(size int) [][]int {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]int
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size : end])
i++
}
return n
}
// Pad pads array to the specified length with <value>.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// then no padding takes place.
func (a *IntArray) Pad(size int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]int, n)
for i := 0; i < n; i++ {
tmp[i] = value
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
return a
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *IntArray) SubSlice(offset, size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset + size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting).
func (a *IntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *IntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]int, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size - 1 {
break
}
}
return n
}
// Shuffle randomly shuffles the array.
func (a *IntArray) Shuffle() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
return a
}
// Reverse makes array with elements in reverse order.
func (a *IntArray) Reverse() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array) - 1; i < j; i, j = i + 1, j - 1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
return a
}
// Join joins array elements with a string <glue>.
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *IntArray) CountValues() map[int]int {
m := make(map[int]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *IntArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -0,0 +1,574 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"bytes"
"fmt"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
)
type Array struct {
mu *rwmutex.RWMutex
array []interface{}
}
// New creates and returns an empty array.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func New(unsafe...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
// See New.
func NewArray(unsafe...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
// NewArraySize create and returns an array with given size and cap.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewArraySize(size int, cap int, unsafe...bool) *Array {
return &Array{
mu : rwmutex.New(unsafe...),
array : make([]interface{}, size, cap),
}
}
// See NewArrayFrom.
func NewFrom(array []interface{}, unsafe...bool) *Array {
return NewArrayFrom(array, unsafe...)
}
// See NewArrayFromCopy.
func NewFromCopy(array []interface{}, unsafe...bool) *Array {
return NewArrayFromCopy(array, unsafe...)
}
// NewArrayFrom creates and returns an array with given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewArrayFrom(array []interface{}, unsafe...bool) *Array {
return &Array{
mu : rwmutex.New(unsafe...),
array : array,
}
}
// NewArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewArrayFromCopy(array []interface{}, unsafe...bool) *Array {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &Array{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *Array) Get(index int) interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set sets value to specified index.
func (a *Array) Set(index int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// SetArray sets the underlying slice array with the given <array>.
func (a *Array) SetArray(array []interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
func (a *Array) Replace(array []interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
return a
}
// Sum returns the sum of values in an array.
func (a *Array) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// SortFunc sorts the array by custom function <less>.
func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
return a
}
// InsertBefore inserts the <value> to the front of <index>.
func (a *Array) InsertBefore(index int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]interface{}{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
return a
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *Array) InsertAfter(index int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]interface{}{}, a.array[index + 1 : ]...)
a.array = append(a.array[0 : index + 1], value)
a.array = append(a.array, rear...)
return a
}
// Remove removes an item by index.
func (a *Array) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency。
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *Array) PushLeft(value...interface{}) *Array {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *Array) PushRight(value...interface{}) *Array {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// PopRand randomly pops and return an item out of array.
func (a *Array) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *Array) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// PopLeft pops and returns an item from the beginning of array.
func (a *Array) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// PopRight pops and returns an item from the end of array.
func (a *Array) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *Array) PopLefts(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0 : size]
a.array = a.array[size : ]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *Array) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index :]
a.array = a.array[ : index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *Array) Range(start, end int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
if start > length || start > end {
return nil
}
if start < 0 {
start = 0
}
if end > length {
end = length
}
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]interface{}, end - start)
copy(array, a.array[start : end])
} else {
array = a.array[start : end]
}
return array
}
// See PushRight.
func (a *Array) Append(value...interface{}) *Array {
a.PushRight(value...)
return a
}
// Len returns the length of array.
func (a *Array) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *Array) Slice() []interface{} {
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]interface{}, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *Array) Clone() (newArray *Array) {
a.mu.RLock()
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *Array) Clear() *Array {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]interface{}, 0)
}
a.mu.Unlock()
return a
}
// Contains checks whether a value exists in the array.
func (a *Array) Contains(value interface{}) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *Array) Search(value interface{}) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
func (a *Array) Unique() *Array {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// LockFunc locks writing by callback function <f>.
func (a *Array) LockFunc(f func(array []interface{})) *Array {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *Array) Merge(array interface{}) *Array {
switch v := array.(type) {
case *Array: a.Append(gconv.Interfaces(v.Slice())...)
case *IntArray: a.Append(gconv.Interfaces(v.Slice())...)
case *StringArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Interfaces(v.Slice())...)
default:
a.Append(gconv.Interfaces(array)...)
}
return a
}
// Fill fills an array with num entries of the value <value>,
// keys starting at the <startIndex> parameter.
func (a *Array) Fill(startIndex int, num int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
}
for i := startIndex; i < startIndex + num; i++ {
if i > len(a.array) - 1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *Array) Chunk(size int) [][]interface{} {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]interface{}
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size : end])
i++
}
return n
}
// Pad pads array to the specified length with <value>.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// then no padding takes place.
func (a *Array) Pad(size int, val interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]interface{}, n)
for i := 0; i < n; i++ {
tmp[i] = val
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
return a
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *Array) SubSlice(offset, size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset + size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting).
func (a *Array) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *Array) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]interface{}, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size - 1 {
break
}
}
return n
}
// Shuffle randomly shuffles the array.
func (a *Array) Shuffle() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
return a
}
// Reverse makes array with elements in reverse order.
func (a *Array) Reverse() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array) - 1; i < j; i, j = i + 1, j - 1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
return a
}
// Join joins array elements with a string <glue>.
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *Array) CountValues() map[interface{}]int {
m := make(map[interface{}]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *Array) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -0,0 +1,580 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"bytes"
"fmt"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
type StringArray struct {
mu *rwmutex.RWMutex
array []string
}
// NewStringArray creates and returns an empty array.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArray(unsafe...bool) *StringArray {
return NewStringArraySize(0, 0, unsafe...)
}
// NewStringArraySize create and returns an array with given size and cap.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArraySize(size int, cap int, unsafe...bool) *StringArray {
return &StringArray{
mu : rwmutex.New(unsafe...),
array : make([]string, size, cap),
}
}
// NewStringArrayFrom creates and returns an array with given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArrayFrom(array []string, unsafe...bool) *StringArray {
return &StringArray {
mu : rwmutex.New(unsafe...),
array : array,
}
}
// NewStringArrayFromCopy creates and returns an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewStringArrayFromCopy(array []string, unsafe...bool) *StringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &StringArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *StringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set sets value to specified index.
func (a *StringArray) Set(index int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// SetArray sets the underlying slice array with the given <array>.
func (a *StringArray) SetArray(array []string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace replaces the array items by given <array> from the beginning of array.
func (a *StringArray) Replace(array []string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
return a
}
// Sum returns the sum of values in an array.
func (a *StringArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Sort sorts the array in increasing order.
// The param <reverse> controls whether sort
// in increasing order(default) or decreasing order
func (a *StringArray) Sort(reverse...bool) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if len(reverse) > 0 && reverse[0] {
sort.Slice(a.array, func(i, j int) bool {
if strings.Compare(a.array[i], a.array[j]) < 0 {
return false
}
return true
})
} else {
sort.Strings(a.array)
}
return a
}
// SortFunc sorts the array by custom function <less>.
func (a *StringArray) SortFunc(less func(v1, v2 string) bool) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
return a
}
// InsertBefore inserts the <value> to the front of <index>.
func (a *StringArray) InsertBefore(index int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]string{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
return a
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *StringArray) InsertAfter(index int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]string{}, a.array[index + 1:]...)
a.array = append(a.array[ 0: index + 1], value)
a.array = append(a.array, rear...)
return a
}
// Remove removes an item by index.
func (a *StringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
// Determine array boundaries when deleting to improve deletion efficiency。
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *StringArray) PushLeft(value...string) *StringArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *StringArray) PushRight(value...string) *StringArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// PopLeft pops and returns an item from the beginning of array.
func (a *StringArray) PopLeft() string {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// PopRight pops and returns an item from the end of array.
func (a *StringArray) PopRight() string {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
// PopRand randomly pops and return an item out of array.
func (a *StringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *StringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]string, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *StringArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0 : size]
a.array = a.array[size : ]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *StringArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index :]
a.array = a.array[ : index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *StringArray) Range(start, end int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
if start > length || start > end {
return nil
}
if start < 0 {
start = 0
}
if end > length {
end = length
}
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]string, end - start)
copy(array, a.array[start : end])
} else {
array = a.array[start : end]
}
return array
}
// See PushRight.
func (a *StringArray) Append(value...string) *StringArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Len returns the length of array.
func (a *StringArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *StringArray) Slice() []string {
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]string, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
}
// Clone returns a new array, which is a copy of current array.
func (a *StringArray) Clone() (newArray *StringArray) {
a.mu.RLock()
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewStringArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *StringArray) Clear() *StringArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]string, 0)
}
a.mu.Unlock()
return a
}
// Contains checks whether a value exists in the array.
func (a *StringArray) Contains(value string) bool {
return a.Search(value) != -1
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *StringArray) Search(value string) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
func (a *StringArray) Unique() *StringArray {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// LockFunc locks writing by callback function <f>.
func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function <f>.
func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *StringArray) Merge(array interface{}) *StringArray {
switch v := array.(type) {
case *Array: a.Append(gconv.Strings(v.Slice())...)
case *IntArray: a.Append(gconv.Strings(v.Slice())...)
case *StringArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Strings(v.Slice())...)
default:
a.Append(gconv.Strings(array)...)
}
return a
}
// Fill fills an array with num entries of the value <value>,
// keys starting at the <startIndex> parameter.
func (a *StringArray) Fill(startIndex int, num int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
}
for i := startIndex; i < startIndex + num; i++ {
if i > len(a.array) - 1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *StringArray) Chunk(size int) [][]string {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]string
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size : end])
i++
}
return n
}
// Pad pads array to the specified length with <value>.
// If size is positive then the array is padded on the right, or negative on the left.
// If the absolute value of <size> is less than or equal to the length of the array
// then no padding takes place.
func (a *StringArray) Pad(size int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]string, n)
for i := 0; i < n; i++ {
tmp[i] = value
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
return a
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *StringArray) SubSlice(offset, size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset + size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting).
func (a *StringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *StringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]string, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size - 1 {
break
}
}
return n
}
// Shuffle randomly shuffles the array.
func (a *StringArray) Shuffle() *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
return a
}
// Reverse makes array with elements in reverse order.
func (a *StringArray) Reverse() *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array) - 1; i < j; i, j = i + 1, j - 1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
return a
}
// Join joins array elements with a string <glue>.
func (a *StringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *StringArray) CountValues() map[string]int {
m := make(map[string]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *StringArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,32 +1,46 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/internal/rwmutex"
"bytes"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
)
// 默认按照从低到高进行排序
// It's using increasing order in default.
type SortedIntArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []int // 底层数组
unique *gtype.Bool // 是否要求不能重复
compareFunc func(v1, v2 int) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
mu *rwmutex.RWMutex
array []int
unique *gtype.Bool // Whether enable unique feature(false)
comparator func(v1, v2 int) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
}
// 创建一个排序的int数组
func NewSortedIntArray(cap int, safe...bool) *SortedIntArray {
// NewSortedIntArray creates and returns an empty sorted array.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArray(unsafe...bool) *SortedIntArray {
return NewSortedIntArraySize(0, unsafe...)
}
// NewSortedIntArraySize create and returns an sorted array with given size and cap.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArraySize(cap int, unsafe...bool) *SortedIntArray {
return &SortedIntArray {
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
array : make([]int, 0, cap),
unique : gtype.NewBool(),
compareFunc : func(v1, v2 int) int {
comparator : func(v1, v2 int) int {
if v1 < v2 {
return -1
}
@ -38,10 +52,51 @@ func NewSortedIntArray(cap int, safe...bool) *SortedIntArray {
}
}
// 添加加数据项
func (a *SortedIntArray) Add(values...int) {
// NewIntArrayFrom creates and returns an sorted array with given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArrayFrom(array []int, unsafe...bool) *SortedIntArray {
a := NewSortedIntArraySize(0, unsafe...)
a.array = array
sort.Ints(a.array)
return a
}
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedIntArrayFromCopy(array []int, unsafe...bool) *SortedIntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &SortedIntArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// SetArray sets the underlying slice array with the given <array>.
func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Ints(a.array)
return a
}
// Sort sorts the array in increasing order.
// The param <reverse> controls whether sort
// in increasing order(default) or decreasing order.
func (a *SortedIntArray) Sort() *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Ints(a.array)
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedIntArray) Add(values...int) *SortedIntArray {
if len(values) == 0 {
return
return a
}
a.mu.Lock()
defer a.mu.Unlock()
@ -54,7 +109,6 @@ func (a *SortedIntArray) Add(values...int) {
a.array = append(a.array, value)
continue
}
// 加到指定索引后面
if cmp > 0 {
index++
}
@ -62,9 +116,11 @@ func (a *SortedIntArray) Add(values...int) {
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *SortedIntArray) Get(index int) int {
a.mu.RLock()
defer a.mu.RUnlock()
@ -72,11 +128,11 @@ func (a *SortedIntArray) Get(index int) int {
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove removes an item by index.
func (a *SortedIntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
@ -86,13 +142,15 @@ func (a *SortedIntArray) Remove(index int) int {
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// PopLeft pops and returns an item from the beginning of array.
func (a *SortedIntArray) PopLeft() int {
a.mu.Lock()
defer a.mu.Unlock()
@ -101,7 +159,7 @@ func (a *SortedIntArray) PopLeft() int {
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// PopRight pops and returns an item from the end of array.
func (a *SortedIntArray) PopRight() int {
a.mu.Lock()
defer a.mu.Unlock()
@ -111,7 +169,82 @@ func (a *SortedIntArray) PopRight() int {
return value
}
// 数组长度
// PopRand randomly pops and return an item out of array.
func (a *SortedIntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *SortedIntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]int, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *SortedIntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0 : size]
a.array = a.array[size : ]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *SortedIntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index :]
a.array = a.array[ : index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedIntArray) Range(start, end int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
if start > length || start > end {
return nil
}
if start < 0 {
start = 0
}
if end > length {
end = length
}
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]int, end - start)
copy(array, a.array[start : end])
} else {
array = a.array[start : end]
}
return array
}
// Len returns the length of array.
func (a *SortedIntArray) Len() int {
a.mu.RLock()
length := len(a.array)
@ -119,28 +252,45 @@ func (a *SortedIntArray) Len() int {
return length
}
// 返回原始数据数组
// Sum returns the sum of values in an array.
func (a *SortedIntArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += v
}
return
}
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedIntArray) Slice() []int {
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]int, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
copy(array, a.array)
} else {
array = a.array
}
return array
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedIntArray) Search(value int) (index int, result int) {
return a.binSearch(value, true)
// Contains checks whether a value exists in the array.
func (a *SortedIntArray) Contains(value int) bool {
return a.Search(value) == 0
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedIntArray) Search(value int) (index int) {
index, _ = a.binSearch(value, true)
return
}
// Binary search.
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
@ -153,66 +303,198 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
max := len(a.array) - 1
mid := 0
cmp := -2
for {
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
break
cmp = a.comparator(value, a.array[mid])
switch {
case cmp < 0 : max = mid - 1
case cmp > 0 : min = mid + 1
default :
return mid, cmp
}
}
return mid, cmp
}
// 设置是否允许数组唯一
func (a *SortedIntArray) SetUnique(unique bool) {
// SetUnique sets unique mark to the array,
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
if unique && oldUnique != unique {
a.doUnique()
a.Unique()
}
return a
}
// 清理数组中重复的元素项
func (a *SortedIntArray) doUnique() {
// Unique uniques the array, clear repeated items.
func (a *SortedIntArray) Unique() *SortedIntArray {
a.mu.Lock()
i := 0
for {
if i == len(a.array) - 1 {
break
}
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
if a.comparator(a.array[i], a.array[i + 1]) == 0 {
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
} else {
i++
}
}
a.mu.Unlock()
return a
}
// 清空数据数组
func (a *SortedIntArray) Clear() {
// Clone returns a new array, which is a copy of current array.
func (a *SortedIntArray) Clone() (newArray *SortedIntArray) {
a.mu.RLock()
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedIntArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *SortedIntArray) Clear() *SortedIntArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]int, 0, a.cap)
a.array = make([]int, 0)
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *SortedIntArray) LockFunc(f func(array []int)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
// LockFunc locks writing by callback function <f>.
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedIntArray) RLockFunc(f func(array []int)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
// RLockFunc locks reading by callback function <f>.
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Ints(v.Slice())...)
case *IntArray: a.Add(gconv.Ints(v.Slice())...)
case *StringArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Ints(v.Slice())...)
default:
a.Add(gconv.Ints(array)...)
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *SortedIntArray) Chunk(size int) [][]int {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]int
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size : end])
i++
}
return n
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *SortedIntArray) SubSlice(offset, size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset + size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedIntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *SortedIntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]int, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size - 1 {
break
}
}
return n
}
// Join joins array elements with a string <glue>.
func (a *SortedIntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *SortedIntArray) CountValues() map[int]int {
m := make(map[int]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *SortedIntArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,38 +1,103 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/internal/rwmutex"
"bytes"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
)
// 默认按照从低到高进行排序
// It's using increasing order in default.
type SortedArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []interface{} // 底层数组
unique *gtype.Bool // 是否要求不能重复
compareFunc func(v1, v2 interface{}) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
mu *rwmutex.RWMutex
array []interface{}
unique *gtype.Bool // Whether enable unique feature(false)
comparator func(v1, v2 interface{}) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
}
func NewSortedArray(cap int, compareFunc func(v1, v2 interface{}) int, safe...bool) *SortedArray {
// NewSortedArray creates and returns an empty sorted array.
// The param <unsafe> used to specify whether using array in un-concurrent-safety, which is false in default.
// The param <comparator> used to compare values to sort in array,
// if it returns value < 0, means v1 < v2;
// if it returns value = 0, means v1 = v2;
// if it returns value > 0, means v1 > v2;
func NewSortedArray(comparator func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
return NewSortedArraySize(0, comparator, unsafe...)
}
// NewSortedArraySize create and returns an sorted array with given size and cap.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedArraySize(cap int, comparator func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
return &SortedArray{
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
unique : gtype.NewBool(),
array : make([]interface{}, 0, cap),
compareFunc : compareFunc,
comparator : comparator,
}
}
// 添加加数据项
func (a *SortedArray) Add(values...interface{}) {
// NewSortedArrayFrom creates and returns an sorted array with given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedArrayFrom(array []interface{}, comparator func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
a := NewSortedArraySize(0, comparator, unsafe...)
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
})
return a
}
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedArrayFromCopy(array []interface{}, unsafe...bool) *SortedArray {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &SortedArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// SetArray sets the underlying slice array with the given <array>.
func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
})
return a
}
// Sort sorts the array in increasing order.
// The param <reverse> controls whether sort
// in increasing order(default) or decreasing order
func (a *SortedArray) Sort() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
})
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedArray) Add(values...interface{}) *SortedArray {
if len(values) == 0 {
return
return a
}
a.mu.Lock()
defer a.mu.Unlock()
@ -45,7 +110,6 @@ func (a *SortedArray) Add(values...interface{}) {
a.array = append(a.array, value)
continue
}
// 加到指定索引后面
if cmp > 0 {
index++
}
@ -53,9 +117,11 @@ func (a *SortedArray) Add(values...interface{}) {
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *SortedArray) Get(index int) interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
@ -63,11 +129,11 @@ func (a *SortedArray) Get(index int) interface{} {
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove removes an item by index.
func (a *SortedArray) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
@ -77,13 +143,15 @@ func (a *SortedArray) Remove(index int) interface{} {
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// PopLeft pops and returns an item from the beginning of array.
func (a *SortedArray) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -92,7 +160,7 @@ func (a *SortedArray) PopLeft() interface{} {
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// PopRight pops and returns an item from the end of array.
func (a *SortedArray) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -102,7 +170,92 @@ func (a *SortedArray) PopRight() interface{} {
return value
}
// 数组长度
// PopRand randomly pops and return an item out of array.
func (a *SortedArray) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *SortedArray) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *SortedArray) PopLefts(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0 : size]
a.array = a.array[size : ]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *SortedArray) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index :]
a.array = a.array[ : index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedArray) Range(start, end int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
if start > length || start > end {
return nil
}
if start < 0 {
start = 0
}
if end > length {
end = length
}
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]interface{}, end - start)
copy(array, a.array[start : end])
} else {
array = a.array[start : end]
}
return array
}
// Sum returns the sum of values in an array.
func (a *SortedArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Len returns the length of array.
func (a *SortedArray) Len() int {
a.mu.RLock()
length := len(a.array)
@ -110,30 +263,35 @@ func (a *SortedArray) Len() int {
return length
}
// 返回原始数据数组
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedArray) Slice() []interface{} {
array := ([]interface{})(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]interface{}, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
copy(array, a.array)
} else {
array = a.array
}
return array
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedArray) Search(value interface{}) (index int, result int) {
return a.binSearch(value, true)
// Contains checks whether a value exists in the array.
func (a *SortedArray) Contains(value interface{}) bool {
return a.Search(value) == 0
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedArray) Search(value interface{}) (index int) {
index, _ = a.binSearch(value, true)
return
}
// Binary search.
func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result int) {
if len(a.array) == 0 {
return -1, -2
@ -146,32 +304,33 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result
max := len(a.array) - 1
mid := 0
cmp := -2
for {
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
break
cmp = a.comparator(value, a.array[mid])
switch {
case cmp < 0 : max = mid - 1
case cmp > 0 : min = mid + 1
default :
return mid, cmp
}
}
return mid, cmp
}
// 设置是否允许数组唯一
func (a *SortedArray) SetUnique(unique bool) {
// SetUnique sets unique mark to the array,
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedArray) SetUnique(unique bool) *SortedArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
if unique && oldUnique != unique {
a.doUnique()
a.Unique()
}
return a
}
// 清理数组中重复的元素项
func (a *SortedArray) doUnique() {
// Unique uniques the array, clear repeated items.
func (a *SortedArray) Unique() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
i := 0
@ -179,33 +338,164 @@ func (a *SortedArray) doUnique() {
if i == len(a.array) - 1 {
break
}
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
if a.comparator(a.array[i], a.array[i + 1]) == 0 {
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
} else {
i++
}
}
return a
}
// 清空数据数组
func (a *SortedArray) Clear() {
// Clone returns a new array, which is a copy of current array.
func (a *SortedArray) Clone() (newArray *SortedArray) {
a.mu.RLock()
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedArrayFrom(array, a.comparator, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *SortedArray) Clear() *SortedArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]interface{}, 0, a.cap)
a.array = make([]interface{}, 0)
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *SortedArray) LockFunc(f func(array []interface{})) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
// LockFunc locks writing by callback function <f>.
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedArray) RLockFunc(f func(array []interface{})) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
// RLockFunc locks reading by callback function <f>.
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedArray) Merge(array interface{}) *SortedArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Interfaces(v.Slice())...)
case *IntArray: a.Add(gconv.Interfaces(v.Slice())...)
case *StringArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Interfaces(v.Slice())...)
default:
a.Add(gconv.Interfaces(array)...)
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *SortedArray) Chunk(size int) [][]interface{} {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]interface{}
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size : end])
i++
}
return n
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *SortedArray) SubSlice(offset, size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset + size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedArray) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *SortedArray) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]interface{}, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size - 1 {
break
}
}
return n
}
// Join joins array elements with a string <glue>.
func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *SortedArray) CountValues() map[interface{}]int {
m := make(map[interface{}]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *SortedArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,41 +1,97 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package garray
import (
"gitee.com/johng/gf/g/container/gtype"
"bytes"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
// 默认按照从低到高进行排序
// It's using increasing order in default.
type SortedStringArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []string // 底层数组
unique *gtype.Bool // 是否要求不能重复
compareFunc func(v1, v2 string) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
mu *rwmutex.RWMutex
array []string
unique *gtype.Bool // Whether enable unique feature(false)
comparator func(v1, v2 string) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
}
func NewSortedStringArray(cap int, safe...bool) *SortedStringArray {
// NewSortedStringArray creates and returns an empty sorted array.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArray(unsafe...bool) *SortedStringArray {
return NewSortedStringArraySize(0, unsafe...)
}
// NewSortedStringArraySize create and returns an sorted array with given size and cap.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArraySize(cap int, unsafe...bool) *SortedStringArray {
return &SortedStringArray {
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
array : make([]string, 0, cap),
unique : gtype.NewBool(),
compareFunc : func(v1, v2 string) int {
comparator : func(v1, v2 string) int {
return strings.Compare(v1, v2)
},
}
}
// 添加加数据项
func (a *SortedStringArray) Add(values...string) {
// NewSortedStringArrayFrom creates and returns an sorted array with given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArrayFrom(array []string, unsafe...bool) *SortedStringArray {
a := NewSortedStringArraySize(0, unsafe...)
a.array = array
sort.Strings(a.array)
return a
}
// NewSortedStringArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
// which is false in default.
func NewSortedStringArrayFromCopy(array []string, unsafe...bool) *SortedStringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &SortedStringArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// SetArray sets the underlying slice array with the given <array>.
func (a *SortedStringArray) SetArray(array []string) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Strings(a.array)
return a
}
// Sort sorts the array in increasing order.
// The param <reverse> controls whether sort
// in increasing order(default) or decreasing order.
func (a *SortedStringArray) Sort() *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Strings(a.array)
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedStringArray) Add(values...string) *SortedStringArray {
if len(values) == 0 {
return
return a
}
a.mu.Lock()
defer a.mu.Unlock()
@ -48,7 +104,6 @@ func (a *SortedStringArray) Add(values...string) {
a.array = append(a.array, value)
continue
}
// 加到指定索引后面
if cmp > 0 {
index++
}
@ -56,9 +111,11 @@ func (a *SortedStringArray) Add(values...string) {
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *SortedStringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -66,11 +123,11 @@ func (a *SortedStringArray) Get(index int) string {
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove removes an item by index.
func (a *SortedStringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
@ -80,13 +137,15 @@ func (a *SortedStringArray) Remove(index int) string {
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// PopLeft pops and returns an item from the beginning of array.
func (a *SortedStringArray) PopLeft() string {
a.mu.Lock()
defer a.mu.Unlock()
@ -95,7 +154,7 @@ func (a *SortedStringArray) PopLeft() string {
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// PopRight pops and returns an item from the end of array.
func (a *SortedStringArray) PopRight() string {
a.mu.Lock()
defer a.mu.Unlock()
@ -105,7 +164,92 @@ func (a *SortedStringArray) PopRight() string {
return value
}
// 数组长度
// PopRand randomly pops and return an item out of array.
func (a *SortedStringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns <size> items out of array.
func (a *SortedStringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]string, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *SortedStringArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
value := a.array[0 : size]
a.array = a.array[size : ]
return value
}
// PopRights pops and returns <size> items from the end of array.
func (a *SortedStringArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - size
if index < 0 {
index = 0
}
value := a.array[index :]
a.array = a.array[ : index]
return value
}
// Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedStringArray) Range(start, end int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
if start > length || start > end {
return nil
}
if start < 0 {
start = 0
}
if end > length {
end = length
}
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]string, end - start)
copy(array, a.array[start : end])
} else {
array = a.array[start : end]
}
return array
}
// Sum returns the sum of values in an array.
func (a *SortedStringArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Len returns the length of array.
func (a *SortedStringArray) Len() int {
a.mu.RLock()
length := len(a.array)
@ -113,28 +257,35 @@ func (a *SortedStringArray) Len() int {
return length
}
// 返回原始数据数组
// Slice returns the underlying data of array.
// Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
func (a *SortedStringArray) Slice() []string {
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]string, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
copy(array, a.array)
} else {
array = a.array
}
return array
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedStringArray) Search(value string) (index int, result int) {
return a.binSearch(value, true)
// Contains checks whether a value exists in the array.
func (a *SortedStringArray) Contains(value string) bool {
return a.Search(value) == 0
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedStringArray) Search(value string) (index int) {
index, _ = a.binSearch(value, true)
return
}
// Binary search.
func (a *SortedStringArray) binSearch(value string, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
@ -147,66 +298,198 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul
max := len(a.array) - 1
mid := 0
cmp := -2
for {
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
break
cmp = a.comparator(value, a.array[mid])
switch {
case cmp < 0 : max = mid - 1
case cmp > 0 : min = mid + 1
default :
return mid, cmp
}
}
return mid, cmp
}
// 设置是否允许数组唯一
func (a *SortedStringArray) SetUnique(unique bool) {
// SetUnique sets unique mark to the array,
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedStringArray) SetUnique(unique bool) *SortedStringArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
if unique && oldUnique != unique {
a.doUnique()
a.Unique()
}
return a
}
// 清理数组中重复的元素项
func (a *SortedStringArray) doUnique() {
// Unique uniques the array, clear repeated items.
func (a *SortedStringArray) Unique() *SortedStringArray {
a.mu.Lock()
i := 0
for {
if i == len(a.array) - 1 {
break
}
if a.compareFunc(a.array[i], a.array[i + 1]) == 0 {
if a.comparator(a.array[i], a.array[i + 1]) == 0 {
a.array = append(a.array[ : i + 1], a.array[i + 1 + 1 : ]...)
} else {
i++
}
}
a.mu.Unlock()
return a
}
// 清空数据数组
func (a *SortedStringArray) Clear() {
// Clone returns a new array, which is a copy of current array.
func (a *SortedStringArray) Clone() (newArray *SortedStringArray) {
a.mu.RLock()
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewSortedStringArrayFrom(array, !a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *SortedStringArray) Clear() *SortedStringArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]string, 0, a.cap)
a.array = make([]string, 0)
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *SortedStringArray) LockFunc(f func(array []string)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
// LockFunc locks writing by callback function <f>.
func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedStringArray) RLockFunc(f func(array []string)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
// RLockFunc locks reading by callback function <f>.
func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge merges <array> into current array.
// The parameter <array> can be any garray or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedStringArray) Merge(array interface{}) *SortedStringArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Strings(v.Slice())...)
case *IntArray: a.Add(gconv.Strings(v.Slice())...)
case *StringArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Strings(v.Slice())...)
default:
a.Add(gconv.Strings(array)...)
}
return a
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by <size>.
// The last chunk may contain less than size elements.
func (a *SortedStringArray) Chunk(size int) [][]string {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]string
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size : end])
i++
}
return n
}
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *SortedStringArray) SubSlice(offset, size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset + size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedStringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands randomly returns <size> items from array(no deleting).
func (a *SortedStringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
}
n := make([]string, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size - 1 {
break
}
}
return n
}
// Join joins array elements with a string <glue>.
func (a *SortedStringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *SortedStringArray) CountValues() map[string]int {
m := make(map[string]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
}
// String returns current array as a string.
func (a *SortedStringArray) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -1,150 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package garray
import (
"strings"
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type StringArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
size int // 初始化设置的数组大小
array []string // 底层数组
}
func NewStringArray(size int, cap int, safe...bool) *StringArray {
a := &StringArray{
mu : rwmutex.New(safe...),
}
a.size = size
if cap > 0 {
a.cap = cap
a.array = make([]string, size, cap)
} else {
a.array = make([]string, size)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
func (a *StringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *StringArray) Set(index int, value string) {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *StringArray) InsertBefore(index int, value string) {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]string{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
// 在当前索引位置后插入一个数据项, 调用方注意判断数组边界
func (a *StringArray) InsertAfter(index int, value string) {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]string{}, a.array[index + 1:]...)
a.array = append(a.array[ 0: index + 1], value)
a.array = append(a.array, rear...)
}
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *StringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.RUnlock()
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 追加数据项
func (a *StringArray) Append(value...string) {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
}
// 数组长度
func (a *StringArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// 返回原始数据数组
func (a *StringArray) Slice() []string {
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
array = make([]string, len(a.array))
for k, v := range a.array {
array[k] = v
}
a.mu.RUnlock()
} else {
array = a.array
}
return array
}
// 清空数据数组
func (a *StringArray) Clear() {
a.mu.Lock()
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]string, a.size, a.cap)
} else {
a.array = make([]string, a.size)
}
}
a.mu.Unlock()
}
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
func (a *StringArray) Search(value string) int {
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
result = index
break
}
}
a.mu.RUnlock()
return result
}
// 使用自定义方法执行加锁修改操作
func (a *StringArray) LockFunc(f func(array []string)) {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
}
// 使用自定义方法执行加锁读取操作
func (a *StringArray) RLockFunc(f func(array []string)) {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
}

View File

@ -0,0 +1,43 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"testing"
)
var (
sortedIntArray = garray.NewSortedIntArray()
)
func BenchmarkSortedIntArray_Add(b *testing.B) {
b.N = 1000
for i := 0; i < b.N; i++ {
sortedIntArray.Add(i)
}
}
func BenchmarkSortedIntArray_Search(b *testing.B) {
for i := 0; i < b.N; i++ {
sortedIntArray.Search(i)
}
}
func BenchmarkSortedIntArray_PopLeft(b *testing.B) {
for i := 0; i < b.N; i++ {
sortedIntArray.PopLeft()
}
}
func BenchmarkSortedIntArray_PopRight(b *testing.B) {
for i := 0; i < b.N; i++ {
sortedIntArray.PopLeft()
}
}

View File

@ -0,0 +1,111 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package garray_test
import (
"fmt"
"github.com/gogf/gf/g/container/garray"
)
func Example_basic() {
// 创建普通的数组,默认并发安全(带锁)
a := garray.New()
// 添加数据项
for i := 0; i < 10; i++ {
a.Append(i)
}
// 获取当前数组长度
fmt.Println(a.Len())
// 获取当前数据项列表
fmt.Println(a.Slice())
// 获取指定索引项
fmt.Println(a.Get(6))
// 查找指定数据项是否存在
fmt.Println(a.Contains(6))
fmt.Println(a.Contains(100))
// 在指定索引前插入数据项
a.InsertAfter(9, 11)
// 在指定索引后插入数据项
a.InsertBefore(10, 10)
fmt.Println(a.Slice())
// 修改指定索引的数据项
a.Set(0, 100)
fmt.Println(a.Slice())
// 搜索数据项,返回搜索到的索引位置
fmt.Println(a.Search(5))
// 删除指定索引的数据项
a.Remove(0)
fmt.Println(a.Slice())
// 清空数组
fmt.Println(a.Slice())
a.Clear()
fmt.Println(a.Slice())
// Output:
// 10
// [0 1 2 3 4 5 6 7 8 9]
// 6
// true
// false
// [0 1 2 3 4 5 6 7 8 9 10 11]
// [100 1 2 3 4 5 6 7 8 9 10 11]
// 5
// [1 2 3 4 5 6 7 8 9 10 11]
// [1 2 3 4 5 6 7 8 9 10 11]
// []
}
func Example_rand() {
array := garray.NewFrom([]interface{}{1,2,3,4,5,6,7,8,9})
// 随机返回两个数据项(不删除)
fmt.Println(array.Rands(2))
fmt.Println(array.PopRand())
}
func Example_pop() {
array := garray.NewFrom([]interface{}{1,2,3,4,5,6,7,8,9})
fmt.Println(array.PopLeft())
fmt.Println(array.PopLefts(2))
fmt.Println(array.PopRight())
fmt.Println(array.PopRights(2))
// Output:
// 1
// [2 3]
// 9
// [7 8]
}
func Example_merge() {
array1 := garray.NewFrom([]interface{}{1,2})
array2 := garray.NewFrom([]interface{}{3,4})
slice1 := []interface{}{5,6}
slice2 := []int{7,8}
slice3 := []string{"9","0"}
fmt.Println(array1.Slice())
array1.Merge(array1)
array1.Merge(array2)
array1.Merge(slice1)
array1.Merge(slice2)
array1.Merge(slice3)
fmt.Println(array1.Slice())
// Output:
// [1 2]
// [1 2 1 2 3 4 5 6 7 8 9 0]
}

View File

@ -0,0 +1,83 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"strings"
"testing"
)
func Test_IntArray_Unique(t *testing.T) {
expect := []int{1, 2, 3, 4, 5, 6}
array := garray.NewIntArray()
array.Append(1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6)
array.Unique()
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray1(t *testing.T) {
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
array := garray.NewSortedIntArray()
for i := 10; i > -1; i-- {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray2(t *testing.T) {
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
array := garray.NewSortedIntArray()
for i := 0; i <= 10; i++ {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray1(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedStringArray()
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray2(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedStringArray()
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray1(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedArray(func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray2(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedArray(func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}

View File

@ -0,0 +1,201 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_IntArray_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []int{0, 1, 2, 3}
array := garray.NewIntArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search(100), 0)
gtest.Assert(array.Contains(100), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array.Contains(100), false)
array.Append(4)
gtest.Assert(array.Len(), 4)
array.InsertBefore(0, 100)
array.InsertAfter(0, 200)
gtest.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 4})
array.InsertBefore(5, 300)
array.InsertAfter(6, 400)
gtest.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 300, 4, 400})
gtest.Assert(array.Clear().Len(), 0)
})
}
func TestIntArray_Sort(t *testing.T) {
gtest.Case(t, func() {
expect1 := []int{0, 1, 2, 3}
expect2 := []int{3, 2, 1, 0}
array := garray.NewIntArray()
for i := 3; i >= 0; i-- {
array.Append(i)
}
array.Sort()
gtest.Assert(array.Slice(), expect1)
array.Sort(true)
gtest.Assert(array.Slice(), expect2)
})
}
func TestIntArray_Unique(t *testing.T) {
gtest.Case(t, func() {
expect := []int{1, 1, 2, 3}
array := garray.NewIntArrayFrom(expect)
gtest.Assert(array.Unique().Slice(), []int{1, 2, 3})
})
}
func TestIntArray_PushAndPop(t *testing.T) {
gtest.Case(t, func() {
expect := []int{0, 1, 2, 3}
array := garray.NewIntArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.PopLeft(), 0)
gtest.Assert(array.PopRight(), 3)
gtest.AssertIN(array.PopRand(), []int{1, 2})
gtest.AssertIN(array.PopRand(), []int{1, 2})
gtest.Assert(array.Len(), 0)
array.PushLeft(1).PushRight(2)
gtest.Assert(array.Slice(), []int{1, 2})
})
}
func TestIntArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []int{0,1,2,3,4,5,6}
value2 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(value1)
array2 := garray.NewIntArrayFrom(value2)
gtest.Assert(array1.PopLefts(2), []int{0,1})
gtest.Assert(array1.Slice(), []int{2,3,4,5,6})
gtest.Assert(array1.PopRights(2), []int{5,6})
gtest.Assert(array1.Slice(), []int{2,3,4})
gtest.Assert(array1.PopRights(20), []int{2,3,4})
gtest.Assert(array1.Slice(), []int{})
gtest.Assert(array2.PopLefts(20), []int{0,1,2,3,4,5,6})
gtest.Assert(array2.Slice(), []int{})
})
}
func TestIntArray_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(value1)
gtest.Assert(array1.Range(0, 1), []int{0})
gtest.Assert(array1.Range(1, 2), []int{1})
gtest.Assert(array1.Range(0, 2), []int{0, 1})
gtest.Assert(array1.Range(-1, 10), value1)
})
}
func TestIntArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3}
a2 := []int{4, 5, 6, 7}
array1 := garray.NewIntArrayFrom(a1)
array2 := garray.NewIntArrayFrom(a2)
gtest.Assert(array1.Merge(array2).Slice(), []int{0,1,2,3,4,5,6,7})
})
}
func TestIntArray_Fill(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0}
a2 := []int{0}
array1 := garray.NewIntArrayFrom(a1)
array2 := garray.NewIntArrayFrom(a2)
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []int{0,100,100})
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []int{100,100})
})
}
func TestIntArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{1,2,3,4,5}
array1 := garray.NewIntArrayFrom(a1)
chunks := array1.Chunk(2)
gtest.Assert(len(chunks), 3)
gtest.Assert(chunks[0], []int{1,2})
gtest.Assert(chunks[1], []int{3,4})
gtest.Assert(chunks[2], []int{5})
})
}
func TestIntArray_Pad(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Pad(3, 1).Slice(), []int{0,1,1})
gtest.Assert(array1.Pad(-4, 1).Slice(), []int{1,0,1,1})
gtest.Assert(array1.Pad(3, 1).Slice(), []int{1,0,1,1})
})
}
func TestIntArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.SubSlice(0, 2), []int{0,1})
gtest.Assert(array1.SubSlice(2, 2), []int{2,3})
gtest.Assert(array1.SubSlice(5, 8), []int{5,6})
})
}
func TestIntArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
gtest.AssertIN(array1.Rand(), a1)
})
}
func TestIntArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{100, 200, 300, 400, 500, 600}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}
func TestIntArray_Shuffle(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Shuffle().Len(), 7)
})
}
func TestIntArray_Reverse(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Reverse().Slice(), []int{6,5,4,3,2,1,0})
})
}
func TestIntArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}

View File

@ -0,0 +1,204 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_Array_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []interface{}{0, 1, 2, 3}
array := garray.NewArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
array.Set(0, 100)
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search(100), 0)
gtest.Assert(array.Contains(100), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array.Contains(100), false)
array.Append(4)
gtest.Assert(array.Len(), 4)
array.InsertBefore(0, 100)
array.InsertAfter(0, 200)
gtest.Assert(array.Slice(), []interface{}{100, 200, 1, 2, 3, 4})
array.InsertBefore(5, 300)
array.InsertAfter(6, 400)
gtest.Assert(array.Slice(), []interface{}{100, 200, 1, 2, 3, 300, 4, 400})
gtest.Assert(array.Clear().Len(), 0)
})
}
func TestArray_Sort(t *testing.T) {
gtest.Case(t, func() {
expect1 := []interface{}{0, 1, 2, 3}
expect2 := []interface{}{3, 2, 1, 0}
array := garray.NewArray()
for i := 3; i >= 0; i-- {
array.Append(i)
}
array.SortFunc(func(v1, v2 interface{}) bool {
return v1.(int) < v2.(int)
})
gtest.Assert(array.Slice(), expect1)
array.SortFunc(func(v1, v2 interface{}) bool {
return v1.(int) > v2.(int)
})
gtest.Assert(array.Slice(), expect2)
})
}
func TestArray_Unique(t *testing.T) {
gtest.Case(t, func() {
expect := []interface{}{1, 1, 2, 3}
array := garray.NewArrayFrom(expect)
gtest.Assert(array.Unique().Slice(), []interface{}{1, 2, 3})
})
}
func TestArray_PushAndPop(t *testing.T) {
gtest.Case(t, func() {
expect := []interface{}{0, 1, 2, 3}
array := garray.NewArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.PopLeft(), 0)
gtest.Assert(array.PopRight(), 3)
gtest.AssertIN(array.PopRand(), []interface{}{1, 2})
gtest.AssertIN(array.PopRand(), []interface{}{1, 2})
gtest.Assert(array.Len(), 0)
array.PushLeft(1).PushRight(2)
gtest.Assert(array.Slice(), []interface{}{1, 2})
})
}
func TestArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{100, 200, 300, 400, 500, 600}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}
func TestArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []interface{}{0,1,2,3,4,5,6}
value2 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(value1)
array2 := garray.NewArrayFrom(value2)
gtest.Assert(array1.PopLefts(2), []interface{}{0,1})
gtest.Assert(array1.Slice(), []interface{}{2,3,4,5,6})
gtest.Assert(array1.PopRights(2), []interface{}{5,6})
gtest.Assert(array1.Slice(), []interface{}{2,3,4})
gtest.Assert(array1.PopRights(20), []interface{}{2,3,4})
gtest.Assert(array1.Slice(), []interface{}{})
gtest.Assert(array2.PopLefts(20), []interface{}{0,1,2,3,4,5,6})
gtest.Assert(array2.Slice(), []interface{}{})
})
}
func TestArray_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(value1)
gtest.Assert(array1.Range(0, 1), []interface{}{0})
gtest.Assert(array1.Range(1, 2), []interface{}{1})
gtest.Assert(array1.Range(0, 2), []interface{}{0, 1})
gtest.Assert(array1.Range(-1, 10), value1)
})
}
func TestArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0, 1, 2, 3}
a2 := []interface{}{4, 5, 6, 7}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2)
gtest.Assert(array1.Merge(array2).Slice(), []interface{}{0,1,2,3,4,5,6,7})
})
}
func TestArray_Fill(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0}
a2 := []interface{}{0}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2)
gtest.Assert(array1.Fill(1, 2, 100).Slice(), []interface{}{0,100,100})
gtest.Assert(array2.Fill(0, 2, 100).Slice(), []interface{}{100,100})
})
}
func TestArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{1,2,3,4,5}
array1 := garray.NewArrayFrom(a1)
chunks := array1.Chunk(2)
gtest.Assert(len(chunks), 3)
gtest.Assert(chunks[0], []interface{}{1,2})
gtest.Assert(chunks[1], []interface{}{3,4})
gtest.Assert(chunks[2], []interface{}{5})
})
}
func TestArray_Pad(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Pad(3, 1).Slice(), []interface{}{0,1,1})
gtest.Assert(array1.Pad(-4, 1).Slice(), []interface{}{1,0,1,1})
gtest.Assert(array1.Pad(3, 1).Slice(), []interface{}{1,0,1,1})
})
}
func TestArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.SubSlice(0, 2), []interface{}{0,1})
gtest.Assert(array1.SubSlice(2, 2), []interface{}{2,3})
gtest.Assert(array1.SubSlice(5, 8), []interface{}{5,6})
})
}
func TestArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
})
}
func TestArray_Shuffle(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Shuffle().Len(), 7)
})
}
func TestArray_Reverse(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Reverse().Slice(), []interface{}{6,5,4,3,2,1,0})
})
}
func TestArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}

View File

@ -0,0 +1,201 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go
package garray_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
)
func Test_StringArray_Basic(t *testing.T) {
gtest.Case(t, func() {
expect := []string{"0", "1", "2", "3"}
array := garray.NewStringArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
array.Set(0, "100")
gtest.Assert(array.Get(0), 100)
gtest.Assert(array.Get(1), 1)
gtest.Assert(array.Search("100"), 0)
gtest.Assert(array.Contains("100"), true)
gtest.Assert(array.Remove(0), 100)
gtest.Assert(array.Contains("100"), false)
array.Append("4")
gtest.Assert(array.Len(), 4)
array.InsertBefore(0, "100")
array.InsertAfter(0, "200")
gtest.Assert(array.Slice(), []string{"100", "200", "1", "2", "3", "4"})
array.InsertBefore(5, "300")
array.InsertAfter(6, "400")
gtest.Assert(array.Slice(), []string{"100", "200", "1", "2", "3", "300", "4", "400"})
gtest.Assert(array.Clear().Len(), 0)
})
}
func TestStringArray_Sort(t *testing.T) {
gtest.Case(t, func() {
expect1 := []string{"0", "1", "2", "3"}
expect2 := []string{"3", "2", "1", "0"}
array := garray.NewStringArray()
for i := 3; i >= 0; i-- {
array.Append(gconv.String(i))
}
array.Sort()
gtest.Assert(array.Slice(), expect1)
array.Sort(true)
gtest.Assert(array.Slice(), expect2)
})
}
func TestStringArray_Unique(t *testing.T) {
gtest.Case(t, func() {
expect := []string{"1", "1", "2", "3"}
array := garray.NewStringArrayFrom(expect)
gtest.Assert(array.Unique().Slice(), []string{"1", "2", "3"})
})
}
func TestStringArray_PushAndPop(t *testing.T) {
gtest.Case(t, func() {
expect := []string{"0", "1", "2", "3"}
array := garray.NewStringArrayFrom(expect)
gtest.Assert(array.Slice(), expect)
gtest.Assert(array.PopLeft(), "0")
gtest.Assert(array.PopRight(), "3")
gtest.AssertIN(array.PopRand(), []string{"1", "2"})
gtest.AssertIN(array.PopRand(), []string{"1", "2"})
gtest.Assert(array.Len(), 0)
array.PushLeft("1").PushRight("2")
gtest.Assert(array.Slice(), []string{"1", "2"})
})
}
func TestStringArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []string{"0","1","2","3","4","5","6"}
value2 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(value1)
array2 := garray.NewStringArrayFrom(value2)
gtest.Assert(array1.PopLefts(2), []interface{}{"0","1"})
gtest.Assert(array1.Slice(), []interface{}{"2","3","4","5","6"})
gtest.Assert(array1.PopRights(2), []interface{}{"5","6"})
gtest.Assert(array1.Slice(), []interface{}{"2","3","4"})
gtest.Assert(array1.PopRights(20), []interface{}{"2","3","4"})
gtest.Assert(array1.Slice(), []interface{}{})
gtest.Assert(array2.PopLefts(20), []interface{}{"0","1","2","3","4","5","6"})
gtest.Assert(array2.Slice(), []interface{}{})
})
}
func TestString_Range(t *testing.T) {
gtest.Case(t, func() {
value1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(value1)
gtest.Assert(array1.Range(0, 1), []interface{}{"0"})
gtest.Assert(array1.Range(1, 2), []interface{}{"1"})
gtest.Assert(array1.Range(0, 2), []interface{}{"0", "1"})
gtest.Assert(array1.Range(-1, 10), value1)
})
}
func TestStringArray_Merge(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0", "1", "2", "3"}
a2 := []string{"4", "5", "6", "7"}
array1 := garray.NewStringArrayFrom(a1)
array2 := garray.NewStringArrayFrom(a2)
gtest.Assert(array1.Merge(array2).Slice(), []string{"0","1","2","3","4","5","6","7"})
})
}
func TestStringArray_Fill(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0"}
a2 := []string{"0"}
array1 := garray.NewStringArrayFrom(a1)
array2 := garray.NewStringArrayFrom(a2)
gtest.Assert(array1.Fill(1, 2, "100").Slice(), []string{"0","100","100"})
gtest.Assert(array2.Fill(0, 2, "100").Slice(), []string{"100","100"})
})
}
func TestStringArray_Chunk(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"1","2","3","4","5"}
array1 := garray.NewStringArrayFrom(a1)
chunks := array1.Chunk(2)
gtest.Assert(len(chunks), 3)
gtest.Assert(chunks[0], []string{"1","2"})
gtest.Assert(chunks[1], []string{"3","4"})
gtest.Assert(chunks[2], []string{"5"})
})
}
func TestStringArray_Pad(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Pad(3, "1").Slice(), []string{"0","1","1"})
gtest.Assert(array1.Pad(-4, "1").Slice(), []string{"1","0","1","1"})
gtest.Assert(array1.Pad(3, "1").Slice(), []string{"1","0","1","1"})
})
}
func TestStringArray_SubSlice(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.SubSlice(0, 2), []string{"0","1"})
gtest.Assert(array1.SubSlice(2, 2), []string{"2","3"})
gtest.Assert(array1.SubSlice(5, 8), []string{"5","6"})
})
}
func TestStringArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(len(array1.Rands(2)), "2")
gtest.Assert(len(array1.Rands(10)), "7")
gtest.AssertIN(array1.Rands(1)[0], a1)
})
}
func TestStringArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"100", "200", "300", "400", "500", "600"}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}
func TestStringArray_Shuffle(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Shuffle().Len(), 7)
})
}
func TestStringArray_Reverse(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Reverse().Slice(), []string{"6","5","4","3","2","1","0"})
})
}
func TestStringArray_Join(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(array1.Join("."), "0.1.2.3.4.5.6")
})
}

View File

@ -1,52 +1,62 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// 优雅的Channel操作.
// Package gchan provides graceful channel for safe operations.
//
// It's safe to call Chan.Push/Close functions repeatedly.
package gchan
import (
"errors"
"gitee.com/johng/gf/g/container/gtype"
"github.com/gogf/gf/g/container/gtype"
)
type Chan struct {
list chan interface{}
closed *gtype.Bool
channel chan interface{}
closed *gtype.Bool
}
// New creates a graceful channel with given limit.
func New(limit int) *Chan {
return &Chan {
list : make(chan interface{}, limit),
closed : gtype.NewBool(),
channel : make(chan interface{}, limit),
closed : gtype.NewBool(),
}
}
// 将数据压入队列
func (q *Chan) Push(v interface{}) error {
if q.closed.Val() {
// Push pushes <value> to channel.
// It is safe to be called repeatedly.
func (c *Chan) Push(value interface{}) error {
if c.closed.Val() {
return errors.New("closed")
}
q.list <- v
c.channel <- value
return nil
}
// 先进先出地从队列取出一项数据,当没有数据可获取时,阻塞等待
func (q *Chan) Pop() interface{} {
return <- q.list
// Pop pops value from channel.
// If there's no value in channel, it would block to wait.
func (c *Chan) Pop() interface{} {
return <- c.channel
}
// 关闭队列(通知所有通过Pop阻塞的协程退出)
func (q *Chan) Close() {
if !q.closed.Val() {
q.closed.Set(true)
close(q.list)
// Close closes the channel.
// It is safe to be called repeatedly.
func (c *Chan) Close() {
if !c.closed.Set(true) {
close(c.channel)
}
}
// 获取当前队列大小
func (q *Chan) Size() int {
return len(q.list)
// See Len.
func (c *Chan) Size() int {
return c.Len()
}
// Len returns the length of the channel.
func (c *Chan) Len() int {
return len(c.channel)
}

View File

@ -1,8 +1,8 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
@ -10,7 +10,7 @@ package gchan_test
import (
"testing"
"gitee.com/johng/gf/g/container/gchan"
"github.com/gogf/gf/g/container/gchan"
)
var length = 10000000

View File

@ -1,263 +1,371 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// If a copy of the MIT was not distributed with l file,
// You can obtain one at https://github.com/gogf/gf.
//
// 并发安全的双向链表.
// Package glist provides a concurrent-safe/unsafe doubly linked list.
package glist
import (
"container/list"
"gitee.com/johng/gf/g/container/internal/rwmutex"
"container/list"
"github.com/gogf/gf/g/internal/rwmutex"
)
// 变长双向链表
type List struct {
mu *rwmutex.RWMutex
list *list.List
}
type (
List struct {
mu *rwmutex.RWMutex
list *list.List
}
// 获得一个变长链表指针
func New(safe...bool) *List {
return &List{
mu : rwmutex.New(safe...),
list : list.New(),
Element = list.Element
)
// New creates and returns a new empty doubly linked list.
func New(unsafe...bool) *List {
return &List {
mu : rwmutex.New(unsafe...),
list : list.New(),
}
}
// 往链表头入栈数据项
func (this *List) PushFront(v interface{}) *list.Element {
this.mu.Lock()
e := this.list.PushFront(v)
this.mu.Unlock()
return e
// PushFront inserts a new element <e> with value <v> at the front of list <l> and returns <e>.
func (l *List) PushFront(v interface{}) (e *Element) {
l.mu.Lock()
e = l.list.PushFront(v)
l.mu.Unlock()
return
}
// 往链表尾入栈数据项
func (this *List) PushBack(v interface{}) *list.Element {
this.mu.Lock()
r := this.list.PushBack(v)
this.mu.Unlock()
return r
// PushBack inserts a new element <e> with value <v> at the back of list <l> and returns <e>.
func (l *List) PushBack(v interface{}) (e *Element) {
l.mu.Lock()
e = l.list.PushBack(v)
l.mu.Unlock()
return
}
// 在list 中元素mark之后插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
func (this *List) InsertAfter(v interface{}, mark *list.Element) *list.Element {
this.mu.Lock()
r := this.list.InsertAfter(v, mark)
this.mu.Unlock()
return r
// PushFronts inserts multiple new elements with values <values> at the front of list <l>.
func (l *List) PushFronts(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushFront(v)
}
l.mu.Unlock()
}
// 在list 中元素mark之前插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
func (this *List) InsertBefore(v interface{}, mark *list.Element) *list.Element {
this.mu.Lock()
r := this.list.InsertBefore(v, mark)
this.mu.Unlock()
return r
}
// 批量往链表头入栈数据项
func (this *List) BatchPushFront(vs []interface{}) {
this.mu.Lock()
for _, item := range vs {
this.list.PushFront(item)
}
this.mu.Unlock()
}
// 从链表尾端出栈数据项(删除)
func (this *List) PopBack() interface{} {
this.mu.Lock()
if elem := this.list.Back(); elem != nil {
item := this.list.Remove(elem)
this.mu.Unlock()
return item
}
this.mu.Unlock()
return nil
}
// 从链表头端出栈数据项(删除)
func (this *List) PopFront() interface{} {
this.mu.Lock()
if elem := this.list.Front(); elem != nil {
item := this.list.Remove(elem)
this.mu.Unlock()
return item
}
this.mu.Unlock()
return nil
}
// 批量从链表尾端出栈数据项(删除)
func (this *List) BatchPopBack(max int) []interface{} {
this.mu.Lock()
count := this.list.Len()
if count == 0 {
this.mu.Unlock()
return []interface{}{}
}
if count > max {
count = max
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Back())
}
this.mu.Unlock()
return items
}
// 批量从链表头端出栈数据项(删除)
func (this *List) BatchPopFront(max int) []interface{} {
this.mu.Lock()
count := this.list.Len()
if count == 0 {
this.mu.Unlock()
return []interface{}{}
}
if count > max {
count = max
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Front())
}
this.mu.Unlock()
return items
}
// 批量从链表尾端依次获取所有数据(删除)
func (this *List) PopBackAll() []interface{} {
this.mu.Lock()
count := this.list.Len()
if count == 0 {
this.mu.Unlock()
return []interface{}{}
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Back())
}
this.mu.Unlock()
return items
}
// 批量从链表头端依次获取所有数据(删除)
func (this *List) PopFrontAll() []interface{} {
this.mu.Lock()
count := this.list.Len()
if count == 0 {
this.mu.Unlock()
return []interface{}{}
// PushBacks inserts multiple new elements with values <values> at the back of list <l>.
func (l *List) PushBacks(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushBack(v)
}
items := make([]interface{}, count)
for i := 0; i < count; i++ {
items[i] = this.list.Remove(this.list.Front())
l.mu.Unlock()
}
// PopBack removes the element from back of <l> and returns the value of the element.
func (l *List) PopBack() (value interface{}) {
l.mu.Lock()
if e := l.list.Back(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
}
// PopFront removes the element from front of <l> and returns the value of the element.
func (l *List) PopFront() (value interface{}) {
l.mu.Lock()
if e := l.list.Front(); e != nil {
value = l.list.Remove(e)
}
this.mu.Unlock()
return items
l.mu.Unlock()
return
}
// 删除数据项
func (this *List) Remove(e *list.Element) interface{} {
this.mu.Lock()
r := this.list.Remove(e)
this.mu.Unlock()
return r
}
// 删除所有数据项
func (this *List) RemoveAll() {
this.mu.Lock()
this.list = list.New()
this.mu.Unlock()
}
// 从链表头获取所有数据(不删除)
func (this *List) FrontAll() []interface{} {
this.mu.RLock()
count := this.list.Len()
if count == 0 {
this.mu.RUnlock()
return []interface{}{}
}
items := make([]interface{}, 0, count)
for e := this.list.Front(); e != nil; e = e.Next() {
items = append(items, e.Value)
}
this.mu.RUnlock()
return items
}
// 从链表尾获取所有数据(不删除)
func (this *List) BackAll() []interface{} {
this.mu.RLock()
count := this.list.Len()
if count == 0 {
this.mu.RUnlock()
return []interface{}{}
}
items := make([]interface{}, 0, count)
for e := this.list.Back(); e != nil; e = e.Prev() {
items = append(items, e.Value)
}
this.mu.RUnlock()
return items
}
// 获取链表头值(不删除)
func (this *List) FrontItem() interface{} {
this.mu.RLock()
if f := this.list.Front(); f != nil {
this.mu.RUnlock()
return f.Value
}
this.mu.RUnlock()
return nil
}
// 获取链表尾值(不删除)
func (this *List) BackItem() interface{} {
this.mu.RLock()
if f := this.list.Back(); f != nil {
this.mu.RUnlock()
return f.Value
// PopBacks removes <max> elements from back of <l>
// and returns values of the removed elements as slice.
func (l *List) PopBacks(max int) (values []interface{}) {
l.mu.Lock()
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
length = max
}
tempe := (*Element)(nil)
values = make([]interface{}, length)
for i := 0; i < length; i++ {
tempe = l.list.Back()
values[i] = l.list.Remove(tempe)
}
}
this.mu.RUnlock()
return nil
l.mu.Unlock()
return
}
// 获取表头指针
func (this *List) Front() *list.Element {
this.mu.RLock()
r := this.list.Front()
this.mu.RUnlock()
return r
// PopFronts removes <max> elements from front of <l>
// and returns values of the removed elements as slice.
func (l *List) PopFronts(max int) (values []interface{}) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
length = max
}
tempe := (*Element)(nil)
values = make([]interface{}, length)
for i := 0; i < length; i++ {
tempe = l.list.Front()
values[i] = l.list.Remove(tempe)
}
}
l.mu.RUnlock()
return
}
// 获取表位指针
func (this *List) Back() *list.Element {
this.mu.RLock()
r := this.list.Back()
this.mu.RUnlock()
return r
// PopBackAll removes all elements from back of <l>
// and returns values of the removed elements as slice.
func (l *List) PopBackAll() []interface{} {
return l.PopBacks(-1)
}
// 获取链表长度
func (this *List) Len() int {
this.mu.RLock()
length := this.list.Len()
this.mu.RUnlock()
return length
// PopFrontAll removes all elements from front of <l>
// and returns values of the removed elements as slice.
func (l *List) PopFrontAll() []interface{} {
return l.PopFronts(-1)
}
// FrontAll copies and returns values of all elements from front of <l> as slice.
func (l *List) FrontAll() (values []interface{}) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
for i, e := 0, l.list.Front(); i < length; i, e = i + 1, e.Next() {
values[i] = e.Value
}
}
l.mu.RUnlock()
return
}
// BackAll copies and returns values of all elements from back of <l> as slice.
func (l *List) BackAll() (values []interface{}) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
for i, e := 0, l.list.Back(); i < length; i, e = i + 1, e.Prev() {
values[i] = e.Value
}
}
l.mu.RUnlock()
return
}
// FrontValue returns value of the first element of <l> or nil if the list is empty.
func (l *List) FrontValue() (value interface{}) {
l.mu.RLock()
if e := l.list.Front(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
}
// BackValue returns value of the last element of <l> or nil if the list is empty.
func (l *List) BackValue() (value interface{}) {
l.mu.RLock()
if e := l.list.Back(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
}
// Front returns the first element of list <l> or nil if the list is empty.
func (l *List) Front() (e *Element) {
l.mu.RLock()
e = l.list.Front()
l.mu.RUnlock()
return
}
// Back returns the last element of list <l> or nil if the list is empty.
func (l *List) Back() (e *Element) {
l.mu.RLock()
e = l.list.Back()
l.mu.RUnlock()
return
}
// Len returns the number of elements of list <l>.
// The complexity is O(1).
func (l *List) Len() (length int) {
l.mu.RLock()
length = l.list.Len()
l.mu.RUnlock()
return
}
// MoveBefore moves element <e> to its new position before <p>.
// If <e> or <p> is not an element of <l>, or <e> == <p>, the list is not modified.
// The element and <p> must not be nil.
func (l *List) MoveBefore(e, p *Element) {
l.mu.Lock()
l.list.MoveBefore(e, p)
l.mu.Unlock()
}
// MoveAfter moves element <e> to its new position after <p>.
// If <e> or <p> is not an element of <l>, or <e> == <p>, the list is not modified.
// The element and <p> must not be nil.
func (l *List) MoveAfter(e, p *Element) {
l.mu.Lock()
l.list.MoveAfter(e, p)
l.mu.Unlock()
}
// MoveToFront moves element <e> to the front of list <l>.
// If <e> is not an element of <l>, the list is not modified.
// The element must not be nil.
func (l *List) MoveToFront(e *Element) {
l.mu.Lock()
l.list.MoveToFront(e)
l.mu.Unlock()
}
// MoveToBack moves element <e> to the back of list <l>.
// If <e> is not an element of <l>, the list is not modified.
// The element must not be nil.
func (l *List) MoveToBack(e *Element) {
l.mu.Lock()
l.list.MoveToBack(e)
l.mu.Unlock()
}
// PushBackList inserts a copy of an other list at the back of list <l>.
// The lists <l> and <other> may be the same, but they must not be nil.
func (l *List) PushBackList(other *List) {
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushBackList(other.list)
l.mu.Unlock()
}
// PushFrontList inserts a copy of an other list at the front of list <l>.
// The lists <l> and <other> may be the same, but they must not be nil.
func (l *List) PushFrontList(other *List) {
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushFrontList(other.list)
l.mu.Unlock()
}
// InsertAfter inserts a new element <e> with value <v> immediately after <p> and returns <e>.
// If <p> is not an element of <l>, the list is not modified.
// The <p> must not be nil.
func (l *List) InsertAfter(v interface{}, p *Element) (e *Element) {
l.mu.Lock()
e = l.list.InsertAfter(v, p)
l.mu.Unlock()
return
}
// InsertBefore inserts a new element <e> with value <v> immediately before <p> and returns <e>.
// If <p> is not an element of <l>, the list is not modified.
// The <p> must not be nil.
func (l *List) InsertBefore(v interface{}, p *Element) (e *Element) {
l.mu.Lock()
e = l.list.InsertBefore(v, p)
l.mu.Unlock()
return
}
// Remove removes <e> from <l> if <e> is an element of list <l>.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List) Remove(e *Element) (value interface{}) {
l.mu.Lock()
value = l.list.Remove(e)
l.mu.Unlock()
return
}
// Removes removes multiple elements <es> from <l> if <es> are elements of list <l>.
func (l *List) Removes(es []*Element) {
l.mu.Lock()
for _, e := range es {
l.list.Remove(e)
}
l.mu.Unlock()
return
}
// RemoveAll removes all elements from list <l>.
func (l *List) RemoveAll() {
l.mu.Lock()
l.list = list.New()
l.mu.Unlock()
}
// See RemoveAll().
func (l *List) Clear() {
l.RemoveAll()
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (l *List) RLockFunc(f func(list *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(l.list)
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (l *List) LockFunc(f func(list *list.List)) {
l.mu.Lock()
defer l.mu.Unlock()
f(l.list)
}
// Iterator is alias of IteratorAsc.
func (l *List) Iterator(f func (e *Element) bool) {
l.IteratorAsc(f)
}
// IteratorAsc iterates the list in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (l *List) IteratorAsc(f func (e *Element) bool) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Front(); i < length; i, e = i + 1, e.Next() {
if !f(e) {
break
}
}
}
l.mu.RUnlock()
}
// IteratorDesc iterates the list in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (l *List) IteratorDesc(f func (e *Element) bool) {
l.mu.RLock()
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Back(); i < length; i, e = i + 1, e.Prev() {
if !f(e) {
break
}
}
}
l.mu.RUnlock()
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
@ -12,35 +12,45 @@ import (
"testing"
)
var l = New()
var (
l = New()
bn = 20000000
)
func BenchmarkPushBack(b *testing.B) {
func Benchmark_PushBack(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PushBack(i)
}
}
func BenchmarkPopFront(b *testing.B) {
for i := 0; i < b.N; i++ {
l.PopFront()
}
}
func BenchmarkPushFront(b *testing.B) {
func Benchmark_PushFront(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PushFront(i)
}
}
func BenchmarkPopBack(b *testing.B) {
for i := 0; i < b.N; i++ {
l.PopBack()
}
}
func BenchmarkLen(b *testing.B) {
func Benchmark_Len(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.Len()
}
}
func Benchmark_PopFront(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PopFront()
}
}
func Benchmark_PopBack(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PopBack()
}
}

View File

@ -0,0 +1,367 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package glist
import (
"container/list"
"testing"
)
// 检查链表长度
func checkListLen(t *testing.T, l *List, len int) bool {
if n := l.Len(); n != len {
t.Errorf("l.Len() = %d, want %d", n, len)
return false
}
return true
}
// 检查指针地址
func checkListPointers(t *testing.T, l *List, es []*Element) {
if !checkListLen(t, l, len(es)) {
return
}
l.RLockFunc(func(list *list.List) {
for i, e := 0, l.list.Front(); i < list.Len(); i, e = i + 1, e.Next() {
if e.Prev() != es[i].Prev() {
t.Errorf("list[%d].Prev = %p, want %p", i, e.Prev(), es[i].Prev())
}
if e.Next() != es[i].Next() {
t.Errorf("list[%d].Next = %p, want %p", i, e.Next(), es[i].Next())
}
}
})
}
func TestBasic(t *testing.T) {
l := New()
l.PushFront(1)
l.PushFront(2)
if v := l.PopBack(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
//fmt.Println(v)
}
if v := l.PopBack(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
//fmt.Println(v)
}
if v := l.PopBack(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
//fmt.Println(v)
}
l.PushBack(1)
l.PushBack(2)
if v := l.PopFront(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
//fmt.Println(v)
}
if v := l.PopFront(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
//fmt.Println(v)
}
if v := l.PopFront(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
//fmt.Println(v)
}
}
func TestList(t *testing.T) {
l := New()
checkListPointers(t, l, []*Element{})
// Single element list
e := l.PushFront("a")
checkListPointers(t, l, []*Element{e})
l.MoveToFront(e)
checkListPointers(t, l, []*Element{e})
l.MoveToBack(e)
checkListPointers(t, l, []*Element{e})
l.Remove(e)
checkListPointers(t, l, []*Element{})
// Bigger list
e2 := l.PushFront(2)
e1 := l.PushFront(1)
e3 := l.PushBack(3)
e4 := l.PushBack("banana")
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.Remove(e2)
checkListPointers(t, l, []*Element{e1, e3, e4})
l.MoveToFront(e3) // move from middle
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToFront(e1)
l.MoveToBack(e3) // move from middle
checkListPointers(t, l, []*Element{e1, e4, e3})
l.MoveToFront(e3) // move from back
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToFront(e3) // should be no-op
checkListPointers(t, l, []*Element{e3, e1, e4})
l.MoveToBack(e3) // move from front
checkListPointers(t, l, []*Element{e1, e4, e3})
l.MoveToBack(e3) // should be no-op
checkListPointers(t, l, []*Element{e1, e4, e3})
e2 = l.InsertBefore(2, e1) // insert before front
checkListPointers(t, l, []*Element{e2, e1, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e4) // insert before middle
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(2, e3) // insert before back
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e1) // insert after front
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e4) // insert after middle
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(2, e3) // insert after back
checkListPointers(t, l, []*Element{e1, e4, e3, e2})
l.Remove(e2)
// Check standard iteration.
sum := 0
for e := l.Front(); e != nil; e = e.Next() {
if i, ok := e.Value.(int); ok {
sum += i
}
}
if sum != 4 {
t.Errorf("sum over l = %d, want 4", sum)
}
// Clear all elements by iterating
var next *Element
for e := l.Front(); e != nil; e = next {
next = e.Next()
l.Remove(e)
}
checkListPointers(t, l, []*Element{})
}
func checkList(t *testing.T, l *List, es []interface{}) {
if !checkListLen(t, l, len(es)) {
return
}
i := 0
for e := l.Front(); e != nil; e = e.Next() {
le := e.Value.(int)
if le != es[i] {
t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
}
i++
}
}
func TestExtending(t *testing.T) {
l1 := New()
l2 := New()
l1.PushBack(1)
l1.PushBack(2)
l1.PushBack(3)
l2.PushBack(4)
l2.PushBack(5)
l3 := New()
l3.PushBackList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushBackList(l2)
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
l3 = New()
l3.PushFrontList(l2)
checkList(t, l3, []interface{}{4, 5})
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
checkList(t, l1, []interface{}{1, 2, 3})
checkList(t, l2, []interface{}{4, 5})
l3 = New()
l3.PushBackList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushBackList(l3)
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
l3 = New()
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1, 2, 3})
l3.PushFrontList(l3)
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
l3 = New()
l1.PushBackList(l3)
checkList(t, l1, []interface{}{1, 2, 3})
l1.PushFrontList(l3)
checkList(t, l1, []interface{}{1, 2, 3})
}
func TestRemove(t *testing.T) {
l := New()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
checkListPointers(t, l, []*Element{e1, e2})
//e := l.Front()
//l.Remove(e)
//checkListPointers(t, l, []*Element{e2})
//l.Remove(e)
//checkListPointers(t, l, []*Element{e2})
}
func TestIssue4103(t *testing.T) {
l1 := New()
l1.PushBack(1)
l1.PushBack(2)
l2 := New()
l2.PushBack(3)
l2.PushBack(4)
e := l1.Front()
l2.Remove(e) // l2 should not change because e is not an element of l2
if n := l2.Len(); n != 2 {
t.Errorf("l2.Len() = %d, want 2", n)
}
l1.InsertBefore(8, e)
if n := l1.Len(); n != 3 {
t.Errorf("l1.Len() = %d, want 3", n)
}
}
func TestIssue6349(t *testing.T) {
l := New()
l.PushBack(1)
l.PushBack(2)
e := l.Front()
l.Remove(e)
if e.Value != 1 {
t.Errorf("e.value = %d, want 1", e.Value)
}
//if e.Next() != nil {
// t.Errorf("e.Next() != nil")
//}
//if e.Prev() != nil {
// t.Errorf("e.Prev() != nil")
//}
}
func TestMove(t *testing.T) {
l := New()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
e3 := l.PushBack(3)
e4 := l.PushBack(4)
l.MoveAfter(e3, e3)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e2)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveAfter(e3, e2)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e3)
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
l.MoveBefore(e2, e4)
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
e2, e3 = e3, e2
l.MoveBefore(e4, e1)
checkListPointers(t, l, []*Element{e4, e1, e2, e3})
e1, e2, e3, e4 = e4, e1, e2, e3
l.MoveAfter(e4, e1)
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
e2, e3, e4 = e4, e2, e3
l.MoveAfter(e2, e3)
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
e2, e3 = e3, e2
}
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
func TestZeroList(t *testing.T) {
var l1 = New()
l1.PushFront(1)
checkList(t, l1, []interface{}{1})
var l2 = New()
l2.PushBack(1)
checkList(t, l2, []interface{}{1})
var l3 = New()
l3.PushFrontList(l1)
checkList(t, l3, []interface{}{1})
var l4 = New()
l4.PushBackList(l2)
checkList(t, l4, []interface{}{1})
}
// Test that a list l is not modified when calling InsertBefore with a mark that is not an element of l.
func TestInsertBeforeUnknownMark(t *testing.T) {
l := New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertBefore(1, new(Element))
checkList(t, l, []interface{}{1, 2, 3})
}
// Test that a list l is not modified when calling InsertAfter with a mark that is not an element of l.
func TestInsertAfterUnknownMark(t *testing.T) {
l := New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertAfter(1, new(Element))
checkList(t, l, []interface{}{1, 2, 3})
}
// Test that a list l is not modified when calling MoveAfter or MoveBefore with a mark that is not an element of l.
func TestMoveUnknownMark(t *testing.T) {
l1 := New()
e1 := l1.PushBack(1)
l2 := New()
e2 := l2.PushBack(2)
l1.MoveAfter(e1, e2)
checkList(t, l1, []interface{}{1})
checkList(t, l2, []interface{}{2})
l1.MoveBefore(e1, e2)
checkList(t, l1, []interface{}{1})
checkList(t, l2, []interface{}{2})
}
func TestList_RemoveAll(t *testing.T) {
l := New()
l.PushBack(1)
l.RemoveAll()
checkList(t, l, []interface{}{})
l.PushBack(2)
checkList(t, l, []interface{}{2})
}

View File

@ -1,19 +1,44 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
// 并发安全的哈希MAP.
// Package gmap provides concurrent-safe/unsafe map containers.
package gmap
// 默认的Map对象其实就是InterfaceInterfaceMap的别名。
// 注意
// 1、这个Map是所有并发安全Map中效率最低的如果对效率要求比较高的场合请合理选择对应数据类型的Map
// 2、这个Map的优点是使用简便由于键值都是interface{}类型,因此对键值的数据类型要求不高;
// 3、底层实现比较类似于sync.Map
type Map = InterfaceInterfaceMap
// Map based on hash table, alias of AnyAnyMap.
type Map = AnyAnyMap
type HashMap = AnyAnyMap
func New(safe...bool) *Map {
return NewInterfaceInterfaceMap(safe...)
// New returns an empty hash map.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func New(unsafe ...bool) *Map {
return NewAnyAnyMap(unsafe...)
}
// NewFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewFrom(data map[interface{}]interface{}, unsafe...bool) *Map {
return NewAnyAnyMapFrom(data, unsafe...)
}
// NewHashMap returns an empty hash map.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewHashMap(unsafe ...bool) *Map {
return NewAnyAnyMap(unsafe...)
}
// NewHashMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewHashMapFrom(data map[interface{}]interface{}, unsafe...bool) *Map {
return NewAnyAnyMapFrom(data, unsafe...)
}

View File

@ -1,139 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// go test *.go -bench=".*" -benchmem
package gmap
import (
"testing"
"strconv"
)
var ibm = NewIntBoolMap()
var iim = NewIntIntMap()
var iifm = NewIntInterfaceMap()
var ism = NewIntStringMap()
var ififm = NewInterfaceInterfaceMap()
var sbm = NewStringBoolMap()
var sim = NewStringIntMap()
var sifm = NewStringInterfaceMap()
var ssm = NewStringStringMap()
// 写入性能测试
func Benchmark_IntBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ibm.Set(i, true)
}
}
func Benchmark_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Set(i, i)
}
}
func Benchmark_IntInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Set(i, i)
}
}
func Benchmark_IntStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Set(i, strconv.Itoa(i))
}
}
func Benchmark_InterfaceInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Set(i, i)
}
}
func Benchmark_StringBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sbm.Set(strconv.Itoa(i), true)
}
}
func Benchmark_StringIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StringInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StringStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
// 读取性能测试
func Benchmark_IntBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ibm.Get(i)
}
}
func Benchmark_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Get(i)
}
}
func Benchmark_IntInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Get(i)
}
}
func Benchmark_IntStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Get(i)
}
}
func Benchmark_InterfaceInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Get(i)
}
}
func Benchmark_StringBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sbm.Get(strconv.Itoa(i))
}
}
func Benchmark_StringIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Get(strconv.Itoa(i))
}
}
func Benchmark_StringInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Get(strconv.Itoa(i))
}
}
func Benchmark_StringStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Get(strconv.Itoa(i))
}
}

View File

@ -1,139 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// go test *.go -bench=".*" -benchmem
package gmap
import (
"testing"
"strconv"
)
var ibmUnsafe = NewIntBoolMap(false)
var iimUnsafe = NewIntIntMap(false)
var iifmUnsafe = NewIntInterfaceMap(false)
var ismUnsafe = NewIntStringMap(false)
var ififmUnsafe = NewInterfaceInterfaceMap(false)
var sbmUnsafe = NewStringBoolMap(false)
var simUnsafe = NewStringIntMap(false)
var sifmUnsafe = NewStringInterfaceMap(false)
var ssmUnsafe = NewStringStringMap(false)
// 写入性能测试
func Benchmark_Unsafe_IntBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ibmUnsafe.Set(i, true)
}
}
func Benchmark_Unsafe_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Set(i, strconv.Itoa(i))
}
}
func Benchmark_Unsafe_InterfaceInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_StringBoolMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sbmUnsafe.Set(strconv.Itoa(i), true)
}
}
func Benchmark_Unsafe_StringIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StringInterfaceMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StringStringMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
// 读取性能测试
func Benchmark_Unsafe_IntBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ibmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Get(i)
}
}
func Benchmark_Unsafe_InterfaceInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_StringBoolMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sbmUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringInterfaceMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringStringMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Get(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,330 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
type AnyAnyMap struct {
mu *rwmutex.RWMutex
data map[interface{}]interface{}
}
// NewAnyAnyMap returns an empty hash map.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewAnyAnyMap(unsafe ...bool) *AnyAnyMap {
return &AnyAnyMap{
mu : rwmutex.New(unsafe...),
data : make(map[interface{}]interface{}),
}
}
// NewAnyAnyMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewAnyAnyMapFrom(data map[interface{}]interface{}, unsafe...bool) *AnyAnyMap {
return &AnyAnyMap{
mu : rwmutex.New(unsafe...),
data : data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *AnyAnyMap) Iterator(f func (k interface{}, v interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *AnyAnyMap) Clone(unsafe ...bool) *AnyAnyMap {
return NewFrom(m.Map(), unsafe ...)
}
// Map returns a copy of the data of the hash map.
func (m *AnyAnyMap) Map() map[interface{}]interface{} {
m.mu.RLock()
data := make(map[interface{}]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *AnyAnyMap) Set(key interface{}, val interface{}) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *AnyAnyMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *AnyAnyMap) Search(key interface{}) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *AnyAnyMap) Get(key interface{}) interface{} {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *AnyAnyMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok := m.data[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
m.data[key] = value
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *AnyAnyMap) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *AnyAnyMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *AnyAnyMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *AnyAnyMap) SetIfNotExist(key interface{}, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *AnyAnyMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *AnyAnyMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *AnyAnyMap) Remove(key interface{}) interface{} {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// Removes batch deletes values of the map by keys.
func (m *AnyAnyMap) Removes(keys []interface{}) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
}
m.mu.Unlock()
}
// Keys returns all keys of the map as a slice.
func (m *AnyAnyMap) Keys() []interface{} {
m.mu.RLock()
keys := make([]interface{}, len(m.data))
index := 0
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *AnyAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *AnyAnyMap) Contains(key interface{}) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *AnyAnyMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *AnyAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *AnyAnyMap) Clear() {
m.mu.Lock()
m.data = make(map[interface{}]interface{})
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *AnyAnyMap) LockFunc(f func(m map[interface{}]interface{})) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *AnyAnyMap) RLockFunc(f func(m map[interface{}]interface{})) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *AnyAnyMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[interface{}]interface{}, len(m.data))
for k, v := range m.data {
n[v] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,334 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type IntAnyMap struct {
mu *rwmutex.RWMutex
data map[int]interface{}
}
// NewIntAnyMap returns an empty IntAnyMap object.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewIntAnyMap(unsafe...bool) *IntAnyMap {
return &IntAnyMap{
mu : rwmutex.New(unsafe...),
data : make(map[int]interface{}),
}
}
// NewIntAnyMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewIntAnyMapFrom(data map[int]interface{}, unsafe...bool) *IntAnyMap {
return &IntAnyMap{
mu : rwmutex.New(unsafe...),
data : data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntAnyMap) Iterator(f func (k int, v interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *IntAnyMap) Clone() *IntAnyMap {
return NewIntAnyMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *IntAnyMap) Map() map[int]interface{} {
m.mu.RLock()
data := make(map[int]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *IntAnyMap) Set(key int, val interface{}) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *IntAnyMap) Sets(data map[int]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntAnyMap) Search(key int) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntAnyMap) Get(key int) (interface{}) {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *IntAnyMap) doSetWithLockCheck(key int, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok := m.data[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
if value != nil {
m.data[key] = value
}
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *IntAnyMap) GetOrSet(key int, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
func (m *IntAnyMap) GetOrSetFunc(key int, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVar(key int) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSet(key int, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntAnyMap) SetIfNotExist(key int, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *IntAnyMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntAnyMap) Remove(key int) interface{} {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// Keys returns all keys of the map as a slice.
func (m *IntAnyMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *IntAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntAnyMap) Contains(key int) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *IntAnyMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntAnyMap) Clear() {
m.mu.Lock()
m.data = make(map[int]interface{})
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *IntAnyMap) LockFunc(f func(m map[int]interface{})) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *IntAnyMap) RLockFunc(f func(m map[int]interface{})) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *IntAnyMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[int]interface{}, len(m.data))
for k, v := range m.data {
n[gconv.Int(v)] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *IntAnyMap) Merge(other *IntAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,308 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
)
type IntIntMap struct {
mu *rwmutex.RWMutex
data map[int]int
}
// NewIntIntMap returns an empty IntIntMap object.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewIntIntMap(unsafe...bool) *IntIntMap {
return &IntIntMap{
mu : rwmutex.New(unsafe...),
data : make(map[int]int),
}
}
// NewIntIntMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewIntIntMapFrom(data map[int]int, unsafe...bool) *IntIntMap {
return &IntIntMap{
mu : rwmutex.New(unsafe...),
data : data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntIntMap) Iterator(f func (k int, v int) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *IntIntMap) Clone() *IntIntMap {
return NewIntIntMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *IntIntMap) Map() map[int]int {
m.mu.RLock()
data := make(map[int]int, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *IntIntMap) Set(key int, val int) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *IntIntMap) Sets(data map[int]int) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntIntMap) Search(key int) (value int, found bool) {
m.mu.RLock()
value, found = m.data[key]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntIntMap) Get(key int) (int) {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *IntIntMap) doSetWithLockCheck(key int, value int) int {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *IntIntMap) GetOrSet(key int, value int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntIntMap) SetIfNotExist(key int, value int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *IntIntMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntIntMap) Remove(key int) int {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// Keys returns all keys of the map as a slice.
func (m *IntIntMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *IntIntMap) Values() []int {
m.mu.RLock()
values := make([]int, len(m.data))
index := 0
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntIntMap) Contains(key int) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *IntIntMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntIntMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntIntMap) Clear() {
m.mu.Lock()
m.data = make(map[int]int)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *IntIntMap) LockFunc(f func(m map[int]int)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *IntIntMap) RLockFunc(f func(m map[int]int)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *IntIntMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[int]int, len(m.data))
for k, v := range m.data {
n[v] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *IntIntMap) Merge(other *IntIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,309 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type IntStrMap struct {
mu *rwmutex.RWMutex
data map[int]string
}
// NewIntStrMap returns an empty IntStrMap object.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewIntStrMap(unsafe ...bool) *IntStrMap {
return &IntStrMap{
mu : rwmutex.New(unsafe...),
data : make(map[int]string),
}
}
// NewIntStrMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewIntStrMapFrom(data map[int]string, unsafe ...bool) *IntStrMap {
return &IntStrMap{
mu : rwmutex.New(unsafe...),
data : data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *IntStrMap) Clone() *IntStrMap {
return NewIntStrMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *IntStrMap) Map() map[int]string {
m.mu.RLock()
data := make(map[int]string, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *IntStrMap) Set(key int, val string) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *IntStrMap) Sets(data map[int]string) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntStrMap) Search(key int) (value string, found bool) {
m.mu.RLock()
value, found = m.data[key]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntStrMap) Get(key int) string {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *IntStrMap) GetOrSet(key int, value string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntStrMap) SetIfNotExist(key int, value string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *IntStrMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntStrMap) Remove(key int) string {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// Keys returns all keys of the map as a slice.
func (m *IntStrMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *IntStrMap) Values() []string {
m.mu.RLock()
values := make([]string, len(m.data))
index := 0
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntStrMap) Contains(key int) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *IntStrMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *IntStrMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *IntStrMap) Clear() {
m.mu.Lock()
m.data = make(map[int]string)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *IntStrMap) LockFunc(f func(m map[int]string)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *IntStrMap) RLockFunc(f func(m map[int]string)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *IntStrMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[int]string, len(m.data))
for k, v := range m.data {
n[gconv.Int(v)] = gconv.String(k)
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *IntStrMap) Merge(other *IntStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,334 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type StrAnyMap struct {
mu *rwmutex.RWMutex
data map[string]interface{}
}
// NewStrAnyMap returns an empty StrAnyMap object.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewStrAnyMap(unsafe ...bool) *StrAnyMap {
return &StrAnyMap{
mu : rwmutex.New(unsafe...),
data : make(map[string]interface{}),
}
}
// NewStrAnyMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewStrAnyMapFrom(data map[string]interface{}, unsafe ...bool) *StrAnyMap {
return &StrAnyMap{
mu : rwmutex.New(unsafe...),
data : data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrAnyMap) Iterator(f func(k string, v interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *StrAnyMap) Clone() *StrAnyMap {
return NewStrAnyMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *StrAnyMap) Map() map[string]interface{} {
m.mu.RLock()
data := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *StrAnyMap) Set(key string, val interface{}) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *StrAnyMap) Sets(data map[string]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrAnyMap) Search(key string) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrAnyMap) Get(key string) interface{} {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *StrAnyMap) doSetWithLockCheck(key string, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok := m.data[key]; ok {
return v
}
if f, ok := value.(func() interface{}); ok {
value = f()
}
if value != nil {
m.data[key] = value
}
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *StrAnyMap) GetOrSet(key string, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *StrAnyMap) GetOrSetFunc(key string, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVar(key string) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSet(key string, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrAnyMap) SetIfNotExist(key string, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *StrAnyMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrAnyMap) Remove(key string) interface{} {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// Keys returns all keys of the map as a slice.
func (m *StrAnyMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *StrAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrAnyMap) Contains(key string) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *StrAnyMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrAnyMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrAnyMap) Clear() {
m.mu.Lock()
m.data = make(map[string]interface{})
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *StrAnyMap) LockFunc(f func(m map[string]interface{})) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *StrAnyMap) RLockFunc(f func(m map[string]interface{})) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *StrAnyMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
n[gconv.String(v)] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *StrAnyMap) Merge(other *StrAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,312 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type StrIntMap struct {
mu *rwmutex.RWMutex
data map[string]int
}
// NewStrIntMap returns an empty StrIntMap object.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewStrIntMap(unsafe ...bool) *StrIntMap {
return &StrIntMap{
mu : rwmutex.New(unsafe...),
data : make(map[string]int),
}
}
// NewStrIntMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewStrIntMapFrom(data map[string]int, unsafe ...bool) *StrIntMap {
return &StrIntMap{
mu : rwmutex.New(unsafe...),
data : data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *StrIntMap) Clone() *StrIntMap {
return NewStrIntMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *StrIntMap) Map() map[string]int {
m.mu.RLock()
data := make(map[string]int, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *StrIntMap) Set(key string, val int) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *StrIntMap) Sets(data map[string]int) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrIntMap) Search(key string) (value int, found bool) {
m.mu.RLock()
value, found = m.data[key]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrIntMap) Get(key string) int {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *StrIntMap) doSetWithLockCheck(key string, value int) int {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *StrIntMap) GetOrSet(key string, value int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *StrIntMap) GetOrSetFuncLock(key string, f func() int) int {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrIntMap) SetIfNotExist(key string, value int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *StrIntMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrIntMap) Remove(key string) int {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// Keys returns all keys of the map as a slice.
func (m *StrIntMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *StrIntMap) Values() []int {
m.mu.RLock()
values := make([]int, len(m.data))
index := 0
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrIntMap) Contains(key string) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *StrIntMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrIntMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrIntMap) Clear() {
m.mu.Lock()
m.data = make(map[string]int)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *StrIntMap) LockFunc(f func(m map[string]int)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *StrIntMap) RLockFunc(f func(m map[string]int)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *StrIntMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[string]int, len(m.data))
for k, v := range m.data {
n[gconv.String(v)] = gconv.Int(k)
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *StrIntMap) Merge(other *StrIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -0,0 +1,311 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
//
package gmap
import (
"github.com/gogf/gf/g/internal/rwmutex"
)
type StrStrMap struct {
mu *rwmutex.RWMutex
data map[string]string
}
// NewStrStrMap returns an empty StrStrMap object.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewStrStrMap(unsafe...bool) *StrStrMap {
return &StrStrMap{
data : make(map[string]string),
mu : rwmutex.New(unsafe...),
}
}
// NewStrStrMapFrom returns a hash map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewStrStrMapFrom(data map[string]string, unsafe...bool) *StrStrMap {
return &StrStrMap{
mu : rwmutex.New(unsafe...),
data : data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrStrMap) Iterator(f func (k string, v string) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
for k, v := range m.data {
if !f(k, v) {
break
}
}
}
// Clone returns a new hash map with copy of current map data.
func (m *StrStrMap) Clone() *StrStrMap {
return NewStrStrMapFrom(m.Map(), !m.mu.IsSafe())
}
// Map returns a copy of the data of the hash map.
func (m *StrStrMap) Map() map[string]string {
m.mu.RLock()
data := make(map[string]string, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
// Set sets key-value to the hash map.
func (m *StrStrMap) Set(key string, val string) {
m.mu.Lock()
m.data[key] = val
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *StrStrMap) Sets(data map[string]string) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrStrMap) Search(key string) (value string, found bool) {
m.mu.RLock()
value, found = m.data[key]
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrStrMap) Get(key string) string {
m.mu.RLock()
val, _ := m.data[key]
m.mu.RUnlock()
return val
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// It returns value with given <key>.
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
m.mu.Lock()
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *StrStrMap) GetOrSet(key string, value string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if v, ok = m.data[key]; ok {
return v
}
v = f()
m.data[key] = v
return v
} else {
return v
}
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrStrMap) SetIfNotExist(key string, value string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
return true
}
return false
}
// Removes batch deletes values of the map by keys.
func (m *StrStrMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrStrMap) Remove(key string) string {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
}
m.mu.Unlock()
return val
}
// Keys returns all keys of the map as a slice.
func (m *StrStrMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *StrStrMap) Values() []string {
m.mu.RLock()
values := make([]string, len(m.data))
index := 0
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrStrMap) Contains(key string) bool {
m.mu.RLock()
_, exists := m.data[key]
m.mu.RUnlock()
return exists
}
// Size returns the size of the map.
func (m *StrStrMap) Size() int {
m.mu.RLock()
length := len(m.data)
m.mu.RUnlock()
return length
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *StrStrMap) IsEmpty() bool {
m.mu.RLock()
empty := len(m.data) == 0
m.mu.RUnlock()
return empty
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *StrStrMap) Clear() {
m.mu.Lock()
m.data = make(map[string]string)
m.mu.Unlock()
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (m *StrStrMap) LockFunc(f func(m map[string]string)) {
m.mu.Lock()
defer m.mu.Unlock()
f(m.data)
}
// RLockFunc locks reading with given callback function <f> within RWMutex.RLock.
func (m *StrStrMap) RLockFunc(f func(m map[string]string)) {
m.mu.RLock()
defer m.mu.RUnlock()
f(m.data)
}
// Flip exchanges key-value of the map to value-key.
func (m *StrStrMap) Flip() {
m.mu.Lock()
defer m.mu.Unlock()
n := make(map[string]string, len(m.data))
for k, v := range m.data {
n[v] = k
}
m.data = n
}
// Merge merges two hash maps.
// The <other> map will be merged into the map <m>.
func (m *StrStrMap) Merge(other *StrStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
for k, v := range other.data {
m.data[k] = v
}
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type IntBoolMap struct {
m map[int]bool
mu *rwmutex.RWMutex
}
func NewIntBoolMap(safe...bool) *IntBoolMap {
return &IntBoolMap{
m : make(map[int]bool),
mu : rwmutex.New(safe...),
}
}
// 哈希表克隆
func (this *IntBoolMap) Clone() map[int]bool {
m := make(map[int]bool)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntBoolMap) Iterator(f func (k int, v bool) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 设置键值对
func (this *IntBoolMap) Set(key int, val bool) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntBoolMap) BatchSet(m map[int]bool) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntBoolMap) Get(key int) bool {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntBoolMap) doSetWithLockCheck(key int, value bool) bool {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntBoolMap) GetOrSet(key int, value bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntBoolMap) GetOrSetFunc(key int, f func() bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntBoolMap) GetOrSetFuncLock(key int, f func() bool) bool {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntBoolMap) SetIfNotExist(key int, value bool) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntBoolMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntBoolMap) Remove(key int) bool {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntBoolMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
//func (this *IntBoolMap) Values() []bool {
// this.mu.RLock()
// vals := make([]bool, 0)
// for _, val := range this.m {
// vals = append(vals, val)
// }
// this.mu.RUnlock()
// return vals
//}
// 是否存在某个键
func (this *IntBoolMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntBoolMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntBoolMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntBoolMap) Clear() {
this.mu.Lock()
this.m = make(map[int]bool)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntBoolMap) LockFunc(f func(m map[int]bool)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntBoolMap) RLockFunc(f func(m map[int]bool)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type IntIntMap struct {
mu *rwmutex.RWMutex
m map[int]int
}
func NewIntIntMap(safe...bool) *IntIntMap {
return &IntIntMap{
m : make(map[int]int),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntIntMap) Iterator(f func (k int, v int) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntIntMap) Clone() map[int]int {
m := make(map[int]int)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntIntMap) Set(key int, val int) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntIntMap) BatchSet(m map[int]int) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntIntMap) Get(key int) (int) {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntIntMap) doSetWithLockCheck(key int, value int) int {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntIntMap) GetOrSet(key int, value int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntIntMap) GetOrSetFunc(key int, f func() int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntIntMap) SetIfNotExist(key int, value int) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntIntMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntIntMap) Remove(key int) int {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntIntMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntIntMap) Values() []int {
this.mu.RLock()
vals := make([]int, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntIntMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntIntMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntIntMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntIntMap) Clear() {
this.mu.Lock()
this.m = make(map[int]int)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntIntMap) LockFunc(f func(m map[int]int)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntIntMap) RLockFunc(f func(m map[int]int)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,206 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gmap
import "gitee.com/johng/gf/g/container/internal/rwmutex"
type IntInterfaceMap struct {
mu *rwmutex.RWMutex
m map[int]interface{}
}
func NewIntInterfaceMap(safe...bool) *IntInterfaceMap {
return &IntInterfaceMap{
m : make(map[int]interface{}),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntInterfaceMap) Iterator(f func (k int, v interface{}) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntInterfaceMap) Clone() map[int]interface{} {
m := make(map[int]interface{})
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntInterfaceMap) Set(key int, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntInterfaceMap) BatchSet(m map[int]interface{}) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntInterfaceMap) Get(key int) (interface{}) {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntInterfaceMap) doSetWithLockCheck(key int, value interface{}) interface{} {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntInterfaceMap) GetOrSet(key int, value interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntInterfaceMap) GetOrSetFunc(key int, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntInterfaceMap) GetOrSetFuncLock(key int, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntInterfaceMap) SetIfNotExist(key int, value interface{}) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntInterfaceMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntInterfaceMap) Remove(key int) interface{} {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntInterfaceMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntInterfaceMap) Values() []interface{} {
this.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntInterfaceMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[int]interface{})
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntInterfaceMap) LockFunc(f func(m map[int]interface{})) {
this.mu.Lock()
defer this.mu.Unlock()
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type IntStringMap struct {
mu *rwmutex.RWMutex
m map[int]string
}
func NewIntStringMap(safe...bool) *IntStringMap {
return &IntStringMap{
m : make(map[int]string),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntStringMap) Iterator(f func (k int, v string) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntStringMap) Clone() map[int]string {
m := make(map[int]string)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntStringMap) Set(key int, val string) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *IntStringMap) BatchSet(m map[int]string) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *IntStringMap) Get(key int) string {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *IntStringMap) doSetWithLockCheck(key int, value string) string {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntStringMap) GetOrSet(key int, value string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *IntStringMap) GetOrSetFunc(key int, f func() string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *IntStringMap) GetOrSetFuncLock(key int, f func() string) string {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntStringMap) SetIfNotExist(key int, value string) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntStringMap) BatchRemove(keys []int) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntStringMap) Remove(key int) string {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *IntStringMap) Keys() []int {
this.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntStringMap) Values() []string {
this.mu.RLock()
vals := make([]string, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntStringMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntStringMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntStringMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntStringMap) Clear() {
this.mu.Lock()
this.m = make(map[int]string)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntStringMap) LockFunc(f func(m map[int]string)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntStringMap) RLockFunc(f func(m map[int]string)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,208 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type InterfaceInterfaceMap struct {
mu *rwmutex.RWMutex
m map[interface{}]interface{}
}
func NewInterfaceInterfaceMap(safe...bool) *InterfaceInterfaceMap {
return &InterfaceInterfaceMap{
m : make(map[interface{}]interface{}),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *InterfaceInterfaceMap) Iterator(f func (k interface{}, v interface{}) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *InterfaceInterfaceMap) Clone() map[interface{}]interface{} {
m := make(map[interface{}]interface{})
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *InterfaceInterfaceMap) Set(key interface{}, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *InterfaceInterfaceMap) BatchSet(m map[interface{}]interface{}) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *InterfaceInterfaceMap) Get(key interface{}) interface{} {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *InterfaceInterfaceMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *InterfaceInterfaceMap) GetOrSet(key interface{}, value interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *InterfaceInterfaceMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *InterfaceInterfaceMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *InterfaceInterfaceMap) SetIfNotExist(key interface{}, value interface{}) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *InterfaceInterfaceMap) BatchRemove(keys []interface{}) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *InterfaceInterfaceMap) Remove(key interface{}) interface{} {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *InterfaceInterfaceMap) Keys() []interface{} {
this.mu.RLock()
keys := make([]interface{}, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *InterfaceInterfaceMap) Values() []interface{} {
this.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *InterfaceInterfaceMap) Contains(key interface{}) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *InterfaceInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *InterfaceInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *InterfaceInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[interface{}]interface{})
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *InterfaceInterfaceMap) LockFunc(f func(m map[interface{}]interface{})) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *InterfaceInterfaceMap) RLockFunc(f func(m map[interface{}]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -0,0 +1,366 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/container/glist"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
type ListMap struct {
mu *rwmutex.RWMutex
data map[interface{}]*glist.Element
list *glist.List
}
type gListMapNode struct {
key interface{}
value interface{}
}
// NewListMap returns an empty link map.
// ListMap is backed by a hash table to store values and doubly-linked list to store ordering.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewListMap(unsafe ...bool) *ListMap {
return &ListMap{
mu : rwmutex.New(unsafe...),
data : make(map[interface{}]*glist.Element),
list : glist.New(true),
}
}
// NewListMapFrom returns a link map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewListMapFrom(data map[interface{}]interface{}, unsafe...bool) *ListMap {
m := NewListMap(unsafe...)
m.Sets(data)
return m
}
// Iterator is alias of IteratorAsc.
func (m *ListMap) Iterator(f func (key, value interface{}) bool) {
m.IteratorAsc(f)
}
// IteratorAsc iterates the map in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *ListMap) IteratorAsc(f func (key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
// IteratorDesc iterates the map in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *ListMap) IteratorDesc(f func (key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gListMapNode)(nil)
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
// Clone returns a new link map with copy of current map data.
func (m *ListMap) Clone(unsafe ...bool) *ListMap {
return NewListMapFrom(m.Map(), unsafe ...)
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *ListMap) Clear() {
m.mu.Lock()
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New(true)
m.mu.Unlock()
}
// Map returns a copy of the data of the map.
func (m *ListMap) Map() map[interface{}]interface{} {
m.mu.RLock()
node := (*gListMapNode)(nil)
data := make(map[interface{}]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[node.key] = node.value
return true
})
m.mu.RUnlock()
return data
}
// Set sets key-value to the map.
func (m *ListMap) Set(key interface{}, value interface{}) {
m.mu.Lock()
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
e.Value = &gListMapNode{key, value}
}
m.mu.Unlock()
}
// Sets batch sets key-values to the map.
func (m *ListMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
for key, value := range data {
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
e.Value = &gListMapNode{key, value}
}
}
m.mu.Unlock()
}
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *ListMap) Search(key interface{}) (value interface{}, found bool) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
found = ok
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *ListMap) Get(key interface{}) (value interface{}) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
}
m.mu.RUnlock()
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *ListMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if e, ok := m.data[key]; ok {
return e.Value.(*gListMapNode).value
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *ListMap) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *ListMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the map.
func (m *ListMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *ListMap) SetIfNotExist(key interface{}, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *ListMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the map.
func (m *ListMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
}
return false
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *ListMap) Remove(key interface{}) (value interface{}) {
m.mu.Lock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
delete(m.data, key)
m.list.Remove(e)
}
m.mu.Unlock()
return
}
// Removes batch deletes values of the map by keys.
func (m *ListMap) Removes(keys []interface{}) {
m.mu.Lock()
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
}
}
m.mu.Unlock()
}
// Keys returns all keys of the map as a slice in ascending order.
func (m *ListMap) Keys() []interface{} {
m.mu.RLock()
keys := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
keys[index] = e.Value.(*gListMapNode).key
index++
return true
})
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *ListMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
values[index] = e.Value.(*gListMapNode).value
index++
return true
})
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *ListMap) Contains(key interface{}) (ok bool) {
m.mu.RLock()
_, ok = m.data[key]
m.mu.RUnlock()
return
}
// Size returns the size of the map.
func (m *ListMap) Size() (size int) {
m.mu.RLock()
size = len(m.data)
m.mu.RUnlock()
return
}
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *ListMap) IsEmpty() bool {
return m.Size() == 0
}
// Flip exchanges key-value of the map to value-key.
func (m *ListMap) Flip() {
data := m.Map()
m.Clear()
for key, value := range data {
m.Set(value, key)
}
}
// Merge merges two link maps.
// The <other> map will be merged into the map <m>.
func (m *ListMap) Merge(other *ListMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
node := (*gListMapNode)(nil)
other.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if e, ok := m.data[node.key]; !ok {
m.data[node.key] = m.list.PushBack(&gListMapNode{node.key, node.value})
} else {
e.Value = &gListMapNode{node.key, node.value}
}
return true
})
}

View File

@ -1,223 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type StringBoolMap struct {
mu *rwmutex.RWMutex
m map[string]bool
}
func NewStringBoolMap(safe...bool) *StringBoolMap {
return &StringBoolMap{
m : make(map[string]bool),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringBoolMap) Iterator(f func (k string, v bool) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringBoolMap) Clone() map[string]bool {
m := make(map[string]bool)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringBoolMap) Set(key string, val bool) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringBoolMap) BatchSet(m map[string]bool) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringBoolMap) Get(key string) bool {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringBoolMap) doSetWithLockCheck(key string, value bool) bool {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringBoolMap) GetOrSet(key string, value bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringBoolMap) GetOrSetFunc(key string, f func() bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringBoolMap) GetOrSetFuncLock(key string, f func() bool) bool {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringBoolMap) SetIfNotExist(key string, value bool) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringBoolMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringBoolMap) Remove(key string) bool {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringBoolMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
//func (this *StringBoolMap) Values() []bool {
// this.mu.RLock()
// vals := make([]bool, 0)
// for _, val := range this.m {
// vals = append(vals, val)
// }
// this.mu.RUnlock()
// return vals
//}
// 是否存在某个键
func (this *StringBoolMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringBoolMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringBoolMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringBoolMap) Clear() {
this.mu.Lock()
this.m = make(map[string]bool)
this.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *StringBoolMap) LockFunc(f func(m map[string]bool)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *StringBoolMap) RLockFunc(f func(m map[string]bool)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,221 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gmap
import "gitee.com/johng/gf/g/container/internal/rwmutex"
type StringIntMap struct {
mu *rwmutex.RWMutex
m map[string]int
}
func NewStringIntMap(safe...bool) *StringIntMap {
return &StringIntMap{
m : make(map[string]int),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringIntMap) Iterator(f func (k string, v int) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringIntMap) Clone() map[string]int {
m := make(map[string]int)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringIntMap) Set(key string, val int) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringIntMap) BatchSet(m map[string]int) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringIntMap) Get(key string) int {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringIntMap) doSetWithLockCheck(key string, value int) int {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringIntMap) GetOrSet(key string, value int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringIntMap) GetOrSetFunc(key string, f func() int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringIntMap) GetOrSetFuncLock(key string, f func() int) int {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringIntMap) SetIfNotExist(key string, value int) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringIntMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringIntMap) Remove(key string) int {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringIntMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringIntMap) Values() []int {
this.mu.RLock()
vals := make([]int, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringIntMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringIntMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringIntMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringIntMap) Clear() {
this.mu.Lock()
this.m = make(map[string]int)
this.mu.Unlock()
}
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (this *StringIntMap) LockFunc(f func(m map[string]int)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringIntMap) RLockFunc(f func(m map[string]int)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,208 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type StringInterfaceMap struct {
mu *rwmutex.RWMutex
m map[string]interface{}
}
func NewStringInterfaceMap(safe...bool) *StringInterfaceMap {
return &StringInterfaceMap{
m : make(map[string]interface{}),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringInterfaceMap) Iterator(f func (k string, v interface{}) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringInterfaceMap) Clone() map[string]interface{} {
m := make(map[string]interface{})
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringInterfaceMap) Set(key string, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringInterfaceMap) BatchSet(m map[string]interface{}) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringInterfaceMap) Get(key string) interface{} {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringInterfaceMap) doSetWithLockCheck(key string, value interface{}) interface{} {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringInterfaceMap) GetOrSet(key string, value interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringInterfaceMap) GetOrSetFunc(key string, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringInterfaceMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
if v := this.Get(key); v == nil {
return this.doSetWithLockCheck(key, f)
} else {
return v
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringInterfaceMap) SetIfNotExist(key string, value interface{}) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringInterfaceMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringInterfaceMap) Remove(key string) interface{} {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringInterfaceMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringInterfaceMap) Values() []interface{} {
this.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringInterfaceMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[string]interface{})
this.mu.Unlock()
}
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (this *StringInterfaceMap) LockFunc(f func(m map[string]interface{})) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,221 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gmap
import "gitee.com/johng/gf/g/container/internal/rwmutex"
type StringStringMap struct {
mu *rwmutex.RWMutex
m map[string]string
}
func NewStringStringMap(safe...bool) *StringStringMap {
return &StringStringMap{
m : make(map[string]string),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringStringMap) Iterator(f func (k string, v string) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, v := range this.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringStringMap) Clone() map[string]string {
m := make(map[string]string)
this.mu.RLock()
for k, v := range this.m {
m[k] = v
}
this.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringStringMap) Set(key string, val string) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
}
// 批量设置键值对
func (this *StringStringMap) BatchSet(m map[string]string) {
this.mu.Lock()
for k, v := range m {
this.m[k] = v
}
this.mu.Unlock()
}
// 获取键值
func (this *StringStringMap) Get(key string) string {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (this *StringStringMap) doSetWithLockCheck(key string, value string) string {
this.mu.Lock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringStringMap) GetOrSet(key string, value string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
} else {
return v
}
}
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (this *StringStringMap) GetOrSetFunc(key string, f func() string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
} else {
return v
}
}
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (this *StringStringMap) GetOrSetFuncLock(key string, f func() string) string {
this.mu.RLock()
val, ok := this.m[key]
this.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
return v
}
val = f()
this.m[key] = val
return val
} else {
return val
}
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringStringMap) SetIfNotExist(key string, value string) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringStringMap) BatchRemove(keys []string) {
this.mu.Lock()
for _, key := range keys {
delete(this.m, key)
}
this.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringStringMap) Remove(key string) string {
this.mu.Lock()
val, exists := this.m[key]
if exists {
delete(this.m, key)
}
this.mu.Unlock()
return val
}
// 返回键列表
func (this *StringStringMap) Keys() []string {
this.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
keys = append(keys, key)
}
this.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringStringMap) Values() []string {
this.mu.RLock()
vals := make([]string, 0)
for _, val := range this.m {
vals = append(vals, val)
}
this.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringStringMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringStringMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringStringMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringStringMap) Clear() {
this.mu.Lock()
this.m = make(map[string]string)
this.mu.Unlock()
}
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (this *StringStringMap) LockFunc(f func(m map[string]string)) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringStringMap) RLockFunc(f func(m map[string]string)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -0,0 +1,30 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap
import (
"github.com/gogf/gf/g/container/gtree"
)
// Map based on red-black tree, alias of RedBlackTree.
type TreeMap = gtree.RedBlackTree
// NewTreeMap instantiates a tree map with the custom comparator.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewTreeMap(comparator func(v1, v2 interface{}) int, unsafe...bool) *TreeMap {
return gtree.NewRedBlackTree(comparator, unsafe...)
}
// NewTreeMapFrom instantiates a tree map with the custom comparator and <data> map.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewTreeMapFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe...bool) *TreeMap {
return gtree.NewRedBlackTreeFrom(comparator, data, unsafe...)
}

View File

@ -0,0 +1,125 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getValue() interface{} {
return 3
}
func Test_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.New()
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_Map_Set_Fun(t *testing.T) {
m := gmap.New()
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_Map_Batch(t *testing.T) {
m := gmap.New()
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_Map_Iterator(t *testing.T){
expect :=map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewFrom(expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_Map_Lock(t *testing.T){
expect :=map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewFrom(expect)
m.LockFunc(func(m map[interface{}]interface{}) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[interface{}]interface{}) {
gtest.Assert(m, expect)
})
}
func Test_Map_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}
func Test_Map_Basic_Merge(t *testing.T) {
m1 := gmap.New()
m2 := gmap.New()
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"})
}

View File

@ -0,0 +1,55 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
var hashMap = gmap.New()
var listMap = gmap.NewListMap()
var treeMap = gmap.NewTreeMap(gutil.ComparatorInt)
func Benchmark_HashMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
hashMap.Set(i, i)
}
}
func Benchmark_ListMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
listMap.Set(i, i)
}
}
func Benchmark_TreeMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
treeMap.Set(i, i)
}
}
func Benchmark_HashMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
hashMap.Get(i)
}
}
func Benchmark_ListMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
listMap.Get(i)
}
}
func Benchmark_TreeMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
treeMap.Get(i)
}
}

View File

@ -0,0 +1,110 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"testing"
"strconv"
)
var ififm = gmap.New()
var iim = gmap.NewIntIntMap()
var iifm = gmap.NewIntAnyMap()
var ism = gmap.NewIntStrMap()
var sim = gmap.NewStrIntMap()
var sifm = gmap.NewStrAnyMap()
var ssm = gmap.NewStrStrMap()
func Benchmark_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Set(i, i)
}
}
func Benchmark_IntAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Set(i, i)
}
}
func Benchmark_IntStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Set(i, strconv.Itoa(i))
}
}
func Benchmark_AnyAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Set(i, i)
}
}
func Benchmark_StrIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StrAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Set(strconv.Itoa(i), i)
}
}
func Benchmark_StrStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
func Benchmark_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iim.Get(i)
}
}
func Benchmark_IntAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifm.Get(i)
}
}
func Benchmark_IntStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ism.Get(i)
}
}
func Benchmark_AnyAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififm.Get(i)
}
}
func Benchmark_StrIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sim.Get(strconv.Itoa(i))
}
}
func Benchmark_StrAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifm.Get(strconv.Itoa(i))
}
}
func Benchmark_StrStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssm.Get(strconv.Itoa(i))
}
}

View File

@ -1,8 +1,8 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
@ -10,7 +10,7 @@ package gmap_test
import (
"testing"
"gitee.com/johng/gf/g/container/gmap"
"github.com/gogf/gf/g/container/gmap"
"sync"
)

View File

@ -0,0 +1,114 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"testing"
"strconv"
)
var ififmUnsafe = gmap.New(true)
var iimUnsafe = gmap.NewIntIntMap(true)
var iifmUnsafe = gmap.NewIntAnyMap(true)
var ismUnsafe = gmap.NewIntStrMap(true)
var simUnsafe = gmap.NewStrIntMap(true)
var sifmUnsafe = gmap.NewStrAnyMap(true)
var ssmUnsafe = gmap.NewStrStrMap(true)
// 写入性能测试
func Benchmark_Unsafe_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_IntStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Set(i, strconv.Itoa(i))
}
}
func Benchmark_Unsafe_AnyAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Set(i, i)
}
}
func Benchmark_Unsafe_StrIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StrAnyMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Set(strconv.Itoa(i), i)
}
}
func Benchmark_Unsafe_StrStrMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Set(strconv.Itoa(i), strconv.Itoa(i))
}
}
// 读取性能测试
func Benchmark_Unsafe_IntIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iimUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
iifmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_IntStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ismUnsafe.Get(i)
}
}
func Benchmark_Unsafe_AnyAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ififmUnsafe.Get(i)
}
}
func Benchmark_Unsafe_StrIntMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
simUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StrAnyMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
sifmUnsafe.Get(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StrStrMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
ssmUnsafe.Get(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,71 @@
package gmap_test
import (
"fmt"
"github.com/gogf/gf/g/container/gmap"
)
func Example_Normal_Basic() {
m := gmap.New()
//Add data
m.Set("key1", "val1")
//Print size
fmt.Println(m.Size())
//output 1
add_map := make(map[interface{}]interface{})
add_map["key2"] = "val2"
add_map["key3"] = "val3"
add_map[1] = 1
fmt.Println(m.Values())
//Batch add data
m.Sets(add_map)
//Gets the value of the corresponding key
key3_val := m.Get("key3")
fmt.Println(key3_val)
//Get the value by key, or set it with given key-value if not exist.
get_or_set_val := m.GetOrSet("key4", "val4")
fmt.Println(get_or_set_val)
// Set key-value if the key does not exist, then return true; or else return false.
is_set := m.SetIfNotExist("key3", "val3")
fmt.Println(is_set)
//Remove key
m.Remove("key2")
fmt.Println(m.Keys())
//Batch remove keys
remove_keys := []interface{}{"key1", 1}
m.Removes(remove_keys)
fmt.Println(m.Keys())
//Contains checks whether a key exists.
is_contain := m.Contains("key3")
fmt.Println(is_contain)
//Flip exchanges key-value of the map, it will change key-value to value-key.
m.Flip()
fmt.Println(m.Map())
// Clear deletes all data of the map,
m.Clear()
fmt.Println(m.Size())
}
func Example_Normal_Merge(){
m1 := gmap.New()
m2 := gmap.New()
m1.Set("key1","val1")
m2.Set("key2","val2")
m1.Merge(m2)
fmt.Println(m1.Map())
}

View File

@ -0,0 +1,131 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getAny() interface{} {
return 123
}
func intAnyCallBack(int, interface{}) bool {
return true
}
func Test_IntAnyMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntAnyMap()
m.Set(1, 1)
gtest.Assert(m.Get(1), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet(2, "2"), "2")
gtest.Assert(m.SetIfNotExist(2, "2"), false)
gtest.Assert(m.SetIfNotExist(3, 3), true)
gtest.Assert(m.Remove(2), "2")
gtest.Assert(m.Contains(2), false)
gtest.AssertIN(3, m.Keys())
gtest.AssertIN(1, m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]int{1: 1, 3: 3})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewIntAnyMapFrom(map[int]interface{}{1: 1, 2: "2"})
gtest.Assert(m2.Map(), map[int]interface{}{1: 1, 2: "2"})
})
}
func Test_IntAnyMap_Set_Fun(t *testing.T) {
m := gmap.NewIntAnyMap()
m.GetOrSetFunc(1, getAny)
m.GetOrSetFuncLock(2, getAny)
gtest.Assert(m.Get(1), 123)
gtest.Assert(m.Get(2), 123)
gtest.Assert(m.SetIfNotExistFunc(1, getAny), false)
gtest.Assert(m.SetIfNotExistFunc(3, getAny), true)
gtest.Assert(m.SetIfNotExistFuncLock(2, getAny), false)
gtest.Assert(m.SetIfNotExistFuncLock(4, getAny), true)
}
func Test_IntAnyMap_Batch(t *testing.T) {
m := gmap.NewIntAnyMap()
m.Sets(map[int]interface{}{1: 1, 2: "2", 3: 3})
gtest.Assert(m.Map(), map[int]interface{}{1: 1, 2: "2", 3: 3})
m.Removes([]int{1, 2})
gtest.Assert(m.Map(), map[int]interface{}{3: 3})
}
func Test_IntAnyMap_Iterator(t *testing.T){
expect := map[int]interface{}{1: 1, 2: "2"}
m := gmap.NewIntAnyMapFrom(expect)
m.Iterator(func(k int, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k int, v interface{}) bool {
i++
return true
})
m.Iterator(func(k int, v interface{}) bool {
j++
return false
})
gtest.Assert(i, "2")
gtest.Assert(j, 1)
}
func Test_IntAnyMap_Lock(t *testing.T){
expect := map[int]interface{}{1: 1, 2: "2"}
m := gmap.NewIntAnyMapFrom(expect)
m.LockFunc(func(m map[int]interface{}) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[int]interface{}) {
gtest.Assert(m, expect)
})
}
func Test_IntAnyMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewIntAnyMapFrom(map[int]interface{}{1: 1, 2: "2"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove(2)
//修改clone map,原 map 不影响
gtest.AssertIN(2, m.Keys())
}
func Test_IntAnyMap_Merge(t *testing.T) {
m1 := gmap.NewIntAnyMap()
m2 := gmap.NewIntAnyMap()
m1.Set(1, 1)
m2.Set(2, "2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[int]interface{}{1: 1, 2: "2"})
}

View File

@ -0,0 +1,131 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getInt() int {
return 123
}
func intIntCallBack(int, int) bool {
return true
}
func Test_IntIntMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntIntMap()
m.Set(1, 1)
gtest.Assert(m.Get(1), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet(2, 2), 2)
gtest.Assert(m.SetIfNotExist(2, 2), false)
gtest.Assert(m.SetIfNotExist(3, 3), true)
gtest.Assert(m.Remove(2), 2)
gtest.Assert(m.Contains(2), false)
gtest.AssertIN(3, m.Keys())
gtest.AssertIN(1, m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m.Flip()
gtest.Assert(m.Map(), map[int]int{1: 1, 3: 3})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewIntIntMapFrom(map[int]int{1: 1, 2: 2})
gtest.Assert(m2.Map(), map[int]int{1: 1, 2: 2})
})
}
func Test_IntIntMap_Set_Fun(t *testing.T) {
m := gmap.NewIntIntMap()
m.GetOrSetFunc(1, getInt)
m.GetOrSetFuncLock(2, getInt)
gtest.Assert(m.Get(1), 123)
gtest.Assert(m.Get(2), 123)
gtest.Assert(m.SetIfNotExistFunc(1, getInt), false)
gtest.Assert(m.SetIfNotExistFunc(3, getInt), true)
gtest.Assert(m.SetIfNotExistFuncLock(2, getInt), false)
gtest.Assert(m.SetIfNotExistFuncLock(4, getInt), true)
}
func Test_IntIntMap_Batch(t *testing.T) {
m := gmap.NewIntIntMap()
m.Sets(map[int]int{1: 1, 2: 2, 3: 3})
m.Iterator(intIntCallBack)
gtest.Assert(m.Map(), map[int]int{1: 1, 2: 2, 3: 3})
m.Removes([]int{1, 2})
gtest.Assert(m.Map(), map[int]int{3: 3})
}
func Test_IntIntMap_Iterator(t *testing.T){
expect := map[int]int{1: 1, 2: 2}
m := gmap.NewIntIntMapFrom(expect)
m.Iterator(func(k int, v int) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k int, v int) bool {
i++
return true
})
m.Iterator(func(k int, v int) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_IntIntMap_Lock(t *testing.T){
expect := map[int]int{1: 1, 2: 2}
m := gmap.NewIntIntMapFrom(expect)
m.LockFunc(func(m map[int]int) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[int]int) {
gtest.Assert(m, expect)
})
}
func Test_IntIntMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewIntIntMapFrom(map[int]int{1: 1, 2: 2})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove(2)
//修改clone map,原 map 不影响
gtest.AssertIN(2, m.Keys())
}
func Test_IntIntMap_Merge(t *testing.T) {
m1 := gmap.NewIntIntMap()
m2 := gmap.NewIntIntMap()
m1.Set(1, 1)
m2.Set(2, 2)
m1.Merge(m2)
gtest.Assert(m1.Map(), map[int]int{1: 1, 2: 2})
}

View File

@ -0,0 +1,135 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func getStr() string {
return "z"
}
func intStrCallBack(int, string) bool {
return true
}
func Test_IntStrMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewIntStrMap()
m.Set(1, "a")
gtest.Assert(m.Get(1), "a")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet(2, "b"), "b")
gtest.Assert(m.SetIfNotExist(2, "b"), false)
gtest.Assert(m.SetIfNotExist(3, "c"), true)
gtest.Assert(m.Remove(2), "b")
gtest.Assert(m.Contains(2), false)
gtest.AssertIN(3, m.Keys())
gtest.AssertIN(1, m.Keys())
gtest.AssertIN("a", m.Values())
gtest.AssertIN("c", m.Values())
//反转之后不成为以下 map,flip 操作只是翻转原 map
//gtest.Assert(m.Map(), map[string]int{"a": 1, "c": 3})
m_f := gmap.NewIntStrMap()
m_f.Set(1, "2")
m_f.Flip()
gtest.Assert(m_f.Map(), map[int]string{2: "1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewIntStrMapFrom(map[int]string{1: "a", 2: "b"})
gtest.Assert(m2.Map(), map[int]string{1: "a", 2: "b"})
})
}
func Test_IntStrMap_Set_Fun(t *testing.T) {
m := gmap.NewIntStrMap()
m.GetOrSetFunc(1, getStr)
m.GetOrSetFuncLock(2, getStr)
gtest.Assert(m.Get(1), "z")
gtest.Assert(m.Get(2), "z")
gtest.Assert(m.SetIfNotExistFunc(1, getStr), false)
gtest.Assert(m.SetIfNotExistFunc(3, getStr), true)
gtest.Assert(m.SetIfNotExistFuncLock(2, getStr), false)
gtest.Assert(m.SetIfNotExistFuncLock(4, getStr), true)
}
func Test_IntStrMap_Batch(t *testing.T) {
m := gmap.NewIntStrMap()
m.Sets(map[int]string{1: "a", 2: "b", 3: "c"})
gtest.Assert(m.Map(), map[int]string{1: "a", 2: "b",3: "c"})
m.Removes([]int{1, 2})
gtest.Assert(m.Map(), map[int]interface{}{3: "c"})
}
func Test_IntStrMap_Iterator(t *testing.T){
expect := map[int]string{1: "a", 2: "b"}
m := gmap.NewIntStrMapFrom(expect)
m.Iterator(func(k int, v string) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k int, v string) bool {
i++
return true
})
m.Iterator(func(k int, v string) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_IntStrMap_Lock(t *testing.T){
expect := map[int]string{1: "a", 2: "b", 3: "c"}
m := gmap.NewIntStrMapFrom(expect)
m.LockFunc(func(m map[int]string) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[int]string) {
gtest.Assert(m, expect)
})
}
func Test_IntStrMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewIntStrMapFrom(map[int]string{1: "a", 2: "b", 3: "c"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove(2)
//修改clone map,原 map 不影响
gtest.AssertIN(2, m.Keys())
}
func Test_IntStrMap_Merge(t *testing.T) {
m1 := gmap.NewIntStrMap()
m2 := gmap.NewIntStrMap()
m1.Set(1, "a")
m2.Set(2, "b")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[int]string{1: "a", 2: "b"})
}

View File

@ -0,0 +1,120 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_List_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewListMap()
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_List_Map_Set_Fun(t *testing.T) {
m := gmap.NewListMap()
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_List_Map_Batch(t *testing.T) {
m := gmap.NewListMap()
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_List_Map_Iterator(t *testing.T){
expect :=map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewListMapFrom(expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_List_Map_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}
func Test_List_Map_Basic_Merge(t *testing.T) {
m1 := gmap.NewListMap()
m2 := gmap.NewListMap()
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"})
}
func Test_List_Map_Order(t *testing.T) {
m := gmap.NewListMap()
m.Set("k1", "v1")
m.Set("k2", "v2")
m.Set("k3", "v3")
gtest.Assert(m.Keys(), g.Slice{"k1", "k2", "k3"})
gtest.Assert(m.Values(), g.Slice{"v1", "v2", "v3"})
}

View File

@ -0,0 +1,128 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func stringAnyCallBack(string, interface{}) bool {
return true
}
func Test_StrAnyMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrAnyMap()
m.Set("a", 1)
gtest.Assert(m.Get("a"), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("b", "2"), "2")
gtest.Assert(m.SetIfNotExist("b", "2"), false)
gtest.Assert(m.SetIfNotExist("c", 3), true)
gtest.Assert(m.Remove("b"), "2")
gtest.Assert(m.Contains("b"), false)
gtest.AssertIN("c", m.Keys())
gtest.AssertIN("a", m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m.Flip()
gtest.Assert(m.Map(), map[string]interface{}{"1": "a", "3": "c"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewStrAnyMapFrom(map[string]interface{}{"a": 1, "b": "2"})
gtest.Assert(m2.Map(), map[string]interface{}{"a": 1, "b": "2"})
})
}
func Test_StrAnyMap_Set_Fun(t *testing.T) {
m := gmap.NewStrAnyMap()
m.GetOrSetFunc("a", getAny)
m.GetOrSetFuncLock("b", getAny)
gtest.Assert(m.Get("a"), 123)
gtest.Assert(m.Get("b"), 123)
gtest.Assert(m.SetIfNotExistFunc("a", getAny), false)
gtest.Assert(m.SetIfNotExistFunc("c", getAny), true)
gtest.Assert(m.SetIfNotExistFuncLock("b", getAny), false)
gtest.Assert(m.SetIfNotExistFuncLock("d", getAny), true)
}
func Test_StrAnyMap_Batch(t *testing.T) {
m := gmap.NewStrAnyMap()
m.Sets(map[string]interface{}{"a": 1, "b": "2", "c": 3})
gtest.Assert(m.Map(), map[string]interface{}{"a": 1, "b": "2", "c": 3})
m.Removes([]string{"a", "b"})
gtest.Assert(m.Map(), map[string]interface{}{"c": 3})
}
func Test_StrAnyMap_Iterator(t *testing.T) {
expect := map[string]interface{}{"a": true, "b": false}
m := gmap.NewStrAnyMapFrom(expect)
m.Iterator(func(k string, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k string, v interface{}) bool {
i++
return true
})
m.Iterator(func(k string, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_StrAnyMap_Lock(t *testing.T) {
expect := map[string]interface{}{"a": true, "b": false}
m := gmap.NewStrAnyMapFrom(expect)
m.LockFunc(func(m map[string]interface{}) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[string]interface{}) {
gtest.Assert(m, expect)
})
}
func Test_StrAnyMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewStrAnyMapFrom(map[string]interface{}{"a": 1, "b": "2"})
m_clone := m.Clone()
m.Remove("a")
//修改原 map,clone 后的 map 不影响
gtest.AssertIN("a", m_clone.Keys())
m_clone.Remove("b")
//修改clone map,原 map 不影响
gtest.AssertIN("b", m.Keys())
}
func Test_StrAnyMap_Merge(t *testing.T) {
m1 := gmap.NewStrAnyMap()
m2 := gmap.NewStrAnyMap()
m1.Set("a", 1)
m2.Set("b", "2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[string]interface{}{"a": 1, "b": "2"})
}

View File

@ -0,0 +1,131 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func stringIntCallBack(string, int) bool {
return true
}
func Test_StrIntMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrIntMap()
m.Set("a", 1)
gtest.Assert(m.Get("a"), 1)
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("b", 2), 2)
gtest.Assert(m.SetIfNotExist("b", 2), false)
gtest.Assert(m.SetIfNotExist("c", 3), true)
gtest.Assert(m.Remove("b"), 2)
gtest.Assert(m.Contains("b"), false)
gtest.AssertIN("c", m.Keys())
gtest.AssertIN("a", m.Keys())
gtest.AssertIN(3, m.Values())
gtest.AssertIN(1, m.Values())
m_f := gmap.NewStrIntMap()
m_f.Set("1", 2)
m_f.Flip()
gtest.Assert(m_f.Map(), map[string]int{"2": 1})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewStrIntMapFrom(map[string]int{"a": 1, "b": 2})
gtest.Assert(m2.Map(), map[string]int{"a": 1, "b": 2})
})
}
func Test_StrIntMap_Set_Fun(t *testing.T) {
m := gmap.NewStrIntMap()
m.GetOrSetFunc("a", getInt)
m.GetOrSetFuncLock("b", getInt)
gtest.Assert(m.Get("a"), 123)
gtest.Assert(m.Get("b"), 123)
gtest.Assert(m.SetIfNotExistFunc("a", getInt), false)
gtest.Assert(m.SetIfNotExistFunc("c", getInt), true)
gtest.Assert(m.SetIfNotExistFuncLock("b", getInt), false)
gtest.Assert(m.SetIfNotExistFuncLock("d", getInt), true)
}
func Test_StrIntMap_Batch(t *testing.T) {
m := gmap.NewStrIntMap()
m.Sets(map[string]int{"a": 1, "b": 2, "c": 3})
gtest.Assert(m.Map(), map[string]int{"a": 1, "b": 2, "c": 3})
m.Removes([]string{"a", "b"})
gtest.Assert(m.Map(), map[string]int{"c": 3})
}
func Test_StrIntMap_Iterator(t *testing.T) {
expect := map[string]int{"a": 1, "b": 2}
m := gmap.NewStrIntMapFrom(expect)
m.Iterator(func(k string, v int) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k string, v int) bool {
i++
return true
})
m.Iterator(func(k string, v int) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_StrIntMap_Lock(t *testing.T) {
expect := map[string]int{"a": 1, "b": 2}
m := gmap.NewStrIntMapFrom(expect)
m.LockFunc(func(m map[string]int) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[string]int) {
gtest.Assert(m, expect)
})
}
func Test_StrIntMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewStrIntMapFrom(map[string]int{"a": 1, "b": 2, "c": 3})
m_clone := m.Clone()
m.Remove("a")
//修改原 map,clone 后的 map 不影响
gtest.AssertIN("a", m_clone.Keys())
m_clone.Remove("b")
//修改clone map,原 map 不影响
gtest.AssertIN("b", m.Keys())
}
func Test_StrIntMap_Merge(t *testing.T) {
m1 := gmap.NewStrIntMap()
m2 := gmap.NewStrIntMap()
m1.Set("a", 1)
m2.Set("b", 2)
m1.Merge(m2)
gtest.Assert(m1.Map(), map[string]int{"a": 1, "b": 2})
}

View File

@ -0,0 +1,128 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func stringStrCallBack(string, string) bool {
return true
}
func Test_StrStrMap_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewStrStrMap()
m.Set("a", "a")
gtest.Assert(m.Get("a"), "a")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("b", "b"), "b")
gtest.Assert(m.SetIfNotExist("b", "b"), false)
gtest.Assert(m.SetIfNotExist("c", "c"), true)
gtest.Assert(m.Remove("b"), "b")
gtest.Assert(m.Contains("b"), false)
gtest.AssertIN("c", m.Keys())
gtest.AssertIN("a", m.Keys())
gtest.AssertIN("a", m.Values())
gtest.AssertIN("c", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[string]string{"a": "a", "c": "c"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewStrStrMapFrom(map[string]string{"a": "a", "b": "b"})
gtest.Assert(m2.Map(), map[string]string{"a": "a", "b": "b"})
})
}
func Test_StrStrMap_Set_Fun(t *testing.T) {
m := gmap.NewStrStrMap()
m.GetOrSetFunc("a", getStr)
m.GetOrSetFuncLock("b", getStr)
gtest.Assert(m.Get("a"), "z")
gtest.Assert(m.Get("b"), "z")
gtest.Assert(m.SetIfNotExistFunc("a", getStr), false)
gtest.Assert(m.SetIfNotExistFunc("c", getStr), true)
gtest.Assert(m.SetIfNotExistFuncLock("b", getStr), false)
gtest.Assert(m.SetIfNotExistFuncLock("d", getStr), true)
}
func Test_StrStrMap_Batch(t *testing.T) {
m := gmap.NewStrStrMap()
m.Sets(map[string]string{"a": "a", "b": "b", "c": "c"})
gtest.Assert(m.Map(), map[string]string{"a": "a", "b": "b", "c": "c"})
m.Removes([]string{"a", "b"})
gtest.Assert(m.Map(), map[string]string{"c": "c"})
}
func Test_StrStrMap_Iterator(t *testing.T) {
expect := map[string]string{"a": "a", "b": "b"}
m := gmap.NewStrStrMapFrom(expect)
m.Iterator(func(k string, v string) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k string, v string) bool {
i++
return true
})
m.Iterator(func(k string, v string) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_StrStrMap_Lock(t *testing.T) {
expect := map[string]string{"a": "a", "b": "b"}
m := gmap.NewStrStrMapFrom(expect)
m.LockFunc(func(m map[string]string) {
gtest.Assert(m, expect)
})
m.RLockFunc(func(m map[string]string) {
gtest.Assert(m, expect)
})
}
func Test_StrStrMap_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewStrStrMapFrom(map[string]string{"a": "a", "b": "b", "c": "c"})
m_clone := m.Clone()
m.Remove("a")
//修改原 map,clone 后的 map 不影响
gtest.AssertIN("a", m_clone.Keys())
m_clone.Remove("b")
//修改clone map,原 map 不影响
gtest.AssertIN("b", m.Keys())
}
func Test_StrStrMap_Merge(t *testing.T) {
m1 := gmap.NewStrStrMap()
m2 := gmap.NewStrStrMap()
m1.Set("a", "a")
m2.Set("b", "b")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[string]string{"a": "a", "b": "b"})
}

View File

@ -0,0 +1,103 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
func Test_Tree_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewTreeMap(gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewTreeMapFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_Tree_Map_Set_Fun(t *testing.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_Tree_Map_Batch(t *testing.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_Tree_Map_Iterator(t *testing.T){
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewTreeMapFrom(gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_Tree_Map_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewTreeMapFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}

View File

@ -1,58 +1,68 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// 对象复用池.
// Package gpool provides object-reusable concurrent-safe pool.
package gpool
import (
"time"
"errors"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gtype"
"github.com/gogf/gf/g/container/glist"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gtimer"
"time"
)
// 对象池
// Object-Reusable Pool.
type Pool struct {
list *glist.List // 可用/闲置的文件指针链表
closed *gtype.Bool // 连接池是否已关闭
Expire int64 // (毫秒)闲置最大时间,超过该时间则被系统回收
NewFunc func()(interface{}, error) // 创建对象的方法定义
ExpireFunc func(interface{}) // 对象的过期销毁方法(当池对象销毁需要执行额外的销毁操作时,需要定义该方法)
// 例如: net.Conn, os.File等对象都需要执行额外关闭操作
list *glist.List // Available/idle list.
closed *gtype.Bool // Whether the pool is closed.
Expire int64 // Max idle time(ms), after which it is recycled.
NewFunc func()(interface{}, error) // Callback function to create item.
ExpireFunc func(interface{}) // Expired destruction function for objects.
// This function needs to be defined when the pool object
// needs to perform additional destruction operations.
// Eg: net.Conn, os.File, etc.
}
// 对象池数据项
// Pool item.
type poolItem struct {
expire int64 // (毫秒)过期时间
value interface{} // 对象值
expire int64 // Expire time(millisecond).
value interface{} // Value.
}
// 创建一个对象池,为保证执行效率,过期时间一旦设定之后无法修改
// expire = 0表示不过期expire < 0表示使用完立即回收expire > 0表示超时回收
// 注意过期时间单位为**毫秒**
func New(expire int, newFunc...func() (interface{}, error)) *Pool {
// Creation function for object.
type NewFunc func() (interface{}, error)
// Destruction function for object.
type ExpireFunc func(interface{})
// New returns a new object pool.
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
// Expire:
// expire = 0 : not expired;
// expire < 0 : immediate recovery after use;
// expire > 0 : timeout recovery;
// Note that the expiration time unit is ** milliseconds **.
func New(expire int, newFunc NewFunc, expireFunc...ExpireFunc) *Pool {
r := &Pool {
list : glist.New(),
closed : gtype.NewBool(),
Expire : int64(expire),
NewFunc : newFunc,
}
if len(newFunc) > 0 {
r.NewFunc = newFunc[0]
if len(expireFunc) > 0 {
r.ExpireFunc = expireFunc[0]
}
go r.expireCheckingLoop()
gtimer.AddSingleton(time.Second, r.checkExpire)
return r
}
// 设置对象过期销毁时的关闭方法
func (p *Pool) SetExpireFunc(expireFunc func(interface{})) {
p.ExpireFunc = expireFunc
}
// 放一个临时对象到池中
// Put puts an item to pool.
func (p *Pool) Put(value interface{}) {
item := &poolItem {
value : value,
@ -65,12 +75,12 @@ func (p *Pool) Put(value interface{}) {
p.list.PushBack(item)
}
// 清空对象池
// Clear clears pool, which means it will remove all items from pool.
func (p *Pool) Clear() {
p.list.RemoveAll()
}
// 从池中获得一个临时对象
// Get picks an item from pool.
func (p *Pool) Get() (interface{}, error) {
for !p.closed.Val() {
if r := p.list.PopFront(); r != nil {
@ -88,33 +98,33 @@ func (p *Pool) Get() (interface{}, error) {
return nil, errors.New("pool is empty")
}
// 查询当前池中的对象数量
// Size returns the count of available items of pool.
func (p *Pool) Size() int {
return p.list.Len()
}
// 关闭池
// Close closes the pool.
func (p *Pool) Close() {
p.closed.Set(true)
}
// 超时检测循环
func (p *Pool) expireCheckingLoop() {
for !p.closed.Val() {
for {
if r := p.list.PopFront(); r != nil {
item := r.(*poolItem)
if item.expire == 0 || item.expire > gtime.Millisecond() {
p.list.PushFront(item)
break
}
if p.ExpireFunc != nil {
p.ExpireFunc(item.value)
}
} else {
// checkExpire secondly removes expired items from pool.
func (p *Pool) checkExpire() {
if p.closed.Val() {
gtimer.Exit()
}
for {
if r := p.list.PopFront(); r != nil {
item := r.(*poolItem)
if item.expire == 0 || item.expire > gtime.Millisecond() {
p.list.PushFront(item)
break
}
if p.ExpireFunc != nil {
p.ExpireFunc(item.value)
}
} else {
break
}
time.Sleep(time.Second)
}
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
@ -13,7 +13,7 @@ import (
"sync"
)
var pool = New(99999999)
var pool = New(99999999, nil)
var syncp = sync.Pool{}
func BenchmarkGPoolPut(b *testing.B) {

View File

@ -1,70 +1,81 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// 并发安全的动态队列.
// 特点:
// 1、动态队列初始化速度快
// 2、动态的队列大小(不限大小)
// 3、取数据时如果队列为空那么会阻塞等待
// Package gqueue provides a dynamic/static concurrent-safe queue.
//
// Features:
//
// 1. FIFO queue(data -> list -> chan);
//
// 2. Fast creation and initialization;
//
// 3. Support dynamic queue size(unlimited queue size);
//
// 4. Blocking when reading data from queue;
//
package gqueue
import (
"gitee.com/johng/gf/g/container/glist"
"github.com/gogf/gf/g/container/glist"
"math"
)
// 0、这是一个先进先出的队列(chan <-- list)
// 1、当创建Queue对象时限定大小那么等同于一个同步的chan并发安全队列
// 2、不限制大小时list链表用以存储数据临时chan负责为客户端读取数据当从chan获取数据时list往chan中不停补充数据
// 3、由于功能主体是chan那么操作仍然像chan那样具有阻塞效果
type Queue struct {
limit int // 队列限制大小
queue chan interface{} // 用于队列写入限制
list *glist.List // 数据链表
events chan struct{} // 通知chan当不限制队列大小时的写入事件通知
closeChan chan struct{} // 关闭channel
limit int // Limit for queue size.
list *glist.List // Underlying list structure for data maintaining.
events chan struct{} // Events for data writing.
closed chan struct{} // Events for queue closing.
C chan interface{} // Underlying channel for data reading.
}
const (
// 默认临时队列大小,注意是临时的
// Size for queue buffer.
gDEFAULT_QUEUE_SIZE = 10000
)
// 队列大小为非必须参数,默认不限制
// New returns an empty queue object.
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited by default.
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib chan.
func New(limit...int) *Queue {
size := gDEFAULT_QUEUE_SIZE
if len(limit) > 0 {
size = limit[0]
}
q := &Queue {
list : glist.New(),
queue : make(chan interface{}, size),
events : make(chan struct{}, math.MaxInt32),
closeChan : make(chan struct{}, 0),
closed : make(chan struct{}, 0),
}
if len(limit) > 0 {
q.limit = size
q.limit = limit[0]
q.C = make(chan interface{}, limit[0])
} else {
// 如果是动态队列大小那么额外会运行一个goroutine
q.list = glist.New()
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
go q.startAsyncLoop()
}
return q
}
// 异步list->chan同步队列
// startAsyncLoop starts an asynchronous goroutine,
// which handles the data synchronization from list <q.list> to channel <q.C>.
func (q *Queue) startAsyncLoop() {
for {
select {
case <- q.closeChan:
case <- q.closed:
return
case <- q.events:
// 循环读取链表,直到为空才跳出
for {
if v := q.list.PopFront(); v != nil {
q.queue <- v
if length := q.list.Len(); length > 0 {
array := make([]interface{}, length)
for i := 0; i < length; i++ {
if e := q.list.Front(); e != nil {
array[i] = q.list.Remove(e)
} else {
break
}
}
for _, v := range array {
q.C <- v
}
} else {
break
}
@ -73,34 +84,35 @@ func (q *Queue) startAsyncLoop() {
}
}
// 将数据压入队列, 队头
// Push pushes the data <v> into the queue.
// Note that it would panics if Push is called after the queue is closed.
func (q *Queue) Push(v interface{}) {
if q.limit > 0 {
q.queue <- v
q.C <- v
} else {
q.list.PushBack(v)
if len(q.events) == 0 {
q.events <- struct{}{}
}
q.events <- struct{}{}
}
}
// 从队头先进先出地从队列取出一项数据
// Pop pops an item from the queue in FIFO way.
// Note that it would return nil immediately if Pop is called after the queue is closed.
func (q *Queue) Pop() interface{} {
return <- q.queue
return <- q.C
}
// 关闭队列(通知所有通过Pop*阻塞的协程退出)
// Close closes the queue.
// Notice: It would notify all goroutines return immediately,
// which are being blocked reading by Pop method.
func (q *Queue) Close() {
q.list.RemoveAll()
close(q.queue)
close(q.C)
close(q.events)
close(q.closeChan)
close(q.closed)
}
// 获取当前队列大小
// Size returns the length of the queue.
func (q *Queue) Size() int {
return len(q.queue) + q.list.Len()
return len(q.C) + q.list.Len()
}

View File

@ -1,8 +1,8 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
@ -10,39 +10,41 @@ package gqueue_test
import (
"testing"
"gitee.com/johng/gf/g/container/gqueue"
"github.com/gogf/gf/g/container/gqueue"
)
var length = 10000000
var bn = 20000000
var length = 1000000
var qstatic = gqueue.New(length)
var qdynamic = gqueue.New()
var cany = make(chan interface{}, length)
var cint = make(chan int, length)
func Benchmark_GqueueStaticPushAndPop(b *testing.B) {
func Benchmark_Gqueue_StaticPushAndPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qstatic.Push(i)
qstatic.Pop()
}
}
func Benchmark_GqueueDynamicPush(b *testing.B) {
func Benchmark_Gqueue_DynamicPush(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Push(i)
}
}
func Benchmark_ChannelInterfacePushAndPop(b *testing.B) {
func Benchmark_Gqueue_DynamicPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Pop()
}
}
func Benchmark_Channel_PushAndPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
cany <- i
<- cany
}
}
func Benchmark_ChannelIntPushAndPop(b *testing.B) {
for i := 0; i < b.N; i++ {
cint <- i
<- cint
}
}

View File

@ -1,29 +1,30 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// 并发安全的环.
// Package gring provides a concurrent-safe/unsafe ring(circular lists).
package gring
import (
"container/ring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
)
type Ring struct {
mu *rwmutex.RWMutex // 互斥锁
ring *ring.Ring // 底层环形数据结构
len *gtype.Int // 数据大小(已使用的大小)
cap *gtype.Int // 总长度(分配的环大小,包括未使用的数据项数量)
dirty *gtype.Bool // 标记环是否脏了(需要重新计算大小,当环大小发生改变时做标记)
mu *rwmutex.RWMutex
ring *ring.Ring // Underlying ring.
len *gtype.Int // Length(already used size).
cap *gtype.Int // Capability(>=len).
dirty *gtype.Bool // Dirty, which means the len and cap should be recalculated.
// It's marked dirty when the size of ring changes.
}
func New(cap int, safe...bool) *Ring {
func New(cap int, unsafe...bool) *Ring {
return &Ring {
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
ring : ring.New(cap),
len : gtype.NewInt(),
cap : gtype.NewInt(cap),
@ -31,7 +32,7 @@ func New(cap int, safe...bool) *Ring {
}
}
// 返回当前环指向的数据项值
// Val returns the item's value of current position.
func (r *Ring) Val() interface{} {
r.mu.RLock()
v := r.ring.Value
@ -39,19 +40,19 @@ func (r *Ring) Val() interface{} {
return v
}
// 返回当前环已有数据项大小
// Len returns the size of ring.
func (r *Ring) Len() int {
r.checkAndUpdateLenAndCap()
return r.len.Val()
}
// 返回当前环总大小(包含未使用长度)
// Cap returns the capacity of ring.
func (r *Ring) Cap() int {
r.checkAndUpdateLenAndCap()
return r.cap.Val()
}
// 检测并执行len和cap的更新(两者必须一起更新)
// Checks and updates the len and cap of ring when ring is dirty.
func (r *Ring) checkAndUpdateLenAndCap() {
if !r.dirty.Val() {
return
@ -73,7 +74,7 @@ func (r *Ring) checkAndUpdateLenAndCap() {
r.dirty.Set(false)
}
// 当前位置设置数据项值
// Set sets value to the item of current position.
func (r *Ring) Set(value interface{}) *Ring {
r.mu.Lock()
if r.ring.Value == nil {
@ -84,7 +85,7 @@ func (r *Ring) Set(value interface{}) *Ring {
return r
}
// Set & Next
// Put sets <value> to current item of ring and moves position to next item.
func (r *Ring) Put(value interface{}) *Ring {
r.mu.Lock()
if r.ring.Value == nil {
@ -96,7 +97,8 @@ func (r *Ring) Put(value interface{}) *Ring {
return r
}
// 环往后(n > 0)或者往前(n < 0)移动n个元素
// Move moves n % r.Len() elements backward (n < 0) or forward (n >= 0)
// in the ring and returns that ring element. r must not be empty.
func (r *Ring) Move(n int) *Ring {
r.mu.Lock()
r.ring = r.ring.Move(n)
@ -104,7 +106,7 @@ func (r *Ring) Move(n int) *Ring {
return r
}
// 环往前移动1个元素
// Prev returns the previous ring element. r must not be empty.
func (r *Ring) Prev() *Ring {
r.mu.Lock()
r.ring = r.ring.Prev()
@ -112,7 +114,7 @@ func (r *Ring) Prev() *Ring {
return r
}
// 环往后移动1个元素
// Next returns the next ring element. r must not be empty.
func (r *Ring) Next() *Ring {
r.mu.Lock()
r.ring = r.ring.Next()
@ -120,11 +122,22 @@ func (r *Ring) Next() *Ring {
return r
}
// 连接两个环,两个环的大小和位置都有可能会发生改变。
// 1、链接将环r与环s连接使得r.Next()成为s并返回r.Next()的原始值。r一定不能为空。
// 2、如果r和s指向同一个环则链接它们会从环中移除r和s之间的元素。
// 删除的元素形成子环,结果是对该子环的引用(如果没有删除元素结果仍然是r.Next()的原始值而不是nil)。
// 3、如果r和s指向不同的环则链接它们会创建一个单独的环并在r之后插入s的元素。 结果指向插入后s的最后一个元素后面的元素。
// Link connects ring r with ring s such that r.Next()
// becomes s and returns the original value for r.Next().
// r must not be empty.
//
// If r and s point to the same ring, linking
// them removes the elements between r and s from the ring.
// The removed elements form a subring and the result is a
// reference to that subring (if no elements were removed,
// the result is still the original value for r.Next(),
// and not nil).
//
// If r and s point to different rings, linking
// them creates a single ring with the elements of s inserted
// after r. The result points to the element following the
// last element of s after insertion.
//
func (r *Ring) Link(s *Ring) *Ring {
r.mu.Lock()
s.mu.Lock()
@ -136,7 +149,10 @@ func (r *Ring) Link(s *Ring) *Ring {
return r
}
// 删除环中当前位置往后的n个数据项
// Unlink removes n % r.Len() elements from the ring r, starting
// at r.Next(). If n % r.Len() == 0, r remains unchanged.
// The result is the removed subring. r must not be empty.
//
func (r *Ring) Unlink(n int) *Ring {
r.mu.Lock()
r.ring = r.ring.Unlink(n)
@ -145,10 +161,12 @@ func (r *Ring) Unlink(n int) *Ring {
return r
}
// 读锁遍历往后只读遍历回调函数返回true表示继续遍历否则退出遍历
// RLockIteratorNext iterates and locks reading forward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) RLockIteratorNext(f func(value interface{}) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring.Value) {
return
}
@ -159,10 +177,12 @@ func (r *Ring) RLockIteratorNext(f func(value interface{}) bool) {
}
}
// 读锁遍历往前只读遍历回调函数返回true表示继续遍历否则退出遍历
// RLockIteratorPrev iterates and locks reading backward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) RLockIteratorPrev(f func(value interface{}) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring.Value) {
return
}
@ -173,10 +193,12 @@ func (r *Ring) RLockIteratorPrev(f func(value interface{}) bool) {
}
}
// 写锁遍历往后写遍历回调函数返回true表示继续遍历否则退出遍历
// LockIteratorNext iterates and locks writing forward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) LockIteratorNext(f func(item *ring.Ring) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring) {
return
}
@ -187,10 +209,12 @@ func (r *Ring) LockIteratorNext(f func(item *ring.Ring) bool) {
}
}
// 写锁遍历往前写遍历回调函数返回true表示继续遍历否则退出遍历
// LockIteratorPrev iterates and locks writing backward
// with given callback function <f> within RWMutex.RLock.
// If <f> returns true, then it continues iterating; or false to stop.
func (r *Ring) LockIteratorPrev(f func(item *ring.Ring) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring) {
return
}
@ -201,7 +225,7 @@ func (r *Ring) LockIteratorPrev(f func(item *ring.Ring) bool) {
}
}
// 从当前位置,往后只读完整遍历,返回非空数据项值构成的数组
// SliceNext returns a copy of all item values as slice forward from current position.
func (r *Ring) SliceNext() []interface{} {
s := make([]interface{}, 0)
r.mu.RLock()
@ -217,7 +241,7 @@ func (r *Ring) SliceNext() []interface{} {
return s
}
// 从当前位置,往前只读完整遍历,返回非空数据项值构成的数组
// SlicePrev returns a copy of all item values as slice backward from current position.
func (r *Ring) SlicePrev() []interface{} {
s := make([]interface{}, 0)
r.mu.RLock()

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"

View File

@ -0,0 +1,229 @@
package gring_test
import (
"container/ring"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/container/gring"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
type Student struct {
position int
name string
upgrade bool
}
func TestRing_Val(t *testing.T) {
gtest.Case(t, func() {
//定义cap 为3的ring类型数据
r := gring.New(3, true)
//分别给3个元素初始化赋值
r.Put(&Student{1,"jimmy", true})
r.Put(&Student{2,"tom", true})
r.Put(&Student{3,"alon", false})
//元素取值并判断和预设值是否相等
gtest.Assert(r.Val().(*Student).name,"jimmy")
//从当前位置往后移两个元素
r.Move(2)
gtest.Assert(r.Val().(*Student).name,"alon")
//更新元素值
//测试 value == nil
r.Set(nil)
gtest.Assert(r.Val(),nil)
//测试value != nil
r.Set(&Student{3, "jack", true})
})
}
func TestRing_CapLen(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(10)
r.Put("goframe")
//cap长度 10
gtest.Assert(r.Cap(), 10)
//已有数据项 1
gtest.Assert(r.Len(), 1)
})
}
func TestRing_Position(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(2)
r.Put(1)
r.Put(2)
//往后移动1个元素
r.Next()
gtest.Assert(r.Val(),2)
//往前移动1个元素
r.Prev()
gtest.Assert(r.Val(),1)
})
}
func TestRing_Link(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(3)
r.Put(1)
r.Put(2)
r.Put(3)
s := gring.New(2)
s.Put("a")
s.Put("b")
rs := r.Link(s)
gtest.Assert(rs.Move(2).Val(), "b")
})
}
func TestRing_Unlink(t *testing.T) {
gtest.Case(t, func() {
r := gring.New(5)
for i := 0; i< 5; i++ {
r.Put(i+1)
}
// 1 2 3 4
// 删除当前位置往后的2个数据返回被删除的数据
// 重新计算s len
s := r.Unlink(2) // 2 3
gtest.Assert(s.Val(), 2)
gtest.Assert(s.Len(), 1)
})
}
func TestRing_Slice(t *testing.T) {
gtest.Case(t, func() {
ringLen := 5
r := gring.New(ringLen)
for i := 0; i< ringLen; i++ {
r.Put(i+1)
}
r.Move(2) // 3
array := r.SliceNext() // [3 4 5 1 2]
gtest.Assert(array[0], 3)
gtest.Assert(len(array), 5)
//判断array是否等于[3 4 5 1 2]
ra := []int{3,4,5,1,2}
gtest.Assert(ra, array)
//第3个元素设为nil
r.Set(nil)
array2 := r.SliceNext() //[4 5 1 2]
//返回当前位置往后不为空的元素数组长度为4
gtest.Assert(array2, g.Slice{4,5,1,2})
array3 := r.SlicePrev() //[2 1 5 4]
gtest.Assert(array3, g.Slice{2,1,5,4})
s := gring.New(ringLen)
for i := 0; i< ringLen; i++ {
s.Put(i+1)
}
array4 := s.SlicePrev() // []
gtest.Assert(array4, g.Slice{1,5,4,3,2})
})
}
func TestRing_RLockIterator(t *testing.T) {
gtest.Case(t, func() {
ringLen := 5
r := gring.New(ringLen)
//ring不存在有值元素
r.RLockIteratorNext(func(v interface{}) bool {
gtest.Assert(v, nil)
return false
})
r.RLockIteratorNext(func(v interface{}) bool {
gtest.Assert(v, nil)
return true
})
r.RLockIteratorPrev(func(v interface{}) bool {
gtest.Assert(v, nil)
return true
})
for i := 0; i< ringLen; i++ {
r.Put(i+1)
}
//回调函数返回true,RLockIteratorNext遍历5次,期望值分别是1、2、3、4、5
i := 0
r.RLockIteratorNext(func(v interface{}) bool {
gtest.Assert(v, i+1)
i++;
return true
})
//RLockIteratorPrev遍历1次返回 false,退出遍历
r.RLockIteratorPrev(func(v interface{}) bool {
gtest.Assert(v, 1)
return false
})
})
}
func TestRing_LockIterator(t *testing.T) {
gtest.Case(t, func() {
ringLen := 5
r := gring.New(ringLen)
//不存在有值元素
r.LockIteratorNext(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return false
})
r.LockIteratorNext(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return false
})
r.LockIteratorNext(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return true
})
r.LockIteratorPrev(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return false
})
r.LockIteratorPrev(func(item *ring.Ring) bool {
gtest.Assert(item.Value, nil)
return true
})
//ring初始化元素值
for i := 0; i< ringLen; i++ {
r.Put(i+1)
}
//往后遍历组成数据 [1,2,3,4,5]
array1 := g.Slice{1,2,3,4,5}
ii := 0
r.LockIteratorNext(func(item *ring.Ring) bool {
//校验每一次遍历取值是否是期望值
gtest.Assert(item.Value, array1[ii])
ii++;
return true
})
//往后取3个元素组成数组
//获得 [1,5,4]
i := 0
a := g.Slice{1,5,4}
r.LockIteratorPrev(func(item *ring.Ring) bool {
if i > 2 {
return false
}
gtest.Assert(item.Value, a[i])
i++;
return true
})
})
}

View File

@ -1,15 +1,325 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// 并发安全的集合SET.
// Package gset provides kinds of concurrent-safe/unsafe sets.
package gset
type Set = InterfaceSet
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"strings"
)
// 默认Set类型
func New(safe...bool) *Set {
return NewInterfaceSet(safe...)
type Set struct {
mu *rwmutex.RWMutex
m map[interface{}]struct{}
}
// New create and returns a new set, which contains un-repeated items.
// The param <unsafe> used to specify whether using set in un-concurrent-safety,
// which is false in default.
func New(unsafe...bool) *Set {
return NewSet(unsafe...)
}
// See New.
func NewSet(unsafe...bool) *Set {
return &Set{
m : make(map[interface{}]struct{}),
mu : rwmutex.New(unsafe...),
}
}
// NewFrom returns a new set from <items>.
// Parameter <items> can be either a variable of any type, or a slice.
func NewFrom(items interface{}, unsafe...bool) *Set {
m := make(map[interface{}]struct{})
for _, v := range gconv.Interfaces(items) {
m[v] = struct{}{}
}
return &Set{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// Iterator iterates the set with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *Set) Iterator(f func (v interface{}) bool) *Set {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *Set) Add(item...interface{}) *Set {
set.mu.Lock()
for _, v := range item {
set.m[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// Contains checks whether the set contains <item>.
func (set *Set) Contains(item interface{}) bool {
set.mu.RLock()
_, exists := set.m[item]
set.mu.RUnlock()
return exists
}
// Remove deletes <item> from set.
func (set *Set) Remove(item interface{}) *Set {
set.mu.Lock()
delete(set.m, item)
set.mu.Unlock()
return set
}
// Size returns the size of the set.
func (set *Set) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// Clear deletes all items of the set.
func (set *Set) Clear() *Set {
set.mu.Lock()
set.m = make(map[interface{}]struct{})
set.mu.Unlock()
return set
}
// Slice returns the a of items of the set as slice.
func (set *Set) Slice() []interface{} {
set.mu.RLock()
i := 0
ret := make([]interface{}, len(set.m))
for item := range set.m {
ret[i] = item
i++
}
set.mu.RUnlock()
return ret
}
// Join joins items with a string <glue>.
func (set *Set) Join(glue string) string {
return strings.Join(gconv.Strings(set.Slice()), ",")
}
// String returns items as a string, which are joined by char ','.
func (set *Set) String() string {
return set.Join(",")
}
// LockFunc locks writing with callback function <f>.
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) {
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
}
// RLockFunc locks reading with callback function <f>.
func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) {
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
}
// Equal checks whether the two sets equal.
func (set *Set) Equal(other *Set) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
if len(set.m) != len(other.m) {
return false
}
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// IsSubsetOf checks whether the current set is a sub-set of <other>.
func (set *Set) IsSubsetOf(other *Set) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// Union returns a new set which is the union of <set> and <others>.
// Which means, all the items in <newSet> are in <set> or in <others>.
func (set *Set) Union(others ... *Set) (newSet *Set) {
newSet = NewSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
newSet.m[k] = v
}
if set != other {
for k, v := range other.m {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Diff returns a new set which is the difference set from <set> to <others>.
// Which means, all the items in <newSet> are in <set> but not in <others>.
func (set *Set) Diff(others...*Set) (newSet *Set) {
newSet = NewSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set == other {
continue
}
other.mu.RLock()
for k, v := range set.m {
if _, ok := other.m[k]; !ok {
newSet.m[k] = v
}
}
other.mu.RUnlock()
}
return
}
// Intersect returns a new set which is the intersection from <set> to <others>.
// Which means, all the items in <newSet> are in <set> and also in <others>.
func (set *Set) Intersect(others...*Set) (newSet *Set) {
newSet = NewSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
if _, ok := other.m[k]; ok {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Complement returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> are in <full> and not in <set>.
//
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *Set) Complement(full *Set) (newSet *Set) {
newSet = NewSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
full.mu.RLock()
defer full.mu.RUnlock()
}
for k, v := range full.m {
if _, ok := set.m[k]; !ok {
newSet.m[k] = v
}
}
return
}
// Merge adds items from <others> sets into <set>.
func (set *Set) Merge(others ... *Set) *Set {
set.mu.Lock()
defer set.mu.Unlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range other.m {
set.m[k] = v
}
if set != other {
other.mu.RUnlock()
}
}
return set
}
// Sum sums items.
// Note: The items should be converted to int type,
// or you'd get a result that you unexpected.
func (set *Set) Sum() (sum int) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
sum += gconv.Int(k)
}
return
}
// Pops randomly pops an item from set.
func (set *Set) Pop(size int) interface{} {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
return k
}
return nil
}
// Pops randomly pops <size> items from set.
func (set *Set) Pops(size int) []interface{} {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.m) {
size = len(set.m)
}
index := 0
array := make([]interface{}, size)
for k, _ := range set.m {
array[index] = k
index++
if index == size {
break
}
}
return array
}

View File

@ -1,16 +1,16 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
// You can obtain one at https://github.com/gogf/gf.
//
package gset
import (
"fmt"
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"strings"
)
type IntSet struct {
@ -18,99 +18,302 @@ type IntSet struct {
m map[int]struct{}
}
func NewIntSet(safe...bool) *IntSet {
// New create and returns a new set, which contains un-repeated items.
// The param <unsafe> used to specify whether using set in un-concurrent-safety,
// which is false in default.
func NewIntSet(unsafe...bool) *IntSet {
return &IntSet{
m : make(map[int]struct{}),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *IntSet) Iterator(f func (v int) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, _ := range this.m {
// NewIntSetFrom returns a new set from <items>.
func NewIntSetFrom(items []int, unsafe...bool) *IntSet {
m := make(map[int]struct{})
for _, v := range items {
m[v] = struct{}{}
}
return &IntSet{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// Iterator iterates the set with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *IntSet) Iterator(f func (v int) bool) *IntSet {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
return set
}
// 设置键
func (this *IntSet) Add(item int) *IntSet {
this.mu.Lock()
this.m[item] = struct{}{}
this.mu.Unlock()
return this
// Add adds one or multiple items to the set.
func (set *IntSet) Add(item...int) *IntSet {
set.mu.Lock()
for _, v := range item {
set.m[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// 批量添加设置键
func (this *IntSet) BatchAdd(items []int) *IntSet {
this.mu.Lock()
for _, item := range items {
this.m[item] = struct{}{}
}
this.mu.Unlock()
return this
}
// 键是否存在
func (this *IntSet) Contains(item int) bool {
this.mu.RLock()
_, exists := this.m[item]
this.mu.RUnlock()
// Contains checks whether the set contains <item>.
func (set *IntSet) Contains(item int) bool {
set.mu.RLock()
_, exists := set.m[item]
set.mu.RUnlock()
return exists
}
// 删除键值对
func (this *IntSet) Remove(key int) {
this.mu.Lock()
delete(this.m, key)
this.mu.Unlock()
// Remove deletes <item> from set.
func (set *IntSet) Remove(item int) *IntSet {
set.mu.Lock()
delete(set.m, item)
set.mu.Unlock()
return set
}
// 大小
func (this *IntSet) Size() int {
this.mu.RLock()
l := len(this.m)
this.mu.RUnlock()
// Size returns the size of the set.
func (set *IntSet) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// 清空set
func (this *IntSet) Clear() {
this.mu.Lock()
this.m = make(map[int]struct{})
this.mu.Unlock()
// Clear deletes all items of the set.
func (set *IntSet) Clear() *IntSet {
set.mu.Lock()
set.m = make(map[int]struct{})
set.mu.Unlock()
return set
}
// 转换为数组
func (this *IntSet) Slice() []int {
this.mu.RLock()
ret := make([]int, len(this.m))
// Slice returns the a of items of the set as slice.
func (set *IntSet) Slice() []int {
set.mu.RLock()
ret := make([]int, len(set.m))
i := 0
for item := range this.m {
ret[i] = item
for k, _ := range set.m {
ret[i] = k
i++
}
this.mu.RUnlock()
set.mu.RUnlock()
return ret
}
// 转换为字符串
func (this *IntSet) String() string {
return fmt.Sprint(this.Slice())
// Join joins items with a string <glue>.
func (set *IntSet) Join(glue string) string {
return strings.Join(gconv.Strings(set.Slice()), ",")
}
func (this *IntSet) LockFunc(f func(m map[int]struct{})) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
// String returns items as a string, which are joined by char ','.
func (set *IntSet) String() string {
return set.Join(",")
}
func (this *IntSet) RLockFunc(f func(m map[int]struct{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
// LockFunc locks writing with callback function <f>.
func (set *IntSet) LockFunc(f func(m map[int]struct{})) {
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
}
// RLockFunc locks reading with callback function <f>.
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) {
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
}
// Equal checks whether the two sets equal.
func (set *IntSet) Equal(other *IntSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
if len(set.m) != len(other.m) {
return false
}
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// IsSubsetOf checks whether the current set is a sub-set of <other>.
func (set *IntSet) IsSubsetOf(other *IntSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// Union returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> are in <set> or in <other>.
func (set *IntSet) Union(others ... *IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
newSet.m[k] = v
}
if set != other {
for k, v := range other.m {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Diff returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> are in <set> but not in <other>.
func (set *IntSet) Diff(others...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set == other {
continue
}
other.mu.RLock()
for k, v := range set.m {
if _, ok := other.m[k]; !ok {
newSet.m[k] = v
}
}
other.mu.RUnlock()
}
return
}
// Intersect returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> are in <set> and also in <other>.
func (set *IntSet) Intersect(others...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
if _, ok := other.m[k]; ok {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Complement returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> are in <full> and not in <set>.
//
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
full.mu.RLock()
defer full.mu.RUnlock()
}
for k, v := range full.m {
if _, ok := set.m[k]; !ok {
newSet.m[k] = v
}
}
return
}
// Merge adds items from <others> sets into <set>.
func (set *IntSet) Merge(others ... *IntSet) *IntSet {
set.mu.Lock()
defer set.mu.Unlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range other.m {
set.m[k] = v
}
if set != other {
other.mu.RUnlock()
}
}
return set
}
// Sum sums items.
// Note: The items should be converted to int type,
// or you'd get a result that you unexpected.
func (set *IntSet) Sum() (sum int) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
sum += k
}
return
}
// Pops randomly pops an item from set.
func (set *IntSet) Pop(size int) int {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
return k
}
return 0
}
// Pops randomly pops <size> items from set.
func (set *IntSet) Pops(size int) []int {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.m) {
size = len(set.m)
}
index := 0
array := make([]int, size)
for k, _ := range set.m {
array[index] = k
index++
if index == size {
break
}
}
return array
}

View File

@ -1,114 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
//
package gset
import (
"fmt"
"gitee.com/johng/gf/g/container/internal/rwmutex"
)
type InterfaceSet struct {
mu *rwmutex.RWMutex
m map[interface{}]struct{}
}
func NewInterfaceSet(safe...bool) *InterfaceSet {
return &InterfaceSet{
m : make(map[interface{}]struct{}),
mu : rwmutex.New(safe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *InterfaceSet) Iterator(f func (v interface{}) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, _ := range this.m {
if !f(k) {
break
}
}
}
// 添加
func (this *InterfaceSet) Add(item interface{}) *InterfaceSet {
this.mu.Lock()
this.m[item] = struct{}{}
this.mu.Unlock()
return this
}
// 批量添加
func (this *InterfaceSet) BatchAdd(items []interface{}) *InterfaceSet {
this.mu.Lock()
for _, item := range items {
this.m[item] = struct{}{}
}
this.mu.Unlock()
return this
}
// 键是否存在
func (this *InterfaceSet) Contains(item interface{}) bool {
this.mu.RLock()
_, exists := this.m[item]
this.mu.RUnlock()
return exists
}
// 删除键值对
func (this *InterfaceSet) Remove(key interface{}) {
this.mu.Lock()
delete(this.m, key)
this.mu.Unlock()
}
// 大小
func (this *InterfaceSet) Size() int {
this.mu.RLock()
l := len(this.m)
this.mu.RUnlock()
return l
}
// 清空set
func (this *InterfaceSet) Clear() {
this.mu.Lock()
this.m = make(map[interface{}]struct{})
this.mu.Unlock()
}
// 转换为数组
func (this *InterfaceSet) Slice() []interface{} {
this.mu.RLock()
i := 0
ret := make([]interface{}, len(this.m))
for item := range this.m {
ret[i] = item
i++
}
this.mu.RUnlock()
return ret
}
// 转换为字符串
func (this *InterfaceSet) String() string {
return fmt.Sprint(this.Slice())
}
func (this *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
}
func (this *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
}

View File

@ -1,15 +1,16 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
//
package gset
import (
"fmt"
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"strings"
)
type StringSet struct {
@ -17,99 +18,303 @@ type StringSet struct {
m map[string]struct{}
}
func NewStringSet(safe...bool) *StringSet {
return &StringSet{
// New create and returns a new set, which contains un-repeated items.
// The param <unsafe> used to specify whether using set in un-concurrent-safety,
// which is false in default.
func NewStringSet(unsafe...bool) *StringSet {
return &StringSet {
m : make(map[string]struct{}),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (this *StringSet) Iterator(f func (v string) bool) {
this.mu.RLock()
defer this.mu.RUnlock()
for k, _ := range this.m {
// NewStringSetFrom returns a new set from <items>.
func NewStringSetFrom(items []string, unsafe...bool) *StringSet {
m := make(map[string]struct{})
for _, v := range items {
m[v] = struct{}{}
}
return &StringSet{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// Iterator iterates the set with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *StringSet) Iterator(f func (v string) bool) *StringSet {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
if !f(k) {
break
}
}
return set
}
// 设置键
func (this *StringSet) Add(item string) *StringSet {
this.mu.Lock()
this.m[item] = struct{}{}
this.mu.Unlock()
return this
// Add adds one or multiple items to the set.
func (set *StringSet) Add(item...string) *StringSet {
set.mu.Lock()
for _, v := range item {
set.m[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// 批量添加设置键
func (this *StringSet) BatchAdd(items []string) *StringSet {
this.mu.Lock()
for _, item := range items {
this.m[item] = struct{}{}
}
this.mu.Unlock()
return this
}
// 键是否存在
func (this *StringSet) Contains(item string) bool {
this.mu.RLock()
_, exists := this.m[item]
this.mu.RUnlock()
// Contains checks whether the set contains <item>.
func (set *StringSet) Contains(item string) bool {
set.mu.RLock()
_, exists := set.m[item]
set.mu.RUnlock()
return exists
}
// 删除键值对
func (this *StringSet) Remove(key string) {
this.mu.Lock()
delete(this.m, key)
this.mu.Unlock()
// Remove deletes <item> from set.
func (set *StringSet) Remove(item string) *StringSet {
set.mu.Lock()
delete(set.m, item)
set.mu.Unlock()
return set
}
// 大小
func (this *StringSet) Size() int {
this.mu.RLock()
l := len(this.m)
this.mu.RUnlock()
// Size returns the size of the set.
func (set *StringSet) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// 清空set
func (this *StringSet) Clear() {
this.mu.Lock()
this.m = make(map[string]struct{})
this.mu.Unlock()
// Clear deletes all items of the set.
func (set *StringSet) Clear() *StringSet {
set.mu.Lock()
set.m = make(map[string]struct{})
set.mu.Unlock()
return set
}
// 转换为数组
func (this *StringSet) Slice() []string {
this.mu.RLock()
ret := make([]string, len(this.m))
// Slice returns the a of items of the set as slice.
func (set *StringSet) Slice() []string {
set.mu.RLock()
ret := make([]string, len(set.m))
i := 0
for item := range this.m {
for item := range set.m {
ret[i] = item
i++
}
this.mu.RUnlock()
set.mu.RUnlock()
return ret
}
// 转换为字符串
func (this *StringSet) String() string {
return fmt.Sprint(this.Slice())
// Join joins items with a string <glue>.
func (set *StringSet) Join(glue string) string {
return strings.Join(set.Slice(), ",")
}
func (this *StringSet) LockFunc(f func(m map[string]struct{})) {
this.mu.Lock(true)
defer this.mu.Unlock(true)
f(this.m)
// String returns items as a string, which are joined by char ','.
func (set *StringSet) String() string {
return set.Join(",")
}
func (this *StringSet) RLockFunc(f func(m map[string]struct{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
// LockFunc locks writing with callback function <f>.
func (set *StringSet) LockFunc(f func(m map[string]struct{})) {
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
}
// RLockFunc locks reading with callback function <f>.
func (set *StringSet) RLockFunc(f func(m map[string]struct{})) {
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
}
// Equal checks whether the two sets equal.
func (set *StringSet) Equal(other *StringSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
if len(set.m) != len(other.m) {
return false
}
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// IsSubsetOf checks whether the current set is a sub-set of <other>.
func (set *StringSet) IsSubsetOf(other *StringSet) bool {
if set == other {
return true
}
set.mu.RLock()
defer set.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key := range set.m {
if _, ok := other.m[key]; !ok {
return false
}
}
return true
}
// Union returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> are in <set> or in <other>.
func (set *StringSet) Union(others ... *StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
newSet.m[k] = v
}
if set != other {
for k, v := range other.m {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Diff returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> are in <set> but not in <other>.
func (set *StringSet) Diff(others...*StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set == other {
continue
}
other.mu.RLock()
for k, v := range set.m {
if _, ok := other.m[k]; !ok {
newSet.m[k] = v
}
}
other.mu.RUnlock()
}
return
}
// Intersect returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> are in <set> and also in <other>.
func (set *StringSet) Intersect(others...*StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range set.m {
if _, ok := other.m[k]; ok {
newSet.m[k] = v
}
}
if set != other {
other.mu.RUnlock()
}
}
return
}
// Complement returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> are in <full> and not in <set>.
//
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *StringSet) Complement(full *StringSet) (newSet *StringSet) {
newSet = NewStringSet(true)
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
full.mu.RLock()
defer full.mu.RUnlock()
}
for k, v := range full.m {
if _, ok := set.m[k]; !ok {
newSet.m[k] = v
}
}
return
}
// Merge adds items from <others> sets into <set>.
func (set *StringSet) Merge(others ... *StringSet) *StringSet {
set.mu.Lock()
defer set.mu.Unlock()
for _, other := range others {
if set != other {
other.mu.RLock()
}
for k, v := range other.m {
set.m[k] = v
}
if set != other {
other.mu.RUnlock()
}
}
return set
}
// Sum sums items.
// Note: The items should be converted to int type,
// or you'd get a result that you unexpected.
func (set *StringSet) Sum() (sum int) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
sum += gconv.Int(k)
}
return
}
// Pops randomly pops an item from set.
func (set *StringSet) Pop(size int) string {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.m {
return k
}
return ""
}
// Pops randomly pops <size> items from set.
func (set *StringSet) Pops(size int) []string {
set.mu.RLock()
defer set.mu.RUnlock()
if size > len(set.m) {
size = len(set.m)
}
index := 0
array := make([]string, size)
for k, _ := range set.m {
array[index] = k
index++
if index == size {
break
}
}
return array
}

View File

@ -1,73 +0,0 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// go test *.go -bench=".*"
package gset_test
import (
"testing"
"strconv"
"gitee.com/johng/gf/g/container/gset"
)
var ints = gset.NewIntSet()
var itfs = gset.NewInterfaceSet()
var strs = gset.NewStringSet()
func BenchmarkIntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Add(i)
}
}
func BenchmarkIntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Contains(i)
}
}
func BenchmarkIntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Remove(i)
}
}
func BenchmarkInterfaceSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Add(i)
}
}
func BenchmarkInterfaceSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Contains(i)
}
}
func BenchmarkInterfaceSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Remove(i)
}
}
func BenchmarkStringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Add(strconv.Itoa(i))
}
}
func BenchmarkStringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Contains(strconv.Itoa(i))
}
}
func BenchmarkStringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Remove(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,130 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
package gset_test
import (
"testing"
"strconv"
"github.com/gogf/gf/g/container/gset"
)
var ints = gset.NewIntSet()
var itfs = gset.NewSet()
var strs = gset.NewStringSet()
var intsUnsafe = gset.NewIntSet(true)
var itfsUnsafe = gset.NewSet(true)
var strsUnsafe = gset.NewStringSet(true)
func Benchmark_IntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Add(i)
}
}
func Benchmark_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Contains(i)
}
}
func Benchmark_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Remove(i)
}
}
func Benchmark_Set_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Add(i)
}
}
func Benchmark_Set_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Contains(i)
}
}
func Benchmark_Set_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Remove(i)
}
}
func Benchmark_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Add(strconv.Itoa(i))
}
}
func Benchmark_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Contains(strconv.Itoa(i))
}
}
func Benchmark_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Remove(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_Set_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_Set_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_Set_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Add(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Contains(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Remove(strconv.Itoa(i))
}
}

View File

@ -0,0 +1,160 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go
package gset_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func TestIntSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(1).Add(2)
s.Add([]int{3,4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestIntSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v int) bool {
a1.Append(1)
return false
})
s.Iterator(func(v int) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestIntSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[int]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[int]struct{}) {
gtest.Assert(m, map[int]struct{}{
3 : struct{}{},
2 : struct{}{},
})
})
})
}
func TestIntSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestIntSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestIntSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
}
func TestIntSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
}
func TestIntSet_Intersect(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
}
func TestIntSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}

View File

@ -0,0 +1,160 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go
package gset_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func TestStringSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("1").Add("2")
s.Add([]string{"3","4"}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN("1", s.Slice())
gtest.AssertIN("2", s.Slice())
gtest.AssertIN("3", s.Slice())
gtest.AssertIN("4", s.Slice())
gtest.AssertNI("0", s.Slice())
gtest.Assert(s.Contains("4"), true)
gtest.Assert(s.Contains("5"), false)
s.Remove("1")
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestStringSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("2").Add("3")
gtest.Assert(s.Size(), 3)
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v string) bool {
a1.Append("1")
return false
})
s.Iterator(func(v string) bool {
a2.Append("1")
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestStringSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewStringSet()
s.Add("1").Add("2").Add("3")
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[string]struct{}) {
delete(m, "1")
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[string]struct{}) {
gtest.Assert(m, map[string]struct{}{
"3" : struct{}{},
"2" : struct{}{},
})
})
})
}
func TestStringSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s3 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestStringSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s3 := gset.NewStringSet()
s1.Add("1").Add("2")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestStringSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2")
s2.Add("3").Add("4")
s3 := s1.Union(s2)
gtest.Assert(s3.Contains("1"), true)
gtest.Assert(s3.Contains("2"), true)
gtest.Assert(s3.Contains("3"), true)
gtest.Assert(s3.Contains("4"), true)
})
}
func TestStringSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains("1"), true)
gtest.Assert(s3.Contains("2"), true)
gtest.Assert(s3.Contains("3"), false)
gtest.Assert(s3.Contains("4"), false)
})
}
func TestStringSet_Intersect(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains("1"), false)
gtest.Assert(s3.Contains("2"), false)
gtest.Assert(s3.Contains("3"), true)
gtest.Assert(s3.Contains("4"), false)
})
}
func TestStringSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewStringSet()
s2 := gset.NewStringSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains("1"), false)
gtest.Assert(s3.Contains("2"), false)
gtest.Assert(s3.Contains("4"), true)
gtest.Assert(s3.Contains("5"), true)
})
}

View File

@ -0,0 +1,160 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go
package gset_test
import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gset"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func TestSet_Basic(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(1).Add(2)
s.Add([]interface{}{3,4}...)
gtest.Assert(s.Size(), 4)
gtest.AssertIN(1, s.Slice())
gtest.AssertIN(2, s.Slice())
gtest.AssertIN(3, s.Slice())
gtest.AssertIN(4, s.Slice())
gtest.AssertNI(0, s.Slice())
gtest.Assert(s.Contains(4), true)
gtest.Assert(s.Contains(5), false)
s.Remove(1)
gtest.Assert(s.Size(), 3)
s.Clear()
gtest.Assert(s.Size(), 0)
})
}
func TestSet_Iterator(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
a1 := garray.New()
a2 := garray.New()
s.Iterator(func(v interface{}) bool {
a1.Append(1)
return false
})
s.Iterator(func(v interface{}) bool {
a2.Append(1)
return true
})
gtest.Assert(a1.Len(), 1)
gtest.Assert(a2.Len(), 3)
})
}
func TestSet_LockFunc(t *testing.T) {
gtest.Case(t, func() {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
gtest.Assert(s.Size(), 3)
s.LockFunc(func(m map[interface{}]struct{}) {
delete(m, 1)
})
gtest.Assert(s.Size(), 2)
s.RLockFunc(func(m map[interface{}]struct{}) {
gtest.Assert(m, map[interface{}]struct{}{
3 : struct{}{},
2 : struct{}{},
})
})
})
}
func TestSet_Equal(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.Equal(s2), true)
gtest.Assert(s1.Equal(s3), false)
})
}
func TestSet_IsSubsetOf(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
gtest.Assert(s1.IsSubsetOf(s2), true)
gtest.Assert(s2.IsSubsetOf(s3), true)
gtest.Assert(s1.IsSubsetOf(s3), true)
gtest.Assert(s2.IsSubsetOf(s1), false)
gtest.Assert(s3.IsSubsetOf(s2), false)
})
}
func TestSet_Union(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s3 := s1.Union(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), true)
})
}
func TestSet_Diff(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Diff(s2)
gtest.Assert(s3.Contains(1), true)
gtest.Assert(s3.Contains(2), true)
gtest.Assert(s3.Contains(3), false)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Intersect(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Intersect(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(3), true)
gtest.Assert(s3.Contains(4), false)
})
}
func TestSet_Complement(t *testing.T) {
gtest.Case(t, func() {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s3 := s1.Complement(s2)
gtest.Assert(s3.Contains(1), false)
gtest.Assert(s3.Contains(2), false)
gtest.Assert(s3.Contains(4), true)
gtest.Assert(s3.Contains(5), true)
})
}

View File

@ -0,0 +1,8 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gtree provides concurrent-safe/unsafe tree containers.
package gtree

View File

@ -0,0 +1,701 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree
import (
"fmt"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
// AVLTree holds elements of the AVL tree.
type AVLTree struct {
mu *rwmutex.RWMutex
root *AVLTreeNode
comparator func(v1, v2 interface{}) int
size int
}
// AVLTreeNode is a single element within the tree.
type AVLTreeNode struct {
Key interface{}
Value interface{}
parent *AVLTreeNode
children [2]*AVLTreeNode
b int8
}
// NewAVLTree instantiates an AVL tree with the custom comparator.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewAVLTree(comparator func(v1, v2 interface{}) int, unsafe...bool) *AVLTree {
return &AVLTree{
mu : rwmutex.New(unsafe...),
comparator: comparator,
}
}
// NewAVLTreeFrom instantiates an AVL tree with the custom comparator and data map.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe...bool) *AVLTree {
tree := NewAVLTree(comparator, unsafe...)
for k, v := range data {
tree.put(k, v, nil, &tree.root)
}
return tree
}
// Clone returns a new tree with a copy of current tree.
func (tree *AVLTree) Clone(unsafe ...bool) *AVLTree {
newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set inserts node into the tree.
func (tree *AVLTree) Set(key interface{}, value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.put(key, value, nil, &tree.root)
}
// Sets batch sets key-values to the tree.
func (tree *AVLTree) Sets(data map[interface{}]interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for key, value := range data {
tree.put(key, value, nil, &tree.root)
}
}
// Search searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *AVLTree) Search(key interface{}) (value interface{}, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.doSearch(key)
}
// doSearch searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *AVLTree) doSearch(key interface{}) (value interface{}, found bool) {
n := tree.root
for n != nil {
cmp := tree.comparator(key, n.Key)
switch {
case cmp == 0: return n.Value, true
case cmp < 0: n = n.children[0]
case cmp > 0: n = n.children[1]
}
}
return nil, false
}
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
func (tree *AVLTree) Get(key interface{}) (value interface{}) {
value, _ = tree.Search(key)
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (tree *AVLTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
tree.mu.Lock()
defer tree.mu.Unlock()
if v, ok := tree.doSearch(key); ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
tree.put(key, value, nil, &tree.root)
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (tree *AVLTree) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (tree *AVLTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (tree *AVLTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVar(key interface{}) *gvar.Var {
return gvar.New(tree.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(tree.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (tree *AVLTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *AVLTree) SetIfNotExist(key interface{}, value interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *AVLTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (tree *AVLTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f)
return true
}
return false
}
// Contains checks whether <key> exists in the tree.
func (tree *AVLTree) Contains(key interface{}) bool {
_, ok := tree.Search(key)
return ok
}
// Remove remove the node from the tree by key.
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *AVLTree) Remove(key interface{}) (value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
value, _ = tree.remove(key, &tree.root)
return
}
// Removes batch deletes values of the tree by <keys>.
func (tree *AVLTree) Removes(keys []interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.remove(key, &tree.root)
}
}
// IsEmpty returns true if tree does not contain any nodes.
func (tree *AVLTree) IsEmpty() bool {
return tree.Size() == 0
}
// Size returns number of nodes in the tree.
func (tree *AVLTree) Size() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.size
}
// Keys returns all keys in asc order.
func (tree *AVLTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
return true
})
return keys
}
// Values returns all values in asc order based on the key.
func (tree *AVLTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
return true
})
return values
}
// Left returns the minimum element of the AVL tree
// or nil if the tree is empty.
func (tree *AVLTree) Left() *AVLTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.bottom(0)
if tree.mu.IsSafe() {
return &AVLTreeNode {
Key : node.Key,
Value : node.Value,
}
}
return node
}
// Right returns the maximum element of the AVL tree
// or nil if the tree is empty.
func (tree *AVLTree) Right() *AVLTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.bottom(1)
if tree.mu.IsSafe() {
return &AVLTreeNode {
Key : node.Key,
Value : node.Value,
}
}
return node
}
// Floor Finds floor node of the input key, return the floor node or nil if no ceiling is found.
// Second return parameter is true if floor was found, otherwise false.
//
// Floor node is defined as the largest node that is smaller than or equal to the given node.
// A floor node may not be found, either because the tree is empty, or because
// all nodes in the tree is larger than the given node.
//
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *AVLTree) Floor(key interface{}) (floor *AVLTreeNode, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
found = false
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
switch {
case c == 0: return n, true
case c < 0: n = n.children[0]
case c > 0:
floor, found = n, true
n = n.children[1]
}
}
if found {
return
}
return nil, false
}
// Ceiling finds ceiling node of the input key, return the ceiling node or nil if no ceiling is found.
// Second return parameter is true if ceiling was found, otherwise false.
//
// Ceiling node is defined as the smallest node that is larger than or equal to the given node.
// A ceiling node may not be found, either because the tree is empty, or because
// all nodes in the tree is smaller than the given node.
//
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *AVLTree) Ceiling(key interface{}) (floor *AVLTreeNode, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
found = false
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
switch {
case c == 0: return n, true
case c > 0: n = n.children[1]
case c < 0:
floor, found = n, true
n = n.children[0]
}
}
if found {
return
}
return nil, false
}
// Clear removes all nodes from the tree.
func (tree *AVLTree) Clear() {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.root = nil
tree.size = 0
}
// String returns a string representation of container
func (tree *AVLTree) String() string {
tree.mu.RLock()
defer tree.mu.RUnlock()
str := "AVLTree\n"
if tree.size != 0 {
output(tree.root, "", true, &str)
}
return str
}
// Print prints the tree to stdout.
func (tree *AVLTree) Print() {
fmt.Println(tree.String())
}
// Map returns all key-value items as map.
func (tree *AVLTree) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{}, tree.Size())
tree.IteratorAsc(func(key, value interface{}) bool {
m[key] = value
return true
})
return m
}
// Flip exchanges key-value of the tree to value-key.
// Note that you should guarantee the value is the same type as key,
// or else the comparator would panic.
//
// If the type of value is different with key, you pass the new <comparator>.
func (tree *AVLTree) Flip(comparator...func(v1, v2 interface{}) int) {
t := (*AVLTree)(nil)
if len(comparator) > 0 {
t = NewAVLTree(comparator[0], !tree.mu.IsSafe())
} else {
t = NewAVLTree(tree.comparator, !tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.put(value, key, nil, &t.root)
return true
})
tree.mu.Lock()
tree.root = t.root
tree.size = t.size
tree.mu.Unlock()
}
// Iterator is alias of IteratorAsc.
func (tree *AVLTree) Iterator(f func (key, value interface{}) bool) {
tree.IteratorAsc(f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *AVLTree) IteratorAsc(f func (key, value interface{}) bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.bottom(0)
for node != nil {
if !f(node.Key, node.Value) {
return
}
node = node.Next()
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *AVLTree) IteratorDesc(f func (key, value interface{}) bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.bottom(1)
for node != nil {
if !f(node.Key, node.Value) {
return
}
node = node.Prev()
}
}
func (tree *AVLTree) put(key interface{}, value interface{}, p *AVLTreeNode, qp **AVLTreeNode) bool {
q := *qp
if q == nil {
tree.size++
*qp = &AVLTreeNode{Key: key, Value: value, parent: p}
return true
}
c := tree.comparator(key, q.Key)
if c == 0 {
q.Key = key
q.Value = value
return false
}
if c < 0 {
c = -1
} else {
c = 1
}
a := (c + 1) / 2
if tree.put(key, value, q, &q.children[a]) {
return putFix(int8(c), qp)
}
return false
}
func (tree *AVLTree) remove(key interface{}, qp **AVLTreeNode) (value interface{}, fix bool) {
q := *qp
if q == nil {
return nil, false
}
c := tree.comparator(key, q.Key)
if c == 0 {
tree.size--
value = q.Value
fix = true
if q.children[1] == nil {
if q.children[0] != nil {
q.children[0].parent = q.parent
}
*qp = q.children[0]
return
}
if removeMin(&q.children[1], &q.Key, &q.Value) {
return value, removeFix(-1, qp)
}
return
}
if c < 0 {
c = -1
} else {
c = 1
}
a := (c + 1) / 2
value, fix = tree.remove(key, &q.children[a])
if fix {
return value, removeFix(int8(-c), qp)
}
return nil, false
}
func removeMin(qp **AVLTreeNode, minKey *interface{}, minVal *interface{}) bool {
q := *qp
if q.children[0] == nil {
*minKey = q.Key
*minVal = q.Value
if q.children[1] != nil {
q.children[1].parent = q.parent
}
*qp = q.children[1]
return true
}
fix := removeMin(&q.children[0], minKey, minVal)
if fix {
return removeFix(1, qp)
}
return false
}
func putFix(c int8, t **AVLTreeNode) bool {
s := *t
if s.b == 0 {
s.b = c
return true
}
if s.b == -c {
s.b = 0
return false
}
if s.children[(c+1)/2].b == c {
s = singleRotate(c, s)
} else {
s = doubleRotate(c, s)
}
*t = s
return false
}
func removeFix(c int8, t **AVLTreeNode) bool {
s := *t
if s.b == 0 {
s.b = c
return false
}
if s.b == -c {
s.b = 0
return true
}
a := (c + 1) / 2
if s.children[a].b == 0 {
s = rotate(c, s)
s.b = -c
*t = s
return false
}
if s.children[a].b == c {
s = singleRotate(c, s)
} else {
s = doubleRotate(c, s)
}
*t = s
return true
}
func singleRotate(c int8, s *AVLTreeNode) *AVLTreeNode {
s.b = 0
s = rotate(c, s)
s.b = 0
return s
}
func doubleRotate(c int8, s *AVLTreeNode) *AVLTreeNode {
a := (c + 1) / 2
r := s.children[a]
s.children[a] = rotate(-c, s.children[a])
p := rotate(c, s)
switch {
default:
s.b = 0
r.b = 0
case p.b == c:
s.b = -c
r.b = 0
case p.b == -c:
s.b = 0
r.b = c
}
p.b = 0
return p
}
func rotate(c int8, s *AVLTreeNode) *AVLTreeNode {
a := (c + 1) / 2
r := s.children[a]
s.children[a] = r.children[a^1]
if s.children[a] != nil {
s.children[a].parent = s
}
r.children[a^1] = s
r.parent = s.parent
s.parent = r
return r
}
func (tree *AVLTree) bottom(d int) *AVLTreeNode {
n := tree.root
if n == nil {
return nil
}
for c := n.children[d]; c != nil; c = n.children[d] {
n = c
}
return n
}
// Prev returns the previous element in an inorder
// walk of the AVL tree.
func (node *AVLTreeNode) Prev() *AVLTreeNode {
return node.walk1(0)
}
// Next returns the next element in an inorder
// walk of the AVL tree.
func (node *AVLTreeNode) Next() *AVLTreeNode {
return node.walk1(1)
}
func (node *AVLTreeNode) walk1(a int) *AVLTreeNode {
if node == nil {
return nil
}
n := node
if n.children[a] != nil {
n = n.children[a]
for n.children[a^1] != nil {
n = n.children[a^1]
}
return n
}
p := n.parent
for p != nil && p.children[a] == n {
n = p
p = p.parent
}
return p
}
func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
if node.children[1] != nil {
newPrefix := prefix
if isTail {
newPrefix += "│ "
} else {
newPrefix += " "
}
output(node.children[1], newPrefix, false, str)
}
*str += prefix
if isTail {
*str += "└── "
} else {
*str += "┌── "
}
*str += fmt.Sprintf("%v\n", node.Key)
if node.children[0] != nil {
newPrefix := prefix
if isTail {
newPrefix += " "
} else {
newPrefix += "│ "
}
output(node.children[0], newPrefix, true, str)
}
}

View File

@ -0,0 +1,844 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree
import (
"bytes"
"fmt"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
"strings"
)
// BTree holds elements of the B-tree.
type BTree struct {
mu *rwmutex.RWMutex
root *BTreeNode
comparator func(v1, v2 interface{}) int
size int // Total number of keys in the tree
m int // order (maximum number of children)
}
// BTreeNode is a single element within the tree.
type BTreeNode struct {
Parent *BTreeNode
Entries []*BTreeEntry // Contained keys in node
Children []*BTreeNode // Children nodes
}
// BTreeEntry represents the key-value pair contained within nodes.
type BTreeEntry struct {
Key interface{}
Value interface{}
}
// NewBTree instantiates a B-tree with <m> (maximum number of children) and a custom key comparator.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
// Note that the <m> must be greater or equal than 3, or else it panics.
func NewBTree(m int, comparator func(v1, v2 interface{}) int, unsafe...bool) *BTree {
if m < 3 {
panic("Invalid order, should be at least 3")
}
return &BTree{
comparator : comparator,
mu : rwmutex.New(unsafe...),
m : m,
}
}
// NewBTreeFrom instantiates a B-tree with <m> (maximum number of children), a custom key comparator and data map.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe...bool) *BTree {
tree := NewBTree(m, comparator, unsafe...)
for k, v := range data {
tree.doSet(k, v)
}
return tree
}
// Clone returns a new tree with a copy of current tree.
func (tree *BTree) Clone(unsafe ...bool) *BTree {
newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set inserts key-value item into the tree.
func (tree *BTree) Set(key interface{}, value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.doSet(key, value)
}
// doSet inserts key-value pair node into the tree.
// If key already exists, then its value is updated with the new value.
func (tree *BTree) doSet(key interface{}, value interface{}) {
entry := &BTreeEntry{Key: key, Value: value}
if tree.root == nil {
tree.root = &BTreeNode{Entries: []*BTreeEntry{entry}, Children: []*BTreeNode{}}
tree.size++
return
}
if tree.insert(tree.root, entry) {
tree.size++
}
}
// Sets batch sets key-values to the tree.
func (tree *BTree) Sets(data map[interface{}]interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for k, v := range data {
tree.doSet(k, v)
}
}
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
func (tree *BTree) Get(key interface{}) (value interface{}) {
value, _ = tree.Search(key)
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (tree *BTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
tree.mu.Lock()
defer tree.mu.Unlock()
if entry := tree.doSearch(key); entry != nil {
return entry.Value
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
tree.doSet(key, value)
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (tree *BTree) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (tree *BTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (tree *BTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVar(key interface{}) *gvar.Var {
return gvar.New(tree.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(tree.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (tree *BTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *BTree) SetIfNotExist(key interface{}, value interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *BTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (tree *BTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f)
return true
}
return false
}
// Contains checks whether <key> exists in the tree.
func (tree *BTree) Contains(key interface{}) bool {
_, ok := tree.Search(key)
return ok
}
// Remove remove the node from the tree by key.
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (tree *BTree) doRemove(key interface{}) (value interface{}) {
node, index, found := tree.searchRecursively(tree.root, key)
if found {
value = node.Entries[index].Value
tree.delete(node, index)
tree.size--
}
return
}
// Remove removes the node from the tree by <key>.
func (tree *BTree) Remove(key interface{}) (value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
return tree.doRemove(key)
}
// Removes batch deletes values of the tree by <keys>.
func (tree *BTree) Removes(keys []interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.doRemove(key)
}
}
// Empty returns true if tree does not contain any nodes
func (tree *BTree) IsEmpty() bool {
return tree.Size() == 0
}
// Size returns number of nodes in the tree.
func (tree *BTree) Size() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.size
}
// Keys returns all keys in asc order.
func (tree *BTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
return true
})
return keys
}
// Values returns all values in asc order based on the key.
func (tree *BTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
return true
})
return values
}
// Map returns all key-value items as map.
func (tree *BTree) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{}, tree.Size())
tree.IteratorAsc(func(key, value interface{}) bool {
m[key] = value
return true
})
return m
}
// Clear removes all nodes from the tree.
func (tree *BTree) Clear() {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.root = nil
tree.size = 0
}
// Height returns the height of the tree.
func (tree *BTree) Height() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.root.height()
}
// Left returns the left-most (min) entry or nil if tree is empty.
func (tree *BTree) Left() *BTreeEntry {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.left(tree.root)
return node.Entries[0]
}
// Right returns the right-most (max) entry or nil if tree is empty.
func (tree *BTree) Right() *BTreeEntry {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.right(tree.root)
return node.Entries[len(node.Entries) - 1]
}
// String returns a string representation of container (for debugging purposes)
func (tree *BTree) String() string {
tree.mu.RLock()
defer tree.mu.RUnlock()
var buffer bytes.Buffer
if _, err := buffer.WriteString("BTree\n"); err != nil {
}
if tree.size != 0 {
tree.output(&buffer, tree.root, 0, true)
}
return buffer.String()
}
// Search searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *BTree) Search(key interface{}) (value interface{}, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node, index, found := tree.searchRecursively(tree.root, key)
if found {
return node.Entries[index].Value, true
}
return nil, false
}
// Search searches the tree with given <key> without mutex.
// It returns the entry if found or otherwise nil.
func (tree *BTree) doSearch(key interface{}) *BTreeEntry {
node, index, found := tree.searchRecursively(tree.root, key)
if found {
return node.Entries[index]
}
return nil
}
// Print prints the tree to stdout.
func (tree *BTree) Print() {
fmt.Println(tree.String())
}
// Iterator is alias of IteratorAsc.
func (tree *BTree) Iterator(f func (key, value interface{}) bool) {
tree.IteratorAsc(f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) IteratorAsc(f func (key, value interface{}) bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.left(tree.root)
if node == nil {
return
}
entry := node.Entries[0]
loop:
if entry == nil {
return
}
if !f(entry.Key, entry.Value) {
return
}
// Find current entry position in current node
e, _ := tree.search(node, entry.Key)
// Try to go down to the child right of the current entry
if e + 1 < len(node.Children) {
node = node.Children[e + 1]
// Try to go down to the child left of the current node
for len(node.Children) > 0 {
node = node.Children[0]
}
// Return the left-most entry
entry = node.Entries[0]
goto loop
}
// Above assures that we have reached a leaf node, so return the next entry in current node (if any)
if e + 1 < len(node.Entries) {
entry = node.Entries[e + 1]
goto loop
}
// Reached leaf node and there are no entries to the right of the current entry, so go up to the parent
for node.Parent != nil {
node = node.Parent
// Find next entry position in current node (note: search returns the first equal or bigger than entry)
e, _ := tree.search(node, entry.Key)
// Check that there is a next entry position in current node
if e < len(node.Entries) {
entry = node.Entries[e]
goto loop
}
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) IteratorDesc(f func (key, value interface{}) bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.right(tree.root)
if node == nil {
return
}
entry := node.Entries[len(node.Entries) - 1]
loop:
if entry == nil {
return
}
if !f(entry.Key, entry.Value) {
return
}
// Find current entry position in current node
e, _ := tree.search(node, entry.Key)
// Try to go down to the child left of the current entry
if e < len(node.Children) {
node = node.Children[e]
// Try to go down to the child right of the current node
for len(node.Children) > 0 {
node = node.Children[len(node.Children) - 1]
}
// Return the right-most entry
entry = node.Entries[len(node.Entries) - 1]
goto loop
}
// Above assures that we have reached a leaf node, so return the previous entry in current node (if any)
if e - 1 >= 0 {
entry = node.Entries[e - 1]
goto loop
}
// Reached leaf node and there are no entries to the left of the current entry, so go up to the parent
for node.Parent != nil {
node = node.Parent
// Find previous entry position in current node (note: search returns the first equal or bigger than entry)
e, _ := tree.search(node, entry.Key)
// Check that there is a previous entry position in current node
if e - 1 >= 0 {
entry = node.Entries[e - 1]
goto loop
}
}
}
func (tree *BTree) output(buffer *bytes.Buffer, node *BTreeNode, level int, isTail bool) {
for e := 0; e < len(node.Entries)+1; e++ {
if e < len(node.Children) {
tree.output(buffer, node.Children[e], level+1, true)
}
if e < len(node.Entries) {
if _, err := buffer.WriteString(strings.Repeat(" ", level)); err != nil {
}
if _, err := buffer.WriteString(fmt.Sprintf("%v", node.Entries[e].Key) + "\n"); err != nil {
}
}
}
}
func (node *BTreeNode) height() int {
h := 0
n := node
for ; n != nil; n = n.Children[0] {
h++
if len(n.Children) == 0 {
break
}
}
return h
}
func (tree *BTree) isLeaf(node *BTreeNode) bool {
return len(node.Children) == 0
}
func (tree *BTree) isFull(node *BTreeNode) bool {
return len(node.Entries) == tree.maxEntries()
}
func (tree *BTree) shouldSplit(node *BTreeNode) bool {
return len(node.Entries) > tree.maxEntries()
}
func (tree *BTree) maxChildren() int {
return tree.m
}
func (tree *BTree) minChildren() int {
return (tree.m + 1) / 2 // ceil(m/2)
}
func (tree *BTree) maxEntries() int {
return tree.maxChildren() - 1
}
func (tree *BTree) minEntries() int {
return tree.minChildren() - 1
}
func (tree *BTree) middle() int {
// "-1" to favor right nodes to have more keys when splitting
return (tree.m - 1) / 2
}
// search searches only within the single node among its entries
func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) {
low, mid, high := 0, 0, len(node.Entries) - 1
for low <= high {
mid = (high + low) / 2
compare := tree.comparator(key, node.Entries[mid].Key)
switch {
case compare > 0: low = mid + 1
case compare < 0: high = mid - 1
case compare == 0: return mid, true
}
}
return low, false
}
// searchRecursively searches recursively down the tree starting at the startNode
func (tree *BTree) searchRecursively(startNode *BTreeNode, key interface{}) (node *BTreeNode, index int, found bool) {
if tree.size == 0 {
return nil, -1, false
}
node = startNode
for {
index, found = tree.search(node, key)
if found {
return node, index, true
}
if tree.isLeaf(node) {
return nil, -1, false
}
node = node.Children[index]
}
}
func (tree *BTree) insert(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
if tree.isLeaf(node) {
return tree.insertIntoLeaf(node, entry)
}
return tree.insertIntoInternal(node, entry)
}
func (tree *BTree) insertIntoLeaf(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
insertPosition, found := tree.search(node, entry.Key)
if found {
node.Entries[insertPosition] = entry
return false
}
// Insert entry's key in the middle of the node
node.Entries = append(node.Entries, nil)
copy(node.Entries[insertPosition+1:], node.Entries[insertPosition:])
node.Entries[insertPosition] = entry
tree.split(node)
return true
}
func (tree *BTree) insertIntoInternal(node *BTreeNode, entry *BTreeEntry) (inserted bool) {
insertPosition, found := tree.search(node, entry.Key)
if found {
node.Entries[insertPosition] = entry
return false
}
return tree.insert(node.Children[insertPosition], entry)
}
func (tree *BTree) split(node *BTreeNode) {
if !tree.shouldSplit(node) {
return
}
if node == tree.root {
tree.splitRoot()
return
}
tree.splitNonRoot(node)
}
func (tree *BTree) splitNonRoot(node *BTreeNode) {
middle := tree.middle()
parent := node.Parent
left := &BTreeNode{Entries: append([]*BTreeEntry(nil), node.Entries[:middle]...), Parent: parent}
right := &BTreeNode{Entries: append([]*BTreeEntry(nil), node.Entries[middle+1:]...), Parent: parent}
// Move children from the node to be split into left and right nodes
if !tree.isLeaf(node) {
left.Children = append([]*BTreeNode(nil), node.Children[:middle+1]...)
right.Children = append([]*BTreeNode(nil), node.Children[middle+1:]...)
setParent(left.Children, left)
setParent(right.Children, right)
}
insertPosition, _ := tree.search(parent, node.Entries[middle].Key)
// Insert middle key into parent
parent.Entries = append(parent.Entries, nil)
copy(parent.Entries[insertPosition+1:], parent.Entries[insertPosition:])
parent.Entries[insertPosition] = node.Entries[middle]
// Set child left of inserted key in parent to the created left node
parent.Children[insertPosition] = left
// Set child right of inserted key in parent to the created right node
parent.Children = append(parent.Children, nil)
copy(parent.Children[insertPosition+2:], parent.Children[insertPosition+1:])
parent.Children[insertPosition+1] = right
tree.split(parent)
}
func (tree *BTree) splitRoot() {
middle := tree.middle()
left := &BTreeNode{Entries: append([]*BTreeEntry(nil), tree.root.Entries[:middle]...)}
right := &BTreeNode{Entries: append([]*BTreeEntry(nil), tree.root.Entries[middle+1:]...)}
// Move children from the node to be split into left and right nodes
if !tree.isLeaf(tree.root) {
left.Children = append([]*BTreeNode(nil), tree.root.Children[:middle+1]...)
right.Children = append([]*BTreeNode(nil), tree.root.Children[middle+1:]...)
setParent(left.Children, left)
setParent(right.Children, right)
}
// Root is a node with one entry and two children (left and right)
newRoot := &BTreeNode{
Entries: []*BTreeEntry{tree.root.Entries[middle]},
Children: []*BTreeNode{left, right},
}
left.Parent = newRoot
right.Parent = newRoot
tree.root = newRoot
}
func setParent(nodes []*BTreeNode, parent *BTreeNode) {
for _, node := range nodes {
node.Parent = parent
}
}
func (tree *BTree) left(node *BTreeNode) *BTreeNode {
if tree.size == 0 {
return nil
}
current := node
for {
if tree.isLeaf(current) {
return current
}
current = current.Children[0]
}
}
func (tree *BTree) right(node *BTreeNode) *BTreeNode {
if tree.size == 0 {
return nil
}
current := node
for {
if tree.isLeaf(current) {
return current
}
current = current.Children[len(current.Children)-1]
}
}
// leftSibling returns the node's left sibling and child index (in parent) if it exists, otherwise (nil,-1)
// key is any of keys in node (could even be deleted).
func (tree *BTree) leftSibling(node *BTreeNode, key interface{}) (*BTreeNode, int) {
if node.Parent != nil {
index, _ := tree.search(node.Parent, key)
index--
if index >= 0 && index < len(node.Parent.Children) {
return node.Parent.Children[index], index
}
}
return nil, -1
}
// rightSibling returns the node's right sibling and child index (in parent) if it exists, otherwise (nil,-1)
// key is any of keys in node (could even be deleted).
func (tree *BTree) rightSibling(node *BTreeNode, key interface{}) (*BTreeNode, int) {
if node.Parent != nil {
index, _ := tree.search(node.Parent, key)
index++
if index < len(node.Parent.Children) {
return node.Parent.Children[index], index
}
}
return nil, -1
}
// delete deletes an entry in node at entries' index
// ref.: https://en.wikipedia.org/wiki/B-tree#Deletion
func (tree *BTree) delete(node *BTreeNode, index int) {
// deleting from a leaf node
if tree.isLeaf(node) {
deletedKey := node.Entries[index].Key
tree.deleteEntry(node, index)
tree.rebalance(node, deletedKey)
if len(tree.root.Entries) == 0 {
tree.root = nil
}
return
}
// deleting from an internal node
leftLargestNode := tree.right(node.Children[index]) // largest node in the left sub-tree (assumed to exist)
leftLargestEntryIndex := len(leftLargestNode.Entries) - 1
node.Entries[index] = leftLargestNode.Entries[leftLargestEntryIndex]
deletedKey := leftLargestNode.Entries[leftLargestEntryIndex].Key
tree.deleteEntry(leftLargestNode, leftLargestEntryIndex)
tree.rebalance(leftLargestNode, deletedKey)
}
// rebalance rebalances the tree after deletion if necessary and returns true, otherwise false.
// Note that we first delete the entry and then call rebalance, thus the passed deleted key as reference.
func (tree *BTree) rebalance(node *BTreeNode, deletedKey interface{}) {
// check if rebalancing is needed
if node == nil || len(node.Entries) >= tree.minEntries() {
return
}
// try to borrow from left sibling
leftSibling, leftSiblingIndex := tree.leftSibling(node, deletedKey)
if leftSibling != nil && len(leftSibling.Entries) > tree.minEntries() {
// rotate right
node.Entries = append([]*BTreeEntry{node.Parent.Entries[leftSiblingIndex]}, node.Entries...) // prepend parent's separator entry to node's entries
node.Parent.Entries[leftSiblingIndex] = leftSibling.Entries[len(leftSibling.Entries)-1]
tree.deleteEntry(leftSibling, len(leftSibling.Entries)-1)
if !tree.isLeaf(leftSibling) {
leftSiblingRightMostChild := leftSibling.Children[len(leftSibling.Children)-1]
leftSiblingRightMostChild.Parent = node
node.Children = append([]*BTreeNode{leftSiblingRightMostChild}, node.Children...)
tree.deleteChild(leftSibling, len(leftSibling.Children)-1)
}
return
}
// try to borrow from right sibling
rightSibling, rightSiblingIndex := tree.rightSibling(node, deletedKey)
if rightSibling != nil && len(rightSibling.Entries) > tree.minEntries() {
// rotate left
node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1]) // append parent's separator entry to node's entries
node.Parent.Entries[rightSiblingIndex-1] = rightSibling.Entries[0]
tree.deleteEntry(rightSibling, 0)
if !tree.isLeaf(rightSibling) {
rightSiblingLeftMostChild := rightSibling.Children[0]
rightSiblingLeftMostChild.Parent = node
node.Children = append(node.Children, rightSiblingLeftMostChild)
tree.deleteChild(rightSibling, 0)
}
return
}
// merge with siblings
if rightSibling != nil {
// merge with right sibling
node.Entries = append(node.Entries, node.Parent.Entries[rightSiblingIndex-1])
node.Entries = append(node.Entries, rightSibling.Entries...)
deletedKey = node.Parent.Entries[rightSiblingIndex-1].Key
tree.deleteEntry(node.Parent, rightSiblingIndex-1)
tree.appendChildren(node.Parent.Children[rightSiblingIndex], node)
tree.deleteChild(node.Parent, rightSiblingIndex)
} else if leftSibling != nil {
// merge with left sibling
entries := append([]*BTreeEntry(nil), leftSibling.Entries...)
entries = append(entries, node.Parent.Entries[leftSiblingIndex])
node.Entries = append(entries, node.Entries...)
deletedKey = node.Parent.Entries[leftSiblingIndex].Key
tree.deleteEntry(node.Parent, leftSiblingIndex)
tree.prependChildren(node.Parent.Children[leftSiblingIndex], node)
tree.deleteChild(node.Parent, leftSiblingIndex)
}
// make the merged node the root if its parent was the root and the root is empty
if node.Parent == tree.root && len(tree.root.Entries) == 0 {
tree.root = node
node.Parent = nil
return
}
// parent might underflow, so try to rebalance if necessary
tree.rebalance(node.Parent, deletedKey)
}
func (tree *BTree) prependChildren(fromNode *BTreeNode, toNode *BTreeNode) {
children := append([]*BTreeNode(nil), fromNode.Children...)
toNode.Children = append(children, toNode.Children...)
setParent(fromNode.Children, toNode)
}
func (tree *BTree) appendChildren(fromNode *BTreeNode, toNode *BTreeNode) {
toNode.Children = append(toNode.Children, fromNode.Children...)
setParent(fromNode.Children, toNode)
}
func (tree *BTree) deleteEntry(node *BTreeNode, index int) {
copy(node.Entries[index:], node.Entries[index+1:])
node.Entries[len(node.Entries)-1] = nil
node.Entries = node.Entries[:len(node.Entries)-1]
}
func (tree *BTree) deleteChild(node *BTreeNode, index int) {
if index >= len(node.Children) {
return
}
copy(node.Children[index:], node.Children[index+1:])
node.Children[len(node.Children)-1] = nil
node.Children = node.Children[:len(node.Children)-1]
}

View File

@ -0,0 +1,832 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree
import (
"fmt"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/rwmutex"
)
type color bool
const (
black, red color = true, false
)
// RedBlackTree holds elements of the red-black tree.
type RedBlackTree struct {
mu *rwmutex.RWMutex
root *RedBlackTreeNode
size int
comparator func(v1, v2 interface{}) int
}
// RedBlackTreeNode is a single element within the tree.
type RedBlackTreeNode struct {
Key interface{}
Value interface{}
color color
left *RedBlackTreeNode
right *RedBlackTreeNode
parent *RedBlackTreeNode
}
// NewRedBlackTree instantiates a red-black tree with the custom comparator.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewRedBlackTree(comparator func(v1, v2 interface{}) int, unsafe...bool) *RedBlackTree {
return &RedBlackTree {
mu : rwmutex.New(unsafe...),
comparator: comparator,
}
}
// NewRedBlackTreeFrom instantiates a red-black tree with the custom comparator and <data> map.
// The param <unsafe> used to specify whether using tree in un-concurrent-safety,
// which is false in default.
func NewRedBlackTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, unsafe...bool) *RedBlackTree {
tree := NewRedBlackTree(comparator, unsafe...)
for k, v := range data {
tree.doSet(k, v)
}
return tree
}
// Clone returns a new tree with a copy of current tree.
func (tree *RedBlackTree) Clone(unsafe ...bool) *RedBlackTree {
newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
// Set inserts key-value item into the tree.
func (tree *RedBlackTree) Set(key interface{}, value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.doSet(key, value)
}
// Sets batch sets key-values to the tree.
func (tree *RedBlackTree) Sets(data map[interface{}]interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for k, v := range data {
tree.doSet(k, v)
}
}
// doSet inserts key-value item into the tree without mutex.
func (tree *RedBlackTree) doSet(key interface{}, value interface{}) {
insertedNode := (*RedBlackTreeNode)(nil)
if tree.root == nil {
// Assert key is of comparator's type for initial tree
tree.comparator(key, key)
tree.root = &RedBlackTreeNode{Key: key, Value: value, color: red}
insertedNode = tree.root
} else {
node := tree.root
loop := true
for loop {
compare := tree.comparator(key, node.Key)
switch {
case compare == 0:
//node.Key = key
node.Value = value
return
case compare < 0:
if node.left == nil {
node.left = &RedBlackTreeNode{Key: key, Value: value, color: red}
insertedNode = node.left
loop = false
} else {
node = node.left
}
case compare > 0:
if node.right == nil {
node.right = &RedBlackTreeNode{Key: key, Value: value, color: red}
insertedNode = node.right
loop = false
} else {
node = node.right
}
}
}
insertedNode.parent = node
}
tree.insertCase1(insertedNode)
tree.size++
}
// Get searches the node in the tree by <key> and returns its value or nil if key is not found in tree.
func (tree *RedBlackTree) Get(key interface{}) (value interface{}) {
value, _ = tree.Search(key)
return
}
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
// if not exists, set value to the map with given <key>,
// or else just return the existing value.
//
// When setting value, if <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the hash map,
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (tree *RedBlackTree) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
tree.mu.Lock()
defer tree.mu.Unlock()
if node := tree.doSearch(key); node != nil {
return node.Value
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
tree.doSet(key, value)
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (tree *RedBlackTree) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, value)
} else {
return v
}
}
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (tree *RedBlackTree) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f())
} else {
return v
}
}
// GetOrSetFuncLock returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the hash map.
func (tree *RedBlackTree) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := tree.Search(key); !ok {
return tree.doSetWithLockCheck(key, f)
} else {
return v
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVar(key interface{}) *gvar.Var {
return gvar.New(tree.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(tree.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (tree *RedBlackTree) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(tree.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *RedBlackTree) SetIfNotExist(key interface{}, value interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, value)
return true
}
return false
}
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (tree *RedBlackTree) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f())
return true
}
return false
}
// SetIfNotExistFuncLock sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the hash map.
func (tree *RedBlackTree) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !tree.Contains(key) {
tree.doSetWithLockCheck(key, f)
return true
}
return false
}
// Contains checks whether <key> exists in the tree.
func (tree *RedBlackTree) Contains(key interface{}) bool {
_, ok := tree.Search(key)
return ok
}
// doRemove removes the node from the tree by <key> without mutex.
func (tree *RedBlackTree) doRemove(key interface{}) (value interface{}) {
child := (*RedBlackTreeNode)(nil)
node := tree.doSearch(key)
if node == nil {
return
}
value = node.Value
if node.left != nil && node.right != nil {
p := node.left.maximumNode()
node.Key = p.Key
node.Value = p.Value
node = p
}
if node.left == nil || node.right == nil {
if node.right == nil {
child = node.left
} else {
child = node.right
}
if node.color == black {
node.color = tree.nodeColor(child)
tree.deleteCase1(node)
}
tree.replaceNode(node, child)
if node.parent == nil && child != nil {
child.color = black
}
}
tree.size--
return
}
// Remove removes the node from the tree by <key>.
func (tree *RedBlackTree) Remove(key interface{}) (value interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
return tree.doRemove(key)
}
// Removes batch deletes values of the tree by <keys>.
func (tree *RedBlackTree) Removes(keys []interface{}) {
tree.mu.Lock()
defer tree.mu.Unlock()
for _, key := range keys {
tree.doRemove(key)
}
}
// IsEmpty returns true if tree does not contain any nodes.
func (tree *RedBlackTree) IsEmpty() bool {
return tree.Size() == 0
}
// Size returns number of nodes in the tree.
func (tree *RedBlackTree) Size() int {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.size
}
// Keys returns all keys in asc order.
func (tree *RedBlackTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
return true
})
return keys
}
// Values returns all values in asc order based on the key.
func (tree *RedBlackTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
return true
})
return values
}
// Map returns all key-value items as map.
func (tree *RedBlackTree) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{}, tree.Size())
tree.IteratorAsc(func(key, value interface{}) bool {
m[key] = value
return true
})
return m
}
// Left returns the left-most (min) node or nil if tree is empty.
func (tree *RedBlackTree) Left() *RedBlackTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.leftNode()
if tree.mu.IsSafe() {
return &RedBlackTreeNode{
Key : node.Key,
Value : node.Value,
}
}
return node
}
// Right returns the right-most (max) node or nil if tree is empty.
func (tree *RedBlackTree) Right() *RedBlackTreeNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.rightNode()
if tree.mu.IsSafe() {
return &RedBlackTreeNode{
Key : node.Key,
Value : node.Value,
}
}
return node
}
// leftNode returns the left-most (min) node or nil if tree is empty.
func (tree *RedBlackTree) leftNode() *RedBlackTreeNode {
p := (*RedBlackTreeNode)(nil)
n := tree.root
for n != nil {
p = n
n = n.left
}
return p
}
// rightNode returns the right-most (max) node or nil if tree is empty.
func (tree *RedBlackTree) rightNode() *RedBlackTreeNode {
p := (*RedBlackTreeNode)(nil)
n := tree.root
for n != nil {
p = n
n = n.right
}
return p
}
// Floor Finds floor node of the input <key>, return the floor node or nil if no floor is found.
//
// Floor node is defined as the largest node that its key is smaller than or equal to the given <key>.
// A floor node may not be found, either because the tree is empty, or because
// all nodes in the tree are larger than the given node.
func (tree *RedBlackTree) Floor(key interface{}) (floor *RedBlackTreeNode) {
tree.mu.RLock()
defer tree.mu.RUnlock()
found := false
node := tree.root
for node != nil {
compare := tree.comparator(key, node.Key)
switch {
case compare == 0:
return node
case compare < 0:
node = node.left
case compare > 0:
floor, found = node, true
node = node.right
}
}
if found {
return floor
}
return nil
}
// Ceiling finds ceiling node of the input <key>, return the ceiling node or nil if no ceiling is found.
//
// Ceiling node is defined as the smallest node that its key is larger than or equal to the given <key>.
// A ceiling node may not be found, either because the tree is empty, or because
// all nodes in the tree are smaller than the given node.
func (tree *RedBlackTree) Ceiling(key interface{}) (ceiling *RedBlackTreeNode) {
tree.mu.RLock()
defer tree.mu.RUnlock()
found := false
node := tree.root
for node != nil {
compare := tree.comparator(key, node.Key)
switch {
case compare == 0:
return node
case compare < 0:
ceiling, found = node, true
node = node.left
case compare > 0:
node = node.right
}
}
if found {
return ceiling
}
return nil
}
// Iterator is alias of IteratorAsc.
func (tree *RedBlackTree) Iterator(f func (key, value interface{}) bool) {
tree.IteratorAsc(f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *RedBlackTree) IteratorAsc(f func (key, value interface{}) bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.leftNode()
if node == nil {
return
}
loop:
if node == nil {
return
}
if !f(node.Key, node.Value) {
return
}
if node.right != nil {
node = node.right
for node.left != nil {
node = node.left
}
goto loop
}
if node.parent != nil {
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) <= 0 {
goto loop
}
}
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *RedBlackTree) IteratorDesc(f func (key, value interface{}) bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.rightNode()
if node == nil {
return
}
loop:
if node == nil {
return
}
if !f(node.Key, node.Value) {
return
}
if node.left != nil {
node = node.left
for node.right != nil {
node = node.right
}
goto loop
}
if node.parent != nil {
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) >= 0 {
goto loop
}
}
}
}
// Clear removes all nodes from the tree.
func (tree *RedBlackTree) Clear() {
tree.mu.Lock()
defer tree.mu.Unlock()
tree.root = nil
tree.size = 0
}
// String returns a string representation of container.
func (tree *RedBlackTree) String() string {
tree.mu.RLock()
defer tree.mu.RUnlock()
str := "RedBlackTree\n"
if tree.size != 0 {
tree.output(tree.root, "", true, &str)
}
return str
}
// Print prints the tree to stdout.
func (tree *RedBlackTree) Print() {
fmt.Println(tree.String())
}
// Search searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *RedBlackTree) Search(key interface{}) (value interface{}, found bool) {
tree.mu.RLock()
defer tree.mu.RUnlock()
node := tree.doSearch(key)
if node != nil {
return node.Value, true
}
return nil, false
}
// Flip exchanges key-value of the tree to value-key.
// Note that you should guarantee the value is the same type as key,
// or else the comparator would panic.
//
// If the type of value is different with key, you pass the new <comparator>.
func (tree *RedBlackTree) Flip(comparator...func(v1, v2 interface{}) int) {
t := (*RedBlackTree)(nil)
if len(comparator) > 0 {
t = NewRedBlackTree(comparator[0], !tree.mu.IsSafe())
} else {
t = NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.doSet(value, key)
return true
})
tree.mu.Lock()
tree.root = t.root
tree.size = t.size
tree.mu.Unlock()
}
func (tree *RedBlackTree) output(node *RedBlackTreeNode, prefix string, isTail bool, str *string) {
if node.right != nil {
newPrefix := prefix
if isTail {
newPrefix += "│ "
} else {
newPrefix += " "
}
tree.output(node.right, newPrefix, false, str)
}
*str += prefix
if isTail {
*str += "└── "
} else {
*str += "┌── "
}
*str += fmt.Sprintf("%v\n", node.Key)
if node.left != nil {
newPrefix := prefix
if isTail {
newPrefix += " "
} else {
newPrefix += "│ "
}
tree.output(node.left, newPrefix, true, str)
}
}
// doSearch searches the tree with given <key> without mutex.
// It returns the node if found or otherwise nil.
func (tree *RedBlackTree) doSearch(key interface{}) *RedBlackTreeNode {
node := tree.root
for node != nil {
compare := tree.comparator(key, node.Key)
switch {
case compare == 0: return node
case compare < 0: node = node.left
case compare > 0: node = node.right
}
}
return nil
}
func (node *RedBlackTreeNode) grandparent() *RedBlackTreeNode {
if node != nil && node.parent != nil {
return node.parent.parent
}
return nil
}
func (node *RedBlackTreeNode) uncle() *RedBlackTreeNode {
if node == nil || node.parent == nil || node.parent.parent == nil {
return nil
}
return node.parent.sibling()
}
func (node *RedBlackTreeNode) sibling() *RedBlackTreeNode {
if node == nil || node.parent == nil {
return nil
}
if node == node.parent.left {
return node.parent.right
}
return node.parent.left
}
func (tree *RedBlackTree) rotateLeft(node *RedBlackTreeNode) {
right := node.right
tree.replaceNode(node, right)
node.right = right.left
if right.left != nil {
right.left.parent = node
}
right.left = node
node.parent = right
}
func (tree *RedBlackTree) rotateRight(node *RedBlackTreeNode) {
left := node.left
tree.replaceNode(node, left)
node.left = left.right
if left.right != nil {
left.right.parent = node
}
left.right = node
node.parent = left
}
func (tree *RedBlackTree) replaceNode(old *RedBlackTreeNode, new *RedBlackTreeNode) {
if old.parent == nil {
tree.root = new
} else {
if old == old.parent.left {
old.parent.left = new
} else {
old.parent.right = new
}
}
if new != nil {
new.parent = old.parent
}
}
func (tree *RedBlackTree) insertCase1(node *RedBlackTreeNode) {
if node.parent == nil {
node.color = black
} else {
tree.insertCase2(node)
}
}
func (tree *RedBlackTree) insertCase2(node *RedBlackTreeNode) {
if tree.nodeColor(node.parent) == black {
return
}
tree.insertCase3(node)
}
func (tree *RedBlackTree) insertCase3(node *RedBlackTreeNode) {
uncle := node.uncle()
if tree.nodeColor(uncle) == red {
node.parent.color = black
uncle.color = black
node.grandparent().color = red
tree.insertCase1(node.grandparent())
} else {
tree.insertCase4(node)
}
}
func (tree *RedBlackTree) insertCase4(node *RedBlackTreeNode) {
grandparent := node.grandparent()
if node == node.parent.right && node.parent == grandparent.left {
tree.rotateLeft(node.parent)
node = node.left
} else if node == node.parent.left && node.parent == grandparent.right {
tree.rotateRight(node.parent)
node = node.right
}
tree.insertCase5(node)
}
func (tree *RedBlackTree) insertCase5(node *RedBlackTreeNode) {
node.parent.color = black
grandparent := node.grandparent()
grandparent.color = red
if node == node.parent.left && node.parent == grandparent.left {
tree.rotateRight(grandparent)
} else if node == node.parent.right && node.parent == grandparent.right {
tree.rotateLeft(grandparent)
}
}
func (node *RedBlackTreeNode) maximumNode() *RedBlackTreeNode {
if node == nil {
return nil
}
for node.right != nil {
return node.right
}
return node
}
func (tree *RedBlackTree) deleteCase1(node *RedBlackTreeNode) {
if node.parent == nil {
return
}
tree.deleteCase2(node)
}
func (tree *RedBlackTree) deleteCase2(node *RedBlackTreeNode) {
sibling := node.sibling()
if tree.nodeColor(sibling) == red {
node.parent.color = red
sibling.color = black
if node == node.parent.left {
tree.rotateLeft(node.parent)
} else {
tree.rotateRight(node.parent)
}
}
tree.deleteCase3(node)
}
func (tree *RedBlackTree) deleteCase3(node *RedBlackTreeNode) {
sibling := node.sibling()
if tree.nodeColor(node.parent) == black &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.left) == black &&
tree.nodeColor(sibling.right) == black {
sibling.color = red
tree.deleteCase1(node.parent)
} else {
tree.deleteCase4(node)
}
}
func (tree *RedBlackTree) deleteCase4(node *RedBlackTreeNode) {
sibling := node.sibling()
if tree.nodeColor(node.parent) == red &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.left) == black &&
tree.nodeColor(sibling.right) == black {
sibling.color = red
node.parent.color = black
} else {
tree.deleteCase5(node)
}
}
func (tree *RedBlackTree) deleteCase5(node *RedBlackTreeNode) {
sibling := node.sibling()
if node == node.parent.left &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.left) == red &&
tree.nodeColor(sibling.right) == black {
sibling.color = red
sibling.left.color = black
tree.rotateRight(sibling)
} else if node == node.parent.right &&
tree.nodeColor(sibling) == black &&
tree.nodeColor(sibling.right) == red &&
tree.nodeColor(sibling.left) == black {
sibling.color = red
sibling.right.color = black
tree.rotateLeft(sibling)
}
tree.deleteCase6(node)
}
func (tree *RedBlackTree) deleteCase6(node *RedBlackTreeNode) {
sibling := node.sibling()
sibling.color = tree.nodeColor(node.parent)
node.parent.color = black
if node == node.parent.left && tree.nodeColor(sibling.right) == red {
sibling.right.color = black
tree.rotateLeft(node.parent)
} else if tree.nodeColor(sibling.left) == red {
sibling.left.color = black
tree.rotateRight(node.parent)
}
}
func (tree *RedBlackTree) nodeColor(node *RedBlackTreeNode) color {
if node == nil {
return black
}
return node.color
}

View File

@ -0,0 +1,103 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test
import (
"github.com/gogf/gf/g/container/gtree"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
func Test_AVLTree_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewAVLTree(gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gtree.NewAVLTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_AVLTree_Set_Fun(t *testing.T) {
m := gtree.NewAVLTree(gutil.ComparatorString)
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_AVLTree_Batch(t *testing.T) {
m := gtree.NewAVLTree(gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_AVLTree_Iterator(t *testing.T){
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_AVLTree_Clone(t *testing.T) {
//clone 方法是深克隆
m := gtree.NewAVLTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}

View File

@ -0,0 +1,99 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test
import (
"github.com/gogf/gf/g/container/gtree"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
func Test_BTree_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewBTree(3, gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gtree.NewBTreeFrom(3, gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_BTree_Set_Fun(t *testing.T) {
m := gtree.NewBTree(3, gutil.ComparatorString)
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_BTree_Batch(t *testing.T) {
m := gtree.NewBTree(3, gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_BTree_Iterator(t *testing.T){
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gtree.NewBTreeFrom(3, gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_BTree_Clone(t *testing.T) {
//clone 方法是深克隆
m := gtree.NewBTreeFrom(3, gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}

View File

@ -0,0 +1,106 @@
// Copyright 2017-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test
import (
"github.com/gogf/gf/g/container/gtree"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
func getValue() interface{} {
return 3
}
func Test_RedBlackTree_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
gtest.Assert(m.Get("key1"), "val1")
gtest.Assert(m.Size(), 1)
gtest.Assert(m.IsEmpty(), false)
gtest.Assert(m.GetOrSet("key2", "val2"), "val2")
gtest.Assert(m.SetIfNotExist("key2", "val2"), false)
gtest.Assert(m.SetIfNotExist("key3", "val3"), true)
gtest.Assert(m.Remove("key2"), "val2")
gtest.Assert(m.Contains("key2"), false)
gtest.AssertIN("key3", m.Keys())
gtest.AssertIN("key1", m.Keys())
gtest.AssertIN("val3", m.Values())
gtest.AssertIN("val1", m.Values())
m.Flip()
gtest.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_RedBlackTree_Set_Fun(t *testing.T) {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
gtest.Assert(m.Get("fun"), 3)
m.GetOrSetFunc("fun", getValue)
gtest.Assert(m.SetIfNotExistFunc("fun", getValue), false)
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_RedBlackTree_Batch(t *testing.T) {
m := gtree.NewRedBlackTree(gutil.ComparatorString)
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_RedBlackTree_Iterator(t *testing.T){
expect := map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
})
// 断言返回值对遍历控制
i := 0
j := 0
m.Iterator(func(k interface{}, v interface{}) bool {
i++
return true
})
m.Iterator(func(k interface{}, v interface{}) bool {
j++
return false
})
gtest.Assert(i, 2)
gtest.Assert(j, 1)
}
func Test_RedBlackTree_Clone(t *testing.T) {
//clone 方法是深克隆
m := gtree.NewRedBlackTreeFrom(gutil.ComparatorString, map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
gtest.AssertIN(1, m_clone.Keys())
m_clone.Remove("key1")
//修改clone map,原 map 不影响
gtest.AssertIN("key1", m.Keys())
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
@ -11,35 +11,39 @@ import (
)
type Bool struct {
val int32
value int32
}
// NewBool returns a concurrent-safe object for bool type,
// with given initial value <value>.
func NewBool(value...bool) *Bool {
t := &Bool{}
if len(value) > 0 {
if value[0] {
t.val = 1
t.value = 1
} else {
t.val = 0
t.value = 0
}
}
return t
}
// Clone clones and returns a new concurrent-safe object for bool type.
func (t *Bool) Clone() *Bool {
return NewBool(t.Val())
}
// 并发安全设置变量值,返回之前的旧值
// Set atomically stores <value> into t.value and returns the previous value of t.value.
func (t *Bool) Set(value bool) (old bool) {
if value {
old = atomic.SwapInt32(&t.val, 1) == 1
old = atomic.SwapInt32(&t.value, 1) == 1
} else {
old = atomic.SwapInt32(&t.val, 0) == 1
old = atomic.SwapInt32(&t.value, 0) == 1
}
return
}
// Val atomically loads t.valueue.
func (t *Bool) Val() bool {
return atomic.LoadInt32(&t.val) > 0
return atomic.LoadInt32(&t.value) > 0
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
@ -11,29 +11,36 @@ import (
)
type Byte struct {
val int32
value int32
}
// NewByte returns a concurrent-safe object for byte type,
// with given initial value <value>.
func NewByte(value...byte) *Byte {
if len(value) > 0 {
return &Byte{val : int32(value[0])}
return &Byte{
value : int32(value[0]),
}
}
return &Byte{}
}
// Clone clones and returns a new concurrent-safe object for byte type.
func (t *Byte) Clone() *Byte {
return NewByte(t.Val())
}
// 并发安全设置变量值,返回之前的旧值
// Set atomically stores <value> into t.value and returns the previous value of t.value.
func (t *Byte) Set(value byte) (old byte) {
return byte(atomic.SwapInt32(&t.val, int32(value)))
return byte(atomic.SwapInt32(&t.value, int32(value)))
}
// Val atomically loads t.value.
func (t *Byte) Val() byte {
return byte(atomic.LoadInt32(&t.val))
return byte(atomic.LoadInt32(&t.value))
}
func (t *Byte) Add(delta int) byte {
return byte(atomic.AddInt32(&t.val, int32(delta)))
// Add atomically adds <delta> to t.value and returns the new value.
func (t *Byte) Add(delta int) (new byte) {
return byte(atomic.AddInt32(&t.value, int32(delta)))
}

View File

@ -1,37 +1,43 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
import "sync/atomic"
type Bytes struct {
val atomic.Value
value atomic.Value
}
// NewBytes returns a concurrent-safe object for []byte type,
// with given initial value <value>.
func NewBytes(value...[]byte) *Bytes {
t := &Bytes{}
if len(value) > 0 {
t.val.Store(value[0])
t.value.Store(value[0])
}
return t
}
// Clone clones and returns a new concurrent-safe object for []byte type.
func (t *Bytes) Clone() *Bytes {
return NewBytes(t.Val())
}
// Set atomically stores <value> into t.value and returns the previous value of t.value.
// Note: The parameter <value> cannot be nil.
func (t *Bytes) Set(value []byte) (old []byte) {
old = t.Val()
t.val.Store(value)
t.value.Store(value)
return
}
// Val atomically loads t.value.
func (t *Bytes) Val() []byte {
if s := t.val.Load(); s != nil {
if s := t.value.Load(); s != nil {
return s.([]byte)
}
return nil

View File

@ -1,53 +1,59 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
import (
"sync/atomic"
"gitee.com/johng/gf/g/encoding/gbinary"
"math"
"sync/atomic"
"unsafe"
)
type Float32 struct {
val uint32
value uint32
}
// NewFloat32 returns a concurrent-safe object for float32 type,
// with given initial value <value>.
func NewFloat32(value...float32) *Float32 {
if len(value) > 0 {
return &Float32{ val : float32ToUint32InBits(value[0]) }
return &Float32{
value : math.Float32bits(value[0]),
}
}
return &Float32{}
}
// Clone clones and returns a new concurrent-safe object for float32 type.
func (t *Float32) Clone() *Float32 {
return NewFloat32(t.Val())
}
// Set atomically stores <value> into t.value and returns the previous value of t.value.
func (t *Float32) Set(value float32) (old float32) {
return uint32ToFloat32InBits(atomic.SwapUint32(&t.val, float32ToUint32InBits(value)))
return math.Float32frombits(atomic.SwapUint32(&t.value, math.Float32bits(value)))
}
// Val atomically loads t.value.
func (t *Float32) Val() float32 {
return uint32ToFloat32InBits(atomic.LoadUint32(&t.val))
return math.Float32frombits(atomic.LoadUint32(&t.value))
}
func (t *Float32) Add(delta float32) float32 {
return uint32ToFloat32InBits(atomic.AddUint32(&t.val, float32ToUint32InBits(delta)))
}
// 通过二进制的方式将float32转换为uint32(都是32bits)
func float32ToUint32InBits(value float32) uint32 {
b := gbinary.Encode(value)
i := gbinary.DecodeToUint32(b)
return i
}
// 通过二进制的方式将uint32转换为float32(都是32bits)
func uint32ToFloat32InBits(value uint32) float32 {
b := gbinary.Encode(value)
f := gbinary.DecodeToFloat32(b)
return f
// Add atomically adds <delta> to t.value and returns the new value.
func (t *Float32) Add(delta float32) (new float32) {
for {
old := math.Float32frombits(t.value)
new = old + delta
if atomic.CompareAndSwapUint32(
(*uint32)(unsafe.Pointer(&t.value)),
math.Float32bits(old),
math.Float32bits(new),
) {
break
}
}
return
}

View File

@ -1,53 +1,59 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
import (
"sync/atomic"
"gitee.com/johng/gf/g/encoding/gbinary"
"math"
"sync/atomic"
"unsafe"
)
type Float64 struct {
val uint64
value uint64
}
// NewFloat64 returns a concurrent-safe object for float64 type,
// with given initial value <value>.
func NewFloat64(value...float64) *Float64 {
if len(value) > 0 {
return &Float64{ val : float64ToUint64InBits(value[0]) }
return &Float64{
value : math.Float64bits(value[0]),
}
}
return &Float64{}
}
// Clone clones and returns a new concurrent-safe object for float64 type.
func (t *Float64) Clone() *Float64 {
return NewFloat64(t.Val())
}
// Set atomically stores <value> into t.value and returns the previous value of t.value.
func (t *Float64) Set(value float64) (old float64) {
return uint64ToFloat64InBits(atomic.SwapUint64(&t.val, float64ToUint64InBits(value)))
return math.Float64frombits(atomic.SwapUint64(&t.value, math.Float64bits(value)))
}
// Val atomically loads t.value.
func (t *Float64) Val() float64 {
return uint64ToFloat64InBits(atomic.LoadUint64(&t.val))
return math.Float64frombits(atomic.LoadUint64(&t.value))
}
func (t *Float64) Add(delta float64) float64 {
return uint64ToFloat64InBits(atomic.AddUint64(&t.val, float64ToUint64InBits(delta)))
// Add atomically adds <delta> to t.value and returns the new value.
func (t *Float64) Add(delta float64) (new float64) {
for {
old := math.Float64frombits(t.value)
new = old + delta
if atomic.CompareAndSwapUint64(
(*uint64)(unsafe.Pointer(&t.value)),
math.Float64bits(old),
math.Float64bits(new),
) {
break
}
}
return
}
// 通过二进制的方式将float64转换为uint64(都是64bits)
func float64ToUint64InBits(value float64) uint64 {
b := gbinary.Encode(value)
i := gbinary.DecodeToUint64(b)
return i
}
// 通过二进制的方式将uint64转换为float64(都是64bits)
func uint64ToFloat64InBits(value uint64) float64 {
b := gbinary.Encode(value)
f := gbinary.DecodeToFloat64(b)
return f
}

View File

@ -1,14 +1,15 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// 并发安全的基本类型.
// Package gtype provides kinds of high performance and concurrent-safe basic variable types.
package gtype
type Type = Interface
// See NewInterface.
func New(value ... interface{}) *Type {
return NewInterface(value...)
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
@ -11,7 +11,7 @@ package gtype
import (
"testing"
"strconv"
"gitee.com/johng/gf/g/encoding/gbinary"
"github.com/gogf/gf/g/encoding/gbinary"
"sync/atomic"
)

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
@ -11,30 +11,36 @@ import (
)
type Int struct {
val int64
value int64
}
// NewInt returns a concurrent-safe object for int type,
// with given initial value <value>.
func NewInt(value...int) *Int {
if len(value) > 0 {
return &Int{val:int64(value[0])}
return &Int{
value : int64(value[0]),
}
}
return &Int{}
}
// Clone clones and returns a new concurrent-safe object for int type.
func (t *Int) Clone() *Int {
return NewInt(t.Val())
}
// 并发安全设置变量值,返回之前的旧值
// Set atomically stores <value> into t.value and returns the previous value of t.value.
func (t *Int) Set(value int) (old int) {
return int(atomic.SwapInt64(&t.val, int64(value)))
return int(atomic.SwapInt64(&t.value, int64(value)))
}
// Val atomically loads t.value.
func (t *Int) Val() int {
return int(atomic.LoadInt64(&t.val))
return int(atomic.LoadInt64(&t.value))
}
// 数值增加delta并返回新的数值
func (t *Int) Add(delta int) int {
return int(atomic.AddInt64(&t.val, int64(delta)))
// Add atomically adds <delta> to t.value and returns the new value.
func (t *Int) Add(delta int) (new int) {
return int(atomic.AddInt64(&t.value, int64(delta)))
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
@ -11,28 +11,36 @@ import (
)
type Int32 struct {
val int32
value int32
}
// NewInt32 returns a concurrent-safe object for int32 type,
// with given initial value <value>.
func NewInt32(value...int32) *Int32 {
if len(value) > 0 {
return &Int32{val: value[0]}
return &Int32{
value : value[0],
}
}
return &Int32{}
}
// Clone clones and returns a new concurrent-safe object for int32 type.
func (t *Int32) Clone() *Int32 {
return NewInt32(t.Val())
}
// Set atomically stores <value> into t.value and returns the previous value of t.value.
func (t *Int32) Set(value int32) (old int32) {
return atomic.SwapInt32(&t.val, value)
return atomic.SwapInt32(&t.value, value)
}
// Val atomically loads t.value.
func (t *Int32) Val() int32 {
return atomic.LoadInt32(&t.val)
return atomic.LoadInt32(&t.value)
}
func (t *Int32) Add(delta int32) int32 {
return atomic.AddInt32(&t.val, delta)
// Add atomically adds <delta> to t.value and returns the new value.
func (t *Int32) Add(delta int32) (new int32) {
return atomic.AddInt32(&t.value, delta)
}

View File

@ -1,8 +1,8 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// You can obtain one at https://github.com/gogf/gf.
package gtype
@ -11,28 +11,36 @@ import (
)
type Int64 struct {
val int64
value int64
}
// NewInt64 returns a concurrent-safe object for int64 type,
// with given initial value <value>.
func NewInt64(value...int64) *Int64 {
if len(value) > 0 {
return &Int64{val:value[0]}
return &Int64{
value : value[0],
}
}
return &Int64{}
}
// Clone clones and returns a new concurrent-safe object for int64 type.
func (t *Int64) Clone() *Int64 {
return NewInt64(t.Val())
}
// Set atomically stores <value> into t.value and returns the previous value of t.value.
func (t *Int64) Set(value int64) (old int64) {
return atomic.SwapInt64(&t.val, value)
return atomic.SwapInt64(&t.value, value)
}
// Val atomically loads t.value.
func (t *Int64) Val() int64 {
return atomic.LoadInt64(&t.val)
return atomic.LoadInt64(&t.value)
}
// Add atomically adds <delta> to t.value and returns the new value.
func (t *Int64) Add(delta int64) int64 {
return atomic.AddInt64(&t.val, delta)
return atomic.AddInt64(&t.value, delta)
}

Some files were not shown because too many files have changed in this diff Show More