small string split join optimization

This commit is contained in:
yusing 2024-12-19 00:54:31 +08:00
parent 654194b274
commit e7be27413c
20 changed files with 160 additions and 50 deletions

View file

@ -33,8 +33,8 @@ func (c containerHelper) getName() string {
}
func (c containerHelper) getImageName() string {
colonSep := strings.Split(c.Image, ":")
slashSep := strings.Split(colonSep[0], "/")
colonSep := strutils.SplitRune(c.Image, ':')
slashSep := strutils.SplitRune(colonSep[0], '/')
return slashSep[len(slashSep)-1]
}

View file

@ -1,9 +1,8 @@
package docker
import (
"strings"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type LabelMap = map[string]any
@ -13,7 +12,7 @@ func ParseLabels(labels map[string]string) (LabelMap, E.Error) {
errs := E.NewBuilder("labels error")
for lbl, value := range labels {
parts := strings.Split(lbl, ".")
parts := strutils.SplitRune(lbl, '.')
if parts[0] != NSProxy {
continue
}

View file

@ -14,6 +14,7 @@ import (
"github.com/yusing/go-proxy/internal/route/routes"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
var findRouteFunc = findRouteAnyDomain
@ -124,7 +125,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
}
func findRouteAnyDomain(host string) (route.HTTPRoute, error) {
hostSplit := strings.Split(host, ".")
hostSplit := strutils.SplitRune(host, '.')
n := len(hostSplit)
switch {
case n == 3:

View file

@ -3,7 +3,8 @@ package err
import (
"errors"
"fmt"
"strings"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
//nolint:recvcheck
@ -78,7 +79,7 @@ func (err *nestedError) Error() string {
if extras := makeLines(err.Extras, 1); len(extras) > 0 {
lines = append(lines, extras...)
}
return strings.Join(lines, "\n")
return strutils.JoinLines(lines)
}
//go:inline

View file

@ -7,6 +7,7 @@ import (
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
var logger zerolog.Logger
@ -39,14 +40,14 @@ func init() {
FieldsExclude: exclude,
FormatMessage: func(msgI interface{}) string { // pad spaces for each line
msg := msgI.(string)
lines := strings.Split(msg, "\n")
lines := strutils.SplitRune(msg, '\n')
if len(lines) == 1 {
return msg
}
for i := 1; i < len(lines); i++ {
lines[i] = prefix + lines[i]
}
return strings.Join(lines, "\n")
return strutils.JoinRune(lines, '\n')
},
},
).Level(level).With().Timestamp().Logger()

View file

@ -7,6 +7,7 @@ import (
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type (
@ -48,8 +49,9 @@ func (method HTTPMethod) Fulfill(req *http.Request, res *http.Response) bool {
return req.Method == string(method)
}
// Parse implements strutils.Parser.
func (k *HTTPHeader) Parse(v string) error {
split := strings.Split(v, "=")
split := strutils.SplitRune(v, '=')
switch len(split) {
case 1:
split = append(split, "")

View file

@ -2,9 +2,9 @@ package accesslog
import (
"strconv"
"strings"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type StatusCodeRange struct {
@ -18,8 +18,9 @@ func (r *StatusCodeRange) Includes(code int) bool {
return r.Start <= code && code <= r.End
}
// Parse implements strutils.Parser.
func (r *StatusCodeRange) Parse(v string) error {
split := strings.Split(v, "-")
split := strutils.SplitRune(v, '-')
switch len(split) {
case 1:
start, err := strconv.Atoi(split[0])

View file

@ -6,7 +6,6 @@ import (
"io"
"net"
"net/http"
"strings"
"sync"
"time"
@ -113,7 +112,7 @@ func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*types.CIDR) error {
return err
}
for _, line := range strings.Split(string(body), "\n") {
for _, line := range strutils.SplitLine(string(body)) {
if line == "" {
continue
}

View file

@ -30,6 +30,7 @@ import (
"github.com/yusing/go-proxy/internal/net/http/accesslog"
"github.com/yusing/go-proxy/internal/net/types"
U "github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/utils/strutils"
"golang.org/x/net/http/httpguts"
)
@ -528,7 +529,7 @@ func UpgradeType(h http.Header) string {
func RemoveHopByHopHeaders(h http.Header) {
// RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") {
for _, sf := range strutils.SplitComma(f) {
if sf = textproto.TrimString(sf); sf != "" {
h.Del(sf)
}

View file

@ -2,17 +2,17 @@ package types
import (
"net/http"
"strings"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
func ValidateHTTPHeaders(headers map[string]string) (http.Header, E.Error) {
h := make(http.Header)
for k, v := range headers {
vSplit := strings.Split(v, ",")
vSplit := strutils.CommaSeperatedList(v)
for _, header := range vSplit {
h.Add(k, strings.TrimSpace(header))
h.Add(k, header)
}
}
return h, nil

View file

@ -173,14 +173,14 @@ func (e *RawEntry) Finalize() {
}
func (e *RawEntry) splitPorts() (lp string, pp string, extra string) {
portSplit := strings.Split(e.Port, ":")
portSplit := strutils.SplitRune(e.Port, ':')
if len(portSplit) == 1 {
pp = portSplit[0]
} else {
lp = portSplit[0]
pp = portSplit[1]
if len(portSplit) > 2 {
extra = strings.Join(portSplit[2:], ":")
extra = strutils.JoinRune(portSplit[2:], ':')
}
}
return
@ -197,7 +197,7 @@ func joinPorts(lp string, pp string, extra string) string {
if extra != "" {
s = append(s, extra)
}
return strings.Join(s, ":")
return strutils.JoinRune(s, ':')
}
func lowestPort(ports map[string]types.Port) string {

View file

@ -1,9 +1,8 @@
package types
import (
"strings"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type StreamPort struct {
@ -14,7 +13,7 @@ type StreamPort struct {
var ErrStreamPortTooManyColons = E.New("too many colons")
func ValidateStreamPort(p string) (StreamPort, error) {
split := strings.Split(p, ":")
split := strutils.SplitRune(p, ':')
switch len(split) {
case 1:

View file

@ -1,10 +1,8 @@
package types
import (
"fmt"
"strings"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type StreamScheme struct {
@ -14,7 +12,7 @@ type StreamScheme struct {
func ValidateStreamScheme(s string) (*StreamScheme, error) {
ss := &StreamScheme{}
parts := strings.Split(s, ":")
parts := strutils.SplitRune(s, ':')
if len(parts) == 1 {
parts = []string{s, s}
} else if len(parts) != 2 {
@ -33,7 +31,7 @@ func ValidateStreamScheme(s string) (*StreamScheme, error) {
}
func (s StreamScheme) String() string {
return fmt.Sprintf("%s -> %s", s.ListeningScheme, s.ProxyScheme)
return string(s.ListeningScheme) + " -> " + string(s.ProxyScheme)
}
// IsCoherent checks if the ListeningScheme and ProxyScheme of the StreamScheme are equal.

View file

@ -193,7 +193,7 @@ func Deserialize(src SerializedObject, dst any) E.Error {
for _, field := range fields {
var key string
if jsonTag, ok := field.Tag.Lookup("json"); ok {
key = strings.Split(jsonTag, ",")[0]
key = strutils.CommaSeperatedList(jsonTag)[0]
} else {
key = field.Name
}
@ -208,7 +208,7 @@ func Deserialize(src SerializedObject, dst any) E.Error {
aliases, ok := field.Tag.Lookup("aliases")
if ok {
for _, alias := range strings.Split(aliases, ",") {
for _, alias := range strutils.CommaSeperatedList(aliases) {
mapping[alias] = dstV.FieldByName(field.Name)
fieldName[field.Name] = alias
}
@ -425,7 +425,7 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr E.E
lines := []string{}
src = strings.TrimSpace(src)
if src != "" {
lines = strings.Split(src, "\n")
lines = strutils.SplitLine(src)
for i := range lines {
lines[i] = strings.TrimSpace(lines[i])
}

View file

@ -2,8 +2,6 @@ package strutils
import (
"reflect"
"github.com/yusing/go-proxy/internal/logging"
)
type Parser interface {
@ -22,7 +20,7 @@ func Parse[T Parser](from string) (t T, err error) {
func MustParse[T Parser](from string) T {
t, err := Parse[T](from)
if err != nil {
logging.Panic().Err(err).Msg("must failed")
panic("must failed: " + err.Error())
}
return t
}

View file

@ -0,0 +1,81 @@
package strutils
import (
"math"
"strings"
)
// SplitRune is like strings.Split but takes a rune as separator.
func SplitRune(s string, sep rune) []string {
if sep == 0 {
return strings.Split(s, "")
}
n := strings.Count(s, string(sep)) + 1
if n > len(s)+1 {
n = len(s) + 1
}
a := make([]string, n)
n--
i := 0
for i < n {
m := strings.IndexRune(s, sep)
if m < 0 {
break
}
a[i] = s[:m]
s = s[m+1:]
i++
}
a[i] = s
return a[:i+1]
}
// SplitComma is a wrapper around SplitRune(s, ',').
func SplitComma(s string) []string {
return SplitRune(s, ',')
}
// SplitLine is a wrapper around SplitRune(s, '\n').
func SplitLine(s string) []string {
return SplitRune(s, '\n')
}
// SplitSpace is a wrapper around SplitRune(s, ' ').
func SplitSpace(s string) []string {
return SplitRune(s, ' ')
}
// JoinRune is like strings.Join but takes a rune as separator.
func JoinRune(elems []string, sep rune) string {
switch len(elems) {
case 0:
return ""
case 1:
return elems[0]
}
if sep == 0 {
return strings.Join(elems, "")
}
var n int
for _, elem := range elems {
if len(elem) > math.MaxInt-n {
panic("strings: Join output length overflow")
}
n += len(elem)
}
var b strings.Builder
b.Grow(n)
b.WriteString(elems[0])
for _, s := range elems[1:] {
b.WriteRune(sep)
b.WriteString(s)
}
return b.String()
}
// JoinLines is a wrapper around JoinRune(elems, '\n').
func JoinLines(elems []string) string {
return JoinRune(elems, '\n')
}

View file

@ -0,0 +1,38 @@
package strutils_test
import (
"strings"
"testing"
. "github.com/yusing/go-proxy/internal/utils/strutils"
. "github.com/yusing/go-proxy/internal/utils/testing"
)
var alphaNumeric = func() string {
var s strings.Builder
for i := range 'z' - 'a' + 1 {
s.WriteRune('a' + i)
s.WriteRune('A' + i)
s.WriteRune(',')
}
for i := range '9' - '0' + 1 {
s.WriteRune('0' + i)
s.WriteRune(',')
}
return s.String()
}()
func TestSplit(t *testing.T) {
tests := map[string]rune{
"": 0,
"1": '1',
",": ',',
}
for sep, rsep := range tests {
t.Run(sep, func(t *testing.T) {
expected := strings.Split(alphaNumeric, sep)
ExpectDeepEqual(t, SplitRune(alphaNumeric, rsep), expected)
ExpectEqual(t, JoinRune(expected, rsep), alphaNumeric)
})
}
}

View file

@ -1,17 +1,7 @@
package strutils
import (
"errors"
"strconv"
E "github.com/yusing/go-proxy/internal/error"
)
func Atoi(s string) (int, E.Error) {
val, err := strconv.Atoi(s)
if err != nil {
return val, E.From(errors.Unwrap(err)).Subject(s)
}
return val, nil
}
var Atoi = strconv.Atoi

View file

@ -8,8 +8,10 @@ import (
"golang.org/x/text/language"
)
// CommaSeperatedList returns a list of strings split by commas,
// then trim spaces from each element.
func CommaSeperatedList(s string) []string {
res := strings.Split(s, ",")
res := SplitComma(s)
for i, part := range res {
res[i] = strings.TrimSpace(part)
}

View file

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/yusing/go-proxy/internal/common"
@ -141,7 +140,7 @@ func (mon *monitor) Uptime() time.Duration {
// Name implements HealthMonitor.
func (mon *monitor) Name() string {
parts := strings.Split(mon.service, "/")
parts := strutils.SplitRune(mon.service, '/')
return parts[len(parts)-1]
}