我和我的朋友写了下面的代码,以防止在CSV文件中注入代码。 (用java写的)
对于大型CSV文件(比如400列,10000行),代码在最坏的情况下需要大约15秒(所有列和行都不好)。任何人都可以帮我优化它。
3|wscontro | count repetidos 1 116
3|wscontro | count repetidos 2 undefined
最终优化代码,运行速度比上述速度快4倍。
public static String sanitizeInputForCSV(final String inputCSVRow) {
String outputCSVRow = inputCSVRow;
outputCSVRow = escapeMacroTriggersFromCSV(outputCSVRow, "=");
outputCSVRow = escapeMacroTriggersFromCSV(outputCSVRow, "-");
outputCSVRow = escapeMacroTriggersFromCSV(outputCSVRow, "+");
outputCSVRow = escapeMacroTriggersFromCSV(outputCSVRow, "@");
return outputCSVRow;
}
public static String escapeMacroTriggersFromCSV(String inputString, String characterToEscape) {
String outputString = inputString;
// To replace the first ocurrance
if (outputString.startsWith("\"" + characterToEscape)) {
outputString = "\"" + " " + outputString.substring(1, outputString.length());
} else if (outputString.startsWith(characterToEscape)) {
outputString = " " + outputString.substring(0, outputString.length());
}
// To replace subsequent ocurrance
outputString = outputString.replace(",\"" + characterToEscape, ",\"" + " " + characterToEscape);
outputString = outputString.replace("," + characterToEscape, "," + " " + characterToEscape);
return outputString;
}
答案 0 :(得分:1)
正如dpr所述,使用专用于CSV解析的库会更有利,因为它最有可能比我的解决方案更有效。不过,如果您想使用纯Java,我相信以下内容应该足够了:
public static String sanitizeInputForCSV(final String inputCSVRow) {
StringBuilder outputCSVRow = new StringBuilder(inputCSVRow);
escapeMacroTriggersFromCSV(outputCSVRow, '=', '-', '+', '@');
return outputCSVRow.toString();
}
public static void escapeMacroTriggersFromCSV(StringBuilder inputString, char... charactersToEscape) {
for (char c : charactersToEscape) {
// To replace the first ocurrance
if (inputString.charAt(0) == '\"') {
inputString.insert(inputString.charAt(1) == c ? 1 : 0, " ");
}
// To replace subsequent ocurrance
for (int i = 0; i < inputString.length(); i++) {
if (inputString.charAt(i) != c) {
continue;
}
if (inputString.charAt(i - 2) != ',' && inputString.charAt(i - 1) != ',') {
continue;
}
inputString.insert(i, " ");
}
}
}
我的解决方案不是创建大量String
个对象,而是使用StringBuilder
来节省内存,并且可能更有效地执行!
答案 1 :(得分:1)
听起来你想要替换任何=-+@
的出现,当它们在字符串开始时,第二次出现在双引号之后,或者在逗号或双引号之后。
这是以下模式:
private static final Pattern TRIGGER_PATTERN =
Pattern.compile("(^|^\"|,|,\")([-+=@])");
现在只做
String outputCSVRow = Pattern.matcher(inputCSVRow).replaceAll("$1 $2");
我对您的代码的理解可能是错误的,但原则保持不变。请注意,Pattern
简单快捷,replaceAll
可能会在15秒内处理一个千兆字节。
您也可以自己构建状态机:遍历输入字符串并将结果累积到StringBuilder
。你需要一次通过,它会非常快。
如果你只打电话给你的方法一次,那么同样好:
String outputCSVRow = inputCSVRow.replaceAll("(^|^\"|,|,\")([-+=@])", "$1 $2");
Regex有一些开销,你通常可以用一些手写代码做得更好。大多数时候,它不值得,但你的情况可能是一个例外。幸运的是,等效代码非常简单:
for (int i=0; i<in.length(); ++i) {
char c = in.characterAt(i);
switch (c) {
case '+':
case '-':
case '=':
case '@':
if (i == 0
|| in.characterAt(i-1) == ','
|| in.characterAt(i-1) == '"'
&& (i == 1 || in.characterAt(i-2) == ',')
) {
out.append(' ');
}
}
out.append(c);
}
一些有趣的状态机可能更简单,更快:
boolean hadComma1 = true; // was the last char a comma or are we at the start
boolean hadComma2 = true; // the char before
boolean hadQuote = false; // was the last char a double quote
for (int i=0; i<in.length(); ++i) {
char c = in.characterAt(i);
switch (c) {
case '+':
case '-':
case '=':
case '@':
if (hadComma1 || hadComma2 && hadQuote) {
out.append(' ');
}
}
out.append(c);
hadComma2 = hadComma1;
hadComma1 = c == ',';
hadQuote = c == '"';
}
这两个片段都是完全未经测试的。