Java 在特殊的非空格字符上拆分

Java 在特殊的非空格字符上拆分,java,scala,Java,Scala,我正在处理如下所示的日志文件: 98.87.115.89 - - [12/Nov/2014:05:21:26 -0500] "GET /no_cache/bi_page?Log=1&pg_inst=600474500174606089&pg=mdot_fyc_pnt&platform=mdot&ver=10.c110&pid=157876860906745096&rid=157876731027276387&srch_id=-2&r

我正在处理如下所示的日志文件:

98.87.115.89 - - [12/Nov/2014:05:21:26 -0500] "GET /no_cache/bi_page?Log=1&pg_inst=600474500174606089&pg=mdot_fyc_pnt&platform=mdot&ver=10.c110&pid=157876860906745096&rid=157876731027276387&srch_id=-2&row=7&seq=1&tot=1&tsp=1&test_name=m_control&logDomain=http%3A%2F%2Fwww.xyz.com&ref_url=http%3A%2F%2Fm.xyz.com%2F&z=44134 HTTP/1.1" 200 43 "http://m.xyz.com/" "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; SPH-L720 Build/KOT49H) AppleWebKit/537.16 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.16" "98.87.115.89.1415786359690989" web79011
数据看起来像是用空格分隔的,但实际上要复杂得多,因为GET后面和最后一行都有空格,例如Mobile和Safari之间,尽管这两个词都是同一元素的一部分

当我将其粘贴到Excel并在空格上运行TextToColumns时(我不确定我的浏览器是否将此特殊字符转换为普通空格,因此您必须在这方面信任我),我得到以下完美分割:

98.87.115.89|-|-|[12/Nov/2014:05:21:26 -0500]|"GET /no_cache/bi_page?Log=1&pg_inst=600474500174606089&pg=mdot_fyc_pnt&platform=mdot&ver=10.c110&pid=157876860906745096&rid=157876731027276387&srch_id=-2&row=7&seq=1&tot=1&tsp=1&test_name=m_control&logDomain=http%3A%2F%2Fwww.xyz.com&ref_url=http%3A%2F%2Fm.xyz.com%2F&z=44134 HTTP/1.1"|200|43|"http://m.xyz.com/"|"Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; SPH-L720 Build/KOT49H) AppleWebKit/537.16 (KHTML, like Gecko) Version/4.0 Mobile Safari/537.16" "98.87.115.89.1415786359690989"|web79011
请注意,GET和Mobile之后的空格字符不会作为分隔符。这意味着使用了其他一些空白字符

但是,当我将文本粘贴到Scala(这里的Java答案也可以)并运行.split(“”)时,它使用常规空格将所有空白视为一个空格,这会导致很多问题


如何确定使用的是什么特殊字符,以及如何仅拆分空格而不拆分特殊字符?

Excel的数据导入解析器足够聪明,可以跳过引号之间的空格。

我认为最好的选择是使用正则表达式来完成此操作。 以下是我发现有用的参考链接:

根据您的示例字符串,这可能是一种尝试模式

import scala.util.matching.Regex

 [...]
val str = [... your string to be matched ...]
val pattern1 = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(?:.*)(\\[.*\])(?:.*?)(\".+?\")(?:.*?)(\\d+)(?:\\s)(\\d+)(?:\\s)(\".+?\")(?:.*?)(\".+?\")(?:.*?)(\".+?\")(.*)".r
特别是:

(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})  -> matches the IP address
(\\[.*\])                                    -> matches the date and time
(?:.*?)                                      -> matches the bare minimum number of
                                                characters between surrounding patterns
                                                groups
(\".+?\")                                    -> matches the parts between quotes
当然,上面的模式具有非常简单的结构,您可以通过使用重复标记和更仔细地选择一些组来改进它,但它应该能够完成您提供的样本的工作

有了合适的模式,你可以

val newstring = (pattern findAllIn str).mkString("|")
请注意,我是背诵上述内容的,因为我目前没有机会在scala中检查代码,但我希望它能提示您找到一个完全有效的解决方案

编辑:

我突然想到,可能您最后需要的不是让字符串以“|”分隔,而是将所有匹配项作为变量访问。在scala中,您可以根据模式进行匹配,并轻松实现这一点:

val pattern(ip, date, getString, p1, p2, q1, q2, q3) = str

将在
ip
中存储第一组的匹配项,在
date
中存储第二组的匹配项,依此类推。括号内的所有参数都是可用于访问组匹配内容的变量。注意那些将是字符串,因此您可能需要为数字强制转换正确的类型。

有几种方法来表示空白,因为Unicode引入了一些新的方法

我建议使用

\s+ //(normal whitespcaes, pre unicode)

使用正则表达式

你可以反过来想你需要什么,然后在否定处分开,即每个非whitspace字符,可以用正则表达式表示为

[^\w] or [\W]

不幸的是,这比String.split更复杂,因为您想跳过双引号内的空格。 您可能需要使用许多标准解析器,例如apache。或者,如果您不关心诸如双引号字段中的转义双引号之类的极端情况,那么类似的方法可能会起作用(我想不出用惯用的scala写这个的方法……我想看看是否有人想出了一个):

StringTokenizer令牌=新的StringTokenizer(inputString,\”,true);
列表字段=新的ArrayList(tokens.length);
布尔inquotes=false;
while(tokens.hasMoreTokens()){
字符串tok=tokens.nextToken();
如果(tok==“\”){
inquotes=!inquotes;
继续;
}
如果(tok==“”&&!引号)继续;
字段。添加(tok)
}
字符串结果[]=fields.toArray(新字符串[fields.size()]);
[^\w] or [\W]
StringTokenizer tokens = new StringTokenizer(inputString, " \"", true);
List<String> fields = new ArrayList<String>(tokens.length);
boolean inquotes = false;
while(tokens.hasMoreTokens()) {
    String tok = tokens.nextToken();   
    if(tok == "\"") {
        inquotes = !inquotes;
        continue;
    }
    if(tok == " " && !inquotes) continue;
    fields.add(tok)
}
String result[] = fields.toArray(new String[fields.size()]);