Compare commits

...

295 Commits

Author SHA1 Message Date
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
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
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
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
1881 changed files with 109285 additions and 83968 deletions

33
.gitee/ISSUE_TEMPLATE.MD Normal file
View File

@ -0,0 +1,33 @@
<!-- 为更高效率地交流并解决问题请按照以下模板提交issue感谢 -->
### 1. 您当前使用的`Go`版本(将终端`go version`指令结果粘贴到下面)?
<pre>
$ go version
</pre>
### 2. 您当前使用的`GoFrame`版本(可以查看`go.mod`/`version.go`/`gf.VERSION`
### 3. 更新到最新的框架版本是否能够解决问题?
### 4. 问题描述?
<!--
请您尽可能地提供一份最短的,可复现问题的代码。
代码尽可能地完整,最好是可以直接编译运行。
-->
### 5. 您期望得到的结果?
### 6. 您实际得到的结果?

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

@ -0,0 +1,33 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
### 1. What version of `Go` are you using (`go version`)?
<pre>
$ go version
</pre>
### 2. What version of `GoFrame` are you using?
### 3. Does this issue reproduce 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

33
.travis.yml Normal file
View File

@ -0,0 +1,33 @@
language: go
go:
- "1.11.x"
branches:
only:
- master
- develop
env:
- GO111MODULE=on
services:
- mysql
before_install:
- pwd
install:
- pwd
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.

244
README.MD
View File

@ -1,55 +1,53 @@
# 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)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://goframe.org)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.com/gogf/gf)
-->
`GF(GoFrame)` is a modular, loose-coupled and production-ready application development framework written in Go. Providing a series of core components and dozens of practical modules, such as: cache, logging, array/queue/set/map, timer/timing tasks, file/memory lock, object pool, validator, database ORM, etc. Supporting web server with graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing, rewrite rules 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
* [GoDoc](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 +58,52 @@ 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(TOP 10)
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png?1530630243" width="60" align="left"></a>
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://goframe.org/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://goframe.org/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
<br /><br /><br />
# Donators
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>
...
更多特性及示例请查看官方开发文档:[gfer.me](https://gfer.me)

119
README_ZH.MD Normal file
View File

@ -0,0 +1,119 @@
# 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)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://goframe.org)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](https://www.codetriage.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
```
> 如果您是从旧版本`1.x`升级到`1.5.0`那么请参考:[1.x升级到1.5.0](https://goframe.org/upgradeto150)
# 限制
```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>
# 贡献者(TOP 10)
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png" width="60" align="left"></a>
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://goframe.org/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://goframe.org/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
<br /><br /><br />
# 捐赠者
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>

View File

@ -1,136 +1,151 @@
# `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.5.8` (2019-02-28)
# `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. 主库从`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 +242,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)

35
TODO.MD
View File

@ -1,8 +1,4 @@
# ON THE WAY
1. orm增加更多数据库支持
1. 增加对于数据表Model的封装
1. 更多数据库的ORM功能支持
1. 考虑gdb对象管理增加二级连接池特性提高New&Close性能
1. 增加图形验证码支持,至少支持数字和英文字母;
1. 增加热编译工具,提高开发环境的开发/测试效率媲美PHP开发效率
1. 增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
@ -34,16 +30,32 @@
- 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. 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. 改进WebServer获取POST参数处理逻辑当提交非form数据时例如json数据针对某些方法可以直接解析
1. WebServer增加可选择的路由覆盖配置默认情况下不覆盖
1. gkafka这个包比较重未来从框架中剥离出来
1. grpool性能压测结果变慢的问题
1. 增加jumplist的数据结构容器
1. DelayQueue/PriorityQueue
1. gconv针对struct的转换增加json tag支持gconv.Map默认也支持json tag, 完善开发文档;
1. 增加SO_REUSEPORT的支持
1. 权限管理模块;
1. 从ghttp中剥离SESSION功能构成单独的模块gsession
1. 改进gproc进程间通信处理逻辑提高稳定性以应对进程间大批量的数据发送/接收;
1. gdb的Data方法支持struct参数传入
1. gfcache依旧使用gcache作为缓存控制对象不要使用gmap
# DONE
@ -70,7 +82,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 +108,11 @@
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是按照优先级执行的需要增加覆盖特性

View File

@ -1,7 +1,7 @@
// 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 container

View File

@ -1,12 +1,11 @@
// 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 kinds of concurrent-safe(alternative) arrays.
//
// 并发安全数组.
package garray
func New(size int, cap int, safe...bool) *Array {
return NewArray(size, cap, safe...)
}

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 garray

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,592 @@
// 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 (
"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 IntArray struct {
mu *rwmutex.RWMutex // 互斥锁
array []int // 底层数组
}
// Create an empty array.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewIntArray(unsafe...bool) *IntArray {
return NewIntArraySize(0, 0, unsafe...)
}
// Create an array with given size and cap.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个指定大小的数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewIntArraySize(size int, cap int, unsafe...bool) *IntArray {
return &IntArray{
mu : rwmutex.New(unsafe...),
array : make([]int, size, cap),
}
}
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice变量创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewIntArrayFrom(array []int, unsafe...bool) *IntArray {
return &IntArray{
mu : rwmutex.New(unsafe...),
array : array,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
func (a *IntArray) Get(index int) int {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set value by index.
//
// 设置指定索引的数据项, 调用方注意判断数组边界。
func (a *IntArray) Set(index int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
func (a *IntArray) SetArray(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace 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
}
// Calculate 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 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
}
// Sort 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
}
// Insert 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
}
// Insert 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 an item by index.
//
// 删除指定索引的数据项, 调用方注意判断数组边界。
func (a *IntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
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
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// Push new items to the beginning of array.
//
// 将数据项添加到数组的最左端(索引为0)。
func (a *IntArray) PushLeft(value...int) *IntArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// Push new items to the end of array.
//
// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append。
func (a *IntArray) PushRight(value...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Pop an item from the beginning of array.
//
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
func (a *IntArray) PopLeft() int {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// Pop an item from the end of array.
//
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项。
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
}
// Pop an random item from array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *IntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项。
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
}
// Pop <size> items from the end of array.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Get items by range, returns array[start:end].
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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.
//
// 追加数据项, 等于: PushRight。
func (a *IntArray) Append(value...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Get the length of array.
//
// 数组长度。
func (a *IntArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Get the underlying data of array.
// Be aware that, 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
}
// Return 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 array.
//
// 清空数据数组。
func (a *IntArray) Clear() *IntArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]int, 0)
}
a.mu.Unlock()
return a
}
// Check whether a value exists in the array.
//
// 查找指定数值是否存在。
func (a *IntArray) Contains(value int) bool {
return a.Search(value) != -1
}
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
//
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-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
}
// Unique the array, clear repeated values.
//
// 清理数组中重复的元素项。
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
}
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
return a
}
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
return a
}
// Merge two arrays.
//
// 合并两个数组.
func (a *IntArray) Merge(array *IntArray) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
}
a.array = append(a.array, array.array...)
return a
}
// Fills an array with num entries of the value of the value parameter,
// keys starting at the startIndex parameter.
//
// 用value参数的值将数组填充num个条目位置由startIndex参数指定的开始。
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
}
// Chunks an array into arrays with size elements.
// The last chunk may contain less than size elements.
//
// 将一个数组分割成多个数组其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
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 array to the specified length with a 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.
//
// 返回数组的一个拷贝并用value将其填补到size指定的长度。
// 如果size为正数则填补到数组的右侧如果为负数则从左侧开始填补。
// 如果size的绝对值小于或等于数组的长度则没有任何填补。
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
}
// Extract a slice of the array(If in concurrent safe usage,
// it returns a copy of the slice; else a pointer).
// It returns the sequence of elements from the array array as specified
// by the offset and length parameters.
//
// 返回根据offset和size参数所指定的数组中的一段序列。
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:]
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
//
// 从数组中随机取出size个元素项构成slice返回。
func (a *IntArray) Rand(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
}
// 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
}
// Make 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 array elements with a string.
//
// 使用glue字符串串连当前数组的元素项构造成新的字符串返回。
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
}

View File

@ -0,0 +1,587 @@
// 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 (
"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 Array struct {
mu *rwmutex.RWMutex // 互斥锁
array []interface{} // 底层数组
}
// Create an empty array.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func New(unsafe...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
// See New.
//
// 同New方法。
func NewArray(unsafe...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
// Create an array with given size and cap.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个指定大小的数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewArraySize(size int, cap int, unsafe...bool) *Array {
return &Array{
mu : rwmutex.New(unsafe...),
array : make([]interface{}, size, cap),
}
}
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice变量创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewArrayFrom(array []interface{}, unsafe...bool) *Array {
return &Array{
mu : rwmutex.New(unsafe...),
array : array,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Get(index int) interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set value by index.
//
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *Array) Set(index int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
func (a *Array) SetArray(array []interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace 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
}
// Calculate the sum of values in an array.
//
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
func (a *Array) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Sort 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
}
// Insert 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
}
// Insert 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 an item by index.
//
// 删除指定索引的数据项, 调用方注意判断数组边界。
func (a *Array) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
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
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// Push new items to the beginning of array.
//
// 将数据项添加到数组的最左端(索引为0)。
func (a *Array) PushLeft(value...interface{}) *Array {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// Push new items to the end of array.
//
// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append。
func (a *Array) PushRight(value...interface{}) *Array {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Pop an random item from array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *Array) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// Pop an item from the beginning of array.
//
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
func (a *Array) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// Pop an item from the end of array.
//
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项。
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
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
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
}
// Pop <size> items from the end of array.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Get items by range, returns array[start:end].
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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.
//
// 追加数据项, 等于: PushRight。
func (a *Array) Append(value...interface{}) *Array {
a.PushRight(value...)
return a
}
// Get the length of array.
//
// 数组长度。
func (a *Array) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Get the underlying data of array.
// Be aware that, 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
}
// Return 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 array.
//
// 清空数据数组
func (a *Array) Clear() *Array {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]interface{}, 0)
}
a.mu.Unlock()
return a
}
// Check whether a value exists in the array.
//
// 查找指定数值是否存在
func (a *Array) Contains(value interface{}) bool {
return a.Search(value) != -1
}
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
//
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-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
}
// Unique the array, clear repeated values.
//
// 清理数组中重复的元素项
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
}
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作
func (a *Array) LockFunc(f func(array []interface{})) *Array {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
return a
}
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
return a
}
// Merge two arrays.
//
// 合并两个数组.
func (a *Array) Merge(array *Array) *Array {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
}
a.array = append(a.array, array.array...)
return a
}
// Fills an array with num entries of the value of the value parameter,
// keys starting at the start_index parameter.
//
// 用value参数的值将数组填充num个条目位置由startIndex参数指定的开始。
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
}
// Chunks an array into arrays with size elements.
// The last chunk may contain less than size elements.
//
// 将一个数组分割成多个数组其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
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 array to the specified length with a value.
// If size is positive then the array is padded on the right,
// if it's negative then 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.
//
// 返回数组的一个拷贝并用value将其填补到size指定的长度。
// 如果size为正数则填补到数组的右侧如果为负数则从左侧开始填补。
// 如果size的绝对值小于或等于数组的长度则没有任何填补。
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
}
// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer).
// It returns the sequence of elements from the array array as specified by the offset and length parameters.
//
// 返回根据offset和size参数所指定的数组中的一段序列。
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:]
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
//
// 从数组中随机取出size个元素项构成slice返回。
func (a *Array) Rand(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
}
// 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
}
// Make 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 array elements with a string.
//
// 使用glue字符串串连当前数组的元素项构造成新的字符串返回。
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
}
// Counts all the values of an 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
}

View File

@ -0,0 +1,592 @@
// 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 (
"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 // 底层数组
}
// Create an empty array.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewStringArray(unsafe...bool) *StringArray {
return NewStringArraySize(0, 0, unsafe...)
}
// Create an array with given size and cap.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个指定大小的数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewStringArraySize(size int, cap int, unsafe...bool) *StringArray {
return &StringArray{
mu : rwmutex.New(unsafe...),
array : make([]string, size, cap),
}
}
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice变量创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewStringArrayFrom(array []string, unsafe...bool) *StringArray {
return &StringArray {
mu : rwmutex.New(unsafe...),
array : array,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
func (a *StringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
}
// Set value by index.
//
// 设置指定索引的数据项, 调用方注意判断数组边界。
func (a *StringArray) Set(index int, value string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array[index] = value
return a
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
func (a *StringArray) SetArray(array []string) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace 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
}
// Calculate the sum of values in an array.
//
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
func (a *StringArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Sort 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
}
// Sort 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
}
// Insert 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
}
// Insert 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 an item by index.
//
// 删除指定索引的数据项, 调用方注意判断数组边界。
func (a *StringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
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
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// Push new items to the beginning of array.
//
// 将数据项添加到数组的最左端(索引为0)。
func (a *StringArray) PushLeft(value...string) *StringArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// Push new items to the end of array.
//
// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append。
func (a *StringArray) PushRight(value...string) *StringArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Pop an item from the beginning of array.
//
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
func (a *StringArray) PopLeft() string {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// Pop an item from the end of array.
//
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项。
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
}
// Pop an random item from array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *StringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
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
}
// Pop <size> items from the end of array.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Get items by range, returns array[start:end].
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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.
//
// 追加数据项, 等于: PushRight。
func (a *StringArray) Append(value...string) *StringArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// Get the length of array.
//
// 数组长度。
func (a *StringArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Get the underlying data of array.
// Be aware that, 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
}
// Return 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 array.
//
// 清空数据数组。
func (a *StringArray) Clear() *StringArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]string, 0)
}
a.mu.Unlock()
return a
}
// Check whether a value exists in the array.
//
// 查找指定数值是否存在。
func (a *StringArray) Contains(value string) bool {
return a.Search(value) != -1
}
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
//
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-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
}
// Unique the array, clear repeated values.
//
// 清理数组中重复的元素项。
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
}
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
return a
}
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
return a
}
// Merge two arrays.
//
// 合并两个数组.
func (a *StringArray) Merge(array *StringArray) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
}
a.array = append(a.array, array.array...)
return a
}
// Fills an array with num entries of the value of the value parameter,
// keys starting at the start_index parameter.
//
// 用value参数的值将数组填充num个条目位置由startIndex参数指定的开始。
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
}
// Chunks an array into arrays with size elements.
// The last chunk may contain less than size elements.
//
// 将一个数组分割成多个数组其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
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 array to the specified length with a value.
// If size is positive then the array is padded on the right,
// if it's negative then 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.
//
// 返回数组的一个拷贝并用value将其填补到size指定的长度。
// 如果size为正数则填补到数组的右侧如果为负数则从左侧开始填补。
// 如果size的绝对值小于或等于数组的长度则没有任何填补。
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
}
// Extract a slice of the array(If in concurrent safe usage,
// it returns a copy of the slice; else a pointer).
// It returns the sequence of elements from the array array as specified
// by the offset and length parameters.
//
// 返回根据offset和size参数所指定的数组中的一段序列。
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:]
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
//
// 从数组中随机取出size个元素项构成slice返回。
func (a *StringArray) Rand(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
}
// 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
}
// Make 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 array elements with a string.
//
// 使用glue字符串串连当前数组的元素项构造成新的字符串返回。
func (a *StringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(a.array, glue)
}

View File

@ -1,29 +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"
"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"
)
// 默认按照从低到高进行排序
// 默认按照从小到大进行排序
type SortedIntArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []int // 底层数组
unique *gtype.Bool // 是否要求不能重复
unique *gtype.Bool // 是否要求不能重复(默认false)
compareFunc func(v1, v2 int) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
}
// 创建一个排序的int数组
func NewSortedIntArray(cap int, safe...bool) *SortedIntArray {
// Create an empty sorted array.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedIntArray(unsafe...bool) *SortedIntArray {
return NewSortedIntArraySize(0, unsafe...)
}
// Create a sorted array with given size and cap.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个指定大小的排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
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 {
@ -38,10 +55,45 @@ func NewSortedIntArray(cap int, safe...bool) *SortedIntArray {
}
}
// 添加加数据项
func (a *SortedIntArray) Add(values...int) {
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice变量创建排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedIntArrayFrom(array []int, unsafe...bool) *SortedIntArray {
a := NewSortedIntArraySize(0, unsafe...)
a.array = array
sort.Ints(a.array)
return a
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Ints(a.array)
return a
}
// Sort the array in increasing order.
//
// 将数组排序(默认从低到高).
func (a *SortedIntArray) Sort() *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Ints(a.array)
return a
}
// And 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()
@ -62,9 +114,12 @@ func (a *SortedIntArray) Add(values...int) {
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
func (a *SortedIntArray) Get(index int) int {
a.mu.RLock()
defer a.mu.RUnlock()
@ -72,7 +127,9 @@ func (a *SortedIntArray) Get(index int) int {
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove an item by index.
//
// 删除指定索引的数据项, 调用方注意判断数组边界。
func (a *SortedIntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
@ -92,7 +149,9 @@ func (a *SortedIntArray) Remove(index int) int {
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// Push new items to the beginning of array.
//
// 将数据项添加到数组的最左端(索引为0)。
func (a *SortedIntArray) PopLeft() int {
a.mu.Lock()
defer a.mu.Unlock()
@ -101,7 +160,9 @@ func (a *SortedIntArray) PopLeft() int {
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// Push new items to the end of array.
//
// 将数据项添加到数组的最右端(索引为length - 1)。
func (a *SortedIntArray) PopRight() int {
a.mu.Lock()
defer a.mu.Unlock()
@ -111,7 +172,76 @@ func (a *SortedIntArray) PopRight() int {
return value
}
// 数组长度
// Pop an random item from array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedIntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
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
}
// Pop <size> items from the end of array.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Get items by range, returns array[start:end].
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Get the length of array.
//
// 数组长度。
func (a *SortedIntArray) Len() int {
a.mu.RLock()
length := len(a.array)
@ -119,28 +249,54 @@ func (a *SortedIntArray) Len() int {
return length
}
// 返回原始数据数组
// Calculate 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
}
// Get the underlying data of array.
// Be aware that, 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)
// Check whether a value exists in the array.
//
// 查找指定数值是否存在。
func (a *SortedIntArray) Contains(value int) bool {
return a.Search(value) == 0
}
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
//
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
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,32 +309,37 @@ 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
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) {
// Set 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() {
// Do unique check, remove all repeated items.
//
// 清理数组中重复的元素项.
func (a *SortedIntArray) Unique() *SortedIntArray {
a.mu.Lock()
i := 0
for {
@ -192,27 +353,140 @@ func (a *SortedIntArray) doUnique() {
}
}
a.mu.Unlock()
return a
}
// 清空数据数组
func (a *SortedIntArray) Clear() {
// Return 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 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)) {
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedIntArray) RLockFunc(f func(array []int)) {
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
return a
}
// Merge two arrays.
//
// 合并两个数组.
func (a *SortedIntArray) Merge(array *SortedIntArray) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
}
a.array = append(a.array, array.array...)
sort.Ints(a.array)
return a
}
// Chunks an array into arrays with size elements.
// The last chunk may contain less than size elements.
//
// 将一个数组分割成多个数组其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
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
}
// Extract a slice of the array(If in concurrent safe usage,
// it returns a copy of the slice; else a pointer).
// It returns the sequence of elements from the array array as specified
// by the offset and length parameters.
//
// 返回根据offset和size参数所指定的数组中的一段序列。
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:]
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
//
// 从数组中随机取出size个元素项构成slice返回。
func (a *SortedIntArray) Rand(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 array elements with a string.
//
// 使用glue字符串串连当前数组的元素项构造成新的字符串返回。
func (a *SortedIntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
}

View File

@ -1,38 +1,105 @@
// 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"
"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"
)
// 默认按照从低到高进行排序
// 默认按照从小到大进行排序
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
}
func NewSortedArray(cap int, compareFunc func(v1, v2 interface{}) int, safe...bool) *SortedArray {
// Create an empty sorted array.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
// The param <compareFunc> 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;
//
// 创建一个空的排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
// 参数compareFunc用于指定排序方法
// 如果返回值 < 0, 表示 v1 < v2;
// 如果返回值 = 0, 表示 v1 = v2;
// 如果返回值 > 0, 表示 v1 > v2;
func NewSortedArray(compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
return NewSortedArraySize(0, compareFunc, unsafe...)
}
// Create a sorted array with given size and cap.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个指定大小的排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedArraySize(cap int, compareFunc 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,
}
}
// 添加加数据项
func (a *SortedArray) Add(values...interface{}) {
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice变量创建排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedArrayFrom(array []interface{}, compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
a := NewSortedArraySize(0, compareFunc, unsafe...)
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.compareFunc(a.array[i], a.array[j]) < 0
})
return a
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
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.compareFunc(a.array[i], a.array[j]) < 0
})
return a
}
// Sort the array by comparing function.
//
// 将数组按照比较方法进行排序.
func (a *SortedArray) Sort() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return a.compareFunc(a.array[i], a.array[j]) < 0
})
return a
}
// And 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()
@ -53,9 +120,12 @@ func (a *SortedArray) Add(values...interface{}) {
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
func (a *SortedArray) Get(index int) interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
@ -63,7 +133,9 @@ func (a *SortedArray) Get(index int) interface{} {
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove an item by index.
//
// 删除指定索引的数据项, 调用方注意判断数组边界。
func (a *SortedArray) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -83,7 +155,9 @@ func (a *SortedArray) Remove(index int) interface{} {
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// Push new items to the beginning of array.
//
// 将数据项添加到数组的最左端(索引为0)。
func (a *SortedArray) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -92,7 +166,9 @@ func (a *SortedArray) PopLeft() interface{} {
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// Push new items to the end of array.
//
// 将数据项添加到数组的最右端(索引为length - 1)。
func (a *SortedArray) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
@ -102,7 +178,88 @@ func (a *SortedArray) PopRight() interface{} {
return value
}
// 数组长度
// Pop an random item from array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedArray) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
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
}
// Pop <size> items from the end of array.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Get items by range, returns array[start:end].
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Calculate the sum of values in an array.
//
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
func (a *SortedArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Get the length of array.
//
// 数组长度。
func (a *SortedArray) Len() int {
a.mu.RLock()
length := len(a.array)
@ -110,30 +267,43 @@ func (a *SortedArray) Len() int {
return length
}
// 返回原始数据数组
// Get the underlying data of array.
// Be aware that, 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)
// Check whether a value exists in the array.
//
// 查找指定数值是否存在。
func (a *SortedArray) Contains(value interface{}) bool {
return a.Search(value) == 0
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
//
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
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 +316,37 @@ 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
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) {
// Set 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() {
// Do unique check, remove all repeated items.
//
// 清理数组中重复的元素项.
func (a *SortedArray) Unique() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
i := 0
@ -185,27 +360,142 @@ func (a *SortedArray) doUnique() {
i++
}
}
return a
}
// 清空数据数组
func (a *SortedArray) Clear() {
// Return 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.compareFunc, !a.mu.IsSafe())
}
// Clear 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{})) {
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedArray) RLockFunc(f func(array []interface{})) {
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
return a
}
// Merge two arrays.
//
// 合并两个数组.
func (a *SortedArray) Merge(array *SortedArray) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
}
a.array = append(a.array, array.array...)
sort.Slice(a.array, func(i, j int) bool {
return a.compareFunc(a.array[i], a.array[j]) < 0
})
return a
}
// Chunks an array into arrays with size elements.
// The last chunk may contain less than size elements.
//
// 将一个数组分割成多个数组其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
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
}
// Extract a slice of the array(If in concurrent safe usage,
// it returns a copy of the slice; else a pointer).
// It returns the sequence of elements from the array array as specified
// by the offset and length parameters.
//
// 返回根据offset和size参数所指定的数组中的一段序列。
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:]
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
//
// 从数组中随机取出size个元素项构成slice返回。
func (a *SortedArray) Rand(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 array elements with a string.
//
// 使用glue字符串串连当前数组的元素项构造成新的字符串返回。
func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
}

View File

@ -1,29 +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"
"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"
)
// 默认按照从低到高进行排序
// 默认按照从小到大进行排序
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
}
func NewSortedStringArray(cap int, safe...bool) *SortedStringArray {
// Create an empty sorted array.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedStringArray(unsafe...bool) *SortedStringArray {
return NewSortedStringArraySize(0, unsafe...)
}
// Create a sorted array with given size and cap.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个指定大小的排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
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 {
@ -32,10 +49,45 @@ func NewSortedStringArray(cap int, safe...bool) *SortedStringArray {
}
}
// 添加加数据项
func (a *SortedStringArray) Add(values...string) {
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice变量创建排序数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedStringArrayFrom(array []string, unsafe...bool) *SortedStringArray {
a := NewSortedStringArraySize(0, unsafe...)
a.array = array
sort.Strings(a.array)
return a
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
func (a *SortedStringArray) SetArray(array []string) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Strings(a.array)
return a
}
// Sort the array in increasing order.
//
// 将数组排序(默认从低到高).
func (a *SortedStringArray) Sort() *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Strings(a.array)
return a
}
// And 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()
@ -56,9 +108,12 @@ func (a *SortedStringArray) Add(values...string) {
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
}
return a
}
// 获取指定索引的数据项, 调用方注意判断数组边界
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
func (a *SortedStringArray) Get(index int) string {
a.mu.RLock()
defer a.mu.RUnlock()
@ -66,7 +121,9 @@ func (a *SortedStringArray) Get(index int) string {
return value
}
// 删除指定索引的数据项, 调用方注意判断数组边界
// Remove an item by index.
//
// 删除指定索引的数据项, 调用方注意判断数组边界。
func (a *SortedStringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
@ -86,7 +143,9 @@ func (a *SortedStringArray) Remove(index int) string {
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
// Push new items to the beginning of array.
//
// 将数据项添加到数组的最左端(索引为0)。
func (a *SortedStringArray) PopLeft() string {
a.mu.Lock()
defer a.mu.Unlock()
@ -95,7 +154,9 @@ func (a *SortedStringArray) PopLeft() string {
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
// Push new items to the end of array.
//
// 将数据项添加到数组的最右端(索引为length - 1)。
func (a *SortedStringArray) PopRight() string {
a.mu.Lock()
defer a.mu.Unlock()
@ -105,7 +166,88 @@ func (a *SortedStringArray) PopRight() string {
return value
}
// 数组长度
// Pop an random item from array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedStringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
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
}
// Pop <size> items from the end of array.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Get items by range, returns array[start:end].
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data.
//
// 将最右端(尾部)的size个数据项移出数组并返回该数据项
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
}
// Calculate the sum of values in an array.
//
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
func (a *SortedStringArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// Get the length of array.
//
// 数组长度。
func (a *SortedStringArray) Len() int {
a.mu.RLock()
length := len(a.array)
@ -113,28 +255,42 @@ func (a *SortedStringArray) Len() int {
return length
}
// 返回原始数据数组
// Get the underlying data of array.
// Be aware that, 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)
// Check whether a value exists in the array.
//
// 查找指定数值是否存在。
func (a *SortedStringArray) Contains(value string) bool {
return a.Search(value) == 0
}
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
//
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
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,32 +303,37 @@ 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
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) {
// Set 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() {
// Do unique check, remove all repeated items.
//
// 清理数组中重复的元素项.
func (a *SortedStringArray) Unique() *SortedStringArray {
a.mu.Lock()
i := 0
for {
@ -186,27 +347,140 @@ func (a *SortedStringArray) doUnique() {
}
}
a.mu.Unlock()
return a
}
// 清空数据数组
func (a *SortedStringArray) Clear() {
// Return 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 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)) {
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
f(a.array)
return a
}
// 使用自定义方法执行加锁读取操作
func (a *SortedStringArray) RLockFunc(f func(array []string)) {
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
f(a.array)
return a
}
// Merge two arrays.
//
// 合并两个数组.
func (a *SortedStringArray) Merge(array *SortedStringArray) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
}
a.array = append(a.array, array.array...)
sort.Strings(a.array)
return a
}
// Chunks an array into arrays with size elements.
// The last chunk may contain less than size elements.
//
// 将一个数组分割成多个数组其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
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
}
// Extract a slice of the array(If in concurrent safe usage,
// it returns a copy of the slice; else a pointer).
// It returns the sequence of elements from the array array as specified
// by the offset and length parameters.
//
// 返回根据offset和size参数所指定的数组中的一段序列。
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:]
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
//
// 从数组中随机取出size个元素项构成slice返回。
func (a *SortedStringArray) Rand(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 array elements with a string.
//
// 使用glue字符串串连当前数组的元素项构造成新的字符串返回。
func (a *SortedStringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(a.array, glue)
}

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,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,192 @@
// 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.Rand(2)), 2)
gtest.Assert(len(array1.Rand(10)), 7)
gtest.AssertIN(array1.Rand(1)[0], 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,196 @@
// 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_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.Rand(2)), 2)
gtest.Assert(len(array1.Rand(10)), 7)
gtest.AssertIN(array1.Rand(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,193 @@
// 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.Rand(2)), "2")
gtest.Assert(len(array1.Rand(10)), "7")
gtest.AssertIN(array1.Rand(1)[0], 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,15 +1,17 @@
// 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 gchan provides graceful operations for channel.
//
// 优雅的Channel操作.
package gchan
import (
"errors"
"gitee.com/johng/gf/g/container/gtype"
"github.com/gogf/gf/g/container/gtype"
)
type Chan struct {
@ -40,8 +42,7 @@ func (q *Chan) Pop() interface{} {
// 关闭队列(通知所有通过Pop阻塞的协程退出)
func (q *Chan) Close() {
if !q.closed.Val() {
q.closed.Set(true)
if !q.closed.Set(true) {
close(q.list)
}
}

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,306 @@
// 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(alternative) 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
mu *rwmutex.RWMutex
list *list.List
}
type Element = list.Element
// 获得一个变长链表指针
func New(safe...bool) *List {
return &List{
mu : rwmutex.New(safe...),
list : list.New(),
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
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
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
}
// 在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)
func (l *List) BatchPushFront(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushFront(v)
}
this.mu.Unlock()
l.mu.Unlock()
}
// 批量往链表尾入栈数据项
func (l *List) BatchPushBack(values []interface{}) {
l.mu.Lock()
for _, v := range values {
l.list.PushBack(v)
}
l.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
func (l *List) PopBack() (value interface{}) {
l.mu.Lock()
if e := l.list.Back(); e != nil {
value = l.list.Remove(e)
}
this.mu.Unlock()
return nil
l.mu.Unlock()
return
}
// 从链表头端出栈数据项(删除)
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 (l *List) PopFront() (value interface{}) {
l.mu.Lock()
if e := l.list.Front(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
}
// 批量从链表尾端出栈数据项(删除)
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 (l *List) BatchPopBack(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)
}
}
l.mu.Unlock()
return
}
// 批量从链表头端出栈数据项(删除)
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 (l *List) BatchPopFront(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) 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 (l *List) PopBackAll() []interface{} {
return l.BatchPopBack(-1)
}
// 批量从链表头端依次获取所有数据(删除)
func (this *List) PopFrontAll() []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.Front())
}
this.mu.Unlock()
return items
}
// 删除数据项
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 (l *List) PopFrontAll() []interface{} {
return l.BatchPopFront(-1)
}
// 从链表头获取所有数据(不删除)
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 (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
}
// 从链表尾获取所有数据(不删除)
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 (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
}
// 获取链表头值(不删除)
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 (l *List) FrontItem() (value interface{}) {
l.mu.RLock()
if e := l.list.Front(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
}
// 获取链表尾值(不删除)
func (this *List) BackItem() interface{} {
this.mu.RLock()
if f := this.list.Back(); f != nil {
this.mu.RUnlock()
return f.Value
func (l *List) BackItem() (value interface{}) {
l.mu.RLock()
if e := l.list.Back(); e != nil {
value = e.Value
}
this.mu.RUnlock()
return nil
l.mu.RUnlock()
return
}
// 获取表头指针
func (this *List) Front() *list.Element {
this.mu.RLock()
r := this.list.Front()
this.mu.RUnlock()
return r
func (l *List) Front() (e *Element) {
l.mu.RLock()
e = l.list.Front()
l.mu.RUnlock()
return
}
// 获取表位指针
func (this *List) Back() *list.Element {
this.mu.RLock()
r := this.list.Back()
this.mu.RUnlock()
return r
func (l *List) Back() (e *Element) {
l.mu.RLock()
e = l.list.Back()
l.mu.RUnlock()
return
}
// 获取链表长度
func (this *List) Len() int {
this.mu.RLock()
length := this.list.Len()
this.mu.RUnlock()
return length
func (l *List) Len() (length int) {
l.mu.RLock()
length = l.list.Len()
l.mu.RUnlock()
return
}
func (l *List) MoveBefore(e, p *Element) {
l.mu.Lock()
l.list.MoveBefore(e, p)
l.mu.Unlock()
}
func (l *List) MoveAfter(e, p *Element) {
l.mu.Lock()
l.list.MoveAfter(e, p)
l.mu.Unlock()
}
func (l *List) MoveToFront(e *Element) {
l.mu.Lock()
l.list.MoveToFront(e)
l.mu.Unlock()
}
func (l *List) MoveToBack(e *Element) {
l.mu.Lock()
l.list.MoveToBack(e)
l.mu.Unlock()
}
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()
}
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()
}
// 在list中元素项p之后插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
func (l *List) InsertAfter(v interface{}, p *Element) (e *Element) {
l.mu.Lock()
e = l.list.InsertAfter(v, p)
l.mu.Unlock()
return
}
// 在list中元素项p之前插入一个值为v的元素并返回该元素如果mark不是list中元素则list不改变。
func (l *List) InsertBefore(v interface{}, p *Element) (e *Element) {
l.mu.Lock()
e = l.list.InsertBefore(v, p)
l.mu.Unlock()
return
}
// 删除数据项e, 并返回删除项的元素项
func (l *List) Remove(e *Element) (value interface{}) {
l.mu.Lock()
value = l.list.Remove(e)
l.mu.Unlock()
return
}
// 批量删除数据项
func (l *List) BatchRemove(es []*Element) {
l.mu.Lock()
for _, e := range es {
l.list.Remove(e)
}
l.mu.Unlock()
return
}
// 删除所有数据项
func (l *List) RemoveAll() {
l.mu.Lock()
l.list = list.New()
l.mu.Unlock()
}
// 读锁操作
func (l *List) RLockFunc(f func(list *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(l.list)
}
// 写锁操作
func (l *List) LockFunc(f func(list *list.List)) {
l.mu.Lock()
defer l.mu.Unlock()
f(l.list)
}

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,334 @@
// 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 kinds of concurrent-safe(alternative) maps.
//
// 并发安全MAP.
package gmap
// 默认的Map对象其实就是InterfaceInterfaceMap的别名。
// 注意
import "github.com/gogf/gf/g/internal/rwmutex"
// 注意:
// 1、这个Map是所有并发安全Map中效率最低的如果对效率要求比较高的场合请合理选择对应数据类型的Map
// 2、这个Map的优点是使用简便由于键值都是interface{}类型,因此对键值的数据类型要求不高;
// 3、底层实现比较类似于sync.Map
type Map = InterfaceInterfaceMap
type Map struct {
mu *rwmutex.RWMutex
m map[interface{}]interface{}
}
func New(safe...bool) *Map {
return NewInterfaceInterfaceMap(safe...)
// Create an empty hash map.
// The param <unsafe> used to specify whether using map with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的哈希表参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func New(unsafe...bool) *Map {
return NewMap(unsafe...)
}
// See New.
//
// 同New方法。
func NewMap(unsafe...bool) *Map {
return &Map{
m : make(map[interface{}]interface{}),
mu : rwmutex.New(unsafe...),
}
}
// Create a hash map from given map.
// Be aware that, the param map is a type of pointer,
// there might be some concurrent-safe issues when changing the map outside.
//
// 基于给定的map变量创建哈希表对象注意由于map是指针类型外部对map有操作时会有并发安全问题。
func NewFrom(m map[interface{}]interface{}, unsafe...bool) *Map {
return &Map{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// Create a hash map from given arrays.
// The param <keys> given as the keys of the map,
// and <values> as the corresponding values.
//
// 基于给定的数组变量创建哈希表对象keys作为键名, values作为键值。
// 当keys数组大小比values数组大时多余的键名将会使用对应类型默认的键值。
func NewFromArray(keys []interface{}, values []interface{}, unsafe...bool) *Map {
m := make(map[interface{}]interface{})
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = interface{}(nil)
}
}
return &Map{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// Iterate the hash map with custom callback function <f>.
// If f returns true, then continue iterating; or false to stop.
//
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历
func (gm *Map) Iterator(f func (k interface{}, v interface{}) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// Clone current hash map with copy of underlying data,
// return a new hash map.
//
// 哈希表克隆.
func (gm *Map) Clone() *Map {
return NewFrom(gm.Map(), !gm.mu.IsSafe())
}
// Returns copy of the data of the hash map.
//
// 返回当前哈希表的数据Map.
func (gm *Map) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{})
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
gm.mu.RUnlock()
return m
}
// Set key-value to the hash map.
//
// 设置键值对
func (gm *Map) Set(key interface{}, val interface{}) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// Batch set key-values to the hash map.
//
// 批量设置键值对
func (gm *Map) BatchSet(m map[interface{}]interface{}) {
gm.mu.Lock()
for k, v := range m {
gm.m[k] = v
}
gm.mu.Unlock()
}
// Get value by key.
//
// 获取键值
func (gm *Map) Get(key interface{}) interface{} {
gm.mu.RLock()
val, _ := gm.m[key]
gm.mu.RUnlock()
return val
}
// 设置kv缓存键值对内部会对键名的存在性使用写锁进行二次检索确认如果存在则不再写入返回键名对应的键值。
// 在高并发下有用,防止数据写入的并发逻辑错误。
func (gm *Map) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
gm.m[key] = value
return value
}
// Get the value by key, or set it with given key-value if not exist.
//
// 当键名存在时返回其键值,否则写入指定的键值
func (gm *Map) GetOrSet(key interface{}, value interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.doSetWithLockCheck(key, value)
} else {
return v
}
}
// Get the value by key, or set the it with return of callback function <f> if not exist.
//
// 当键名存在时返回其键值,否则写入指定的键值,键值由指定的函数生成
func (gm *Map) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.doSetWithLockCheck(key, f())
} else {
return v
}
}
// Get the value by key, or set the it with return of callback function <f> if not exist.
// The difference with GetOrSetFunc is, it locks in executing callback function <f>.
//
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (gm *Map) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.doSetWithLockCheck(key, f)
} else {
return v
}
}
// Set key-value if the key does not exist, then return true; or else return false.
//
// 当键名不存在时写入并返回true否则返回false。
func (gm *Map) SetIfNotExist(key interface{}, value interface{}) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// Batch remove by keys.
//
// 批量删除键值对
func (gm *Map) BatchRemove(keys []interface{}) {
gm.mu.Lock()
for _, key := range keys {
delete(gm.m, key)
}
gm.mu.Unlock()
}
// Remove by given key.
//
// 返回对应的键值,并删除该键值
func (gm *Map) Remove(key interface{}) interface{} {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(gm.m, key)
}
gm.mu.Unlock()
return val
}
// Return all the keys of hash map as a slice.
//
// 返回键列表.
func (gm *Map) Keys() []interface{} {
gm.mu.RLock()
keys := make([]interface{}, 0)
for key, _ := range gm.m {
keys = append(keys, key)
}
gm.mu.RUnlock()
return keys
}
// Return all the values of hash map as a slice.
//
// 返回值列表(注意是随机排序)
func (gm *Map) Values() []interface{} {
gm.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range gm.m {
vals = append(vals, val)
}
gm.mu.RUnlock()
return vals
}
// Check whether a key exist.
//
// 是否存在某个键
func (gm *Map) Contains(key interface{}) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// Get the size of hash map.
//
// 哈希表大小
func (gm *Map) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// Check whether the hash map is empty.
//
// 哈希表是否为空
func (gm *Map) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// Clear the hash map, it will remake a new underlying map data map.
//
// 清空哈希表
func (gm *Map) Clear() {
gm.mu.Lock()
gm.m = make(map[interface{}]interface{})
gm.mu.Unlock()
}
// Lock writing with given callback <f>.
//
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (gm *Map) LockFunc(f func(m map[interface{}]interface{})) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// Lock reading with given callback <f>.
//
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (gm *Map) RLockFunc(f func(m map[interface{}]interface{})) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// Exchange key-value in the hash map, it will change key-value to value-key.
//
// 交换Map中的键和值.
func (gm *Map) Flip() {
gm.mu.Lock()
defer gm.mu.Unlock()
n := make(map[interface{}]interface{}, len(gm.m))
for i, v := range gm.m {
n[v] = i
}
gm.m = n
}
// Merge two hash maps.
//
// 合并两个Map.
func (gm *Map) Merge(m *Map) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

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 gm file,
// You can obtain one at https://github.com/gogf/gf.
package gmap

View File

@ -1,14 +1,14 @@
// 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.
//
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
)
type IntBoolMap struct {
@ -16,29 +16,57 @@ type IntBoolMap struct {
mu *rwmutex.RWMutex
}
func NewIntBoolMap(safe...bool) *IntBoolMap {
func NewIntBoolMap(unsafe...bool) *IntBoolMap {
return &IntBoolMap{
m : make(map[int]bool),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
// 哈希表克隆
func (this *IntBoolMap) Clone() map[int]bool {
func NewIntBoolMapFrom(m map[int]bool, unsafe...bool) *IntBoolMap {
return &IntBoolMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewIntBoolMapFromArray(keys []int, values []bool, unsafe...bool) *IntBoolMap {
m := make(map[int]bool)
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = false
}
}
return &IntBoolMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 哈希表克隆.
func (gm *IntBoolMap) Clone() *IntBoolMap {
return NewIntBoolMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *IntBoolMap) Map() map[int]bool {
m := make(map[int]bool)
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.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 {
func (gm *IntBoolMap) Iterator(f func (k int, v bool) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
@ -46,80 +74,80 @@ func (this *IntBoolMap) Iterator(f func (k int, v bool) bool) {
}
// 设置键值对
func (this *IntBoolMap) Set(key int, val bool) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *IntBoolMap) Set(key int, val bool) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *IntBoolMap) BatchSet(m map[int]bool) {
this.mu.Lock()
func (gm *IntBoolMap) BatchSet(m map[int]bool) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *IntBoolMap) Get(key int) bool {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *IntBoolMap) Get(key int) bool {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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()
func (gm *IntBoolMap) doSetWithLockCheck(key int, value bool) bool {
gm.mu.Lock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
gm.m[key] = value
gm.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntBoolMap) GetOrSet(key int, value bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
func (gm *IntBoolMap) GetOrSet(key int, value bool) bool {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
return gm.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()
func (gm *IntBoolMap) GetOrSetFunc(key int, f func() bool) bool {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
return gm.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()
func (gm *IntBoolMap) GetOrSetFuncLock(key int, f func() bool) bool {
gm.mu.RLock()
val, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
val = f()
this.m[key] = val
gm.m[key] = val
return val
} else {
return val
@ -127,97 +155,110 @@ func (this *IntBoolMap) GetOrSetFuncLock(key int, f func() bool) bool {
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntBoolMap) SetIfNotExist(key int, value bool) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
func (gm *IntBoolMap) SetIfNotExist(key int, value bool) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntBoolMap) BatchRemove(keys []int) {
this.mu.Lock()
func (gm *IntBoolMap) BatchRemove(keys []int) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntBoolMap) Remove(key int) bool {
this.mu.Lock()
val, exists := this.m[key]
func (gm *IntBoolMap) Remove(key int) bool {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *IntBoolMap) Keys() []int {
this.mu.RLock()
func (gm *IntBoolMap) Keys() []int {
gm.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
//func (this *IntBoolMap) Values() []bool {
// this.mu.RLock()
//func (gm *IntBoolMap) Values() []bool {
// gm.mu.RLock()
// vals := make([]bool, 0)
// for _, val := range this.m {
// for _, val := range gm.m {
// vals = append(vals, val)
// }
// this.mu.RUnlock()
// gm.mu.RUnlock()
// return vals
//}
// 是否存在某个键
func (this *IntBoolMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *IntBoolMap) Contains(key int) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntBoolMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *IntBoolMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntBoolMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *IntBoolMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntBoolMap) Clear() {
this.mu.Lock()
this.m = make(map[int]bool)
this.mu.Unlock()
func (gm *IntBoolMap) Clear() {
gm.mu.Lock()
gm.m = make(map[int]bool)
gm.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 (gm *IntBoolMap) LockFunc(f func(m map[int]bool)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntBoolMap) RLockFunc(f func(m map[int]bool)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *IntBoolMap) RLockFunc(f func(m map[int]bool)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 合并两个Map.
func (gm *IntBoolMap) Merge(m *IntBoolMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

View File

@ -1,14 +1,14 @@
// 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.
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
)
type IntIntMap struct {
@ -16,110 +16,138 @@ type IntIntMap struct {
m map[int]int
}
func NewIntIntMap(safe...bool) *IntIntMap {
func NewIntIntMap(unsafe...bool) *IntIntMap {
return &IntIntMap{
m : make(map[int]int),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
func NewIntIntMapFrom(m map[int]int, unsafe...bool) *IntIntMap {
return &IntIntMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewIntIntMapFromArray(keys []int, values []int, unsafe...bool) *IntIntMap {
m := make(map[int]int)
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = 0
}
}
return &IntIntMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回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 {
func (gm *IntIntMap) Iterator(f func (k int, v int) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntIntMap) Clone() map[int]int {
// 哈希表克隆.
func (gm *IntIntMap) Clone() *IntIntMap {
return NewIntIntMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *IntIntMap) Map() map[int]int {
m := make(map[int]int)
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntIntMap) Set(key int, val int) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *IntIntMap) Set(key int, val int) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *IntIntMap) BatchSet(m map[int]int) {
this.mu.Lock()
func (gm *IntIntMap) BatchSet(m map[int]int) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *IntIntMap) Get(key int) (int) {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *IntIntMap) Get(key int) (int) {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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()
func (gm *IntIntMap) doSetWithLockCheck(key int, value int) int {
gm.mu.Lock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
gm.m[key] = value
gm.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntIntMap) GetOrSet(key int, value int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
func (gm *IntIntMap) GetOrSet(key int, value int) int {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
return gm.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()
func (gm *IntIntMap) GetOrSetFunc(key int, f func() int) int {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
return gm.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()
func (gm *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
gm.mu.RLock()
val, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
val = f()
this.m[key] = val
gm.m[key] = val
return val
} else {
return val
@ -127,97 +155,121 @@ func (this *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntIntMap) SetIfNotExist(key int, value int) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
func (gm *IntIntMap) SetIfNotExist(key int, value int) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntIntMap) BatchRemove(keys []int) {
this.mu.Lock()
func (gm *IntIntMap) BatchRemove(keys []int) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntIntMap) Remove(key int) int {
this.mu.Lock()
val, exists := this.m[key]
func (gm *IntIntMap) Remove(key int) int {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *IntIntMap) Keys() []int {
this.mu.RLock()
func (gm *IntIntMap) Keys() []int {
gm.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntIntMap) Values() []int {
this.mu.RLock()
func (gm *IntIntMap) Values() []int {
gm.mu.RLock()
vals := make([]int, 0)
for _, val := range this.m {
for _, val := range gm.m {
vals = append(vals, val)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntIntMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *IntIntMap) Contains(key int) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntIntMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *IntIntMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntIntMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *IntIntMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntIntMap) Clear() {
this.mu.Lock()
this.m = make(map[int]int)
this.mu.Unlock()
func (gm *IntIntMap) Clear() {
gm.mu.Lock()
gm.m = make(map[int]int)
gm.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 (gm *IntIntMap) LockFunc(f func(m map[int]int)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntIntMap) RLockFunc(f func(m map[int]int)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *IntIntMap) RLockFunc(f func(m map[int]int)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 交换Map中的键和值.
func (gm *IntIntMap) Flip() {
gm.mu.Lock()
defer gm.mu.Unlock()
n := make(map[int]int, len(gm.m))
for k, v := range gm.m {
n[v] = k
}
gm.m = n
}
// 合并两个Map.
func (gm *IntIntMap) Merge(m *IntIntMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

View File

@ -1,206 +1,261 @@
// 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.
//
package gmap
import "gitee.com/johng/gf/g/container/internal/rwmutex"
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type IntInterfaceMap struct {
mu *rwmutex.RWMutex
m map[int]interface{}
}
func NewIntInterfaceMap(safe...bool) *IntInterfaceMap {
func NewIntInterfaceMap(unsafe...bool) *IntInterfaceMap {
return &IntInterfaceMap{
m : make(map[int]interface{}),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
func NewIntInterfaceMapFrom(m map[int]interface{}, unsafe...bool) *IntInterfaceMap {
return &IntInterfaceMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewIntInterfaceMapFromArray(keys []int, values []interface{}, unsafe...bool) *IntInterfaceMap {
m := make(map[int]interface{})
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = interface{}(nil)
}
}
return &IntInterfaceMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回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 {
func (gm *IntInterfaceMap) Iterator(f func (k int, v interface{}) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntInterfaceMap) Clone() map[int]interface{} {
// 哈希表克隆.
func (gm *IntInterfaceMap) Clone() *IntInterfaceMap {
return NewIntInterfaceMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *IntInterfaceMap) Map() map[int]interface{} {
m := make(map[int]interface{})
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntInterfaceMap) Set(key int, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *IntInterfaceMap) Set(key int, val interface{}) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *IntInterfaceMap) BatchSet(m map[int]interface{}) {
this.mu.Lock()
func (gm *IntInterfaceMap) BatchSet(m map[int]interface{}) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *IntInterfaceMap) Get(key int) (interface{}) {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *IntInterfaceMap) Get(key int) (interface{}) {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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 {
func (gm *IntInterfaceMap) doSetWithLockCheck(key int, value interface{}) interface{} {
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
gm.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)
func (gm *IntInterfaceMap) GetOrSet(key int, value interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.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())
func (gm *IntInterfaceMap) GetOrSetFunc(key int, f func() interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.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)
func (gm *IntInterfaceMap) GetOrSetFuncLock(key int, f func() interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.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)
func (gm *IntInterfaceMap) SetIfNotExist(key int, value interface{}) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntInterfaceMap) BatchRemove(keys []int) {
this.mu.Lock()
func (gm *IntInterfaceMap) BatchRemove(keys []int) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntInterfaceMap) Remove(key int) interface{} {
this.mu.Lock()
val, exists := this.m[key]
func (gm *IntInterfaceMap) Remove(key int) interface{} {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *IntInterfaceMap) Keys() []int {
this.mu.RLock()
func (gm *IntInterfaceMap) Keys() []int {
gm.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntInterfaceMap) Values() []interface{} {
this.mu.RLock()
func (gm *IntInterfaceMap) Values() []interface{} {
gm.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
for _, val := range gm.m {
vals = append(vals, val)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntInterfaceMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *IntInterfaceMap) Contains(key int) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *IntInterfaceMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *IntInterfaceMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[int]interface{})
this.mu.Unlock()
func (gm *IntInterfaceMap) Clear() {
gm.mu.Lock()
gm.m = make(map[int]interface{})
gm.mu.Unlock()
}
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (this *IntInterfaceMap) LockFunc(f func(m map[int]interface{})) {
this.mu.Lock()
defer this.mu.Unlock()
f(this.m)
func (gm *IntInterfaceMap) LockFunc(f func(m map[int]interface{})) {
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 交换Map中的键和值.
func (gm *IntInterfaceMap) Flip() {
gm.mu.Lock()
defer gm.mu.Unlock()
n := make(map[int]interface{}, len(gm.m))
for k, v := range gm.m {
n[gconv.Int(v)] = k
}
gm.m = n
}
// 合并两个Map.
func (gm *IntInterfaceMap) Merge(m *IntInterfaceMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

View File

@ -1,14 +1,15 @@
// 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.
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type IntStringMap struct {
@ -16,110 +17,138 @@ type IntStringMap struct {
m map[int]string
}
func NewIntStringMap(safe...bool) *IntStringMap {
func NewIntStringMap(unsafe...bool) *IntStringMap {
return &IntStringMap{
m : make(map[int]string),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
func NewIntStringMapFrom(m map[int]string, unsafe...bool) *IntStringMap {
return &IntStringMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewIntStringMapFromArray(keys []int, values []string, unsafe...bool) *IntStringMap {
m := make(map[int]string)
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = ""
}
}
return &IntStringMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回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 {
func (gm *IntStringMap) Iterator(f func (k int, v string) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *IntStringMap) Clone() map[int]string {
// 哈希表克隆.
func (gm *IntStringMap) Clone() *IntStringMap {
return NewIntStringMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *IntStringMap) Map() map[int]string {
m := make(map[int]string)
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.mu.RUnlock()
return m
}
// 设置键值对
func (this *IntStringMap) Set(key int, val string) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *IntStringMap) Set(key int, val string) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *IntStringMap) BatchSet(m map[int]string) {
this.mu.Lock()
func (gm *IntStringMap) BatchSet(m map[int]string) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *IntStringMap) Get(key int) string {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *IntStringMap) Get(key int) string {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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()
func (gm *IntStringMap) doSetWithLockCheck(key int, value string) string {
gm.mu.Lock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
gm.m[key] = value
gm.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *IntStringMap) GetOrSet(key int, value string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
func (gm *IntStringMap) GetOrSet(key int, value string) string {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
return gm.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()
func (gm *IntStringMap) GetOrSetFunc(key int, f func() string) string {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
return gm.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()
func (gm *IntStringMap) GetOrSetFuncLock(key int, f func() string) string {
gm.mu.RLock()
val, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
val = f()
this.m[key] = val
gm.m[key] = val
return val
} else {
return val
@ -127,97 +156,121 @@ func (this *IntStringMap) GetOrSetFuncLock(key int, f func() string) string {
}
// 当键名不存在时写入并返回true否则返回false。
func (this *IntStringMap) SetIfNotExist(key int, value string) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
func (gm *IntStringMap) SetIfNotExist(key int, value string) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *IntStringMap) BatchRemove(keys []int) {
this.mu.Lock()
func (gm *IntStringMap) BatchRemove(keys []int) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *IntStringMap) Remove(key int) string {
this.mu.Lock()
val, exists := this.m[key]
func (gm *IntStringMap) Remove(key int) string {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *IntStringMap) Keys() []int {
this.mu.RLock()
func (gm *IntStringMap) Keys() []int {
gm.mu.RLock()
keys := make([]int, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *IntStringMap) Values() []string {
this.mu.RLock()
func (gm *IntStringMap) Values() []string {
gm.mu.RLock()
vals := make([]string, 0)
for _, val := range this.m {
for _, val := range gm.m {
vals = append(vals, val)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *IntStringMap) Contains(key int) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *IntStringMap) Contains(key int) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *IntStringMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *IntStringMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *IntStringMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *IntStringMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *IntStringMap) Clear() {
this.mu.Lock()
this.m = make(map[int]string)
this.mu.Unlock()
func (gm *IntStringMap) Clear() {
gm.mu.Lock()
gm.m = make(map[int]string)
gm.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 (gm *IntStringMap) LockFunc(f func(m map[int]string)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *IntStringMap) RLockFunc(f func(m map[int]string)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *IntStringMap) RLockFunc(f func(m map[int]string)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 交换Map中的键和值.
func (gm *IntStringMap) Flip() {
gm.mu.Lock()
defer gm.mu.Unlock()
n := make(map[int]string, len(gm.m))
for k, v := range gm.m {
n[gconv.Int(v)] = gconv.String(k)
}
gm.m = n
}
// 合并两个Map.
func (gm *IntStringMap) Merge(m *IntStringMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

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

@ -1,14 +1,14 @@
// 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.
//
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
)
type StringBoolMap struct {
@ -16,110 +16,138 @@ type StringBoolMap struct {
m map[string]bool
}
func NewStringBoolMap(safe...bool) *StringBoolMap {
func NewStringBoolMap(unsafe...bool) *StringBoolMap {
return &StringBoolMap{
m : make(map[string]bool),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
func NewStringBoolMapFrom(m map[string]bool, unsafe...bool) *StringBoolMap {
return &StringBoolMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewStringBoolMapFromArray(keys []string, values []bool, unsafe...bool) *StringBoolMap {
m := make(map[string]bool)
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = false
}
}
return &StringBoolMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回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 {
func (gm *StringBoolMap) Iterator(f func (k string, v bool) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringBoolMap) Clone() map[string]bool {
// 哈希表克隆.
func (gm *StringBoolMap) Clone() *StringBoolMap {
return NewStringBoolMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *StringBoolMap) Map() map[string]bool {
m := make(map[string]bool)
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringBoolMap) Set(key string, val bool) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *StringBoolMap) Set(key string, val bool) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *StringBoolMap) BatchSet(m map[string]bool) {
this.mu.Lock()
func (gm *StringBoolMap) BatchSet(m map[string]bool) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *StringBoolMap) Get(key string) bool {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *StringBoolMap) Get(key string) bool {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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()
func (gm *StringBoolMap) doSetWithLockCheck(key string, value bool) bool {
gm.mu.Lock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
gm.m[key] = value
gm.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringBoolMap) GetOrSet(key string, value bool) bool {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
func (gm *StringBoolMap) GetOrSet(key string, value bool) bool {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
return gm.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()
func (gm *StringBoolMap) GetOrSetFunc(key string, f func() bool) bool {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
return gm.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()
func (gm *StringBoolMap) GetOrSetFuncLock(key string, f func() bool) bool {
gm.mu.RLock()
val, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
val = f()
this.m[key] = val
gm.m[key] = val
return val
} else {
return val
@ -127,97 +155,110 @@ func (this *StringBoolMap) GetOrSetFuncLock(key string, f func() bool) bool {
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringBoolMap) SetIfNotExist(key string, value bool) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
func (gm *StringBoolMap) SetIfNotExist(key string, value bool) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringBoolMap) BatchRemove(keys []string) {
this.mu.Lock()
func (gm *StringBoolMap) BatchRemove(keys []string) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringBoolMap) Remove(key string) bool {
this.mu.Lock()
val, exists := this.m[key]
func (gm *StringBoolMap) Remove(key string) bool {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *StringBoolMap) Keys() []string {
this.mu.RLock()
func (gm *StringBoolMap) Keys() []string {
gm.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
//func (this *StringBoolMap) Values() []bool {
// this.mu.RLock()
//func (gm *StringBoolMap) Values() []bool {
// gm.mu.RLock()
// vals := make([]bool, 0)
// for _, val := range this.m {
// for _, val := range gm.m {
// vals = append(vals, val)
// }
// this.mu.RUnlock()
// gm.mu.RUnlock()
// return vals
//}
// 是否存在某个键
func (this *StringBoolMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *StringBoolMap) Contains(key string) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringBoolMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *StringBoolMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringBoolMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *StringBoolMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringBoolMap) Clear() {
this.mu.Lock()
this.m = make(map[string]bool)
this.mu.Unlock()
func (gm *StringBoolMap) Clear() {
gm.mu.Lock()
gm.m = make(map[string]bool)
gm.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 (gm *StringBoolMap) LockFunc(f func(m map[string]bool)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (this *StringBoolMap) RLockFunc(f func(m map[string]bool)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *StringBoolMap) RLockFunc(f func(m map[string]bool)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 合并两个Map.
func (gm *StringBoolMap) Merge(m *StringBoolMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

View File

@ -1,123 +1,154 @@
// 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.
//
package gmap
import "gitee.com/johng/gf/g/container/internal/rwmutex"
import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type StringIntMap struct {
mu *rwmutex.RWMutex
m map[string]int
}
func NewStringIntMap(safe...bool) *StringIntMap {
func NewStringIntMap(unsafe...bool) *StringIntMap {
return &StringIntMap{
m : make(map[string]int),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
func NewStringIntMapFrom(m map[string]int, unsafe...bool) *StringIntMap {
return &StringIntMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewStringIntMapFromArray(keys []string, values []int, unsafe...bool) *StringIntMap {
m := make(map[string]int)
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = 0
}
}
return &StringIntMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回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 {
func (gm *StringIntMap) Iterator(f func (k string, v int) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringIntMap) Clone() map[string]int {
// 哈希表克隆.
func (gm *StringIntMap) Clone() *StringIntMap {
return NewStringIntMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *StringIntMap) Map() map[string]int {
m := make(map[string]int)
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringIntMap) Set(key string, val int) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *StringIntMap) Set(key string, val int) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *StringIntMap) BatchSet(m map[string]int) {
this.mu.Lock()
func (gm *StringIntMap) BatchSet(m map[string]int) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *StringIntMap) Get(key string) int {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *StringIntMap) Get(key string) int {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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()
func (gm *StringIntMap) doSetWithLockCheck(key string, value int) int {
gm.mu.Lock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
gm.m[key] = value
gm.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringIntMap) GetOrSet(key string, value int) int {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
func (gm *StringIntMap) GetOrSet(key string, value int) int {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
return gm.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()
func (gm *StringIntMap) GetOrSetFunc(key string, f func() int) int {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
return gm.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()
func (gm *StringIntMap) GetOrSetFuncLock(key string, f func() int) int {
gm.mu.RLock()
val, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
val = f()
this.m[key] = val
gm.m[key] = val
return val
} else {
return val
@ -125,97 +156,121 @@ func (this *StringIntMap) GetOrSetFuncLock(key string, f func() int) int {
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringIntMap) SetIfNotExist(key string, value int) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
func (gm *StringIntMap) SetIfNotExist(key string, value int) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringIntMap) BatchRemove(keys []string) {
this.mu.Lock()
func (gm *StringIntMap) BatchRemove(keys []string) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringIntMap) Remove(key string) int {
this.mu.Lock()
val, exists := this.m[key]
func (gm *StringIntMap) Remove(key string) int {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *StringIntMap) Keys() []string {
this.mu.RLock()
func (gm *StringIntMap) Keys() []string {
gm.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringIntMap) Values() []int {
this.mu.RLock()
func (gm *StringIntMap) Values() []int {
gm.mu.RLock()
vals := make([]int, 0)
for _, val := range this.m {
for _, val := range gm.m {
vals = append(vals, val)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringIntMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *StringIntMap) Contains(key string) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringIntMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *StringIntMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringIntMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *StringIntMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringIntMap) Clear() {
this.mu.Lock()
this.m = make(map[string]int)
this.mu.Unlock()
func (gm *StringIntMap) Clear() {
gm.mu.Lock()
gm.m = make(map[string]int)
gm.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 (gm *StringIntMap) LockFunc(f func(m map[string]int)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringIntMap) RLockFunc(f func(m map[string]int)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *StringIntMap) RLockFunc(f func(m map[string]int)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 交换Map中的键和值.
func (gm *StringIntMap) Flip() {
gm.mu.Lock()
defer gm.mu.Unlock()
n := make(map[string]int, len(gm.m))
for k, v := range gm.m {
n[gconv.String(v)] = gconv.Int(k)
}
gm.m = n
}
// 合并两个Map.
func (gm *StringIntMap) Merge(m *StringIntMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

View File

@ -1,14 +1,15 @@
// 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.
//
package gmap
import (
"gitee.com/johng/gf/g/container/internal/rwmutex"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
)
type StringInterfaceMap struct {
@ -16,193 +17,245 @@ type StringInterfaceMap struct {
m map[string]interface{}
}
func NewStringInterfaceMap(safe...bool) *StringInterfaceMap {
func NewStringInterfaceMap(unsafe...bool) *StringInterfaceMap {
return &StringInterfaceMap{
m : make(map[string]interface{}),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
func NewStringInterfaceMapFrom(m map[string]interface{}, unsafe...bool) *StringInterfaceMap {
return &StringInterfaceMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewStringInterfaceMapFromArray(keys []string, values []interface{}, unsafe...bool) *StringInterfaceMap {
m := make(map[string]interface{})
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = interface{}(nil)
}
}
return &StringInterfaceMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回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 {
func (gm *StringInterfaceMap) Iterator(f func (k string, v interface{}) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringInterfaceMap) Clone() map[string]interface{} {
// 哈希表克隆.
func (gm *StringInterfaceMap) Clone() *StringInterfaceMap {
return NewStringInterfaceMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *StringInterfaceMap) Map() map[string]interface{} {
m := make(map[string]interface{})
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringInterfaceMap) Set(key string, val interface{}) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *StringInterfaceMap) Set(key string, val interface{}) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *StringInterfaceMap) BatchSet(m map[string]interface{}) {
this.mu.Lock()
func (gm *StringInterfaceMap) BatchSet(m map[string]interface{}) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *StringInterfaceMap) Get(key string) interface{} {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *StringInterfaceMap) Get(key string) interface{} {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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 {
func (gm *StringInterfaceMap) doSetWithLockCheck(key string, value interface{}) interface{} {
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
return v
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
this.m[key] = value
gm.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)
func (gm *StringInterfaceMap) GetOrSet(key string, value interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.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())
func (gm *StringInterfaceMap) GetOrSetFunc(key string, f func() interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.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)
func (gm *StringInterfaceMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
if v := gm.Get(key); v == nil {
return gm.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)
func (gm *StringInterfaceMap) SetIfNotExist(key string, value interface{}) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringInterfaceMap) BatchRemove(keys []string) {
this.mu.Lock()
func (gm *StringInterfaceMap) BatchRemove(keys []string) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringInterfaceMap) Remove(key string) interface{} {
this.mu.Lock()
val, exists := this.m[key]
func (gm *StringInterfaceMap) Remove(key string) interface{} {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *StringInterfaceMap) Keys() []string {
this.mu.RLock()
func (gm *StringInterfaceMap) Keys() []string {
gm.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringInterfaceMap) Values() []interface{} {
this.mu.RLock()
func (gm *StringInterfaceMap) Values() []interface{} {
gm.mu.RLock()
vals := make([]interface{}, 0)
for _, val := range this.m {
for _, val := range gm.m {
vals = append(vals, val)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringInterfaceMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *StringInterfaceMap) Contains(key string) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringInterfaceMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *StringInterfaceMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringInterfaceMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *StringInterfaceMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringInterfaceMap) Clear() {
this.mu.Lock()
this.m = make(map[string]interface{})
this.mu.Unlock()
func (gm *StringInterfaceMap) Clear() {
gm.mu.Lock()
gm.m = make(map[string]interface{})
gm.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 (gm *StringInterfaceMap) LockFunc(f func(m map[string]interface{})) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 交换Map中的键和值.
func (gm *StringInterfaceMap) Flip() {
gm.mu.Lock()
defer gm.mu.Unlock()
n := make(map[string]interface{}, len(gm.m))
for k, v := range gm.m {
n[gconv.String(v)] = k
}
gm.m = n
}
// 合并两个Map.
func (gm *StringInterfaceMap) Merge(m *StringInterfaceMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

View File

@ -1,123 +1,153 @@
// 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.
//
package gmap
import "gitee.com/johng/gf/g/container/internal/rwmutex"
import (
"github.com/gogf/gf/g/internal/rwmutex"
)
type StringStringMap struct {
mu *rwmutex.RWMutex
m map[string]string
}
func NewStringStringMap(safe...bool) *StringStringMap {
func NewStringStringMap(unsafe...bool) *StringStringMap {
return &StringStringMap{
m : make(map[string]string),
mu : rwmutex.New(safe...),
mu : rwmutex.New(unsafe...),
}
}
func NewStringStringMapFrom(m map[string]string, unsafe...bool) *StringStringMap {
return &StringStringMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
func NewStringStringMapFromArray(keys []string, values []string, unsafe...bool) *StringStringMap {
m := make(map[string]string)
l := len(values)
for i, k := range keys {
if i < l {
m[k] = values[i]
} else {
m[k] = ""
}
}
return &StringStringMap{
m : m,
mu : rwmutex.New(unsafe...),
}
}
// 给定回调函数对原始内容进行遍历回调函数返回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 {
func (gm *StringStringMap) Iterator(f func (k string, v string) bool) {
gm.mu.RLock()
defer gm.mu.RUnlock()
for k, v := range gm.m {
if !f(k, v) {
break
}
}
}
// 哈希表克隆
func (this *StringStringMap) Clone() map[string]string {
// 哈希表克隆.
func (gm *StringStringMap) Clone() *StringStringMap {
return NewStringStringMapFrom(gm.Map(), !gm.mu.IsSafe())
}
// 返回当前哈希表的数据Map.
func (gm *StringStringMap) Map() map[string]string {
m := make(map[string]string)
this.mu.RLock()
for k, v := range this.m {
gm.mu.RLock()
for k, v := range gm.m {
m[k] = v
}
this.mu.RUnlock()
gm.mu.RUnlock()
return m
}
// 设置键值对
func (this *StringStringMap) Set(key string, val string) {
this.mu.Lock()
this.m[key] = val
this.mu.Unlock()
func (gm *StringStringMap) Set(key string, val string) {
gm.mu.Lock()
gm.m[key] = val
gm.mu.Unlock()
}
// 批量设置键值对
func (this *StringStringMap) BatchSet(m map[string]string) {
this.mu.Lock()
func (gm *StringStringMap) BatchSet(m map[string]string) {
gm.mu.Lock()
for k, v := range m {
this.m[k] = v
gm.m[k] = v
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 获取键值
func (this *StringStringMap) Get(key string) string {
this.mu.RLock()
val, _ := this.m[key]
this.mu.RUnlock()
func (gm *StringStringMap) Get(key string) string {
gm.mu.RLock()
val, _ := gm.m[key]
gm.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()
func (gm *StringStringMap) doSetWithLockCheck(key string, value string) string {
gm.mu.Lock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
this.m[key] = value
this.mu.Unlock()
gm.m[key] = value
gm.mu.Unlock()
return value
}
// 当键名存在时返回其键值,否则写入指定的键值
func (this *StringStringMap) GetOrSet(key string, value string) string {
this.mu.RLock()
v, ok := this.m[key]
this.mu.RUnlock()
func (gm *StringStringMap) GetOrSet(key string, value string) string {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, value)
return gm.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()
func (gm *StringStringMap) GetOrSetFunc(key string, f func() string) string {
gm.mu.RLock()
v, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
return this.doSetWithLockCheck(key, f())
return gm.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()
func (gm *StringStringMap) GetOrSetFuncLock(key string, f func() string) string {
gm.mu.RLock()
val, ok := gm.m[key]
gm.mu.RUnlock()
if !ok {
this.mu.Lock()
defer this.mu.Unlock()
if v, ok := this.m[key]; ok {
this.mu.Unlock()
gm.mu.Lock()
defer gm.mu.Unlock()
if v, ok := gm.m[key]; ok {
gm.mu.Unlock()
return v
}
val = f()
this.m[key] = val
gm.m[key] = val
return val
} else {
return val
@ -125,97 +155,121 @@ func (this *StringStringMap) GetOrSetFuncLock(key string, f func() string) strin
}
// 当键名不存在时写入并返回true否则返回false。
func (this *StringStringMap) SetIfNotExist(key string, value string) bool {
if !this.Contains(key) {
this.doSetWithLockCheck(key, value)
func (gm *StringStringMap) SetIfNotExist(key string, value string) bool {
if !gm.Contains(key) {
gm.doSetWithLockCheck(key, value)
return true
}
return false
}
// 批量删除键值对
func (this *StringStringMap) BatchRemove(keys []string) {
this.mu.Lock()
func (gm *StringStringMap) BatchRemove(keys []string) {
gm.mu.Lock()
for _, key := range keys {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
}
// 返回对应的键值,并删除该键值
func (this *StringStringMap) Remove(key string) string {
this.mu.Lock()
val, exists := this.m[key]
func (gm *StringStringMap) Remove(key string) string {
gm.mu.Lock()
val, exists := gm.m[key]
if exists {
delete(this.m, key)
delete(gm.m, key)
}
this.mu.Unlock()
gm.mu.Unlock()
return val
}
// 返回键列表
func (this *StringStringMap) Keys() []string {
this.mu.RLock()
func (gm *StringStringMap) Keys() []string {
gm.mu.RLock()
keys := make([]string, 0)
for key, _ := range this.m {
for key, _ := range gm.m {
keys = append(keys, key)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return keys
}
// 返回值列表(注意是随机排序)
func (this *StringStringMap) Values() []string {
this.mu.RLock()
func (gm *StringStringMap) Values() []string {
gm.mu.RLock()
vals := make([]string, 0)
for _, val := range this.m {
for _, val := range gm.m {
vals = append(vals, val)
}
this.mu.RUnlock()
gm.mu.RUnlock()
return vals
}
// 是否存在某个键
func (this *StringStringMap) Contains(key string) bool {
this.mu.RLock()
_, exists := this.m[key]
this.mu.RUnlock()
func (gm *StringStringMap) Contains(key string) bool {
gm.mu.RLock()
_, exists := gm.m[key]
gm.mu.RUnlock()
return exists
}
// 哈希表大小
func (this *StringStringMap) Size() int {
this.mu.RLock()
length := len(this.m)
this.mu.RUnlock()
func (gm *StringStringMap) Size() int {
gm.mu.RLock()
length := len(gm.m)
gm.mu.RUnlock()
return length
}
// 哈希表是否为空
func (this *StringStringMap) IsEmpty() bool {
this.mu.RLock()
empty := len(this.m) == 0
this.mu.RUnlock()
func (gm *StringStringMap) IsEmpty() bool {
gm.mu.RLock()
empty := len(gm.m) == 0
gm.mu.RUnlock()
return empty
}
// 清空哈希表
func (this *StringStringMap) Clear() {
this.mu.Lock()
this.m = make(map[string]string)
this.mu.Unlock()
func (gm *StringStringMap) Clear() {
gm.mu.Lock()
gm.m = make(map[string]string)
gm.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 (gm *StringStringMap) LockFunc(f func(m map[string]string)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
f(gm.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (this *StringStringMap) RLockFunc(f func(m map[string]string)) {
this.mu.RLock(true)
defer this.mu.RUnlock(true)
f(this.m)
func (gm *StringStringMap) RLockFunc(f func(m map[string]string)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
f(gm.m)
}
// 交换Map中的键和值.
func (gm *StringStringMap) Flip() {
gm.mu.Lock()
defer gm.mu.Unlock()
n := make(map[string]string, len(gm.m))
for k, v := range gm.m {
n[v] = k
}
gm.m = n
}
// 合并两个Map.
func (gm *StringStringMap) Merge(m *StringStringMap) {
gm.mu.Lock()
defer gm.mu.Unlock()
if m != gm {
m.mu.RLock()
defer m.mu.RUnlock()
}
for k, v := range m.m {
gm.m[k] = v
}
}

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
@ -18,7 +18,7 @@ var ibm = NewIntBoolMap()
var iim = NewIntIntMap()
var iifm = NewIntInterfaceMap()
var ism = NewIntStringMap()
var ififm = NewInterfaceInterfaceMap()
var ififm = NewMap()
var sbm = NewStringBoolMap()
var sim = NewStringIntMap()
var sifm = NewStringInterfaceMap()

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

@ -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
@ -14,15 +14,15 @@ import (
)
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)
var ibmUnsafe = NewIntBoolMap(true)
var iimUnsafe = NewIntIntMap(true)
var iifmUnsafe = NewIntInterfaceMap(true)
var ismUnsafe = NewIntStringMap(true)
var ififmUnsafe = NewMap(true)
var sbmUnsafe = NewStringBoolMap(true)
var simUnsafe = NewStringIntMap(true)
var sifmUnsafe = NewStringInterfaceMap(true)
var ssmUnsafe = NewStringStringMap(true)
// 写入性能测试

View File

@ -1,18 +1,21 @@
// 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 a 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"
)
// 对象池
@ -31,27 +34,29 @@ type poolItem struct {
value interface{} // 对象值
}
// 对象创建方法类型
type NewFunc func() (interface{}, error)
// 对象过期方法类型
type ExpireFunc func(interface{})
// 创建一个对象池,为保证执行效率,过期时间一旦设定之后无法修改
// expire = 0表示不过期expire < 0表示使用完立即回收expire > 0表示超时回收
// 注意过期时间单位为**毫秒**
func New(expire int, newFunc...func() (interface{}, error)) *Pool {
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
}
// 放一个临时对象到池中
func (p *Pool) Put(value interface{}) {
item := &poolItem {
@ -99,22 +104,22 @@ func (p *Pool) Close() {
}
// 超时检测循环
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 {
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,54 +1,58 @@
// 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(alternative) queue.
//
// 并发安全动态队列.
//
// 特点:
// 1. 动态队列初始化速度快;
// 2. 动态的队列大小(不限大小)
// 3. 取数据时如果队列为空那么会阻塞等待;
package gqueue
import (
"gitee.com/johng/gf/g/container/glist"
"container/list"
"math"
"sync"
)
// 0、这是一个先进先出的队列(chan <-- list)
// 1、当创建Queue对象时限定大小那么等同于一个同步的chan并发安全队列
// 2、不限制大小时list链表用以存储数据临时chan负责为客户端读取数据当从chan获取数据时list往chan中不停补充数据
// 3、由于功能主体是chan那么操作仍然像chan那样具有阻塞效果
// 1、这是一个先进先出的队列(chan <-- list)
//
// 2、当创建Queue对象时限定大小那么等同于一个同步的chan并发安全队列
//
// 3、不限制大小时list链表用以存储数据临时chan负责为客户端读取数据当从chan获取数据时list往chan中不停补充数据
//
// 4、由于功能主体是chan那么操作仍然像chan那样具有阻塞效果
type Queue struct {
mu sync.Mutex // 底层链表写锁
limit int // 队列限制大小
queue chan interface{} // 用于队列写入限制
list *glist.List // 数据链表
events chan struct{} // 通知chan当不限制队列大小时的写入事件通知
closeChan chan struct{} // 关闭channel
list *list.List // 底层数据链表
events chan struct{} // 写入事件通知
closed chan struct{} // 队列关闭通知
C chan interface{} // 队列数据读取
}
const (
// 默认临时队列大小,注意是临时的
// 动态队列缓冲区大小
gDEFAULT_QUEUE_SIZE = 10000
)
// 队列大小为非必须参数,默认不限制
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 = list.New()
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
go q.startAsyncLoop()
}
return q
@ -58,13 +62,24 @@ func New(limit...int) *Queue {
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)
q.mu.Lock()
for i := 0; i < length; i++ {
if e := q.list.Front(); e != nil {
array[i] = q.list.Remove(e)
} else {
break
}
}
q.mu.Unlock()
for _, v := range array {
q.C <- v
}
} else {
break
}
@ -73,34 +88,33 @@ func (q *Queue) startAsyncLoop() {
}
}
// 将数据压入队列, 队
// 将数据压入队列, 队
func (q *Queue) Push(v interface{}) {
if q.limit > 0 {
q.queue <- v
q.C <- v
} else {
q.mu.Lock()
q.list.PushBack(v)
if len(q.events) == 0 {
q.events <- struct{}{}
}
q.mu.Unlock()
q.events <- struct{}{}
}
}
// 从队头先进先出地从队列取出一项数据
func (q *Queue) Pop() interface{} {
return <- q.queue
return <- q.C
}
// 关闭队列(通知所有通过Pop*阻塞的协程退出)
func (q *Queue) Close() {
q.list.RemoveAll()
close(q.queue)
close(q.C)
close(q.events)
close(q.closeChan)
close(q.closed)
}
// 获取当前队列大小
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,16 +1,18 @@
// 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(alternative) 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 {
@ -21,9 +23,9 @@ type Ring struct {
dirty *gtype.Bool // 标记环是否脏了(需要重新计算大小,当环大小发生改变时做标记)
}
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),

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

@ -1,15 +1,293 @@
// 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(alternative) 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{}
}
// Create a set, which contains un-repeated items.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的集合对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func New(unsafe...bool) *Set {
return NewSet(unsafe...)
}
// See New.
//
// 同New.
func NewSet(unsafe...bool) *Set {
return &Set{
m : make(map[interface{}]struct{}),
mu : rwmutex.New(unsafe...),
}
}
// Iterate the set by given callback <f>,
// if <f> returns true then continue iterating; or false to stop.
//
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历。
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 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
}
// Check 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 <item> from set.
//
// 删除元素项。
func (set *Set) Remove(item interface{}) *Set {
set.mu.Lock()
delete(set.m, item)
set.mu.Unlock()
return set
}
// Get size of the set.
//
// 获得集合大小。
func (set *Set) Size() int {
set.mu.RLock()
l := len(set.m)
set.mu.RUnlock()
return l
}
// Clear the set.
//
// 清空集合。
func (set *Set) Clear() *Set {
set.mu.Lock()
set.m = make(map[interface{}]struct{})
set.mu.Unlock()
return set
}
// Get the copy of items from 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 set items with a string.
//
// 使用glue字符串串连当前集合的元素项构造成新的字符串返回。
func (set *Set) Join(glue string) string {
return strings.Join(gconv.Strings(set.Slice()), ",")
}
// Return set items as a string, which are joined by char ','.
//
// 使用glue字符串串连当前集合的元素项构造成新的字符串返回。
func (set *Set) String() string {
return set.Join(",")
}
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) *Set {
set.mu.Lock(true)
defer set.mu.Unlock(true)
f(set.m)
return set
}
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) *Set {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
f(set.m)
return set
}
// Check 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
}
// Check whether the current set is sub-set of <other>.
//
// 判断当前集合是否为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
}
// Returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> is in <set> or in <other>.
//
// 并集, 返回新的集合属于set或属于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
}
// Returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> is in <set> and not in <other>.
//
// 差集, 返回新的集合: 属于set且不属于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
}
// Returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> is in <set> and also in <other>.
//
// 交集, 返回新的集合: 属于set且属于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
}
// Returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> is in <full> and not in <set>.
//
// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合.
// 如果给定的full集合不是set的全集时返回full与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
}

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,267 @@ type IntSet struct {
m map[int]struct{}
}
func NewIntSet(safe...bool) *IntSet {
// Create a set, which contains un-repeated items.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的集合对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
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 {
// Iterate the set by given callback <f>,
// if <f> returns true then continue iterating; or false to stop.
//
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历。
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 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()
// Check 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 <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()
// Get 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 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))
// Get the copy of items from 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 set 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)
// Return set items as a string, which are joined by char ','.
//
// 使用glue字符串串连当前集合的元素项构造成新的字符串返回。
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)
}
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (set *IntSet) LockFunc(f func(m map[int]struct{})) *IntSet {
set.mu.Lock(true)
defer set.mu.Unlock(true)
f(set.m)
return set
}
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) *IntSet {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
f(set.m)
return set
}
// Check 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
}
// Check whether the current set is sub-set of <other>.
//
// 判断当前集合是否为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
}
// Returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> is in <set> or in <other>.
//
// 并集, 返回新的集合属于set或属于others的元素为元素的集合.
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
}
// Returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> is in <set> and not in <other>.
//
// 差集, 返回新的集合: 属于set且不属于others的元素为元素的集合.
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
}
// Returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> is in <set> and also in <other>.
//
// 交集, 返回新的集合: 属于set且属于others的元素为元素的集合.
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
}
// Returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> is in <full> and not in <set>.
//
// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合.
// 如果给定的full集合不是set的全集时返回full与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
}

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,15 @@
// 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"
"strings"
)
type StringSet struct {
@ -17,99 +17,268 @@ type StringSet struct {
m map[string]struct{}
}
func NewStringSet(safe...bool) *StringSet {
return &StringSet{
// Create a set, which contains un-repeated items.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 创建一个空的集合对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
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 {
// Iterate the set by given callback <f>,
// if <f> returns true then continue iterating; or false to stop.
//
// 给定回调函数对原始内容进行遍历回调函数返回true表示继续遍历否则停止遍历。
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 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()
// Check 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 <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()
// Get 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 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))
// Get the copy of items from 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 set 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)
// Return set items as a string, which are joined by char ','.
//
// 使用glue字符串串连当前集合的元素项构造成新的字符串返回。
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)
// Lock writing by callback function f.
//
// 使用自定义方法执行加锁修改操作。
func (set *StringSet) LockFunc(f func(m map[string]struct{})) *StringSet {
set.mu.Lock(true)
defer set.mu.Unlock(true)
f(set.m)
return set
}
// Lock reading by callback function f.
//
// 使用自定义方法执行加锁读取操作。
func (set *StringSet) RLockFunc(f func(m map[string]struct{})) *StringSet {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
f(set.m)
return set
}
// Check 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
}
// Check whether the current set is sub-set of <other>.
//
// 判断当前集合是否为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
}
// Returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> is in <set> or in <other>.
//
// 并集, 返回新的集合属于set或属于others的元素为元素的集合.
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
}
// Returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> is in <set> and not in <other>.
//
// 差集, 返回新的集合: 属于set且不属于others的元素为元素的集合.
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
}
// Returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> is in <set> and also in <other>.
//
// 交集, 返回新的集合: 属于set且属于others的元素为元素的集合.
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
}
// Returns a new set which is the complement from <set> to <full>.
// Which means, all the items in <newSet> is in <full> and not in <set>.
//
// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合.
// 如果给定的full集合不是set的全集时返回full与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
}

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

@ -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

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

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

View File

@ -1,14 +1,14 @@
// 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"
"github.com/gogf/gf/g/encoding/gbinary"
)
type Float32 struct {

View File

@ -1,14 +1,14 @@
// 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"
"github.com/gogf/gf/g/encoding/gbinary"
)
type Float64 struct {

View File

@ -1,10 +1,12 @@
// 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, concurrent-safe basic variable types.
//
// 并发安全基本类型.
package gtype
type Type = Interface

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
@ -34,7 +34,7 @@ func (t *Int) Val() int {
return int(atomic.LoadInt64(&t.val))
}
// 数值增加delta并返回的数值
// 数值增加delta并返回**新**的数值
func (t *Int) Add(delta int) int {
return int(atomic.AddInt64(&t.val, 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

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

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

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

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

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

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

View File

@ -1,16 +1,19 @@
// 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 gvar provides an universal variable type, like generics.
//
// 通用动态变量.
package gvar
import (
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"time"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/container/gtype"
)
type Var struct {
@ -19,10 +22,10 @@ type Var struct {
}
// 创建一个动态变量value参数可以为nil
func New(value interface{}, safe...bool) *Var {
func New(value interface{}, unsafe...bool) *Var {
v := &Var{}
if len(safe) > 0 && safe[0] {
v.safe = safe[0]
if len(unsafe) == 0 || !unsafe[0] {
v.safe = true
v.value = gtype.NewInterface(value)
} else {
v.value = value
@ -30,6 +33,16 @@ func New(value interface{}, safe...bool) *Var {
return v
}
// 创建一个只读动态变量value参数可以为nil
func NewRead(value interface{}, unsafe...bool) VarRead {
return VarRead(New(value, unsafe...))
}
// 返回动态变量的只读接口
func (v *Var) ReadOnly() VarRead {
return VarRead(v)
}
func (v *Var) Set(value interface{}) (old interface{}) {
if v.safe {
old = v.value.(*gtype.Interface).Set(value)
@ -48,6 +61,7 @@ func (v *Var) Val() interface{} {
}
}
// Val() 别名
func (v *Var) Interface() interface{} {
return v.Val()
}
@ -77,8 +91,16 @@ func (v *Var) Floats() []float64 { return gconv.Floats(v.Val()) }
func (v *Var) Strings() []string { return gconv.Strings(v.Val()) }
func (v *Var) Interfaces() []interface{} { return gconv.Interfaces(v.Val()) }
func (v *Var) Time(format...string) time.Time { return gconv.Time(v.Val(), format...) }
func (v *Var) TimeDuration() time.Duration { return gconv.TimeDuration(v.Val()) }
func (v *Var) Time(format...string) time.Time {
return gconv.Time(v.Val(), format...)
}
func (v *Var) TimeDuration() time.Duration {
return gconv.TimeDuration(v.Val())
}
func (v *Var) GTime(format...string) *gtime.Time {
return gconv.GTime(v.Val(), format...)
}
// 将变量转换为对象,注意 objPointer 参数必须为struct指针
func (v *Var) Struct(objPointer interface{}, attrMapping...map[string]string) error {

View File

@ -0,0 +1,42 @@
// 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 gvar
import (
"github.com/gogf/gf/g/os/gtime"
"time"
)
// 只读变量接口
type VarRead interface {
Val() interface{}
IsNil() bool
Bytes() []byte
String() string
Bool() bool
Int() int
Int8() int8
Int16() int16
Int32() int32
Int64() int64
Uint() uint
Uint8() uint8
Uint16() uint16
Uint32() uint32
Uint64() uint64
Float32() float32
Float64() float64
Interface() interface{}
Ints() []int
Floats() []float64
Strings() []string
Interfaces() []interface{}
Time(format ...string) time.Time
TimeDuration() time.Duration
GTime(format...string) *gtime.Time
Struct(objPointer interface{}, attrMapping ...map[string]string) error
}

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

View File

@ -1,48 +0,0 @@
package rwmutex
import "sync"
// RWMutex的封装支持对并发安全开启/关闭的控制。
// 但是只能初始化时确定并发安全性,不能在运行时动态修改并发安全特性设置。
type RWMutex struct {
sync.RWMutex
safe bool
}
func New(safe...bool) *RWMutex {
mu := new(RWMutex)
if len(safe) > 0 {
mu.safe = safe[0]
} else {
mu.safe = true
}
return mu
}
func (mu *RWMutex) IsSafe() bool {
return mu.safe
}
func (mu *RWMutex) Lock(force...bool) {
if mu.safe || (len(force) > 0 && force[0]) {
mu.RWMutex.Lock()
}
}
func (mu *RWMutex) Unlock(force...bool) {
if mu.safe || (len(force) > 0 && force[0]) {
mu.RWMutex.Unlock()
}
}
func (mu *RWMutex) RLock(force...bool) {
if mu.safe || (len(force) > 0 && force[0]) {
mu.RWMutex.RLock()
}
}
func (mu *RWMutex) RUnlock(force...bool) {
if mu.safe || (len(force) > 0 && force[0]) {
mu.RWMutex.RUnlock()
}
}

7
g/crypto/crypto.go Normal file
View File

@ -0,0 +1,7 @@
// 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 crypto

View File

@ -1,10 +1,10 @@
// 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.
// AES
// Package gaes provides useful API for AES encryption/decryption algorithms.
package gaes
import (

View File

@ -1,10 +1,10 @@
// 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.
// CRC32
// Package gcrc32 provides useful API for CRC32 encryption/decryption algorithms.
package gcrc32
import (

View File

@ -1,10 +1,11 @@
// 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.
// @author: wenzi1<liyz23@qq.com>
// Package gdes provides useful API for DES encryption/decryption algorithms.
package gdes
import (
@ -261,14 +262,14 @@ func PKCS5Unpadding(text []byte) []byte{
//补位方法
func Padding(text []byte, padding int)([]byte, error) {
switch padding {
case NOPADDING:
if len(text) % 8 != 0 {
return nil, errors.New("text length invalid")
}
case PKCS5PADDING:
return PKCS5Padding(text, 8), nil
default:
return nil, errors.New("padding type error")
case NOPADDING:
if len(text) % 8 != 0 {
return nil, errors.New("text length invalid")
}
case PKCS5PADDING:
return PKCS5Padding(text, 8), nil
default:
return nil, errors.New("padding type error")
}
return text, nil
@ -277,14 +278,14 @@ func Padding(text []byte, padding int)([]byte, error) {
//去除补位方法
func UnPadding(text []byte, padding int)([]byte, error) {
switch padding {
case NOPADDING:
if len(text) % 8 != 0 {
return nil, errors.New("text length invalid")
}
case PKCS5PADDING:
return PKCS5Unpadding(text), nil
default:
return nil, errors.New("padding type error.")
case NOPADDING:
if len(text) % 8 != 0 {
return nil, errors.New("text length invalid")
}
case PKCS5PADDING:
return PKCS5Unpadding(text), nil
default:
return nil, errors.New("padding type error.")
}
return text, nil
}

View File

@ -1,11 +1,11 @@
package gdes
package gdes_test
import (
"testing"
"bytes"
"encoding/hex"
"fmt"
"gitee.com/johng/gf/g/encoding/gdes"
"github.com/gogf/gf/g/crypto/gdes"
)
func TestDesECB(t *testing.T){

View File

@ -1,10 +1,10 @@
// 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.
// MD5
// Package gmd5 provides useful API for MD5 encryption/decryption algorithms.
package gmd5
import (
@ -12,7 +12,7 @@ import (
"fmt"
"os"
"io"
"gitee.com/johng/gf/g/util/gconv"
"github.com/gogf/gf/g/util/gconv"
)
// 将任意类型的变量进行md5摘要(注意map等非排序变量造成的不同结果)
@ -35,6 +35,7 @@ func EncryptFile(path string) string {
if e != nil {
return ""
}
defer f.Close()
h := md5.New()
_, e = io.Copy(h, f)
if e != nil {

View File

@ -1,10 +1,10 @@
// 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.
// SHA1
// Package gsha1 provides useful API for SHA1 encryption/decryption algorithms.
package gsha1
import (
@ -12,7 +12,7 @@ import (
"encoding/hex"
"os"
"io"
"gitee.com/johng/gf/g/util/gconv"
"github.com/gogf/gf/g/util/gconv"
)
// 将任意类型的变量进行SHA摘要(注意map等非排序变量造成的不同结果)
@ -34,6 +34,7 @@ func EncryptFile(path string) string {
if e != nil {
return ""
}
defer f.Close()
h := sha1.New()
_, e = io.Copy(h, f)
if e != nil {

View File

@ -1,7 +1,7 @@
// 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 database

View File

@ -1,10 +1,12 @@
// 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.
// 数据库ORM.
// Package gdb provides ORM features for popular relationship databases.
//
// 数据库ORM,
// 默认内置支持MySQL, 其他数据库需要手动import对应的数据库引擎第三方包.
package gdb
@ -12,59 +14,61 @@ import (
"database/sql"
"errors"
"fmt"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/gvar"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/util/grand"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"github.com/gogf/gf/g/container/gring"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/util/grand"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"time"
)
const (
OPTION_INSERT = 0
OPTION_REPLACE = 1
OPTION_SAVE = 2
OPTION_IGNORE = 3
)
// 数据库操作接口
type Link interface {
// 打开数据库连接,建立数据库操作对象
Open(c *ConfigNode) (*sql.DB, error)
type DB interface {
// 建立数据库连接方法(开发者一般不需要直接调用)
Open(config *ConfigNode) (*sql.DB, error)
// SQL操作方法
Query(q string, args ...interface{}) (*sql.Rows, error)
Exec(q string, args ...interface{}) (sql.Result, error)
Prepare(q string) (*sql.Stmt, error)
// SQL操作方法 API
Query(query string, args ...interface{}) (*sql.Rows, error)
Exec(sql string, args ...interface{}) (sql.Result, error)
Prepare(sql string, execOnMaster...bool) (*sql.Stmt, error)
// 内部实现API的方法(不同数据库可覆盖这些方法实现自定义的操作)
doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
doPrepare(link dbLink, query string) (*sql.Stmt, error)
doInsert(link dbLink, table string, data interface{}, option int, batch...int) (result sql.Result, err error)
doBatchInsert(link dbLink, table string, list interface{}, option int, batch...int) (result sql.Result, err error)
doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error)
doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error)
// 数据库查询
GetAll(q string, args ...interface{}) (Result, error)
GetOne(q string, args ...interface{}) (Record, error)
GetValue(q string, args ...interface{}) (Value, error)
GetAll(query string, args ...interface{}) (Result, error)
GetOne(query string, args ...interface{}) (Record, error)
GetValue(query string, args ...interface{}) (Value, error)
GetCount(query string, args ...interface{}) (int, error)
GetStruct(obj interface{}, query string, args ...interface{}) error
// Ping
// 创建底层数据库master/slave链接对象
Master() (*sql.DB, error)
Slave() (*sql.DB, error)
// Ping
PingMaster() error
PingSlave() error
// 连接属性设置
SetMaxIdleConns(n int)
SetMaxOpenConns(n int)
SetConnMaxLifetime(n int)
// 开启事务操作
Begin() (*Tx, error)
Begin() (*TX, error)
// 数据表插入/更新/保存操作
Insert(table string, data Map) (sql.Result, error)
Replace(table string, data Map) (sql.Result, error)
Save(table string, data Map) (sql.Result, error)
Insert(table string, data interface{}, batch...int) (sql.Result, error)
Replace(table string, data interface{}, batch...int) (sql.Result, error)
Save(table string, data interface{}, batch...int) (sql.Result, error)
// 数据表插入/更新/保存操作(批量)
BatchInsert(table string, list List, batch int) (sql.Result, error)
BatchReplace(table string, list List, batch int) (sql.Result, error)
BatchSave(table string, list List, batch int) (sql.Result, error)
BatchInsert(table string, list interface{}, batch...int) (sql.Result, error)
BatchReplace(table string, list interface{}, batch...int) (sql.Result, error)
BatchSave(table string, list interface{}, batch...int) (sql.Result, error)
// 数据修改/删除
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
@ -74,27 +78,45 @@ type Link interface {
Table(tables string) *Model
From(tables string) *Model
// 内部方法
insert(table string, data Map, option uint8) (sql.Result, error)
batchInsert(table string, list List, batch int, option uint8) (sql.Result, error)
// 设置管理
SetDebug(debug bool)
SetSchema(schema string)
GetQueriedSqls() []*Sql
PrintQueriedSqls()
SetMaxIdleConns(n int)
SetMaxOpenConns(n int)
SetConnMaxLifetime(n int)
getQuoteCharLeft() string
getQuoteCharRight() string
handleSqlBeforeExec(q *string) *string
// 内部方法接口
getCache() (*gcache.Cache)
getChars() (charLeft string, charRight string)
getDebug() bool
filterFields(table string, data map[string]interface{}) map[string]interface{}
convertValue(fieldValue interface{}, fieldType string) interface{}
getTableFields(table string) (map[string]string, error)
rowsToResult(rows *sql.Rows) (Result, error)
handleSqlBeforeExec(sql string) string
}
// 执行底层数据库操作的核心接口
type dbLink interface {
Query(query string, args ...interface{}) (*sql.Rows, error)
Exec(sql string, args ...interface{}) (sql.Result, error)
Prepare(sql string) (*sql.Stmt, error)
}
// 数据库链接对象
type Db struct {
link Link // 底层数据库类型管理对象
group string // 配置分组名称
charl string // SQL安全符号(左)
charr string // SQL安全符号(右)
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
cache *gcache.Cache // 查询缓存,需要注意的是,事务查询不支持缓存
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
maxOpenConnCount *gtype.Int // 连接池最大打开的连接数
maxConnLifetime *gtype.Int // (单位秒)连接对象可重复使用的时间长度
type dbBase struct {
db DB // 数据库对象
group string // 配置分组名称
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
cache *gcache.Cache // 数据库缓存,包括底层连接池对象缓存及查询缓存;需要注意的是,事务查询不支持查询缓存
schema *gtype.String // 手动切换的数据库名称
tables map[string]map[string]string // 数据库表结构
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
maxOpenConnCount *gtype.Int // 连接池最大打开的连接数
maxConnLifetime *gtype.Int // (单位秒)连接对象可重复使用的时间长度
}
// 执行的SQL对象
@ -104,7 +126,7 @@ type Sql struct {
Error error // 执行结果(nil为成功)
Start int64 // 执行开始时间(毫秒)
End int64 // 执行结束时间(毫秒)
Func string // 执行方法名称
Func string // 执行方法
}
// 返回数据表记录值
@ -117,26 +139,25 @@ type Record map[string]Value
type Result []Record
// 关联数组,绑定一条数据表记录(使用别名)
type Map = map[string]interface{}
type Map = map[string]interface{}
// 关联数组列表(索引从0开始的数组),绑定多条记录(使用别名)
type List = []Map
var (
// 支持的数据库类型map
driverMap = make(map[string]interface{})
// 数据库查询缓存对象map使用数据库连接名称作为键名键值为查询缓存对象
dbCaches = gmap.NewStringInterfaceMap()
const (
OPTION_INSERT = 0
OPTION_REPLACE = 1
OPTION_SAVE = 2
OPTION_IGNORE = 3
// 默认批量操作的数量值(Batch*操作)
gDEFAULT_BATCH_NUM = 10
// 默认的连接池连接存活时间(秒)
gDEFAULT_CONN_MAX_LIFE_TIME = 30
)
func init() {
driverMap["mysql"] = linkMysql
driverMap["oracle"] = linkOracle
driverMap["sqlite"] = linkSqlite
driverMap["pgsql"] = linkPgsql
}
// 使用默认/指定分组配置进行连接数据库集群配置项default
func New(groupName ...string) (*Db, error) {
func New(groupName ...string) (db DB, err error) {
group := config.d
if len(groupName) > 0 {
group = groupName[0]
@ -149,24 +170,30 @@ func New(groupName ...string) (*Db, error) {
}
if _, ok := config.c[group]; ok {
if node, err := getConfigNodeByGroup(group, true); err == nil {
link, err := getLinkByType(node.Type)
if err != nil {
return nil, err
}
db := &Db {
link : link,
base := &dbBase {
group : group,
charl : link.getQuoteCharLeft(),
charr : link.getQuoteCharRight(),
debug : gtype.NewBool(),
cache : gcache.New(),
schema : gtype.NewString(),
maxIdleConnCount : gtype.NewInt(),
maxOpenConnCount : gtype.NewInt(),
maxConnLifetime : gtype.NewInt(),
maxConnLifetime : gtype.NewInt(gDEFAULT_CONN_MAX_LIFE_TIME),
}
db.cache = dbCaches.GetOrSetFuncLock(group, func() interface{} {
return gcache.New()
}).(*gcache.Cache)
return db, nil
switch node.Type {
case "mysql":
base.db = &dbMysql{dbBase : base}
case "pgsql":
base.db = &dbPgsql{dbBase : base}
case "mssql":
base.db = &dbMssql{dbBase : base}
case "sqlite":
base.db = &dbSqlite{dbBase : base}
case "oracle":
base.db = &dbOracle{dbBase : base}
default:
return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
}
return base.db, nil
} else {
return nil, err
}
@ -218,6 +245,13 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
for i := 0; i < len(cg); i++ {
total += cg[i].Priority * 100
}
// 如果total为0表示所有连接都没有配置priority属性那么默认都是1
if total == 0 {
for i := 0; i < len(cg); i++ {
cg[i].Priority = 1
total += cg[i].Priority * 100
}
}
// 不能取到末尾的边界点
r := grand.Rand(0, total)
if r > 0 {
@ -237,58 +271,63 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
return nil
}
// 根据配置的数据库类型获得Link接口对象
func getLinkByType(dbType string) (Link, error) {
if dblink, ok := driverMap[dbType]; ok == false {
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
} else {
return dblink.(Link), nil
}
// 获得底层数据库链接对象
func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) {
// 负载均衡
node, err := getConfigNodeByGroup(bs.group, master)
if err != nil {
return nil, err
}
// 默认值设定
if node.Charset == "" {
node.Charset = "utf8"
}
v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} {
sqlDb, err = bs.db.Open(node)
if err != nil {
return nil
}
if n := bs.maxIdleConnCount.Val(); n > 0 {
sqlDb.SetMaxIdleConns(n)
} else if node.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
}
if n := bs.maxOpenConnCount.Val(); n > 0 {
sqlDb.SetMaxOpenConns(n)
} else if node.MaxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
}
if n := bs.maxConnLifetime.Val(); n > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
} else if node.MaxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
}
return sqlDb
}, 0)
if v != nil && sqlDb == nil {
sqlDb = v.(*sql.DB)
}
// 是否手动选择数据库
if v := bs.schema.Val(); v != "" {
sqlDb.Exec("USE " + v)
}
return
}
// 获得底层数据库链接对象
func (db *Db) getSqlDb(master bool) (*sql.DB, error) {
node, err := getConfigNodeByGroup(db.group, master)
if err != nil {
return nil, err
}
link, err := getLinkByType(node.Type)
if err != nil {
return nil, err
}
sqlDb, err := link.Open(node)
if err != nil {
return nil, err
}
if node.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
}
if n := db.maxIdleConnCount.Val(); n > 0 {
sqlDb.SetMaxIdleConns(n)
}
if node.MaxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
}
if n := db.maxOpenConnCount.Val(); n > 0 {
sqlDb.SetMaxOpenConns(n)
}
if node.MaxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
}
if n := db.maxConnLifetime.Val(); n > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
}
return sqlDb, nil
// 切换操作的数据库(注意该切换是全局的)
func (bs *dbBase) SetSchema(schema string) {
bs.schema.Set(schema)
}
// 创建底层数据库master链接对象
func (db *Db) Master() (*sql.DB, error) {
return db.getSqlDb(true)
func (bs *dbBase) Master() (*sql.DB, error) {
return bs.getSqlDb(true)
}
// 创建底层数据库slave链接对象
func (db *Db) Slave() (*sql.DB, error) {
return db.getSqlDb(false)
func (bs *dbBase) Slave() (*sql.DB, error) {
return bs.getSqlDb(false)
}

View File

@ -1,46 +1,37 @@
// 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 gdb
import (
"fmt"
"errors"
"strings"
"reflect"
"database/sql"
"gitee.com/johng/gf/g/util/gstr"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/container/gvar"
"errors"
"fmt"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/util/gconv"
"reflect"
"strings"
)
const (
gDEFAULT_DEBUG_SQL_LENGTH = 1000 // 默认调试模式下记录的SQL条数
)
// 是否开启调试服务
func (db *Db) SetDebug(debug bool) {
db.debug.Set(debug)
if debug && db.sqls == nil {
db.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH)
}
}
// 获取已经执行的SQL列表(仅在debug=true时有效)
func (db *Db) GetQueriedSqls() []*Sql {
if db.sqls == nil {
func (bs *dbBase) GetQueriedSqls() []*Sql {
if bs.sqls == nil {
return nil
}
sqls := make([]*Sql, 0)
db.sqls.Prev()
db.sqls.RLockIteratorPrev(func(value interface{}) bool {
bs.sqls.Prev()
bs.sqls.RLockIteratorPrev(func(value interface{}) bool {
if value == nil {
return false
}
@ -51,8 +42,8 @@ func (db *Db) GetQueriedSqls() []*Sql {
}
// 打印已经执行的SQL列表(仅在debug=true时有效)
func (db *Db) PrintQueriedSqls() {
sqls := db.GetQueriedSqls()
func (bs *dbBase) PrintQueriedSqls() {
sqls := bs.GetQueriedSqls()
for k, v := range sqls {
fmt.Println(len(sqls) - k, ":")
fmt.Println(" Sql :", v.Sql)
@ -61,145 +52,110 @@ func (db *Db) PrintQueriedSqls() {
fmt.Println(" Start:", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"))
fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"))
fmt.Println(" Cost :", v.End - v.Start, "ms")
fmt.Println(" Func :", v.Func)
}
}
// 打印SQL对象(仅在debug=true时有效)
func (db *Db) printSql(v *Sql) {
s := fmt.Sprintf("%s, %v, %s, %s, %d ms, %s", v.Sql, v.Args,
gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"),
gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"),
v.End - v.Start, v.Func,
)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
glog.Backtrace(true, 2).Error(s)
} else {
glog.Debug(s)
}
}
// 数据库sql查询操作主要执行查询
func (db *Db) Query(query string, args ...interface{}) (*sql.Rows, error) {
var err error
var rows *sql.Rows
var slave *sql.DB
slave, err = db.Slave();
func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
link, err := bs.db.Slave()
if err != nil {
return nil,err
}
defer slave.Close()
p := db.link.handleSqlBeforeExec(&query)
if db.debug.Val() {
militime1 := gtime.Millisecond()
rows, err = slave.Query(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
return bs.db.doQuery(link, query, args...)
}
// 数据库sql查询操作主要执行查询
func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error) {
query = bs.db.handleSqlBeforeExec(query)
if bs.db.getDebug() {
mTime1 := gtime.Millisecond()
rows, err = link.Query(query, args...)
mTime2 := gtime.Millisecond()
s := &Sql {
Sql : query,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "DB:Query",
Start : mTime1,
End : mTime2,
}
db.sqls.Put(s)
db.printSql(s)
bs.sqls.Put(s)
printSql(s)
} else {
rows, err = slave.Query(*p, args ...)
rows, err = link.Query(query, args ...)
}
if err == nil {
return rows, nil
} else {
err = db.formatError(err, p, args...)
err = formatError(err, query, args...)
}
return nil, err
}
// 执行一条sql并返回执行情况主要用于非查询操作
func (db *Db) Exec(query string, args ...interface{}) (sql.Result, error) {
var err error
var result sql.Result
var master *sql.DB
master, err = db.Master();
func (bs *dbBase) Exec(query string, args ...interface{}) (result sql.Result, err error) {
link, err := bs.db.Master()
if err != nil {
return nil,err
}
defer master.Close()
p := db.link.handleSqlBeforeExec(&query)
if db.debug.Val() {
militime1 := gtime.Millisecond()
result, err = master.Exec(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
return bs.db.doExec(link, query, args...)
}
// 执行一条sql并返回执行情况主要用于非查询操作
func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error) {
query = bs.db.handleSqlBeforeExec(query)
if bs.db.getDebug() {
mTime1 := gtime.Millisecond()
result, err = link.Exec(query, args ...)
mTime2 := gtime.Millisecond()
s := &Sql{
Sql : query,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "DB:Exec",
Start : mTime1,
End : mTime2,
}
db.sqls.Put(s)
db.printSql(s)
bs.sqls.Put(s)
printSql(s)
} else {
result, err = master.Exec(*p, args ...)
result, err = link.Exec(query, args ...)
}
return result, db.formatError(err, p, args...)
return result, formatError(err, query, args...)
}
// 格式化错误信息
func (db *Db) formatError(err error, query *string, args ...interface{}) error {
if err != nil {
errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
errstr += fmt.Sprintf("DB QUERY: %s\n", *query)
if len(args) > 0 {
errstr += fmt.Sprintf("DB PARAM: %v\n", args)
// SQL预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作; 默认执行在Slave上, 通过第二个参数指定执行在Master上
func (bs *dbBase) Prepare(query string, execOnMaster...bool) (*sql.Stmt, error) {
err := (error)(nil)
link := (dbLink)(nil)
if len(execOnMaster) > 0 && execOnMaster[0] {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
} else {
if link, err = bs.db.Slave(); err != nil {
return nil, err
}
err = errors.New(errstr)
}
return err
return bs.db.doPrepare(link, query)
}
// SQL预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
func (bs *dbBase) doPrepare(link dbLink, query string) (*sql.Stmt, error) {
return link.Prepare(query)
}
// 数据库查询,获取查询结果集,以列表结构返回
func (db *Db) GetAll(query string, args ...interface{}) (Result, error) {
// 执行sql
rows, err := db.Query(query, args ...)
func (bs *dbBase) GetAll(query string, args ...interface{}) (Result, error) {
rows, err := bs.Query(query, args ...)
if err != nil || rows == nil {
return nil, err
}
// 列名称列表
columns, err := rows.Columns()
if err != nil {
return nil, err
}
// 返回结构组装
values := make([]sql.RawBytes, len(columns))
scanArgs := make([]interface{}, len(values))
records := make(Result, 0)
for i := range values {
scanArgs[i] = &values[i]
}
for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
return records, err
}
row := make(Record)
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
for i, col := range values {
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(v)
}
records = append(records, row)
}
return records, nil
defer rows.Close()
return bs.db.rowsToResult(rows)
}
// 数据库查询,获取查询结果记录,以关联数组结构返回
func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
list, err := db.GetAll(query, args ...)
func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
list, err := bs.GetAll(query, args ...)
if err != nil {
return nil, err
}
@ -210,18 +166,17 @@ func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
}
// 数据库查询获取查询结果记录自动映射数据到给定的struct对象中
func (db *Db) GetStruct(obj interface{}, query string, args ...interface{}) error {
one, err := db.GetOne(query, args...)
func (bs *dbBase) GetStruct(obj interface{}, query string, args ...interface{}) error {
one, err := bs.GetOne(query, args...)
if err != nil {
return err
}
return one.ToStruct(obj)
}
// 数据库查询,获取查询字段值
func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
one, err := db.GetOne(query, args ...)
func (bs *dbBase) GetValue(query string, args ...interface{}) (Value, error) {
one, err := bs.GetOne(query, args ...)
if err != nil {
return nil, err
}
@ -232,72 +187,44 @@ func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
}
// 数据库查询,获取查询数量
func (db *Db) GetCount(query string, args ...interface{}) (int, error) {
val, err := db.GetValue(query, args ...)
func (bs *dbBase) GetCount(query string, args ...interface{}) (int, error) {
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
}
value, err := bs.GetValue(query, args ...)
if err != nil {
return 0, err
}
return gconv.Int(val), nil
}
// 数据表查询其中tables可以是多个联表查询语句这种查询方式较复杂建议使用链式操作
func (db *Db) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
if condition != nil {
s += fmt.Sprintf("WHERE %s ", db.formatCondition(condition))
}
if len(groupBy) > 0 {
s += fmt.Sprintf("GROUP BY %s ", groupBy)
}
if len(orderBy) > 0 {
s += fmt.Sprintf("ORDER BY %s ", orderBy)
}
if limit > 0 {
s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
}
return db.GetAll(s, args ... )
}
// sql预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
// 记得调用sql.Stmt.Close关闭操作对象
func (db *Db) Prepare(query string) (*sql.Stmt, error) {
if master, err := db.Master(); err != nil {
return nil, err
} else {
defer master.Close()
return master.Prepare(query)
}
return value.Int(), nil
}
// ping一下判断或保持数据库链接(master)
func (db *Db) PingMaster() error {
if master, err := db.Master(); err != nil {
func (bs *dbBase) PingMaster() error {
if master, err := bs.db.Master(); err != nil {
return err
} else {
defer master.Close()
return master.Ping()
}
}
// ping一下判断或保持数据库链接(slave)
func (db *Db) PingSlave() error {
if slave, err := db.Slave(); err != nil {
func (bs *dbBase) PingSlave() error {
if slave, err := bs.db.Slave(); err != nil {
return err
} else {
defer slave.Close()
return slave.Ping()
}
}
// 事务操作,开启,会返回一个底层的事务操作对象链接如需要嵌套事务,那么可以使用该对象,否则请忽略
// 只有在tx.Commit/tx.Rollback时链接会自动Close
func (db *Db) Begin() (*Tx, error) {
if master, err := db.Master(); err != nil {
func (bs *dbBase) Begin() (*TX, error) {
if master, err := bs.db.Master(); err != nil {
return nil, err
} else {
if tx, err := master.Begin(); err == nil {
return &Tx {
db : db,
return &TX {
db : bs.db,
tx : tx,
master : master,
}, nil
@ -307,201 +234,318 @@ func (db *Db) Begin() (*Tx, error) {
}
}
// 根据insert选项获得操作名称
func (db *Db) getInsertOperationByOption(option uint8) string {
oper := "INSERT"
switch option {
case OPTION_REPLACE:
oper = "REPLACE"
case OPTION_SAVE:
case OPTION_IGNORE:
oper = "INSERT IGNORE"
}
return oper
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回。
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Insert(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_INSERT, batch...)
}
// insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
func (db *Db) insert(table string, data Map, option uint8) (sql.Result, error) {
var fields []string
var values []string
var params []interface{}
for k, v := range data {
fields = append(fields, db.charl + k + db.charr)
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条。
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Replace(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_REPLACE, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据。
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Save(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_SAVE, batch...)
}
// 支持insert、replace, save ignore操作。
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回;
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条;
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据;
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做;
//
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option int, batch...int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
var dataMap Map
// 使用反射判断data数据类型如果为slice类型那么自动转为批量操作
rv := reflect.ValueOf(data)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice: fallthrough
case reflect.Array:
return bs.db.doBatchInsert(link, table, data, option, batch...)
case reflect.Map: fallthrough
case reflect.Struct:
dataMap = Map(gconv.Map(data))
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
charL, charR := bs.db.getChars()
for k, v := range dataMap {
fields = append(fields, charL + k + charR)
values = append(values, "?")
params = append(params, v)
}
operation := db.getInsertOperationByOption(option)
updatestr := ""
operation := getInsertOperationByOption(option)
updateStr := ""
if option == OPTION_SAVE {
var updates []string
for k, _ := range data {
for k, _ := range dataMap {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
db.charl, k, db.charr,
db.charl, k, db.charr,
charL, k, charR,
charL, k, charR,
),
)
}
updatestr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
return db.Exec(
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(fields, ","),
strings.Join(values, ","),
updatestr),
params...
)
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (db *Db) Insert(table string, data Map) (sql.Result, error) {
return db.insert(table, data, OPTION_INSERT)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (db *Db) Replace(table string, data Map) (sql.Result, error) {
return db.insert(table, data, OPTION_REPLACE)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (db *Db) Save(table string, data Map) (sql.Result, error) {
return db.insert(table, data, OPTION_SAVE)
}
// 批量写入数据
func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
var keys []string
var values []string
var bvalues []string
var params []interface{}
var result sql.Result
var size = len(list)
// 判断长度
if size < 1 {
return result, errors.New("empty data list")
}
// 首先获取字段名称及记录长度
for k, _ := range list[0] {
keys = append(keys, k)
values = append(values, "?")
}
keyStr := db.charl + strings.Join(keys, db.charl + "," + db.charr) + db.charr
valueHolderStr := "(" + strings.Join(values, ",") + ")"
// 操作判断
operation := db.getInsertOperationByOption(option)
updatestr := ""
if option == OPTION_SAVE {
var updates []string
for _, k := range keys {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
db.charl, k, db.charr,
db.charl, k, db.charr,
),
)
}
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
// 构造批量写入数据格式(注意map的遍历是无序的)
for i := 0; i < size; i++ {
for _, k := range keys {
params = append(params, list[i][k])
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
return result, err
}
result = r
params = params[:0]
bvalues = bvalues[:0]
if link == nil {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
return result, err
}
result = r
}
return result, nil
return bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(fields, ","),
strings.Join(values, ","), updateStr),
params...)
}
// CURD操作:批量数据指定批次量写入
func (db *Db) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return db.batchInsert(table, list, batch, OPTION_INSERT)
func (bs *dbBase) BatchInsert(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_INSERT, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (db *Db) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return db.batchInsert(table, list, batch, OPTION_REPLACE)
func (bs *dbBase) BatchReplace(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_REPLACE, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (db *Db) BatchSave(table string, list List, batch int) (sql.Result, error) {
return db.batchInsert(table, list, batch, OPTION_SAVE)
func (bs *dbBase) BatchSave(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_SAVE, batch...)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型内部会自行做判断处理
func (db *Db) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
var params []interface{}
var updates string
refValue := reflect.ValueOf(data)
if refValue.Kind() == reflect.Map {
var fields []string
keys := refValue.MapKeys()
for _, k := range keys {
fields = append(fields, fmt.Sprintf("%s%s%s=?", db.charl, k, db.charr))
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
// 批量写入数据, 参数list支持slice类型例如: []map/[]struct/[]*struct。
func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, option int, batch...int) (result sql.Result, err error) {
var keys []string
var values []string
var params []interface{}
listMap := (List)(nil)
switch v := list.(type) {
case List:
listMap = v
case Map:
listMap = List{v}
default:
rv := reflect.ValueOf(list)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// 如果是slice那么转换为List类型
case reflect.Slice: fallthrough
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = gconv.Map(rv.Index(i).Interface())
}
case reflect.Map: fallthrough
case reflect.Struct:
listMap = List{Map(gconv.Map(list))}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}
}
// 判断长度
if len(listMap) < 1 {
return result, errors.New("empty data list")
}
if link == nil {
if link, err = bs.db.Master(); err != nil {
return
}
updates = strings.Join(fields, ",")
} else {
updates = gconv.String(data)
}
// 首先获取字段名称及记录长度
holders := []string(nil)
for k, _ := range listMap[0] {
keys = append(keys, k)
holders = append(holders, "?")
}
batchResult := new(batchSqlResult)
charL, charR := bs.db.getChars()
keyStr := charL + strings.Join(keys, charL + "," + charR) + charR
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
// 操作判断
operation := getInsertOperationByOption(option)
updateStr := ""
if option == OPTION_SAVE {
var updates []string
for _, k := range keys {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
charL, k, charR,
charL, k, charR,
),
)
}
updateStr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
// 构造批量写入数据格式(注意map的遍历是无序的)
batchNum := gDEFAULT_BATCH_NUM
if len(batch) > 0 {
batchNum = batch[0]
}
for i := 0; i < len(listMap); i++ {
for _, k := range keys {
params = append(params, listMap[i][k])
}
values = append(values, valueHolderStr)
if len(values) == batchNum {
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(values, ","),
updateStr),
params...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.lastResult = r
batchResult.rowsAffected += n
}
params = params[:0]
values = values[:0]
}
}
// 处理最后不构成指定批量的数据
if len(values) > 0 {
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(values, ","),
updateStr),
params...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.lastResult = r
batchResult.rowsAffected += n
}
}
return batchResult, nil
}
// CURD操作:数据更新统一采用sql预处理。
// data参数支持string/map/struct/*struct类型。
func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
link, err := bs.db.Master()
if err != nil {
return nil, err
}
return bs.db.doUpdate(link, table, data, condition, args ...)
}
// CURD操作:数据更新统一采用sql预处理。
// data参数支持string/map/struct/*struct类型类型。
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error) {
params := ([]interface{})(nil)
updates := ""
charL, charR := bs.db.getChars()
// 使用反射进行类型判断
rv := reflect.ValueOf(data)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Map: fallthrough
case reflect.Struct:
var fields []string
for k, v := range gconv.Map(data) {
fields = append(fields, fmt.Sprintf("%s%s%s=?", charL, k, charR))
params = append(params, gconv.String(v))
}
updates = strings.Join(fields, ",")
default:
updates = gconv.String(data)
}
for _, v := range args {
params = append(params, gconv.String(v))
}
return db.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, db.formatCondition(condition)), params...)
if link == nil {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
}
newWhere, newArgs := formatCondition(condition, params)
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, newWhere), newArgs...)
}
// CURD操作:删除数据
func (db *Db) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return db.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, db.formatCondition(condition)), args...)
func (bs *dbBase) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
link, err := bs.db.Master()
if err != nil {
return nil, err
}
return bs.db.doDelete(link, table, condition, args ...)
}
// 格式化SQL查询条件
func (db *Db) formatCondition(condition interface{}) (where string) {
if reflect.ValueOf(condition).Kind() == reflect.Map {
ks := reflect.ValueOf(condition).MapKeys()
vs := reflect.ValueOf(condition)
for _, k := range ks {
key := gconv.String(k.Interface())
value := gconv.String(vs.MapIndex(k).Interface())
isNum := gstr.IsNumeric(value)
if len(where) > 0 {
where += " AND "
}
if isNum || value == "?" {
where += key + "=" + value
// CURD操作:删除数据
func (bs *dbBase) doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
newWhere, newArgs := formatCondition(condition, args)
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s WHERE %s", table, newWhere), newArgs...)
}
// 获得缓存对象
func (bs *dbBase) getCache() *gcache.Cache {
return bs.cache
}
// 将数据查询的列表数据*sql.Rows转换为Result类型
func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
// 列信息列表, 名称与类型
types := make([]string, 0)
columns := make([]string, 0)
columnTypes, _ := rows.ColumnTypes()
for _, t := range columnTypes {
types = append(types, t.DatabaseTypeName())
columns = append(columns, t.Name())
}
// 返回结构组装
values := make([]sql.RawBytes, len(columns))
scanArgs := make([]interface{}, len(values))
records := make(Result, 0)
for i := range values {
scanArgs[i] = &values[i]
}
for rows.Next() {
if err := rows.Scan(scanArgs...); err != nil {
return records, err
}
row := make(Record)
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
for i, col := range values {
if col == nil {
row[columns[i]] = gvar.New(nil, true)
} else {
where += key + "='" + value + "'"
// 由于 sql.RawBytes 是slice类型, 这里必须使用值复制
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(bs.db.convertValue(v, types[i]), true)
}
}
} else {
where += gconv.String(condition)
records = append(records, row)
}
return
}
return records, nil
}

View File

@ -0,0 +1,25 @@
// 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 gdb
import "database/sql"
// 批量执行的结果对象
type batchSqlResult struct {
rowsAffected int64
lastResult sql.Result
}
// see sql.Result.RowsAffected
func (r *batchSqlResult) RowsAffected() (int64, error) {
return r.rowsAffected, nil
}
// see sql.Result.LastInsertId
func (r *batchSqlResult) LastInsertId() (int64, error) {
return r.lastResult.LastInsertId()
}

View File

@ -1,13 +1,15 @@
// 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 gdb
import (
"fmt"
"github.com/gogf/gf/g/container/gring"
"sync"
)
@ -113,6 +115,13 @@ func AddDefaultConfigGroup (nodes ConfigGroup) {
AddConfigGroup(DEFAULT_GROUP_NAME, nodes)
}
// 添加一台数据库服务器配置
func GetConfig (group string) ConfigGroup {
config.RLock()
defer config.RUnlock()
return config.c[group]
}
// 设置默认链接的数据库链接配置项(默认是 default)
func SetDefaultGroup (groupName string) {
config.Lock()
@ -121,17 +130,41 @@ func SetDefaultGroup (groupName string) {
}
// 设置数据库连接池中空闲链接的大小
func (db *Db) SetMaxIdleConns(n int) {
db.maxIdleConnCount.Set(n)
func (bs *dbBase) SetMaxIdleConns(n int) {
bs.maxIdleConnCount.Set(n)
}
// 设置数据库连接池最大打开的链接数量
func (db *Db) SetMaxOpenConns(n int) {
db.maxOpenConnCount.Set(n)
func (bs *dbBase) SetMaxOpenConns(n int) {
bs.maxOpenConnCount.Set(n)
}
// 设置数据库连接可重复利用的时间,超过该时间则被关闭废弃
// 如果 d <= 0 表示该链接会一直重复利用
func (db *Db) SetConnMaxLifetime(n int) {
db.maxConnLifetime.Set(n)
func (bs *dbBase) SetConnMaxLifetime(n int) {
bs.maxConnLifetime.Set(n)
}
// 节点配置转换为字符串
func (node *ConfigNode) String() string {
if node.Linkinfo != "" {
return node.Linkinfo
}
return fmt.Sprintf(`%s@%s:%s,%s,%s,%s,%s,%d-%d-%d`, node.User, node.Host, node.Port,
node.Name, node.Type, node.Role, node.Charset,
node.MaxIdleConnCount, node.MaxOpenConnCount, node.MaxConnLifetime,
)
}
// 是否开启调试服务
func (bs *dbBase) SetDebug(debug bool) {
bs.debug.Set(debug)
if debug && bs.sqls == nil {
bs.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH)
}
}
// 获取是否开启调试服务
func (bs *dbBase) getDebug() bool {
return bs.debug.Val()
}

129
g/database/gdb/gdb_func.go Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2017-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 gdb
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 格式化SQL查询条件
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
// 条件字符串处理
buffer := bytes.NewBuffer(nil)
// 使用反射进行类型判断
rv := reflect.ValueOf(where)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Map: fallthrough
case reflect.Struct:
for k, v := range gconv.Map(where) {
key := gconv.String(k)
value := gconv.String(v)
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
if gstr.IsNumeric(value) || value == "?" {
buffer.WriteString(key + "=" + value)
} else {
buffer.WriteString(key + "='" + value + "'")
}
}
default:
buffer.WriteString(gconv.String(where))
}
if buffer.Len() == 0 {
buffer.WriteString("1=1")
}
// 查询条件处理
newWhere := buffer.String()
newArgs := make([]interface{}, 0)
if len(args) > 0 {
for index, arg := range args {
rv := reflect.ValueOf(arg)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// Where条件参数支持slice类型
case reflect.Slice: fallthrough
case reflect.Array:
for i := 0; i < rv.Len(); i++ {
newArgs = append(newArgs, rv.Index(i).Interface())
}
counter := 0
newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string {
counter++
if counter == index + 1 {
return "?" + strings.Repeat(",?", rv.Len() - 1)
}
return s
})
default:
newArgs = append(newArgs, arg)
}
}
}
return newWhere, newArgs
}
// 打印SQL对象(仅在debug=true时有效)
func printSql(v *Sql) {
s := fmt.Sprintf("%s, %v, %s, %s, %d ms, %s", v.Sql, v.Args,
gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"),
gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"),
v.End - v.Start,
v.Func,
)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
glog.Backtrace(true, 2).Error(s)
} else {
glog.Debug(s)
}
}
// 格式化错误信息
func formatError(err error, query string, args ...interface{}) error {
if err != nil {
errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
errstr += fmt.Sprintf("DB QUERY: %s\n", query)
if len(args) > 0 {
errstr += fmt.Sprintf("DB PARAM: %v\n", args)
}
err = errors.New(errstr)
}
return err
}
// 根据insert选项获得操作名称
func getInsertOperationByOption(option int) string {
operator := "INSERT"
switch option {
case OPTION_REPLACE:
operator = "REPLACE"
case OPTION_SAVE:
case OPTION_IGNORE:
operator = "INSERT IGNORE"
}
return operator
}

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.
package gdb
@ -10,14 +10,17 @@ import (
"fmt"
"errors"
"database/sql"
"gitee.com/johng/gf/g/util/gconv"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 数据库链式操作模型对象
type Model struct {
tx *Tx // 数据库事务对象
db *Db // 数据库操作对象
db DB // 数据库操作对象
tx *TX // 数据库事务对象
tablesInit string // 初始化Model时的表名称(可以是多个)
tables string // 数据库操作表
fields string // 操作字段
where string // 操作条件
@ -28,126 +31,221 @@ type Model struct {
limit int // 分页条数
data interface{} // 操作记录(支持Map/List/string类型)
batch int // 批量操作条数
filter bool // 是否按照表字段过滤data参数
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
cacheTime int // 查询缓存时间
cacheName string // 查询缓存名称
}
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
func (db *Db) Table(tables string) (*Model) {
return &Model{
db: db,
tables: tables,
fields: "*",
func (bs *dbBase) Table(tables string) (*Model) {
return &Model {
db : bs.db,
tablesInit : tables,
tables : tables,
fields : "*",
}
}
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
func (db *Db) From(tables string) (*Model) {
return db.Table(tables)
func (bs *dbBase) From(tables string) (*Model) {
return bs.db.Table(tables)
}
// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接
func (tx *Tx) Table(tables string) (*Model) {
func (tx *TX) Table(tables string) (*Model) {
return &Model{
db: tx.db,
tx: tx,
tables: tables,
db : tx.db,
tx : tx,
tablesInit : tables,
tables : tables,
}
}
// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接
func (tx *Tx) From(tables string) (*Model) {
func (tx *TX) From(tables string) (*Model) {
return tx.Table(tables)
}
// 克隆一个当前对象
func (md *Model) Clone() *Model {
newModel := (*Model)(nil)
if md.tx != nil {
newModel = md.tx.Table(md.tablesInit)
} else {
newModel = md.db.Table(md.tablesInit)
}
*newModel = *md
return newModel
}
// 链式操作,左联表
func (md *Model) LeftJoin(joinTable string, on string) (*Model) {
md.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
return md
model := md.Clone()
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,右联表
func (md *Model) RightJoin(joinTable string, on string) (*Model) {
md.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
return md
model := md.Clone()
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,内联表
func (md *Model) InnerJoin(joinTable string, on string) (*Model) {
md.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
return md
model := md.Clone()
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,查询字段
func (md *Model) Fields(fields string) (*Model) {
md.fields = fields
return md
model := md.Clone()
model.fields = fields
return model
}
// 链式操作,过滤字段
func (md *Model) Filter() (*Model) {
model := md.Clone()
model.filter = true
return model
}
// 链式操作condition支持string & gdb.Map
func (md *Model) Where(where interface{}, args ...interface{}) (*Model) {
md.where = md.db.formatCondition(where)
md.whereArgs = append(md.whereArgs, args...)
return md
model := md.Clone()
newWhere, newArgs := formatCondition(where, args)
model.where = newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
// 支持 Where("uid", 1)这种格式
if len(args) == 1 && strings.Index(model.where , "?") < 0 {
model.where += "=?"
}
return model
}
// 链式操作添加AND条件到Where中
func (md *Model) And(where interface{}, args ...interface{}) (*Model) {
md.where += " AND " + md.db.formatCondition(where)
md.whereArgs = append(md.whereArgs, args...)
return md
model := md.Clone()
newWhere, newArgs := formatCondition(where, args)
model.where += " AND " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作添加OR条件到Where中
func (md *Model) Or(where interface{}, args ...interface{}) (*Model) {
md.where += " OR " + md.db.formatCondition(where)
md.whereArgs = append(md.whereArgs, args...)
return md
model := md.Clone()
newWhere, newArgs := formatCondition(where, args)
model.where += " OR " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作group by
func (md *Model) GroupBy(groupBy string) (*Model) {
md.groupBy = groupBy
return md
model := md.Clone()
model.groupBy = groupBy
return model
}
// 链式操作order by
func (md *Model) OrderBy(orderBy string) (*Model) {
md.orderBy = orderBy
return md
model := md.Clone()
model.orderBy = orderBy
return model
}
// 链式操作limit
func (md *Model) Limit(start int, limit int) (*Model) {
md.start = start
md.limit = limit
return md
model := md.Clone()
model.start = start
model.limit = limit
return model
}
// 链式操作,翻页
// @author ymrjqyy
func (md *Model) ForPage(page, limit int) (*Model) {
md.start = (page - 1) * limit
md.limit = limit
return md
model := md.Clone()
model.start = (page - 1) * limit
model.limit = limit
return model
}
// 链式操作操作数据记录项可以是string/Map, 也可以是key,value,key,value,...
func (md *Model) Data(data ...interface{}) (*Model) {
// 设置批处理的大小
func (md *Model) Batch(batch int) *Model {
model := md.Clone()
model.batch = batch
return model
}
// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。
// 当time < 0时表示清除缓存 time=0时表示不过期, time > 0时表示过期时间time过期时间单位
// name表示自定义的缓存名称便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
func (md *Model) Cache(time int, name ... string) *Model {
model := md.Clone()
model.cacheTime = time
if len(name) > 0 {
model.cacheName = name[0]
}
// 查询缓存特性不支持事务操作
if model.tx == nil {
model.cacheEnabled = true
}
return model
}
// 链式操作操作数据项参数data类型支持 string/map/slice/struct/*struct ,
// 也可以是key,value,key,value,...。
func (md *Model) Data(data ...interface{}) *Model {
model := md.Clone()
if len(data) > 1 {
m := make(map[string]interface{})
for i := 0; i < len(data); i += 2 {
m[gconv.String(data[i])] = data[i+1]
m[gconv.String(data[i])] = data[i + 1]
}
md.data = m
model.data = m
} else {
md.data = data[0]
switch data[0].(type) {
case List:
model.data = data[0]
case Map:
model.data = data[0]
default:
rv := reflect.ValueOf(data[0])
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// 如果是slice那么转换为List类型
case reflect.Slice: fallthrough
case reflect.Array:
list := make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
list[i] = gconv.Map(rv.Index(i).Interface())
}
model.data = list
case reflect.Map: fallthrough
case reflect.Struct:
model.data = Map(gconv.Map(data[0]))
default:
model.data = data[0]
}
}
}
return md
return model
}
// 链式操作, CURD - Insert/BatchInsert
// 链式操作, CURD - Insert/BatchInsert
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Insert() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -163,22 +261,32 @@ func (md *Model) Insert() (result sql.Result, err error) {
if md.batch > 0 {
batch = md.batch
}
if md.filter {
for k, m := range list {
list[k] = md.db.filterFields(md.tables, m)
}
}
if md.tx == nil {
return md.db.BatchInsert(md.tables, list, batch)
} else {
return md.tx.BatchInsert(md.tables, list, batch)
}
} else if dataMap, ok := md.data.(Map); ok {
} else if data, ok := md.data.(Map); ok {
if md.filter {
data = md.db.filterFields(md.tables, data)
}
if md.tx == nil {
return md.db.Insert(md.tables, dataMap)
return md.db.Insert(md.tables, data)
} else {
return md.tx.Insert(md.tables, dataMap)
return md.tx.Insert(md.tables, data)
}
}
return nil, errors.New("inserting into table with invalid data type")
}
// 链式操作, CURD - Replace/BatchReplace
// 链式操作, CURD - Replace/BatchReplace
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Replace() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -194,22 +302,32 @@ func (md *Model) Replace() (result sql.Result, err error) {
if md.batch > 0 {
batch = md.batch
}
if md.filter {
for k, m := range list {
list[k] = md.db.filterFields(md.tables, m)
}
}
if md.tx == nil {
return md.db.BatchReplace(md.tables, list, batch)
} else {
return md.tx.BatchReplace(md.tables, list, batch)
}
} else if dataMap, ok := md.data.(Map); ok {
} else if data, ok := md.data.(Map); ok {
if md.filter {
data = md.db.filterFields(md.tables, data)
}
if md.tx == nil {
return md.db.Insert(md.tables, dataMap)
return md.db.Replace(md.tables, data)
} else {
return md.tx.Insert(md.tables, dataMap)
return md.tx.Replace(md.tables, data)
}
}
return nil, errors.New("replacing into table with invalid data type")
}
// 链式操作, CURD - Save/BatchSave
// 链式操作, CURD - Save/BatchSave
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Save() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -221,20 +339,28 @@ func (md *Model) Save() (result sql.Result, err error) {
}
// 批量操作
if list, ok := md.data.(List); ok {
batch := 10
batch := gDEFAULT_BATCH_NUM
if md.batch > 0 {
batch = md.batch
}
if md.filter {
for k, m := range list {
list[k] = md.db.filterFields(md.tables, m)
}
}
if md.tx == nil {
return md.db.BatchSave(md.tables, list, batch)
} else {
return md.tx.BatchSave(md.tables, list, batch)
}
} else if dataMap, ok := md.data.(Map); ok {
} else if data, ok := md.data.(Map); ok {
if md.filter {
data = md.db.filterFields(md.tables, data)
}
if md.tx == nil {
return md.db.Save(md.tables, dataMap)
return md.db.Save(md.tables, data)
} else {
return md.tx.Save(md.tables, dataMap)
return md.tx.Save(md.tables, data)
}
}
return nil, errors.New("saving into table with invalid data type")
@ -250,6 +376,13 @@ func (md *Model) Update() (result sql.Result, err error) {
if md.data == nil {
return nil, errors.New("updating table with empty data")
}
if md.filter {
if data, ok := md.data.(Map); ok {
if md.filter {
md.data = md.db.filterFields(md.tables, data)
}
}
}
if md.tx == nil {
return md.db.Update(md.tables, md.data, md.where, md.whereArgs ...)
} else {
@ -264,9 +397,6 @@ func (md *Model) Delete() (result sql.Result, err error) {
md.checkAndRemoveCache()
}
}()
if md.where == "" {
return nil, errors.New("where is required while deleting")
}
if md.tx == nil {
return md.db.Delete(md.tables, md.where, md.whereArgs...)
} else {
@ -274,36 +404,14 @@ func (md *Model) Delete() (result sql.Result, err error) {
}
}
// 设置批处理的大小
func (md *Model) Batch(batch int) *Model {
md.batch = batch
return md
}
// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。
// 当time < 0时表示清除缓存 time=0时表示不过期, time > 0时表示过期时间time过期时间单位
// name表示自定义的缓存名称便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
func (md *Model) Cache(time int, name ... string) *Model {
md.cacheTime = time
if len(name) > 0 {
md.cacheName = name[0]
}
// 查询缓存特性不支持事务操作
if md.tx == nil {
md.cacheEnabled = true
}
return md
}
// 链式操作select
func (md *Model) Select() (Result, error) {
return md.getAll(md.getFormattedSql(), md.whereArgs...)
return md.All()
}
// 链式操作,查询所有记录
func (md *Model) All() (Result, error) {
return md.Select()
return md.getAll(md.getFormattedSql(), md.whereArgs...)
}
// 链式操作,查询单条记录
@ -342,8 +450,13 @@ func (md *Model) Struct(obj interface{}) error {
// 链式操作查询数量fields可以为空也可以自定义查询字段
// 当给定自定义查询字段时该字段必须为数量结果否则会引起歧义使用如md.Fields("COUNT(id)")
func (md *Model) Count() (int, error) {
defer func(fields string) {
md.fields = fields
}(md.fields)
if md.fields == "" || md.fields == "*" {
md.fields = "COUNT(1)"
} else {
md.fields = fmt.Sprintf(`COUNT(%s)`, md.fields)
}
s := md.getFormattedSql()
if len(md.groupBy) > 0 {
@ -362,29 +475,30 @@ func (md *Model) Count() (int, error) {
}
// 查询操作对底层SQL操作的封装
func (md *Model) getAll(sql string, args ...interface{}) (result Result, err error) {
var cacheKey string
func (md *Model) getAll(query string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// 查询缓存查询处理
if md.cacheEnabled {
cacheKey = md.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
cacheKey = query + "/" + gconv.String(args)
}
if v := md.db.cache.Get(cacheKey); v != nil {
if v := md.db.getCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
if md.tx == nil {
result, err = md.db.GetAll(sql, args...)
result, err = md.db.GetAll(query, args...)
} else {
result, err = md.tx.GetAll(sql, args...)
result, err = md.tx.GetAll(query, args...)
}
// 查询缓存保存处理
if len(cacheKey) > 0 && err == nil {
if md.cacheTime < 0 {
md.db.cache.Remove(cacheKey)
md.db.getCache().Remove(cacheKey)
} else {
md.db.cache.Set(cacheKey, result, md.cacheTime*1000)
md.db.getCache().Set(cacheKey, result, md.cacheTime*1000)
}
}
return result, err
@ -393,7 +507,7 @@ func (md *Model) getAll(sql string, args ...interface{}) (result Result, err err
// 检查是否需要查询查询缓存
func (md *Model) checkAndRemoveCache() {
if md.cacheEnabled && md.cacheTime < 0 && len(md.cacheName) > 0 {
md.db.cache.Remove(md.cacheName)
md.db.getCache().Remove(md.cacheName)
}
}
@ -422,11 +536,10 @@ func (md *Model) getFormattedSql() string {
// @author ymrjqyy
// @author 2018-08-15
func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) {
var page = 1
page := 1
for {
md.ForPage(page, limit)
sqls := md.getFormattedSql()
data, err := md.getAll(sqls, md.whereArgs...)
data, err := md.getAll(md.getFormattedSql(), md.whereArgs...)
if err != nil {
callback(nil, err)
break

177
g/database/gdb/gdb_mssql.go Normal file
View File

@ -0,0 +1,177 @@
// 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.
/*
@author wenzi1<liyz23@qq.com>
@date 20181109
说明:
1.需要导入sqlserver驱动 github.com/denisenkom/go-mssqldb
2.不支持save/replace方法
3.不支持LastInsertId方法
*/
package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/g/text/gregex"
"strconv"
"strings"
)
// 数据库链接对象
type dbMssql struct {
*dbBase
}
// 创建SQL操作对象
func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
source := ""
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = fmt.Sprintf("user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
config.User, config.Pass, config.Host, config.Port, config.Name)
}
if db, err := sql.Open("sqlserver", source); err == nil {
return db, nil
} else {
return nil, err
}
}
// 获得关键字操作符
func (db *dbMssql) getChars() (charLeft string, charRight string) {
return "\"", "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dbMssql) handleSqlBeforeExec(query string) string {
index := 0
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
index++
return fmt.Sprintf("@p%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return db.parseSql(str)
}
//将MYSQL的SQL语法转换为MSSQL的语法
//1.由于mssql不支持limit写法所以需要对mysql中的limit用法做转换
func (db *dbMssql) parseSql(sql string) string {
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理如有LIMIT则将LIMIT的关键字也匹配出
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, sql) == false {
fmt.Println("not matched..")
return sql
}
res, err := gregex.MatchAllString(patten, sql)
if err != nil {
fmt.Println("MatchString error.", err)
return ""
}
index := 0
keyword := strings.TrimSpace(res[index][0])
keyword = strings.ToUpper(keyword)
index++
switch keyword {
case "SELECT":
//不含LIMIT关键字则不处理
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
break
}
//不含LIMIT则不处理
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
break
}
//判断SQL中是否含有order by
selectStr := ""
orderbyStr := ""
haveOrderby := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
if haveOrderby {
//取order by 前面的字符串
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
break
}
selectStr = queryExpr[2]
//取order by表达式的值
orderbyExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false {
break
}
orderbyStr = orderbyExpr[2]
} else {
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
break
}
selectStr = queryExpr[2]
}
//取limit后面的取值范围
first, limit := 0, 0
for i := 1; i < len(res[index]); i++ {
if len(strings.TrimSpace(res[index][i])) == 0 {
continue
}
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
first, _ = strconv.Atoi(res[index][i+1])
limit, _ = strconv.Atoi(res[index][i+2])
break
}
}
if haveOrderby {
sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit)
} else {
if first == 0 {
first = limit
} else {
first = limit - first
}
sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr)
}
default:
}
return sql
}
// 获得指定表表的数据结构构造成map哈希表返回其中键名为表字段名称键值暂无用途(默认为字段数据类型).
func (db *dbMssql) getTableFields(table string) (fields map[string]string, err error) {
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
v := db.cache.GetOrSetFunc("table_fields_"+table, func() interface{} {
result := (Result)(nil)
result, err = db.GetAll(fmt.Sprintf(`
SELECT c.name as FIELD, CASE t.name
WHEN 'numeric' THEN t.name + '(' + convert(varchar(20),c.xprec) + ',' + convert(varchar(20),c.xscale) + ')'
WHEN 'char' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
WHEN 'varchar' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
ELSE t.name + '(' + convert(varchar(20),c.length)+ ')' END as TYPE
FROM systypes t,syscolumns c WHERE t.xtype=c.xtype AND c.id = (SELECT id FROM sysobjects WHERE name='%s') ORDER BY c.colid`, strings.ToUpper(table)))
if err != nil {
return nil
}
fields = make(map[string]string)
for _, m := range result {
fields[strings.ToLower(m["FIELD"].String())] = strings.ToLower(m["TYPE"].String()) //sqlserver返回的field为大写的需要转为小写的
}
return fields
}, 0)
if err == nil {
fields = v.(map[string]string)
}
return
}

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.
package gdb
@ -12,22 +12,19 @@ import (
"database/sql"
)
// MySQL接口对象
var linkMysql = &dbmysql{}
// 数据库链接对象
type dbmysql struct {
Db
type dbMysql struct {
*dbBase
}
// 创建SQL操作对象内部采用了lazy link处理
func (db *dbmysql) Open (c *ConfigNode) (*sql.DB, error) {
func (db *dbMysql) Open (config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", c.User, c.Pass, c.Host, c.Port, c.Name)
source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true",
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset)
}
if db, err := sql.Open("mysql", source); err == nil {
return db, nil
@ -36,17 +33,12 @@ func (db *dbmysql) Open (c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dbmysql) getQuoteCharLeft () string {
return "`"
}
// 获得关键字操作符 - 右
func (db *dbmysql) getQuoteCharRight () string {
return "`"
// 获得关键字操作符
func (db *dbMysql) getChars () (charLeft string, charRight string) {
return "`", "`"
}
// 在执行sql之前对sql进行进一步处理
func (db *dbmysql) handleSqlBeforeExec(q *string) *string {
return q
func (db *dbMysql) handleSqlBeforeExec(query string) string {
return query
}

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.
/*
@author wenzi1<liyz23@qq.com>
@date 20181026
@ -16,25 +16,23 @@ package gdb
import (
"database/sql"
"fmt"
"gitee.com/johng/gf/g/util/gregex"
"github.com/gogf/gf/g/text/gregex"
"strconv"
"strings"
)
var linkOracle = &dboracle{}
// 数据库链接对象
type dboracle struct {
Db
type dbOracle struct {
*dbBase
}
// 创建SQL操作对象
func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = fmt.Sprintf("%s/%s@%s", c.User, c.Pass, c.Name)
source = fmt.Sprintf("%s/%s@%s", config.User, config.Pass, config.Name)
}
if db, err := sql.Open("oci8", source); err == nil {
return db, nil
@ -43,42 +41,37 @@ func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dboracle) getQuoteCharLeft() string {
return "\""
}
// 获得关键字操作符 - 右
func (db *dboracle) getQuoteCharRight() string {
return "\""
// 获得关键字操作符
func (db *dbOracle) getChars() (charLeft string, charRight string) {
return "\"", "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dboracle) handleSqlBeforeExec(q *string) *string {
func (db *dbOracle) handleSqlBeforeExec(query string) string {
index := 0
str, _ := gregex.ReplaceStringFunc("\\?", *q, func(s string) string {
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
index++
return fmt.Sprintf(":%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return db.parseSql(&str)
return db.parseSql(str)
}
//由于ORACLE中对LIMIT和批量插入的语法与MYSQL不一致所以这里需要对LIMIT和批量插入做语法上的转换
func (db *dboracle) parseSql(sql *string) *string {
func (db *dbOracle) parseSql(sql string) string {
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理如有LIMIT则将LIMIT的关键字也匹配出
patten := `^\s*(?i)(SELECT)|(INSERT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, *sql) == false {
if gregex.IsMatchString(patten, sql) == false {
fmt.Println("not matched..")
return sql
}
res, err := gregex.MatchAllString(patten, *sql)
res, err := gregex.MatchAllString(patten, sql)
if err != nil {
fmt.Println("MatchString error.", err)
return nil
return ""
}
index := 0
@ -94,13 +87,14 @@ func (db *dboracle) parseSql(sql *string) *string {
}
//取limit前面的字符串
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql) == false {
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
break
}
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql)
queryExpr[0] = strings.TrimRight(queryExpr[0], "LIMIT")
queryExpr[0] = strings.TrimRight(queryExpr[0], "limit")
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
break
}
//取limit后面的取值范围
first, limit := 0, 0
@ -117,10 +111,10 @@ func (db *dboracle) parseSql(sql *string) *string {
}
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
*sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[0], limit, first)
sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
case "INSERT":
//获取VALUE的值匹配所有带括号的值,会将INSERT INTO后的值匹配到所以下面的判断语句会判断数组长度是否小于3
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, *sql)
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, sql)
if err != nil {
return sql
}
@ -131,19 +125,46 @@ func (db *dboracle) parseSql(sql *string) *string {
}
//获取INTO后面的值
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, *sql)
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, sql)
if err != nil {
return sql
}
tableExpr[0] = strings.TrimSpace(tableExpr[0])
*sql = "INSERT ALL"
sql = "INSERT ALL"
for i := 1; i < len(valueExpr); i++ {
*sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
}
*sql += " SELECT 1 FROM DUAL"
sql += " SELECT 1 FROM DUAL"
default:
}
return sql
}
// 获得指定表表的数据结构构造成map哈希表返回其中键名为表字段名称键值暂无用途(默认为字段数据类型).
func (db *dbOracle) getTableFields(table string) (fields map[string]string, err error) {
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
v := db.cache.GetOrSetFunc("table_fields_"+table, func() interface{} {
result := (Result)(nil)
result, err = db.GetAll(fmt.Sprintf(`
SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table)))
if err != nil {
return nil
}
fields = make(map[string]string)
for _, m := range result {
fields[strings.ToLower(m["FIELD"].String())] = strings.ToLower(m["TYPE"].String()) //ORACLE返回的值默认都是大写的需要转为小写
}
return fields
}, 0)
if err == nil {
fields = v.(map[string]string)
}
return
}

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.
package gdb
@ -15,25 +15,21 @@ import (
// PostgreSQL的适配.
// 使用时需要import:
// _ "gitee.com/johng/gf/third/github.com/lib/pq"
// _ "github.com/gogf/gf/third/github.com/lib/pq"
// @todo 需要完善replace和save的操作覆盖
// PostgreSQL接口对象
var linkPgsql = &dbpgsql{}
// 数据库链接对象
type dbpgsql struct {
Db
type dbPgsql struct {
*dbBase
}
// 创建SQL操作对象内部采用了lazy link处理
func (db *dbpgsql) Open (c *ConfigNode) (*sql.DB, error) {
func (db *dbPgsql) Open (config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", c.User, c.Pass, c.Host, c.Port, c.Name)
source = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", config.User, config.Pass, config.Host, config.Port, config.Name)
}
if db, err := sql.Open("postgres", source); err == nil {
return db, nil
@ -42,23 +38,18 @@ func (db *dbpgsql) Open (c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dbpgsql) getQuoteCharLeft () string {
return "\""
}
// 获得关键字操作符 - 右
func (db *dbpgsql) getQuoteCharRight () string {
return "\""
// 获得关键字操作符
func (db *dbPgsql) getChars () (charLeft string, charRight string) {
return "\"", "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dbpgsql) handleSqlBeforeExec(q *string) *string {
func (db *dbPgsql) handleSqlBeforeExec(query string) string {
reg := regexp.MustCompile("\\?")
index := 0
str := reg.ReplaceAllStringFunc(*q, func (s string) string {
str := reg.ReplaceAllStringFunc(query, func (s string) string {
index ++
return fmt.Sprintf("$%d", index)
})
return &str
return str
}

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.
// @author wxkj<wxscz@qq.com>
package gdb
@ -12,24 +12,22 @@ import (
)
// 使用时需要import:
// _ "gitee.com/johng/gf/third/github.com/mattn/go-sqlite3"
// _ "github.com/gogf/gf/third/github.com/mattn/go-sqlite3"
// Sqlite接口对象
// @author wxkj<wxscz@qq.com>
var linkSqlite = &dbsqlite{}
// 数据库链接对象
type dbsqlite struct {
Db
type dbSqlite struct {
*dbBase
}
func (db *dbsqlite) Open(c *ConfigNode) (*sql.DB, error) {
func (db *dbSqlite) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = c.Name
source = config.Name
}
if db, err := sql.Open("sqlite3", source); err == nil {
return db, nil
@ -38,20 +36,14 @@ func (db *dbsqlite) Open(c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dbsqlite) getQuoteCharLeft() string {
return "`"
}
// 获得关键字操作符 - 右
func (db *dbsqlite) getQuoteCharRight() string {
return "`"
// 获得关键字操作符
func (db *dbSqlite) getChars () (charLeft string, charRight string) {
return "`", "`"
}
// 在执行sql之前对sql进行进一步处理
// @todo 需要增加对Save方法的支持可使用正则来实现替换
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
func (db *dbsqlite) handleSqlBeforeExec(q *string) *string {
return q
func (db *dbSqlite) handleSqlBeforeExec(query string) string {
return query
}

View File

@ -0,0 +1,120 @@
// 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 gdb
import (
"fmt"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"strings"
)
/*
// 同步数据库表结构到内存中
func (bs *dbBase) syncTableStructure() {
bs.tables = make(map[string]map[string]string)
for _, table := range bs.db.getTables() {
bs.tables[table], _ = bs.db.getTableFields(table)
}
}
*/
// 字段类型转换将数据库字段类型转换为golang变量类型
func (bs *dbBase) convertValue(fieldValue interface{}, fieldType string) interface{} {
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
t = strings.ToLower(t)
switch t {
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
return gconv.Bytes(fieldValue)
case "bit", "int", "tinyint", "small_int", "medium_int":
return gconv.Int(fieldValue)
case "big_int":
return gconv.Int64(fieldValue)
case "float", "double", "decimal":
return gconv.Float64(fieldValue)
case "bool":
return gconv.Bool(fieldValue)
default:
// 自动识别类型, 以便默认支持更多数据库类型
switch {
case strings.Contains(t, "int"):
return gconv.Int(fieldValue)
case strings.Contains(t, "text") || strings.Contains(t, "char"):
return gconv.String(fieldValue)
case strings.Contains(t, "float") || strings.Contains(t, "double"):
return gconv.Float64(fieldValue)
case strings.Contains(t, "bool"):
return gconv.Bool(fieldValue)
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
return gconv.Bytes(fieldValue)
default:
return gconv.String(fieldValue)
}
}
}
// 将map的数据按照fields进行过滤只保留与表字段同名的数据
func (bs *dbBase) filterFields(table string, data map[string]interface{}) map[string]interface{} {
if fields, err := bs.db.getTableFields(table); err == nil {
for k, _ := range data {
if _, ok := fields[k]; !ok {
delete(data, k)
}
}
}
return data
}
// 获得指定表表的数据结构构造成map哈希表返回其中键名为表字段名称键值暂无用途(默认为字段数据类型).
func (bs *dbBase) getTableFields(table string) (fields map[string]string, err error) {
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
v := bs.cache.GetOrSetFunc("table_fields_" + table, func() interface{} {
result := (Result)(nil)
charL, charR := bs.db.getChars()
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s%s%s`, charL, table, charR))
if err != nil {
return nil
}
fields = make(map[string]string)
for _, m := range result {
fields[m["Field"].String()] = m["Type"].String()
}
return fields
}, 0)
if err == nil {
fields = v.(map[string]string)
}
return
}
/*
// 获取当前数据库所有的表结构
func (bs *dbBase) getTables() []string {
if result, _ := bs.GetAll(`SHOW TABLES`); result != nil {
array := make([]string, len(result))
for i, m := range result {
for _, v := range m {
array[i] = v.String()
break
}
}
return array
}
return nil
}
*/

View File

@ -1,138 +1,61 @@
// 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 gdb
import (
"fmt"
"errors"
"strings"
"reflect"
"database/sql"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gconv"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"gitee.com/johng/gf/g/container/gvar"
"github.com/gogf/gf/g/text/gregex"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
)
// 数据库事务对象
type Tx struct {
db *Db
type TX struct {
db DB
tx *sql.Tx
master *sql.DB
}
// 事务操作,提交
func (tx *Tx) Commit() error {
err := tx.tx.Commit()
tx.master.Close()
return err
func (tx *TX) Commit() error {
return tx.tx.Commit()
}
// 事务操作,回滚
func (tx *Tx) Rollback() error {
err := tx.tx.Rollback()
tx.master.Close()
return err
func (tx *TX) Rollback() error {
return tx.tx.Rollback()
}
// (事务)数据库sql查询操作主要执行查询
func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) {
var err error
var rows *sql.Rows
p := tx.db.link.handleSqlBeforeExec(&query)
if tx.db.debug.Val() {
militime1 := gtime.Millisecond()
rows, err = tx.tx.Query(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "TX:Query",
}
tx.db.sqls.Put(s)
tx.db.printSql(s)
} else {
rows, err = tx.tx.Query(*p, args ...)
}
if err == nil {
return rows, nil
} else {
err = tx.db.formatError(err, p, args...)
}
return nil, err
func (tx *TX) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
return tx.db.doQuery(tx.tx, query, args...)
}
// (事务)执行一条sql并返回执行情况主要用于非查询操作
func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
var err error
var result sql.Result
p := tx.db.link.handleSqlBeforeExec(&query)
if tx.db.debug.Val() {
militime1 := gtime.Millisecond()
result, err = tx.tx.Exec(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "TX:Exec",
}
tx.db.sqls.Put(s)
tx.db.printSql(s)
} else {
result, err = tx.tx.Exec(*p, args ...)
}
return result, tx.db.formatError(err, p, args...)
func (tx *TX) Exec(query string, args ...interface{}) (sql.Result, error) {
return tx.db.doExec(tx.tx, query, args...)
}
// sql预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
func (tx *TX) Prepare(query string) (*sql.Stmt, error) {
return tx.db.doPrepare(tx.tx, query)
}
// 数据库查询,获取查询结果集,以列表结构返回
func (tx *Tx) GetAll(query string, args ...interface{}) (Result, error) {
// 执行sql
func (tx *TX) GetAll(query string, args ...interface{}) (Result, error) {
rows, err := tx.Query(query, args ...)
if err != nil || rows == nil {
return nil, err
}
// 列名称列表
columns, err := rows.Columns()
if err != nil {
return nil, err
}
// 返回结构组装
values := make([]sql.RawBytes, len(columns))
scanArgs := make([]interface{}, len(values))
records := make(Result, 0)
for i := range values {
scanArgs[i] = &values[i]
}
for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
return records, err
}
row := make(Record)
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
for i, col := range values {
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(v)
}
//fmt.Printf("%p\n", row["typeid"])
records = append(records, row)
}
return records, nil
defer rows.Close()
return tx.db.rowsToResult(rows)
}
// 数据库查询,获取查询结果记录,以关联数组结构返回
func (tx *Tx) GetOne(query string, args ...interface{}) (Record, error) {
func (tx *TX) GetOne(query string, args ...interface{}) (Record, error) {
list, err := tx.GetAll(query, args ...)
if err != nil {
return nil, err
@ -144,7 +67,7 @@ func (tx *Tx) GetOne(query string, args ...interface{}) (Record, error) {
}
// 数据库查询获取查询结果记录自动映射数据到给定的struct对象中
func (tx *Tx) GetStruct(obj interface{}, query string, args ...interface{}) error {
func (tx *TX) GetStruct(obj interface{}, query string, args ...interface{}) error {
one, err := tx.GetOne(query, args...)
if err != nil {
return err
@ -152,9 +75,8 @@ func (tx *Tx) GetStruct(obj interface{}, query string, args ...interface{}) erro
return one.ToStruct(obj)
}
// 数据库查询,获取查询字段值
func (tx *Tx) GetValue(query string, args ...interface{}) (Value, error) {
func (tx *TX) GetValue(query string, args ...interface{}) (Value, error) {
one, err := tx.GetOne(query, args ...)
if err != nil {
return nil, err
@ -166,187 +88,55 @@ func (tx *Tx) GetValue(query string, args ...interface{}) (Value, error) {
}
// 数据库查询,获取查询数量
func (tx *Tx) GetCount(query string, args ...interface{}) (int, error) {
val, err := tx.GetValue(query, args ...)
func (tx *TX) GetCount(query string, args ...interface{}) (int, error) {
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
}
value, err := tx.GetValue(query, args ...)
if err != nil {
return 0, err
}
return gconv.Int(val), nil
}
// 数据表查询其中tables可以是多个联表查询语句这种查询方式较复杂建议使用链式操作
func (tx *Tx) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
if condition != nil {
s += fmt.Sprintf("WHERE %s ", tx.db.formatCondition(condition))
}
if len(groupBy) > 0 {
s += fmt.Sprintf("GROUP BY %s ", groupBy)
}
if len(orderBy) > 0 {
s += fmt.Sprintf("ORDER BY %s ", orderBy)
}
if limit > 0 {
s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
}
return tx.GetAll(s, args ... )
}
// sql预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
// 记得调用sql.Stmt.Close关闭操作对象
func (tx *Tx) Prepare(query string) (*sql.Stmt, error) {
return tx.tx.Prepare(query)
}
// insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
func (tx *Tx) insert(table string, data Map, option uint8) (sql.Result, error) {
var keys []string
var values []string
var params []interface{}
for k, v := range data {
keys = append(keys, tx.db.charl + k + tx.db.charr)
values = append(values, "?")
params = append(params, v)
}
operation := tx.db.getInsertOperationByOption(option)
updatestr := ""
if option == OPTION_SAVE {
var updates []string
for k, _ := range data {
updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", tx.db.charl, k, tx.db.charr, k))
}
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
return tx.Exec(
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(keys, ","),
strings.Join(values, ","),
updatestr),
params...
)
return value.Int(), nil
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (tx *Tx) Insert(table string, data Map) (sql.Result, error) {
return tx.insert(table, data, OPTION_INSERT)
func (tx *TX) Insert(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *Tx) Replace(table string, data Map) (sql.Result, error) {
return tx.insert(table, data, OPTION_REPLACE)
func (tx *TX) Replace(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *Tx) Save(table string, data Map) (sql.Result, error) {
return tx.insert(table, data, OPTION_SAVE)
}
// 批量写入数据
func (tx *Tx) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
var keys []string
var values []string
var bvalues []string
var params []interface{}
var result sql.Result
var size = len(list)
// 判断长度
if size < 1 {
return result, errors.New("empty data list")
}
// 首先获取字段名称及记录长度
for k, _ := range list[0] {
keys = append(keys, k)
values = append(values, "?")
}
keyStr := tx.db.charl + strings.Join(keys, tx.db.charl + "," + tx.db.charr) + tx.db.charr
valueHolderStr := "(" + strings.Join(values, ",") + ")"
// 操作判断
operation := tx.db.getInsertOperationByOption(option)
updatestr := ""
if option == OPTION_SAVE {
var updates []string
for _, k := range keys {
updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", tx.db.charl, k, tx.db.charr, k))
}
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
// 构造批量写入数据格式(注意map的遍历是无序的)
for i := 0; i < size; i++ {
for _, k := range keys {
params = append(params, list[i][k])
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
return result, err
}
result = r
params = params[:0]
bvalues = bvalues[:0]
}
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
return result, err
}
result = r
}
return result, nil
func (tx *TX) Save(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE, batch...)
}
// CURD操作:批量数据指定批次量写入
func (tx *Tx) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return tx.batchInsert(table, list, batch, OPTION_INSERT)
func (tx *TX) BatchInsert(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_INSERT, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *Tx) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return tx.batchInsert(table, list, batch, OPTION_REPLACE)
func (tx *TX) BatchReplace(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_REPLACE, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *Tx) BatchSave(table string, list List, batch int) (sql.Result, error) {
return tx.batchInsert(table, list, batch, OPTION_SAVE)
func (tx *TX) BatchSave(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_SAVE, batch...)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型内部会自行做判断处理
func (tx *Tx) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
var params []interface{}
var updates string
refValue := reflect.ValueOf(data)
if refValue.Kind() == reflect.Map {
var fields []string
keys := refValue.MapKeys()
for _, k := range keys {
fields = append(fields, fmt.Sprintf("%s%s%s=?", tx.db.charl, k, tx.db.charr))
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
updates = strings.Join(fields, ",")
}
} else {
updates = gconv.String(data)
}
for _, v := range args {
params = append(params, gconv.String(v))
}
return tx.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, tx.db.formatCondition(condition)), params...)
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
return tx.db.doUpdate(tx.tx, table, data, condition, args ...)
}
// CURD操作:删除数据
func (tx *Tx) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, tx.db.formatCondition(condition)), args...)
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return tx.db.doDelete(tx.tx, table, condition, args ...)
}

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