String.replace和相同的方法调用mutliple次

时间:2017-07-21 11:49:52

标签: java string csv security optimization

我和我的朋友写了下面的代码,以防止在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;

}

2 个答案:

答案 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 == '"';
}

这两个片段都是完全未经测试的。