88import java .util .*;
99import java .util .concurrent .ConcurrentHashMap ;
1010import java .util .logging .Level ;
11- import java .util .regex .Matcher ;
1211
1312/**
1413@@ -296,17 +295,98 @@ public static void error(String format, Object... arguments) {
296295 log (LogLevel .ERROR , ErrorCode .NO_CODE , format , arguments );
297296 }
298297
299- private static String format (String from , Object ... arguments ) {
300- if (from != null ) {
301- String computed = from ;
302- if (arguments != null && arguments .length != 0 ) {
303- for (Object argument : arguments ) {
304- computed = computed .replaceFirst ("\\ {\\ }" , argument == null ? "NULL" : Matcher .quoteReplacement (argument .toString ()));
305- }
298+ /**
299+ * 格式化字符串
300+ *
301+ * @param pattern 用来格式化的pattern,比如 "my name is {}, today is {}"
302+ * @param arguments 用来填充上面的pattern的参数,比如 []String{"CC11001100", "2023-7-12"}
303+ * @return 格式化之后的字符串,比如 "my name is CC11001100, today is 2023-7-12"
304+ */
305+ static String format (String pattern , Object ... arguments ) {
306+ // if (from != null) {
307+ // String computed = from;
308+ // if (arguments != null && arguments.length != 0) {
309+ // for (Object argument : arguments) {
310+ // computed = computed.replaceFirst("\\{\\}", argument == null ? "NULL" : Matcher.quoteReplacement(argument.toString()));
311+ // }
312+ // }
313+ // return computed;
314+ // }
315+ // return null;
316+
317+ // 如果没有参数格式化的话快速退出
318+ if (arguments .length == 0 || pattern == null ) {
319+ return pattern ;
320+ }
321+
322+ // 先把参数都转为字符串,并且预估参数的字符长度
323+ int argumentCharCount = 0 ;
324+ String [] argumentStrings = new String [arguments .length ];
325+ for (int i = 0 ; i < arguments .length ; i ++) {
326+ argumentStrings [i ] = arguments [i ] == null ? "NULL" : arguments [i ].toString ();
327+ argumentCharCount += argumentStrings [i ].length ();
328+ }
329+
330+ // 现在,就能算出结果的长度避免扩容了,这里没有使用减去 参数长度乘以2的占位符长度 是因为pattern中占位符的实际个数可能并不足以消费完参数
331+ // (因为并没有强制pattern里的占位符与参数一一对应,所以它们的个数是很随意的),这种情况下如果做乐观假设的话可能会导致特殊情况下扩容,
332+ // 所以此处采取比较保守的策略,宁愿在特殊情况下浪费几个字节的空间也不愿在特殊情况下扩容
333+ // 举一个具体的例子,比如传入的pattern: "a{}c", 传入的参数 String[]{"b", "d", .. , "z"},参数可能有几十个,远超占位符的个数,则此时会被减去错误的长度,甚至导致长度为负
334+ // StringBuilder buff = new StringBuilder(pattern.length() - argumentStrings.length * 2 + argumentCharCount);
335+ StringBuilder buff = new StringBuilder (pattern .length () + argumentCharCount );
336+ // 下一个被消费的参数的下标
337+ int argumentConsumeIndex = 0 ;
338+ for (int i = 0 ; i < pattern .length (); i ++) {
339+ char c = pattern .charAt (i );
340+ switch (c ) {
341+ case '\\' :
342+ // 如果是转义字符的话,则看下它在转义个啥
343+ if (i + 1 < pattern .length ()) {
344+ // 非最后一个字符,则根据不同的转义保留不同的字符,并且一次消费两个字符
345+ char nextChar = pattern .charAt (++i );
346+ switch (nextChar ) {
347+ case '{' :
348+ case '}' :
349+ // 只保留被转义的左花括号或者右花括号,转义字符本身将被丢弃
350+ buff .append (nextChar );
351+ break ;
352+ default :
353+ // 转移的是其它字符,则原样保持转义,相当于是在通用转义的基础上扩展了对左右花括号的转义
354+ buff .append (c ).append (nextChar );
355+ break ;
356+ }
357+ } else {
358+ // 最后一个字符了,原样保留
359+ buff .append (c );
360+ }
361+ break ;
362+ case '{' :
363+ // 如果是左括号的话,则看一下是否是个占位符
364+ if (i + 1 < pattern .length () && pattern .charAt (i + 1 ) == '}' ) {
365+ // 一次消费两个字符,把占位符 {} 消费掉
366+ i ++;
367+ // 使用参数替换
368+ buff .append (argumentStrings [argumentConsumeIndex ++]);
369+ // 实际传递的参数个数和pattern中的占位符数量可能会不匹配,参数更多的时候没问题多余的将被忽略
370+ // 参数更少的时候继续也没啥意义了,所以如果参数被消费完了则快速退出
371+ if (argumentConsumeIndex == argumentStrings .length ) {
372+ // 把 } 后边的一股脑儿消费了,如果有的话
373+ if (i + 1 < pattern .length ()) {
374+ buff .append (pattern .substring (i + 1 , pattern .length ()));
375+ }
376+ return buff .toString ();
377+ }
378+ } else {
379+ // 最后一个字符或者不是匹配的右花括号,原样保留
380+ buff .append (c );
381+ }
382+ break ;
383+ default :
384+ // 普通字符,原样复制
385+ buff .append (c );
386+ break ;
306387 }
307- return computed ;
308388 }
309- return null ;
389+ return buff . toString () ;
310390 }
311391
312392 private static String getTime () {
@@ -315,6 +395,7 @@ private static String getTime() {
315395 return simpleDateFormat .format (new Date ()) + " " ;
316396 }
317397
398+ // TODO 2023-7-12 12:00:06 写日志文件效率问题
318399 private static void writeLogToFile (String msg , Throwable t ) {
319400 if (LOG_PATH == null || LOG_PATH .isEmpty ()) {
320401 return ;
0 commit comments