当前位置:   article > 正文

JAVA 字符串中 {} 快速替换需要的内容_java 根据{} 替换内容

java 根据{} 替换内容

基于slf4j中打印日志的方法比较好用,提取出来用于别处

背景

不管是单体应用还是海量应用,都是从小应用开始慢慢演进。当应用逐渐变大,业务逻辑变得更加复杂之后,系统开始变得不稳定,这时候你需要将系统拆分,或者重构。但改造的成本是相对高昂的,这时候你需要考虑的是人力、物力、时间等等。如果系统还没有到无可救药的地步(还能跑),简单的查错打印日志是再好不过的了。打印日志伴随着字符串拼接的效率问题,一行代码打印System.out.print()对系统来说轻而易举,但十万到百万甚至千万的字符替换就需要考虑效率更高的替换方式。

简介

如果实际写过企业级应用的人都知道,打印日志都是为了方便我们排查问题。从简单的log各种级别的output,到海量日志中通过requestIdtraceId去跟踪调用链路。
这些日志的拼接方式,都要通过字符串截取、拼接、再组合来实现。
Java语言中,常用的日志系统有log4jlogbackjul等。其中为了达到日志系统实现与应用快速替换的方式,采用门面模式和桥接模式实现的slf4j,都需要面对快速的字符串处理。
该篇文章主要简述slf4jlog4j2结合在处理带{}字符串的替换过程。

打印

这里是基于Maven项目来演示,首先我们需要写一串简单的代码,并将log4j.properties文件写入resource中。
pom.xml文件的dependencies

  1. <dependency>
  2. <groupId>org.slf4j</groupId>
  3. <artifactId>slf4j-log4j12</artifactId>
  4. <version>1.7.30</version>
  5. </dependency>

简单打印:

  1. import lombok.extern.slf4j.Slf4j;
  2. @Slf4j
  3. public class Slf4jTest {
  4. public static void main(String[] args) {
  5. // 这里使用 lombok 插件的 slf4j 注解
  6. // log 源码在 org.slf4j.Logger
  7. // 实际调用为 org.slf4j.impl.Log4jLoggerAdapter ,它实现了一个 wrapper,去包装实现 log4j org.apache.log4j.Logger
  8. log.info("qqq{}ppp", 123);
  9. // 打印结果:
  10. [INFO ] 2020-06-10 22:56:50,872 method:com.test.slf4j.Slf4jTest.main(Slf4jTest.java:9)qqq123ppp
  11. }
  12. }

log4j.properties的配置 

  1. log4j.rootLogger=info,stdout # info 级别的日志,stdout 标志符的打印追加器(appender)生效
  2. log4j.appender.stdout= org.apache.log4j.ConsoleAppender
  3. log4j.appender.stdout.Target= System.out
  4. log4j.appender.stdout.layout= org.apache.log4j.PatternLayout
  5. log4j.appender.stdout.layout.ConversionPattern= [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%m%n

我们如果debug代码的话,会发现在调用slf4j代码的时候,程序已经拼接好了字符串,它的调用链路是

  1. org.slf4j.impl.Log4jLoggerAdapter#info(java.lang.String, java.lang.Object)
  2. -> org.slf4j.helpers.MessageFormatter#format(java.lang.String, java.lang.Object)
  3. -> org.slf4j.helpers.MessageFormatter#arrayFormat(java.lang.String, java.lang.Object[])
  4. -> org.slf4j.helpers.MessageFormatter#arrayFormat(java.lang.String, java.lang.Object[], java.lang.Throwable)

在这里最关键的代码是MessageFormatter,它实现了一套基于StringBuilder和字符串动态索引的算法,代码如下:

  1. int i = 0;
  2. int j;
  3. // use string builder for better multicore performance
  4. // StringBuilder 来提高多核性能,无锁,非线程安全,并提前预支了 50 个字符位
  5. StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
  6. int L;
  7. // argArray 是输入替换的 Object 数组,这里是 new Object[]{ 123 }
  8. for (L = 0; L < argArray.length; L++) {
  9. // DELIM_STR == {} 字符
  10. j = messagePattern.indexOf(DELIM_STR, i);
  11. // 无 {}
  12. if (j == -1) {
  13. // no more variables
  14. if (i == 0) { // this is a simple string
  15. // 直接返回
  16. return new FormattingTuple(messagePattern, argArray, throwable);
  17. } else { // add the tail string which contains no variables and return
  18. // the result.
  19. // 还需要截断下字符
  20. sbuf.append(messagePattern, i, messagePattern.length());
  21. return new FormattingTuple(sbuf.toString(), argArray, throwable);
  22. }
  23. } else {
  24. // 不是转义字符,类似这种:\{}
  25. if (isEscapedDelimeter(messagePattern, j)) {
  26. if (!isDoubleEscaped(messagePattern, j)) {
  27. L--; // DELIM_START was escaped, thus should not be incremented
  28. sbuf.append(messagePattern, i, j - 1);
  29. sbuf.append(DELIM_START);
  30. i = j + 1;
  31. } else {
  32. // The escape character preceding the delimiter start is
  33. // itself escaped: "abc x:\\{}"
  34. // we have to consume one backward slash
  35. sbuf.append(messagePattern, i, j - 1);
  36. deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
  37. i = j + 2;
  38. }
  39. } else {
  40. // normal case
  41. sbuf.append(messagePattern, i, j);
  42. // 其中附带数组写入,如 boolean[],char[]等,argArray[L] 传入的即使是基本类型,也可以通过自动装配,实现对象类型的转换
  43. deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
  44. i = j + 2;
  45. }
  46. }
  47. }
  48. // append the characters following the last {} pair.
  49. sbuf.append(messagePattern, i, messagePattern.length());

扩展

如果要在项目中对大量字符进行替换拆分,其实可以复用MessageFormatter这个类。经过了无数次考验,稳定性和效率那必定是很高的了。
所以基于MessageFormatter重新复写了一个简版的MessageFormatter只返回字符串:

 

  1. public class MessageFormatter {
  2. private static final char DELIM_START = '{';
  3. private static final String DELIM_STR = "{}";
  4. private static final char ESCAPE_CHAR = '\\';
  5. public static String format(final String messagePattern, final Object... argArray) {
  6. int i = 0;
  7. int j;
  8. // use string builder for better multicore performance
  9. StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
  10. int L;
  11. for (L = 0; L < argArray.length; L++) {
  12. j = messagePattern.indexOf(DELIM_STR, i);
  13. if (j == -1) {
  14. // no more variables
  15. if (i == 0) { // this is a simple string
  16. return messagePattern;
  17. } else { // add the tail string which contains no variables and return
  18. // the result.
  19. sbuf.append(messagePattern, i, messagePattern.length());
  20. return sbuf.toString();
  21. }
  22. } else {
  23. if (isEscapedDelimeter(messagePattern, j)) {
  24. if (!isDoubleEscaped(messagePattern, j)) {
  25. L--; // DELIM_START was escaped, thus should not be incremented
  26. sbuf.append(messagePattern, i, j - 1);
  27. sbuf.append(DELIM_START);
  28. i = j + 1;
  29. } else {
  30. // The escape character preceding the delimiter start is
  31. // itself escaped: "abc x:\\{}"
  32. // we have to consume one backward slash
  33. sbuf.append(messagePattern, i, j - 1);
  34. deeplyAppendParameter(sbuf, argArray[L], new HashMap<>());
  35. i = j + 2;
  36. }
  37. } else {
  38. // normal case
  39. sbuf.append(messagePattern, i, j);
  40. deeplyAppendParameter(sbuf, argArray[L], new HashMap<>());
  41. i = j + 2;
  42. }
  43. }
  44. }
  45. // append the characters following the last {} pair.
  46. sbuf.append(messagePattern, i, messagePattern.length());
  47. return sbuf.toString();
  48. }
  49. private static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) {
  50. return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;
  51. }
  52. private static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) {
  53. if (delimeterStartIndex == 0) {
  54. return false;
  55. }
  56. char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1);
  57. return potentialEscape == ESCAPE_CHAR;
  58. }
  59. // special treatment of array values was suggested by 'lizongbo'
  60. private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map<Object[], Object> seenMap) {
  61. if (o == null) {
  62. sbuf.append("null");
  63. return;
  64. }
  65. if (!o.getClass().isArray()) {
  66. safeObjectAppend(sbuf, o);
  67. } else {
  68. // check for primitive array types because they
  69. // unfortunately cannot be cast to Object[]
  70. if (o instanceof boolean[]) {
  71. booleanArrayAppend(sbuf, (boolean[]) o);
  72. } else if (o instanceof byte[]) {
  73. byteArrayAppend(sbuf, (byte[]) o);
  74. } else if (o instanceof char[]) {
  75. charArrayAppend(sbuf, (char[]) o);
  76. } else if (o instanceof short[]) {
  77. shortArrayAppend(sbuf, (short[]) o);
  78. } else if (o instanceof int[]) {
  79. intArrayAppend(sbuf, (int[]) o);
  80. } else if (o instanceof long[]) {
  81. longArrayAppend(sbuf, (long[]) o);
  82. } else if (o instanceof float[]) {
  83. floatArrayAppend(sbuf, (float[]) o);
  84. } else if (o instanceof double[]) {
  85. doubleArrayAppend(sbuf, (double[]) o);
  86. } else {
  87. objectArrayAppend(sbuf, (Object[]) o, seenMap);
  88. }
  89. }
  90. }
  91. private static void safeObjectAppend(StringBuilder sbuf, Object o) {
  92. try {
  93. String oAsString = o.toString();
  94. sbuf.append(oAsString);
  95. } catch (Throwable t) {
  96. Util.report("SLF4J: Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t);
  97. sbuf.append("[FAILED toString()]");
  98. }
  99. }
  100. private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map<Object[], Object> seenMap) {
  101. sbuf.append('[');
  102. if (!seenMap.containsKey(a)) {
  103. seenMap.put(a, null);
  104. final int len = a.length;
  105. for (int i = 0; i < len; i++) {
  106. deeplyAppendParameter(sbuf, a[i], seenMap);
  107. if (i != len - 1)
  108. sbuf.append(", ");
  109. }
  110. // allow repeats in siblings
  111. seenMap.remove(a);
  112. } else {
  113. sbuf.append("...");
  114. }
  115. sbuf.append(']');
  116. }
  117. private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
  118. sbuf.append('[');
  119. final int len = a.length;
  120. for (int i = 0; i < len; i++) {
  121. sbuf.append(a[i]);
  122. if (i != len - 1)
  123. sbuf.append(", ");
  124. }
  125. sbuf.append(']');
  126. }
  127. private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
  128. sbuf.append('[');
  129. final int len = a.length;
  130. for (int i = 0; i < len; i++) {
  131. sbuf.append(a[i]);
  132. if (i != len - 1)
  133. sbuf.append(", ");
  134. }
  135. sbuf.append(']');
  136. }
  137. private static void charArrayAppend(StringBuilder sbuf, char[] a) {
  138. sbuf.append('[');
  139. final int len = a.length;
  140. for (int i = 0; i < len; i++) {
  141. sbuf.append(a[i]);
  142. if (i != len - 1)
  143. sbuf.append(", ");
  144. }
  145. sbuf.append(']');
  146. }
  147. private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
  148. sbuf.append('[');
  149. final int len = a.length;
  150. for (int i = 0; i < len; i++) {
  151. sbuf.append(a[i]);
  152. if (i != len - 1)
  153. sbuf.append(", ");
  154. }
  155. sbuf.append(']');
  156. }
  157. private static void intArrayAppend(StringBuilder sbuf, int[] a) {
  158. sbuf.append('[');
  159. final int len = a.length;
  160. for (int i = 0; i < len; i++) {
  161. sbuf.append(a[i]);
  162. if (i != len - 1)
  163. sbuf.append(", ");
  164. }
  165. sbuf.append(']');
  166. }
  167. private static void longArrayAppend(StringBuilder sbuf, long[] a) {
  168. sbuf.append('[');
  169. final int len = a.length;
  170. for (int i = 0; i < len; i++) {
  171. sbuf.append(a[i]);
  172. if (i != len - 1)
  173. sbuf.append(", ");
  174. }
  175. sbuf.append(']');
  176. }
  177. private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
  178. sbuf.append('[');
  179. final int len = a.length;
  180. for (int i = 0; i < len; i++) {
  181. sbuf.append(a[i]);
  182. if (i != len - 1)
  183. sbuf.append(", ");
  184. }
  185. sbuf.append(']');
  186. }
  187. private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
  188. sbuf.append('[');
  189. final int len = a.length;
  190. for (int i = 0; i < len; i++) {
  191. sbuf.append(a[i]);
  192. if (i != len - 1)
  193. sbuf.append(", ");
  194. }
  195. sbuf.append(']');
  196. }
  197. }

测试结果:

  1. System.out.println(MessageFormatter.format("qqq{}ppp{}end", 123, 321));
  2. 打印结果:qqq123ppp321end
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/829803
推荐阅读
相关标签
  

闽ICP备14008679号