Как сохранить китайские иероглифы в файл с помощью java?

Я использую следующий код для сохранения китайских иероглифов в файл .txt, но когда я открыл его с помощью Wordpad, я не смог его прочитать.

StringBuffer Shanghai_StrBuf = new StringBuffer("\u4E0A\u6D77");
boolean Append = true;

FileOutputStream fos;
fos = new FileOutputStream(FileName, Append);
for (int i = 0;i < Shanghai_StrBuf.length(); i++) {
    fos.write(Shanghai_StrBuf.charAt(i));
}
fos.close();

Что я могу сделать ? Я знаю, что если я вырезаю и вставляю китайские иероглифы в Wordpad, я могу сохранить их в файл .txt. Как мне это сделать в Java?


person Frank    schedule 19.04.2009    source источник
comment
возможный дубликат Что такое кодировка символов и почему Я заморачиваюсь с этим   -  person Raedwald    schedule 10.04.2015


Ответы (5)


Здесь действуют несколько факторов:

  • Текстовые файлы не имеют встроенных метаданных для описания их кодировки (несмотря на все разговоры о налогах на угловые скобки, есть причины, по которым XML популярен).
  • Кодировка по умолчанию для Windows по-прежнему 8-битная (или двухбайтная) "Набор символов ANSI" с ограниченным диапазоном значений — текстовые файлы, записанные в этом формате, не переносимы.
  • Чтобы отличить файл Unicode от файла ANSI, приложения Windows полагаются на наличие метки порядка следования байтов в начале файла (не совсем верно - Раймонд Чен объясняет). Теоретически спецификация должна сообщать вам порядок следования байтов (порядок байтов) данных. Для UTF-8, несмотря на то, что существует только один порядок байтов, приложения Windows полагаются на байты маркера, чтобы автоматически определить, что это Unicode (хотя вы заметите, что Блокнот имеет параметр кодировки в диалогах открытия/сохранения).
  • Неправильно говорить, что Java не работает, потому что он не записывает спецификацию UTF-8 автоматически. Например, в системах Unix было бы ошибкой записывать спецификацию в файл сценария, и многие системы Unix используют UTF-8 в качестве кодировки по умолчанию. Бывают случаи, когда вам это не нужно и в Windows, например, когда вы добавляете данные в существующий файл: fos = new FileOutputStream(FileName,Append);

Вот метод надежного добавления данных UTF-8 в файл:

  private static void writeUtf8ToFile(File file, boolean append, String data)
      throws IOException {
    boolean skipBOM = append && file.isFile() && (file.length() > 0);
    Closer res = new Closer();
    try {
      OutputStream out = res.using(new FileOutputStream(file, append));
      Writer writer = res.using(new OutputStreamWriter(out, Charset
          .forName("UTF-8")));
      if (!skipBOM) {
        writer.write('\uFEFF');
      }
      writer.write(data);
    } finally {
      res.close();
    }
  }

Применение:

  public static void main(String[] args) throws IOException {
    String chinese = "\u4E0A\u6D77";
    boolean append = true;
    writeUtf8ToFile(new File("chinese.txt"), append, chinese);
  }

Примечание. Если файл уже существует, и вы выбрали добавление, а существующие данные не были закодированы в UTF-8, единственное, что создаст код, — это беспорядок.

Вот тип Closer, используемый в этом коде:

public class Closer implements Closeable {
  private Closeable closeable;

  public <T extends Closeable> T using(T t) {
    closeable = t;
    return t;
  }

  @Override public void close() throws IOException {
    if (closeable != null) {
      closeable.close();
    }
  }
}

Этот код делает лучшее предположение в стиле Windows о том, как читать файл на основе меток порядка байтов:

  private static final Charset[] UTF_ENCODINGS = { Charset.forName("UTF-8"),
      Charset.forName("UTF-16LE"), Charset.forName("UTF-16BE") };

  private static Charset getEncoding(InputStream in) throws IOException {
    charsetLoop: for (Charset encodings : UTF_ENCODINGS) {
      byte[] bom = "\uFEFF".getBytes(encodings);
      in.mark(bom.length);
      for (byte b : bom) {
        if ((0xFF & b) != in.read()) {
          in.reset();
          continue charsetLoop;
        }
      }
      return encodings;
    }
    return Charset.defaultCharset();
  }

  private static String readText(File file) throws IOException {
    Closer res = new Closer();
    try {
      InputStream in = res.using(new FileInputStream(file));
      InputStream bin = res.using(new BufferedInputStream(in));
      Reader reader = res.using(new InputStreamReader(bin, getEncoding(bin)));
      StringBuilder out = new StringBuilder();
      for (int ch = reader.read(); ch != -1; ch = reader.read())
        out.append((char) ch);
      return out.toString();
    } finally {
      res.close();
    }
  }

Применение:

  public static void main(String[] args) throws IOException {
    System.out.println(readText(new File("chinese.txt")));
  }

(System.out использует кодировку по умолчанию, поэтому вывод чего-либо разумного зависит от вашей платформы и конфигурация.)

person McDowell    schedule 20.04.2009
comment
Хорошо ! Из всех ответов, которые я пробовал, ваш лучший! Но как прочитать юникод из только что сохраненного файла? Я использовал свою утилиту, чтобы прочитать его, и получил это: 00ef 00bb 00bf 00e4 00b8 008a 00e6 00b5 00b7 Я вижу из wordpad, что в нем есть два китайских символа: Shang & Hai, но как java может их прочитать? Спасибо ! - person Frank; 20.04.2009
comment
Я добавил код, который лучше всего подходит для чтения произвольных текстовых файлов. - person McDowell; 20.04.2009
comment
Здорово ! Это именно то, что я ищу! Я бы хотел, чтобы это было частью пакета Sun Java, а не тем, о чем нам нужно беспокоиться. Спасибо ! - person Frank; 20.04.2009

Если вы можете быть уверены, что кодировка символов по умолчанию — UTF-8 (или какая-либо другая кодировка Unicode), вы можете использовать следующее:

    Writer w = new FileWriter("test.txt");
    w.append("上海");
    w.close();

Самый безопасный способ — всегда явно указывать кодировку:

    Writer w = new OutputStreamWriter(new FileOutputStream("test.txt"), "UTF-8");
    w.append("上海");
    w.close();

P.S. Вы можете использовать любые символы Unicode в исходном коде Java, даже в качестве имен методов и переменных, если параметр -encoding для javac настроен правильно. Это делает исходный код более читаемым, чем экранированная форма \uXXXX.

person Esko Luontola    schedule 19.04.2009
comment
Я бы хотел, но, поскольку я использую Netbeans, после того, как я вырезал и вставил китайский язык в java-файл и сохранил его, он не будет отображаться (только видеть ???) при повторном открытии java-файла в Netbeans. - person Frank; 20.04.2009
comment
Возможно, NetBeans настроен на использование какой-либо кодировки, отличной от Unicode, или шрифт редактора не содержит всех символов Unicode. Я не использую NetBeans, но из его файла справки я вижу, что вы установили кодировку в Project Properties | Источники | Кодирование. - person Esko Luontola; 21.04.2009
comment
Вы уверены, что с помощью какой кодировки был сохранен файл, если вы сохраняли его с помощью какого-то другого редактора? - person Esko Luontola; 21.04.2009

Будьте очень осторожны с предлагаемыми подходами. Даже указав кодировку для файла следующим образом:

Writer w = новый OutputStreamWriter (новый FileOutputStream («test.txt»), «UTF-8»);

не будет работать, если вы работаете под управлением такой операционной системы, как Windows. Даже установка системного свойства для file.encoding в UTF-8 не решает проблему. Это связано с тем, что Java не может записать метку порядка байтов (BOM) для файла. Даже если вы укажете кодировку при записи в файл, открытие того же файла в таком приложении, как Wordpad, будет отображать текст как мусор, поскольку он не обнаруживает спецификацию. Я попытался запустить примеры здесь в Windows (с кодировкой платформы/контейнера CP1252).

Для описания проблемы в Java существует следующая ошибка:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058

На данный момент решение состоит в том, чтобы самостоятельно написать метку порядка байтов, чтобы файл правильно открывался в других приложениях. Смотрите это для получения более подробной информации о спецификации:

http://mindprod.com/jgloss/bom.html

а более правильное решение смотрите по следующей ссылке:

http://tripoverit.blogspot.com/2007/04/javas-utf-8-and-unicode-writing-is.html

person Jon    schedule 20.04.2009
comment
Я ожидал получить креветку, теперь я нашел акулу и убийцу акул! Спасибо. В правильном решении, которое вы разместили, почему init(); строки, закомментированные в Close() и read() ? Должен ли я раскомментировать их для правильной работы? - person Frank; 20.04.2009
comment
Не совсем уверен, но это не должно иметь значения для написания, только для чтения. Если вы читаете файл UTF-8, вам нужно пропустить спецификацию, так как она чертовски сбивает с толку Java - это то, что делает метод init. Возможно, стоит связаться с автором блога, чтобы узнать причину этого. Извините, я не могу больше помочь. - person Jon; 20.04.2009
comment
Возможно, вы могли бы отказаться от части чтения кода. Похоже, Apache попытался создать свой собственный BOMExclusionReader, см.: issues.apache.org /jira/browse/IO-178 - person Jon; 20.04.2009
comment
Java не записывает спецификацию UTF-8 автоматически, потому что во многих случаях это было бы ошибкой. unicode.org/faq/utf_bom.html#BOM - person McDowell; 20.04.2009

Вот один из многих способов. По сути, мы просто указываем, что преобразование должно выполняться в UTF-8 перед выводом байтов в FileOutputStream:

String FileName = "output.txt";

StringBuffer Shanghai_StrBuf=new StringBuffer("\u4E0A\u6D77");
boolean Append=true;

Writer writer = new OutputStreamWriter(new FileOutputStream(FileName,Append), "UTF-8");
writer.write(Shanghai_StrBuf.toString(), 0, Shanghai_StrBuf.length());
writer.close();

Я вручную проверил это на изображениях на странице http://www.fileformat.info/info/unicode/char/ . В будущем соблюдайте стандарты кодирования Java, включая имена переменных в нижнем регистре. Это улучшает читаемость.

person Matthew Flaschen    schedule 19.04.2009

Попробуй это,

StringBuffer Shanghai_StrBuf=new StringBuffer("\u4E0A\u6D77");
    boolean Append=true;

    Writer out = new BufferedWriter(new OutputStreamWriter(
        new FileOutputStream(FileName,Append), "UTF8"));
    for (int i=0;i<Shanghai_StrBuf.length();i++) out.write(Shanghai_StrBuf.charAt(i));
    out.close();
person Community    schedule 20.04.2009