Как освободить строковый литерал Java в Java?
Я обрабатываю исходный код Java с использованием Java. Я извлекаю строковые литералы и передаю их функции, принимающей строку. Проблема в том, что мне нужно передать неэкранированную версию String в функцию (т.е. это означает преобразование \n
на новую строку и \\
в одиночку \
, так далее).
Есть ли внутри Java API функция, которая делает это? Если нет, могу ли я получить такую функциональность из какой-нибудь библиотеки? Очевидно, компилятор Java должен сделать это преобразование.
В случае, если кто-то хочет знать, я пытаюсь не запутывать строковые литералы в декомпилированных обфусцированных файлах Java.
11 ответов
Эта проблема
org.apache.commons.lang.StringEscapeUtils.unescapeJava()
приведенный здесь в качестве другого ответа на самом деле очень мало помощи вообще.
- Забывает о
\0
для нуля. - Это не обрабатывает восьмеричное вообще.
- Он не может справиться с побегами, допущенными
java.util.regex.Pattern.compile()
и все, что его использует, в том числе\a
,\e
, и особенно\cX
, - Он не поддерживает логические кодовые точки Unicode по номерам, только для UTF-16.
- Это похоже на код UCS-2, а не код UTF-16: они используют устаревший
charAt
интерфейс вместоcodePoint
интерфейс, таким образом провозглашая заблуждение, что Javachar
гарантированно содержит символ Unicode. Это не. Им это сходит с рук только потому, что ни один суррогат UTF-16 не найдет то, что ищет.
Решение
Я написал строку unescaper, которая решает вопрос OP без всяких раздражений кода Apache.
/*
*
* unescape_perl_string()
*
* Tom Christiansen <tchrist@perl.com>
* Sun Nov 28 12:55:24 MST 2010
*
* It's completely ridiculous that there's no standard
* unescape_java_string function. Since I have to do the
* damn thing myself, I might as well make it halfway useful
* by supporting things Java was too stupid to consider in
* strings:
*
* => "?" items are additions to Java string escapes
* but normal in Java regexes
*
* => "!" items are also additions to Java regex escapes
*
* Standard singletons: ?\a ?\e \f \n \r \t
*
* NB: \b is unsupported as backspace so it can pass-through
* to the regex translator untouched; I refuse to make anyone
* doublebackslash it as doublebackslashing is a Java idiocy
* I desperately wish would die out. There are plenty of
* other ways to write it:
*
* \cH, \12, \012, \x08 \x{8}, \u0008, \U00000008
*
* Octal escapes: \0 \0N \0NN \N \NN \NNN
* Can range up to !\777 not \377
*
* TODO: add !\o{NNNNN}
* last Unicode is 4177777
* maxint is 37777777777
*
* Control chars: ?\cX
* Means: ord(X) ^ ord('@')
*
* Old hex escapes: \xXX
* unbraced must be 2 xdigits
*
* Perl hex escapes: !\x{XXX} braced may be 1-8 xdigits
* NB: proper Unicode never needs more than 6, as highest
* valid codepoint is 0x10FFFF, not maxint 0xFFFFFFFF
*
* Lame Java escape: \[IDIOT JAVA PREPROCESSOR]uXXXX must be
* exactly 4 xdigits;
*
* I can't write XXXX in this comment where it belongs
* because the damned Java Preprocessor can't mind its
* own business. Idiots!
*
* Lame Python escape: !\UXXXXXXXX must be exactly 8 xdigits
*
* TODO: Perl translation escapes: \Q \U \L \E \[IDIOT JAVA PREPROCESSOR]u \l
* These are not so important to cover if you're passing the
* result to Pattern.compile(), since it handles them for you
* further downstream. Hm, what about \[IDIOT JAVA PREPROCESSOR]u?
*
*/
public final static
String unescape_perl_string(String oldstr) {
/*
* In contrast to fixing Java's broken regex charclasses,
* this one need be no bigger, as unescaping shrinks the string
* here, where in the other one, it grows it.
*/
StringBuffer newstr = new StringBuffer(oldstr.length());
boolean saw_backslash = false;
for (int i = 0; i < oldstr.length(); i++) {
int cp = oldstr.codePointAt(i);
if (oldstr.codePointAt(i) > Character.MAX_VALUE) {
i++; /****WE HATES UTF-16! WE HATES IT FOREVERSES!!!****/
}
if (!saw_backslash) {
if (cp == '\\') {
saw_backslash = true;
} else {
newstr.append(Character.toChars(cp));
}
continue; /* switch */
}
if (cp == '\\') {
saw_backslash = false;
newstr.append('\\');
newstr.append('\\');
continue; /* switch */
}
switch (cp) {
case 'r': newstr.append('\r');
break; /* switch */
case 'n': newstr.append('\n');
break; /* switch */
case 'f': newstr.append('\f');
break; /* switch */
/* PASS a \b THROUGH!! */
case 'b': newstr.append("\\b");
break; /* switch */
case 't': newstr.append('\t');
break; /* switch */
case 'a': newstr.append('\007');
break; /* switch */
case 'e': newstr.append('\033');
break; /* switch */
/*
* A "control" character is what you get when you xor its
* codepoint with '@'==64. This only makes sense for ASCII,
* and may not yield a "control" character after all.
*
* Strange but true: "\c{" is ";", "\c}" is "=", etc.
*/
case 'c': {
if (++i == oldstr.length()) { die("trailing \\c"); }
cp = oldstr.codePointAt(i);
/*
* don't need to grok surrogates, as next line blows them up
*/
if (cp > 0x7f) { die("expected ASCII after \\c"); }
newstr.append(Character.toChars(cp ^ 64));
break; /* switch */
}
case '8':
case '9': die("illegal octal digit");
/* NOTREACHED */
/*
* may be 0 to 2 octal digits following this one
* so back up one for fallthrough to next case;
* unread this digit and fall through to next case.
*/
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7': --i;
/* FALLTHROUGH */
/*
* Can have 0, 1, or 2 octal digits following a 0
* this permits larger values than octal 377, up to
* octal 777.
*/
case '0': {
if (i+1 == oldstr.length()) {
/* found \0 at end of string */
newstr.append(Character.toChars(0));
break; /* switch */
}
i++;
int digits = 0;
int j;
for (j = 0; j <= 2; j++) {
if (i+j == oldstr.length()) {
break; /* for */
}
/* safe because will unread surrogate */
int ch = oldstr.charAt(i+j);
if (ch < '0' || ch > '7') {
break; /* for */
}
digits++;
}
if (digits == 0) {
--i;
newstr.append('\0');
break; /* switch */
}
int value = 0;
try {
value = Integer.parseInt(
oldstr.substring(i, i+digits), 8);
} catch (NumberFormatException nfe) {
die("invalid octal value for \\0 escape");
}
newstr.append(Character.toChars(value));
i += digits-1;
break; /* switch */
} /* end case '0' */
case 'x': {
if (i+2 > oldstr.length()) {
die("string too short for \\x escape");
}
i++;
boolean saw_brace = false;
if (oldstr.charAt(i) == '{') {
/* ^^^^^^ ok to ignore surrogates here */
i++;
saw_brace = true;
}
int j;
for (j = 0; j < 8; j++) {
if (!saw_brace && j == 2) {
break; /* for */
}
/*
* ASCII test also catches surrogates
*/
int ch = oldstr.charAt(i+j);
if (ch > 127) {
die("illegal non-ASCII hex digit in \\x escape");
}
if (saw_brace && ch == '}') { break; /* for */ }
if (! ( (ch >= '0' && ch <= '9')
||
(ch >= 'a' && ch <= 'f')
||
(ch >= 'A' && ch <= 'F')
)
)
{
die(String.format(
"illegal hex digit #%d '%c' in \\x", ch, ch));
}
}
if (j == 0) { die("empty braces in \\x{} escape"); }
int value = 0;
try {
value = Integer.parseInt(oldstr.substring(i, i+j), 16);
} catch (NumberFormatException nfe) {
die("invalid hex value for \\x escape");
}
newstr.append(Character.toChars(value));
if (saw_brace) { j++; }
i += j-1;
break; /* switch */
}
case 'u': {
if (i+4 > oldstr.length()) {
die("string too short for \\u escape");
}
i++;
int j;
for (j = 0; j < 4; j++) {
/* this also handles the surrogate issue */
if (oldstr.charAt(i+j) > 127) {
die("illegal non-ASCII hex digit in \\u escape");
}
}
int value = 0;
try {
value = Integer.parseInt( oldstr.substring(i, i+j), 16);
} catch (NumberFormatException nfe) {
die("invalid hex value for \\u escape");
}
newstr.append(Character.toChars(value));
i += j-1;
break; /* switch */
}
case 'U': {
if (i+8 > oldstr.length()) {
die("string too short for \\U escape");
}
i++;
int j;
for (j = 0; j < 8; j++) {
/* this also handles the surrogate issue */
if (oldstr.charAt(i+j) > 127) {
die("illegal non-ASCII hex digit in \\U escape");
}
}
int value = 0;
try {
value = Integer.parseInt(oldstr.substring(i, i+j), 16);
} catch (NumberFormatException nfe) {
die("invalid hex value for \\U escape");
}
newstr.append(Character.toChars(value));
i += j-1;
break; /* switch */
}
default: newstr.append('\\');
newstr.append(Character.toChars(cp));
/*
* say(String.format(
* "DEFAULT unrecognized escape %c passed through",
* cp));
*/
break; /* switch */
}
saw_backslash = false;
}
/* weird to leave one at the end */
if (saw_backslash) {
newstr.append('\\');
}
return newstr.toString();
}
/*
* Return a string "U+XX.XXX.XXXX" etc, where each XX set is the
* xdigits of the logical Unicode code point. No bloody brain-damaged
* UTF-16 surrogate crap, just true logical characters.
*/
public final static
String uniplus(String s) {
if (s.length() == 0) {
return "";
}
/* This is just the minimum; sb will grow as needed. */
StringBuffer sb = new StringBuffer(2 + 3 * s.length());
sb.append("U+");
for (int i = 0; i < s.length(); i++) {
sb.append(String.format("%X", s.codePointAt(i)));
if (s.codePointAt(i) > Character.MAX_VALUE) {
i++; /****WE HATES UTF-16! WE HATES IT FOREVERSES!!!****/
}
if (i+1 < s.length()) {
sb.append(".");
}
}
return sb.toString();
}
private static final
void die(String foa) {
throw new IllegalArgumentException(foa);
}
private static final
void say(String what) {
System.out.println(what);
}
Если это помогает другим, добро пожаловать - без всяких условий. Если вы улучшите его, я бы хотел, чтобы вы отправили мне свои улучшения по почте, но вам, конечно, не обязательно.
Ты можешь использовать String unescapeJava(String)
метод StringEscapeUtils
от Apache Commons Lang.
Вот пример фрагмента:
String in = "a\\tb\\n\\\"c\\\"";
System.out.println(in);
// a\tb\n\"c\"
String out = StringEscapeUtils.unescapeJava(in);
System.out.println(out);
// a b
// "c"
У служебного класса есть методы для экранирования и удаления строк для Java, Java Script, HTML, XML и SQL. Он также имеет перегрузки, которые записывают непосредственно в java.io.Writer
,
Предостережения
Это выглядит как StringEscapeUtils
обрабатывает Unicode Escape с одним u
, но не восьмеричные побеги, или юникодовые побеги с посторонними u
s.
/* Unicode escape test #1: PASS */
System.out.println(
"\u0030"
); // 0
System.out.println(
StringEscapeUtils.unescapeJava("\\u0030")
); // 0
System.out.println(
"\u0030".equals(StringEscapeUtils.unescapeJava("\\u0030"))
); // true
/* Octal escape test: FAIL */
System.out.println(
"\45"
); // %
System.out.println(
StringEscapeUtils.unescapeJava("\\45")
); // 45
System.out.println(
"\45".equals(StringEscapeUtils.unescapeJava("\\45"))
); // false
/* Unicode escape test #2: FAIL */
System.out.println(
"\uu0030"
); // 0
System.out.println(
StringEscapeUtils.unescapeJava("\\uu0030")
); // throws NestableRuntimeException:
// Unable to parse unicode value: u003
Цитата из JLS:
Восьмеричные экранирования предусмотрены для совместимости с C, но могут выражать только значения Unicode
\u0000
через\u00FF
, таким образом, экранирование Unicode обычно предпочтительнее.
Если ваша строка может содержать восьмеричные экранированные символы, вы можете сначала преобразовать их в экранированные символы Unicode или использовать другой подход.
Посторонний u
также задокументировано следующее:
Язык программирования Java определяет стандартный способ преобразования программы, написанной на Unicode, в ASCII, которая превращает программу в форму, которая может обрабатываться инструментами на основе ASCII. Преобразование включает в себя преобразование любых выходов Unicode в исходном тексте программы в ASCII путем добавления дополнительного
u
-например,\uxxxx
становится\uuxxxx
- при одновременном преобразовании не-ASCII символов в исходном тексте в экранированные символы Юникода, содержащие по одному символу u каждый.Эта преобразованная версия одинаково приемлема для компилятора для языка программирования Java и представляет собой точно такую же программу. Точный источник Unicode может быть позже восстановлен из этой формы ASCII путем преобразования каждой escape-последовательности, где несколько
u
присутствуют в последовательности символов Unicode с одним меньшимu
, одновременно конвертируя каждую escape-последовательность с однимu
на соответствующий единственный символ Unicode.
Если ваша строка может содержать Unicode, сбегает с посторонним u
, то вам также может понадобиться предварительно обработать это перед использованием StringEscapeUtils
,
В качестве альтернативы вы можете попытаться написать свой собственный строковый литерал Java с нуля, следуя точным спецификациям JLS.
Рекомендации
Наткнулся на похожую проблему, не был удовлетворен представленными решениями и сам внедрил ее.
Также доступно как Gist на Github:
/**
* Unescapes a string that contains standard Java escape sequences.
* <ul>
* <li><strong>\b \f \n \r \t \" \'</strong> :
* BS, FF, NL, CR, TAB, double and single quote.</li>
* <li><strong>\X \XX \XXX</strong> : Octal character
* specification (0 - 377, 0x00 - 0xFF).</li>
* <li><strong>\uXXXX</strong> : Hexadecimal based Unicode character.</li>
* </ul>
*
* @param st
* A string optionally containing standard java escape sequences.
* @return The translated string.
*/
public String unescapeJavaString(String st) {
StringBuilder sb = new StringBuilder(st.length());
for (int i = 0; i < st.length(); i++) {
char ch = st.charAt(i);
if (ch == '\\') {
char nextChar = (i == st.length() - 1) ? '\\' : st
.charAt(i + 1);
// Octal escape?
if (nextChar >= '0' && nextChar <= '7') {
String code = "" + nextChar;
i++;
if ((i < st.length() - 1) && st.charAt(i + 1) >= '0'
&& st.charAt(i + 1) <= '7') {
code += st.charAt(i + 1);
i++;
if ((i < st.length() - 1) && st.charAt(i + 1) >= '0'
&& st.charAt(i + 1) <= '7') {
code += st.charAt(i + 1);
i++;
}
}
sb.append((char) Integer.parseInt(code, 8));
continue;
}
switch (nextChar) {
case '\\':
ch = '\\';
break;
case 'b':
ch = '\b';
break;
case 'f':
ch = '\f';
break;
case 'n':
ch = '\n';
break;
case 'r':
ch = '\r';
break;
case 't':
ch = '\t';
break;
case '\"':
ch = '\"';
break;
case '\'':
ch = '\'';
break;
// Hex Unicode: u????
case 'u':
if (i >= st.length() - 5) {
ch = 'u';
break;
}
int code = Integer.parseInt(
"" + st.charAt(i + 2) + st.charAt(i + 3)
+ st.charAt(i + 4) + st.charAt(i + 5), 16);
sb.append(Character.toChars(code));
i += 5;
continue;
}
i++;
}
sb.append(ch);
}
return sb.toString();
}
В Java 13 добавлен метод, который делает это: String#translateEscapes
.
Тем не менее, это все еще предварительная функция в Java 14, и в Javadoc есть это предупреждение:
Этот метод связан с текстовыми блоками, функцией предварительного просмотра языка Java. Программы могут использовать этот метод, только если включены функции предварительного просмотра. Возможности предварительной версии могут быть удалены в будущем выпуске или обновлены до постоянных функций языка Java.
Смотрите это на http://commons.apache.org/lang/:
StringEscapeUtils.unescapeJava(String str)
Я знаю, что этот вопрос был старым, но я хотел найти решение, не включающее библиотеки вне тех, которые включены в JRE6 (то есть Apache Commons неприемлемо), и я придумал простое решение с использованием встроенного java.io.StreamTokenizer
:
import java.io.*;
// ...
String literal = "\"Has \\\"\\\\\\\t\\\" & isn\\\'t \\\r\\\n on 1 line.\"";
StreamTokenizer parser = new StreamTokenizer(new StringReader(literal));
String result;
try {
parser.nextToken();
if (parser.ttype == '"') {
result = parser.sval;
}
else {
result = "ERROR!";
}
}
catch (IOException e) {
result = e.toString();
}
System.out.println(result);
Выход:
Has "\ " & isn't
on 1 line.
Я немного опоздала с этим, но подумала, что предложу свое решение, поскольку мне нужна та же функциональность. Я решил использовать Java Compiler API, который делает его медленнее, но дает точные результаты. В основном я живу, создаю класс и возвращаю результаты. Вот метод:
public static String[] unescapeJavaStrings(String... escaped) {
//class name
final String className = "Temp" + System.currentTimeMillis();
//build the source
final StringBuilder source = new StringBuilder(100 + escaped.length * 20).
append("public class ").append(className).append("{\n").
append("\tpublic static String[] getStrings() {\n").
append("\t\treturn new String[] {\n");
for (String string : escaped) {
source.append("\t\t\t\"");
//we escape non-escaped quotes here to be safe
// (but something like \\" will fail, oh well for now)
for (int i = 0; i < string.length(); i++) {
char chr = string.charAt(i);
if (chr == '"' && i > 0 && string.charAt(i - 1) != '\\') {
source.append('\\');
}
source.append(chr);
}
source.append("\",\n");
}
source.append("\t\t};\n\t}\n}\n");
//obtain compiler
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//local stream for output
final ByteArrayOutputStream out = new ByteArrayOutputStream();
//local stream for error
ByteArrayOutputStream err = new ByteArrayOutputStream();
//source file
JavaFileObject sourceFile = new SimpleJavaFileObject(
URI.create("string:///" + className + Kind.SOURCE.extension), Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return source;
}
};
//target file
final JavaFileObject targetFile = new SimpleJavaFileObject(
URI.create("string:///" + className + Kind.CLASS.extension), Kind.CLASS) {
@Override
public OutputStream openOutputStream() throws IOException {
return out;
}
};
//file manager proxy, with most parts delegated to the standard one
JavaFileManager fileManagerProxy = (JavaFileManager) Proxy.newProxyInstance(
StringUtils.class.getClassLoader(), new Class[] { JavaFileManager.class },
new InvocationHandler() {
//standard file manager to delegate to
private final JavaFileManager standard =
compiler.getStandardFileManager(null, null, null);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getJavaFileForOutput".equals(method.getName())) {
//return the target file when it's asking for output
return targetFile;
} else {
return method.invoke(standard, args);
}
}
});
//create the task
CompilationTask task = compiler.getTask(new OutputStreamWriter(err),
fileManagerProxy, null, null, null, Collections.singleton(sourceFile));
//call it
if (!task.call()) {
throw new RuntimeException("Compilation failed, output:\n" +
new String(err.toByteArray()));
}
//get the result
final byte[] bytes = out.toByteArray();
//load class
Class<?> clazz;
try {
//custom class loader for garbage collection
clazz = new ClassLoader() {
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.equals(className)) {
return defineClass(className, bytes, 0, bytes.length);
} else {
return super.findClass(name);
}
}
}.loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
//reflectively call method
try {
return (String[]) clazz.getDeclaredMethod("getStrings").invoke(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Он принимает массив, чтобы вы могли выйти в пакетном режиме. Итак, следующий простой тест успешен:
public static void main(String[] meh) {
if ("1\02\03\n".equals(unescapeJavaStrings("1\\02\\03\\n")[0])) {
System.out.println("Success");
} else {
System.out.println("Failure");
}
}
org.apache.commons.lang3.StringEscapeUtils
от commons-lang3 помечен как устаревший. Ты можешь использовать org.apache.commons.text.StringEscapeUtils#unescapeJava(String)
вместо. Требуется дополнительная зависимость Maven:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.4</version>
</dependency>
и, кажется, обрабатывает еще несколько особых случаев, например, unescapes:
- экранированные обратные слэши, одинарные и двойные кавычки
- экранированные восьмеричные и юникодные значения
\\b
,\\n
,\\t
,\\f
,\\r
Для записи, если вы используете Scala, вы можете сделать:
StringContext.treatEscapes(escaped)
Я столкнулся с той же проблемой, но я не был очарован ни одним из решений, которые я нашел здесь. Итак, я написал один, который перебирает символы строки, используя совпадение, чтобы найти и заменить escape-последовательности. Это решение предполагает правильно отформатированный ввод. То есть он успешно пропускает бессмысленные escape-символы и декодирует Unicode-escape-символы для перевода строки и возврата каретки (которые иначе не могут появиться в символьном литерале или строковом литерале из-за определения таких литералов и порядка фаз перевода для Java источник). Извините, код немного упакован для краткости.
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Decoder {
// The encoded character of each character escape.
// This array functions as the keys of a sorted map, from encoded characters to decoded characters.
static final char[] ENCODED_ESCAPES = { '\"', '\'', '\\', 'b', 'f', 'n', 'r', 't' };
// The decoded character of each character escape.
// This array functions as the values of a sorted map, from encoded characters to decoded characters.
static final char[] DECODED_ESCAPES = { '\"', '\'', '\\', '\b', '\f', '\n', '\r', '\t' };
// A pattern that matches an escape.
// What follows the escape indicator is captured by group 1=character 2=octal 3=Unicode.
static final Pattern PATTERN = Pattern.compile("\\\\(?:(b|t|n|f|r|\\\"|\\\'|\\\\)|((?:[0-3]?[0-7])?[0-7])|u+(\\p{XDigit}{4}))");
public static CharSequence decodeString(CharSequence encodedString) {
Matcher matcher = PATTERN.matcher(encodedString);
StringBuffer decodedString = new StringBuffer();
// Find each escape of the encoded string in succession.
while (matcher.find()) {
char ch;
if (matcher.start(1) >= 0) {
// Decode a character escape.
ch = DECODED_ESCAPES[Arrays.binarySearch(ENCODED_ESCAPES, matcher.group(1).charAt(0))];
} else if (matcher.start(2) >= 0) {
// Decode an octal escape.
ch = (char)(Integer.parseInt(matcher.group(2), 8));
} else /* if (matcher.start(3) >= 0) */ {
// Decode a Unicode escape.
ch = (char)(Integer.parseInt(matcher.group(3), 16));
}
// Replace the escape with the decoded character.
matcher.appendReplacement(decodedString, Matcher.quoteReplacement(String.valueOf(ch)));
}
// Append the remainder of the encoded string to the decoded string.
// The remainder is the longest suffix of the encoded string such that the suffix contains no escapes.
matcher.appendTail(decodedString);
return decodedString;
}
public static void main(String... args) {
System.out.println(decodeString(args[0]));
}
}
Должен отметить, что Apache Commons Lang3, похоже, не страдает слабостями, указанными в принятом решении. То есть, StringEscapeUtils
кажется, обрабатывать восьмеричные побеги и множественные u
символы Юникода убегают. Это означает, что если у вас нет веской причины избегать Apache Commons, вам, вероятно, следует использовать его, а не мое решение (или любое другое решение здесь).
Если вы читаете экранированные символы в кодировке Юникод из файла, вам будет трудно это сделать, потому что строка будет читаться буквально вместе с экранированием для обратной косой черты:
my_file.txt
Blah blah...
Column delimiter=;
Word delimiter=\u0020 #This is just unicode for whitespace
.. more stuff
Здесь, когда вы читаете строку 3 из файла, строка / строка будет иметь:
"Word delimiter=\u0020 #This is just unicode for whitespace"
и символ [] в строке покажет:
{...., '=', '\\', 'u', '0', '0', '2', '0', ' ', '#', 't', 'h', ...}
Commons StringUnescape не уберет это для вас (я пробовал unescapeXml()). Вам придется сделать это вручную, как описано здесь.
Таким образом, подстрока "\u0020" должна стать 1 одиночным символом "\u0020"
Но если вы используете это "\u0020", чтобы сделать String.split("... ..... ..", columnDelimiterReadFromFile)
который на самом деле использует регулярное выражение для внутреннего использования, он будет работать напрямую, потому что строка, прочитанная из файла, была экранирована и идеально подходит для использования в шаблоне регулярного выражения!! (Смущенный?)
Возможно, вы захотите взглянуть на реализацию Stringliteral в Eclipse.