1. Обзор
Ошибка компиляции недопустимых символов является ошибкой кодирования типа файла. Он возникает, если мы используем неправильную кодировку в наших файлах при их создании. В результате в таких языках, как Java, мы можем получить этот тип ошибки при попытке скомпилировать наш проект. В этом руководстве мы подробно опишем проблему, а также некоторые сценарии, в которых мы можем с ней столкнуться, а затем представим несколько примеров ее решения.
2. Ошибка компиляции недопустимого символа
2.1. Метка порядка байтов (BOM)
Прежде чем мы перейдем к отметке порядка байтов, нам нужно быстро взглянуть на формат преобразования UCS (Unicode) (UTF). UTF — это формат кодировки символов, который может кодировать все возможные кодовые точки символов в Unicode . Существует несколько видов кодировок UTF. Среди всех них наиболее часто используется UTF-8.
UTF-8 использует 8-битную кодировку переменной ширины для максимальной совместимости с ASCII. Когда мы используем эту кодировку в наших файлах, мы можем найти некоторые байты, представляющие кодовую точку Unicode. В результате наши файлы начинаются с метки порядка байтов (BOM) U+FEFF. Эта метка при правильном использовании невидима. Однако в некоторых случаях это может привести к ошибкам в данных.
В кодировке UTF-8 наличие спецификации не принципиально . Хотя это и не обязательно, спецификация может по-прежнему отображаться в кодировке UTF-8. Добавление спецификации может происходить либо путем преобразования кодировки, либо с помощью текстового редактора, который помечает содержимое как UTF-8.
Текстовые редакторы, такие как «Блокнот» в Windows, могут создавать такие дополнения. Как следствие, когда мы используем текстовый редактор, похожий на Блокнот, для создания примера кода и пытаемся запустить его, мы можем получить ошибку компиляции. Напротив, современные IDE кодируют созданные файлы как UTF-8 без спецификации. В следующих разделах будут показаны некоторые примеры этой проблемы.
2.2. Класс с ошибкой компиляции недопустимого символа
Обычно мы работаем с расширенными IDE, но иногда вместо этого используем текстовый редактор. К сожалению, как мы узнали, некоторые текстовые редакторы могут создавать больше проблем, чем решений, потому что сохранение файла со спецификацией может привести к ошибке компиляции в Java. Ошибка «недопустимый символ» возникает на этапе компиляции, поэтому ее довольно легко обнаружить . Следующий пример показывает нам, как это работает.
Во-первых, давайте напишем простой класс в нашем текстовом редакторе, таком как Блокнот. Этот класс — просто представление — мы можем написать любой код для тестирования. Затем мы сохраняем наш файл со спецификацией для тестирования:
public class TestBOM {
public static void main(String ...args){
System.out.println("BOM Test");
}
}
Теперь, когда мы пытаемся скомпилировать этот файл с помощью команды javac
:
$ javac ./TestBOM.java
В результате получаем сообщение об ошибке:
public class TestBOM {
^
.\TestBOM.java:1: error: illegal character: '\u00bf'
public class TestBOM {
^
2 errors
В идеале, чтобы решить эту проблему, единственное, что нужно сделать, это сохранить файл как UTF-8 без кодировки BOM. После этого проблема решена. Мы всегда должны проверять, что наши файлы сохраняются без спецификации .
Другой способ решить эту проблему — использовать такой инструмент, как dos2unix
. Этот инструмент удалит спецификацию, а также позаботится о других особенностях текстовых файлов Windows.
3. Чтение файлов
Дополнительно разберем несколько примеров чтения файлов, закодированных с помощью BOM.
Первоначально нам нужно создать файл со спецификацией для использования в нашем тесте. Этот файл содержит наш образец текста «Hello world with BOM». – которая будет нашей ожидаемой строкой. Далее приступаем к тестированию.
3.1. Чтение файлов с помощью BufferedReader
Сначала мы проверим файл с помощью класса BufferedReader :
@Test
public void whenInputFileHasBOM_thenUseInputStream() throws IOException {
String line;
String actual = "";
try (BufferedReader br = new BufferedReader(new InputStreamReader(file))) {
while ((line = br.readLine()) != null) {
actual += line;
}
}
assertEquals(expected, actual);
}
В этом случае, когда мы пытаемся утверждать, что строки равны, мы получаем ошибку :
org.opentest4j.AssertionFailedError: expected: <Hello world with BOM.> but was: <Hello world with BOM.>
Expected :Hello world with BOM.
Actual :Hello world with BOM.
На самом деле, если мы пробежимся по тестовому ответу, обе строки выглядят одинаково. Даже в этом случае фактическое значение строки содержит спецификацию. В результате строки не равны.
Более того, быстрым решением будет замена символов спецификации :
@Test
public void whenInputFileHasBOM_thenUseInputStreamWithReplace() throws IOException {
String line;
String actual = "";
try (BufferedReader br = new BufferedReader(new InputStreamReader(file))) {
while ((line = br.readLine()) != null) {
actual += line.replace("\uFEFF", "");
}
}
assertEquals(expected, actual);
}
Метод replace
удаляет спецификацию из нашей строки, поэтому наш тест проходит. Нам нужно аккуратно работать с методом replace .
Большое количество обрабатываемых файлов может привести к проблемам с производительностью.
3.2. Чтение файлов с помощью Apache Commons IO
Кроме того, библиотека Apache Commons IO предоставляет класс BOMInputStream
. Этот класс является оболочкой, которая включает закодированный ByteOrderMark
в качестве первых байтов. Давайте посмотрим, как это работает:
@Test
public void whenInputFileHasBOM_thenUseBOMInputStream() throws IOException {
String line;
String actual = "";
ByteOrderMark[] byteOrderMarks = new ByteOrderMark[] {
ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_32LE
};
InputStream inputStream = new BOMInputStream(ioStream, false, byteOrderMarks);
Reader reader = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(reader);
while ((line = br.readLine()) != null) {
actual += line;
}
assertEquals(expected, actual);
}
Код аналогичен предыдущим примерам, но мы передаем BOMInputStream
в качестве параметра в InputStreamReader
.
3.3. Чтение файлов с использованием данных Google (GData)
С другой стороны, другой полезной библиотекой для обработки спецификации является Google Data (GData) . Это более старая библиотека, но она помогает управлять спецификацией внутри файлов. Он использует XML в качестве базового формата. Давайте посмотрим на это в действии:
@Test
public void whenInputFileHasBOM_thenUseGoogleGdata() throws IOException {
char[] actual = new char[21];
try (Reader r = new UnicodeReader(ioStream, null)) {
r.read(actual);
}
assertEquals(expected, String.valueOf(actual));
}
Наконец, как мы видели в предыдущих примерах, важно удалить спецификацию из файлов. Если мы не обработаем это должным образом в наших файлах, при чтении данных могут возникнуть неожиданные результаты. Вот почему мы должны знать о существовании этой метки в наших файлах.
4. Вывод
В этой статье мы рассмотрели несколько тем, касающихся ошибки компиляции недопустимых символов в Java. Во-первых, мы узнали, что такое UTF и как в него интегрирована спецификация. Во-вторых, мы показали пример класса, созданного с помощью текстового редактора — в данном случае Блокнота Windows. Сгенерированный класс выдал ошибку компиляции для недопустимого символа. Наконец, мы представили несколько примеров кода, как читать файлы со спецификацией.
Как обычно, весь код, используемый для этого примера, можно найти на GitHub .