From 181187e0b25034cbe24a8d593288be91d82de225 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Sun, 25 Feb 2024 22:59:21 +0800 Subject: [PATCH 1/2] x/byteutil --- bytes/bytes_test.go | 21 ++++++++++- byteutil/bytes_go121.go | 31 ++++++++++++++++ byteutil/bytes_reflect.go | 33 +++++++++++++++++ .../string_mock.go => byteutil/bytes_test.go | 15 ++++---- jsonutil/json_string.go | 31 +++++++++++----- jsonutil/json_string_test.go | 20 ++++++++++- stringutil/string_reflect.go | 36 +++++++++++++++++++ 7 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 byteutil/bytes_go121.go create mode 100644 byteutil/bytes_reflect.go rename stringutil/string_mock.go => byteutil/bytes_test.go (80%) create mode 100644 stringutil/string_reflect.go diff --git a/bytes/bytes_test.go b/bytes/bytes_test.go index 5c162f5..4ca481e 100644 --- a/bytes/bytes_test.go +++ b/bytes/bytes_test.go @@ -23,8 +23,27 @@ import ( // --------------------------------------------------- -func TestBuffer(t *testing.T) { +func TestWriter(t *testing.T) { + b := make([]byte, 8) + w := NewWriter(b) + w.Write([]byte("abc")) + w.Write([]byte("1234567")) + if _, e := w.Write([]byte("A")); e != io.EOF { + t.Fatal("w.Write:", e) + } + if v := w.Len(); v != 8 { + t.Fatal("w.Len():", v) + } + if v := string(w.Bytes()); v != "abc12345" { + t.Fatal("w.Bytes():", v) + } + w.Reset() + if v := w.Len(); v != 0 { + t.Fatal("w.Len():", v) + } +} +func TestBuffer(t *testing.T) { b := NewBuffer() n, err := b.WriteStringAt("Hello", 4) if n != 5 || err != nil { diff --git a/byteutil/bytes_go121.go b/byteutil/bytes_go121.go new file mode 100644 index 0000000..37224d3 --- /dev/null +++ b/byteutil/bytes_go121.go @@ -0,0 +1,31 @@ +//go:build go1.21 +// +build go1.21 + +/* + Copyright 2024 Qiniu Limited (qiniu.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package byteutil + +import ( + "unsafe" +) + +// Bytes returns a byte slice whose underlying data is s. +func Bytes(s string) []byte { + // Although unsafe.SliceData/String was introduced in go1.20, but + // the go version in go.mod is 1.18 so we cannot use them. + return unsafe.Slice(unsafe.StringData(s), len(s)) +} diff --git a/byteutil/bytes_reflect.go b/byteutil/bytes_reflect.go new file mode 100644 index 0000000..0a88d1c --- /dev/null +++ b/byteutil/bytes_reflect.go @@ -0,0 +1,33 @@ +//go:build !go1.21 +// +build !go1.21 + +/* + Copyright 2024 Qiniu Limited (qiniu.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package byteutil + +import ( + "reflect" + "unsafe" +) + +// Bytes returns a byte slice whose underlying data is s. +func Bytes(s string) (b []byte) { + bh := *(*reflect.SliceHeader)(unsafe.Pointer(&b)) + sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) + bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len + return +} diff --git a/stringutil/string_mock.go b/byteutil/bytes_test.go similarity index 80% rename from stringutil/string_mock.go rename to byteutil/bytes_test.go index 8fd5efd..48b23b5 100644 --- a/stringutil/string_mock.go +++ b/byteutil/bytes_test.go @@ -1,6 +1,3 @@ -//go:build !go1.21 -// +build !go1.21 - /* Copyright 2024 Qiniu Limited (qiniu.com) @@ -17,8 +14,14 @@ limitations under the License. */ -package stringutil +package byteutil + +import ( + "testing" +) -func String(b []byte) string { - return string(b) +func TestBytes(t *testing.T) { + if b := Bytes("abc"); string(b) != "abc" { + t.Fatal("Bytes:", b) + } } diff --git a/jsonutil/json_string.go b/jsonutil/json_string.go index 8c4cff7..e8a94f5 100644 --- a/jsonutil/json_string.go +++ b/jsonutil/json_string.go @@ -1,19 +1,32 @@ +/* + Copyright 2019 Qiniu Limited (qiniu.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + package jsonutil import ( "encoding/json" - "reflect" - "unsafe" -) -// ---------------------------------------------------------- + "github.com/qiniu/x/byteutil" + "github.com/qiniu/x/stringutil" +) // Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. func Unmarshal(data string, v interface{}) error { - - sh := *(*reflect.StringHeader)(unsafe.Pointer(&data)) - arr := (*[1 << 30]byte)(unsafe.Pointer(sh.Data)) - return json.Unmarshal(arr[:sh.Len], v) + b := byteutil.Bytes(data) + return json.Unmarshal(b, v) } // ---------------------------------------------------------- @@ -21,7 +34,7 @@ func Unmarshal(data string, v interface{}) error { // Stringify converts a value into string. func Stringify(v interface{}) string { b, _ := json.Marshal(v) - return string(b) + return stringutil.String(b) } // ---------------------------------------------------------- diff --git a/jsonutil/json_string_test.go b/jsonutil/json_string_test.go index 91fdfda..2af0553 100644 --- a/jsonutil/json_string_test.go +++ b/jsonutil/json_string_test.go @@ -1,3 +1,19 @@ +/* + Copyright 2019 Qiniu Limited (qiniu.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + package jsonutil import ( @@ -5,7 +21,6 @@ import ( ) func Test(t *testing.T) { - var ret struct { ID string `json:"id"` } @@ -16,4 +31,7 @@ func Test(t *testing.T) { if ret.ID != "123" { t.Fatal("Unmarshal uncorrect:", ret.ID) } + if v := Stringify(ret); v != `{"id":"123"}` { + t.Fatal("Stringify:", v) + } } diff --git a/stringutil/string_reflect.go b/stringutil/string_reflect.go new file mode 100644 index 0000000..5c52de7 --- /dev/null +++ b/stringutil/string_reflect.go @@ -0,0 +1,36 @@ +//go:build !go1.21 +// +build !go1.21 + +/* + Copyright 2024 Qiniu Limited (qiniu.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package stringutil + +import ( + "reflect" + "unsafe" +) + +// String returns a string value whose underlying bytes is b. +// +// Since Go strings are immutable, the bytes passed to String +// must not be modified afterwards. +func String(b []byte) (s string) { + sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := *(*reflect.SliceHeader)(unsafe.Pointer(&b)) + sh.Data, sh.Len = bh.Data, bh.Len + return +} From a1344500690498dc178330702dc9fbaf816caf57 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Sun, 25 Feb 2024 23:02:34 +0800 Subject: [PATCH 2/2] byteutil/stringutil: use reflect --- byteutil/bytes_reflect.go | 2 +- stringutil/string_reflect.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/byteutil/bytes_reflect.go b/byteutil/bytes_reflect.go index 0a88d1c..3d14f8c 100644 --- a/byteutil/bytes_reflect.go +++ b/byteutil/bytes_reflect.go @@ -26,7 +26,7 @@ import ( // Bytes returns a byte slice whose underlying data is s. func Bytes(s string) (b []byte) { - bh := *(*reflect.SliceHeader)(unsafe.Pointer(&b)) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len return diff --git a/stringutil/string_reflect.go b/stringutil/string_reflect.go index 5c52de7..a711f80 100644 --- a/stringutil/string_reflect.go +++ b/stringutil/string_reflect.go @@ -29,7 +29,7 @@ import ( // Since Go strings are immutable, the bytes passed to String // must not be modified afterwards. func String(b []byte) (s string) { - sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := *(*reflect.SliceHeader)(unsafe.Pointer(&b)) sh.Data, sh.Len = bh.Data, bh.Len return