@@ -395,17 +395,36 @@ func isMatchCategory(category1 string, category2 string) bool {
395395}
396396
397397func (c * URL ) String () string {
398- c .paramsLock .Lock ()
399- defer c .paramsLock .Unlock ()
398+ c .paramsLock .RLock ()
399+ defer c .paramsLock .RUnlock ()
400+
401+ encodedParams := c .params .Encode ()
400402 var buf strings.Builder
401- if len (c .Username ) == 0 && len (c .Password ) == 0 {
402- buf .WriteString (fmt .Sprintf ("%s://%s:%s%s" , c .Protocol , c .Ip , c .Port , c .Path ))
403- } else {
404- buf .WriteString (fmt .Sprintf ("%s://%s:%s@%s:%s%s" , c .Protocol , c .Username , c .Password , c .Ip , c .Port , c .Path ))
403+
404+ size := len (c .Protocol ) + len ("://" ) + len (c .Ip ) + len (":" ) + len (c .Port ) + len (c .Path )
405+ if len (c .Username ) != 0 || len (c .Password ) != 0 {
406+ size += len (c .Username ) + len (":" ) + len (c .Password ) + len ("@" )
407+ }
408+ if encodedParams != "" {
409+ size += len ("?" ) + len (encodedParams )
410+ }
411+
412+ buf .Grow (size )
413+ buf .WriteString (c .Protocol )
414+ buf .WriteString ("://" )
415+ if len (c .Username ) != 0 || len (c .Password ) != 0 {
416+ buf .WriteString (c .Username )
417+ buf .WriteString (":" )
418+ buf .WriteString (c .Password )
419+ buf .WriteString ("@" )
405420 }
406- if encoded := c .params .Encode (); encoded != "" {
421+ buf .WriteString (c .Ip )
422+ buf .WriteString (":" )
423+ buf .WriteString (c .Port )
424+ buf .WriteString (c .Path )
425+ if encodedParams != "" {
407426 buf .WriteByte ('?' )
408- buf .WriteString (encoded )
427+ buf .WriteString (encodedParams )
409428 }
410429 return buf .String ()
411430}
@@ -678,23 +697,34 @@ func (c *URL) GetParams() url.Values {
678697 return c .CopyParams ()
679698}
680699
681- // CopyParams returns a deep copy of params.
682- func (c * URL ) CopyParams () url.Values {
683- c .paramsLock .RLock ()
684- defer c .paramsLock .RUnlock ()
685-
686- if c .params == nil {
700+ func copyURLValues (src url.Values ) url.Values {
701+ if src == nil {
687702 return nil
688703 }
689704
690- params := make (url.Values , len (c . params ))
691- for k , vs := range c . params {
705+ dst := make (url.Values , len (src ))
706+ for k , vs := range src {
692707 copied := make ([]string , len (vs ))
693708 copy (copied , vs )
694- params [k ] = copied
709+ dst [k ] = copied
695710 }
696711
697- return params
712+ return dst
713+ }
714+
715+ func valuesHasNonDefaultParam (values url.Values , key string ) bool {
716+ if len (values ) == 0 {
717+ return false
718+ }
719+ return values .Get (key ) != ""
720+ }
721+
722+ // CopyParams returns a deep copy of params.
723+ func (c * URL ) CopyParams () url.Values {
724+ c .paramsLock .RLock ()
725+ defer c .paramsLock .RUnlock ()
726+
727+ return copyURLValues (c .params )
698728}
699729
700730// GetParamAndDecoded gets values and decode
@@ -880,41 +910,53 @@ func (c *URL) ToMap() map[string]string {
880910func (c * URL ) MergeURL (anotherUrl * URL ) * URL {
881911 // After Clone, it is a new URL that there is no thread safe issue.
882912 mergedURL := c .Clone ()
883- params := mergedURL .GetParams ()
884- // iterator the anotherUrl if c not have the key ,merge in
885- // anotherUrl usually will not changed. so change RangeParams to GetParams to avoid the string value copy.// Group get group
886- for key , value := range anotherUrl .GetParams () {
887- if _ , ok := mergedURL .GetNonDefaultParam (key ); ! ok {
888- if len (value ) > 0 {
889- params [key ] = make ([]string , len (value ))
890- copy (params [key ], value )
913+ params := mergedURL .params
914+ if params == nil {
915+ params = url.Values {}
916+ mergedURL .params = params
917+ }
918+ baseTimestamp := params .Get (constant .TimestampKey )
919+ baseHasTimestamp := baseTimestamp != ""
920+
921+ func () {
922+ anotherUrl .paramsLock .RLock ()
923+ defer anotherUrl .paramsLock .RUnlock ()
924+
925+ // Merge params from anotherUrl under one read lock.
926+ for key , value := range anotherUrl .params {
927+ if ! valuesHasNonDefaultParam (params , key ) {
928+ if len (value ) > 0 {
929+ params [key ] = make ([]string , len (value ))
930+ copy (params [key ], value )
931+ }
891932 }
892933 }
893- }
894-
895- // remote timestamp
896- if v , ok := c .GetNonDefaultParam (constant .TimestampKey ); ! ok {
897- params [constant .RemoteTimestampKey ] = []string {v }
898- params [constant .TimestampKey ] = []string {anotherUrl .GetParam (constant .TimestampKey , "" )}
899- }
900934
901- // finally execute methodConfigMergeFcn
902- mergedURL .Methods = make ([]string , len (anotherUrl .Methods ))
903- for i , method := range anotherUrl .Methods {
904- for _ , paramKey := range []string {constant .LoadbalanceKey , constant .ClusterKey , constant .RetriesKey , constant .TimeoutKey } {
905- if v := anotherUrl .GetParam (paramKey , "" ); len (v ) > 0 {
906- params [paramKey ] = []string {v }
907- }
935+ // remote timestamp
936+ if ! baseHasTimestamp {
937+ params [constant .RemoteTimestampKey ] = []string {baseTimestamp }
938+ params [constant .TimestampKey ] = []string {anotherUrl .params .Get (constant .TimestampKey )}
939+ }
908940
909- methodsKey := "methods." + method + "." + paramKey
910- // if len(mergedURL.GetParam(methodsKey, "")) == 0 {
911- if v := anotherUrl .GetParam (methodsKey , "" ); len (v ) > 0 {
912- params [methodsKey ] = []string {v }
941+ // finally execute methodConfigMergeFcn
942+ mergedURL .Methods = make ([]string , len (anotherUrl .Methods ))
943+ for i , method := range anotherUrl .Methods {
944+ for _ , paramKey := range []string {constant .LoadbalanceKey , constant .ClusterKey , constant .RetriesKey , constant .TimeoutKey } {
945+ if v := anotherUrl .params .Get (paramKey ); len (v ) > 0 {
946+ params [paramKey ] = []string {v }
947+ }
948+
949+ methodsKey := "methods." + method + "." + paramKey
950+ // if len(mergedURL.GetParam(methodsKey, "")) == 0 {
951+ if v := anotherUrl .params .Get (methodsKey ); len (v ) > 0 {
952+ params [methodsKey ] = []string {v }
953+ }
954+ // }
955+ mergedURL .Methods [i ] = method
913956 }
914- // }
915- mergedURL .Methods [i ] = method
916957 }
917- }
958+ }()
959+
918960 // merge attributes
919961 anotherUrl .RangeAttributes (func (attrK string , attrV any ) bool {
920962 if _ , ok := mergedURL .GetAttribute (attrK ); ! ok {
@@ -931,7 +973,27 @@ func (c *URL) MergeURL(anotherUrl *URL) *URL {
931973// excludeParams: the set of parameters to exclude from the cloned URL
932974// reserveParams: the set of parameters to retain in the cloned URL
933975func (c * URL ) CloneWithFilter (excludeParams * gxset.HashSet , reserveParams []string ) * URL {
934- newURL := & URL {
976+ newURL := c .newURLForClone ()
977+ newURL .params = c .copyFilteredParams (newURL .params , excludeParams , reserveParams )
978+
979+ // Copy attributes
980+ c .RangeAttributes (
981+ func (key string , value any ) bool {
982+ newURL .SetAttribute (key , value )
983+ return true
984+ },
985+ )
986+
987+ // Copy SubURL if it exists
988+ if c .SubURL != nil {
989+ newURL .SubURL = c .SubURL .Clone ()
990+ }
991+
992+ return newURL
993+ }
994+
995+ func (c * URL ) newURLForClone () * URL {
996+ return & URL {
935997 Protocol : c .Protocol ,
936998 Location : c .Location ,
937999 Ip : c .Ip ,
@@ -945,37 +1007,60 @@ func (c *URL) CloneWithFilter(excludeParams *gxset.HashSet, reserveParams []stri
9451007 attributes : make (map [string ]any ),
9461008 params : url.Values {},
9471009 }
1010+ }
9481011
949- // Copy and filter params based on excludeParams or reserveParams
950- c .RangeParams (
951- func ( key , value string ) bool {
952- // If the param is in excludeParams or not in reserveParams, skip it
953- if excludeParams != nil && excludeParams . Contains ( key ) {
954- return true
955- }
956- if len ( reserveParams ) > 0 && ! slices . Contains ( reserveParams , key ) {
957- return true
1012+ func ( c * URL ) copyFilteredParams ( params url. Values , excludeParams * gxset. HashSet , reserveParams [] string ) url. Values {
1013+ c .paramsLock . RLock ()
1014+ defer c . paramsLock . RUnlock ()
1015+
1016+ if excludeParams == nil && len ( reserveParams ) == 0 {
1017+ if len ( c . params ) > 8 {
1018+ copiedParams := copyURLValues ( c . params )
1019+ if copiedParams != nil {
1020+ return copiedParams
9581021 }
959- // Set the param if it passes the filter
960- newURL .SetParam (key , value )
961- return true
962- },
963- )
1022+ }
1023+ for key , values := range c .params {
1024+ copied := make ([]string , len (values ))
1025+ copy (copied , values )
1026+ params [key ] = copied
1027+ }
1028+ return params
1029+ }
9641030
965- // Copy attributes
966- c .RangeAttributes (
967- func (key string , value any ) bool {
968- newURL .SetAttribute (key , value )
969- return true
970- },
971- )
1031+ if capacity := paramsCapacityForClone (len (c .params ), excludeParams , reserveParams ); capacity > 8 {
1032+ params = make (url.Values , capacity )
1033+ }
9721034
973- // Copy SubURL if it exists
974- if c .SubURL != nil {
975- newURL .SubURL = c .SubURL .Clone ()
1035+ for key , values := range c .params {
1036+ if shouldCopyParam (key , excludeParams , reserveParams ) {
1037+ copied := make ([]string , len (values ))
1038+ copy (copied , values )
1039+ params [key ] = copied
1040+ }
9761041 }
9771042
978- return newURL
1043+ return params
1044+ }
1045+
1046+ func paramsCapacityForClone (paramsLen int , excludeParams * gxset.HashSet , reserveParams []string ) int {
1047+ if len (reserveParams ) > 0 && len (reserveParams ) < paramsLen {
1048+ paramsLen = len (reserveParams )
1049+ }
1050+ if excludeParams != nil {
1051+ paramsLen -= excludeParams .Size ()
1052+ }
1053+ if paramsLen < 0 {
1054+ return 0
1055+ }
1056+ return paramsLen
1057+ }
1058+
1059+ func shouldCopyParam (key string , excludeParams * gxset.HashSet , reserveParams []string ) bool {
1060+ if excludeParams != nil && excludeParams .Contains (key ) {
1061+ return false
1062+ }
1063+ return len (reserveParams ) == 0 || slices .Contains (reserveParams , key )
9791064}
9801065
9811066// Clone will copy the URL
0 commit comments