Guava 拆分器到键值映射,字符串中包含拆分器字符

Guava Splitter to key value map with splitter character included in strings

我正在尝试使用 Guava 拆分器解析日志文件。日志文件如下所示:

appName=XXX clientIp=X.X.X timestamp="2017-06-05T13:22:12-07:00" request="POST /forward HTTP/1.1" statusCode=204 bytesOut=1167 totalTime=0.062 bytesIn=1289 sourceHost=XXXX connId=49936598 connReqs=9 upInstance=XXX:104:XXX-XXX:8664:17F34 upConnectSec=0.052 upAddr="XX.XX.XX:123" upHost="vcv08it-cvcv2801:8464" upHdrTimeSec=0.058 upRespTimeSec=0.058 pid=32561  upStatusCode=204 message="Access Log" corrKey=GMIFCDIKRZR2T4VZQXJA2IT6 upCached=- length=0 partition=XXX location="= /v1/tXXXX" xff="XX.XX.XX.XX" referer="-" user-agent="Apache-HttpAsyncClient/4.1.1 (Java/1.8.0_131)\" rateLimitCurrentValues="--" rateLimitTimeMs=\"-:-"

我用这段代码来解析它:

Map<String, String> parserMap;
parserMap = Splitter.onPattern("\s(?=([^\\"]*\\"[^\\"]*\\")*[^\\"]*$)")
.omitEmptyStrings()
.withKeyValueSeparator(Splitter.onPattern("="))
.split(line);

我的问题是 location="= /v1/tXXXX" 字段,该字段在字符串中包含“=”,而当前的 withKeyValueSeperator 无法解析它。您能帮我如何更改模式以正确获取所有字段吗?

不确定 Guava 拆分器的工作原理,但如果您使用常规 PatternMatcher 类,您可以使用下面的正则表达式来捕获您的键和值:

([\w-]+?)=(".*?"|\S+)

Regex demo

Java代码

String text = "your string";
Pattern pattern = Pattern.compile("([\w-]+?)=(\".*?\"|\S+)");
Matcher m = pattern.matcher(text);
Map<String, String> parserMap = new HashMap<>();

while (m.find()) {
    String key = m.group(1);
    String value = m.group(2);
    parserMap.put(key, value);
}

已在此处准备了一个 IdeOne java 工作演示:

https://ideone.com/y8b8di

您可以在下面看到匹配信息的示例

Match 1
    Group 1.    0-7     `appName`
    Group 2.    8-11    `XXX`

Match 2
    Group 1.    12-20   `clientIp`
    Group 2.    21-26   `X.X.X`

Match 3
    Group 1.    27-36   `timestamp`
    Group 2.    37-64   `"2017-06-05T13:22:12-07:00"`

Match 4
    Group 1.    65-72   `request`
    Group 2.    73-97   `"POST /forward HTTP/1.1"`

我不确定答案是否可以用一个正则表达式来完成,但可以相对轻松地制定一个可行的解决方案:

parserMap = Splitter.onPattern("\s(?=([^\\"]*\\"[^\\"]*\\")*[^\\"]*$)")
    .omitEmptyStrings()
    .splitToList(line)
    .stream()
    .collect(Collectors.toMap(
        s -> s.split("=", 2)[0],  // the first part of split gets the key
        s -> s.split("=", 2)[1]   // everything else is the value
    )
);

尝试对 split 使用正则表达式的问题在于拆分的内在目标只是找到分隔符。这与正常的正则表达式用法不同,在常规正则表达式用法中,您可以使用组来 select 您想要的东西;当你分裂时,你试图匹配你想要的东西,这变得非常混乱。

Exception java.lang.IllegalArgumentException: Chunk [location="= /v1/tXXXX"] is not a valid entry 从您的代码中抛出,因为 keyValueSeparator 在块中出现不止一次。您可以调整 keyValueSeparator 以便仅匹配等号后跟您的值模式。例如:

final String keyPattern = "\S+";
final String valuePattern = "(\S+|\"[^\"]*\")";
parserMap = Splitter.onPattern("\s(?=" + keyPattern + "=" + valuePattern + ")")
        .omitEmptyStrings()
        .withKeyValueSeparator(Splitter.onPattern("=(?=" + valuePattern + ")"))
        .split(line);

请注意,如果您的行中有类似 key="key=value" 的内容,这将不起作用。

withKeyValueSeparator 拆分器上使用 limit

Splitter.onPattern("\s(?=([^\\"]*\\"[^\\"]*\\")*[^\\"]*$)")
    .omitEmptyStrings()
    .withKeyValueSeparator(Splitter.on("=").limit(2).trimResults())
    .split(line);

参见GitHub问题:https://github.com/google/guava/issues/1900