使用FileWriter(Java)以UTF-8格式编写文件?

93
我有以下代码,但是我希望它能够写成UTF-8文件,以处理外来字符。是否有一种方法可以实现这一点?是否需要某些参数?
我非常感谢您的帮助。谢谢。
try {
  BufferedReader reader = new BufferedReader(new FileReader("C:/Users/Jess/My Documents/actresses.list"));
  writer = new BufferedWriter(new FileWriter("C:/Users/Jess/My Documents/actressesFormatted.csv"));
  while( (line = reader.readLine()) != null) {
    //If the line starts with a tab then we just want to add a movie
    //using the current actor's name.
    if(line.length() == 0)
      continue;
    else if(line.charAt(0) == '\t') {
      readMovieLine2(0, line, surname.toString(), forename.toString());
    } //Else we've reached a new actor
    else {
      readActorName(line);
    }
  }
} catch (IOException e) {
  e.printStackTrace();
}
9个回答

86

安全的编码构造函数

让Java正确通知您编码错误是棘手的。您必须使用InputStreamReaderOutputStreamWriter的四个备用构造函数中最冗长的,但可惜它也是最不常用的构造函数,才能在编码故障时接收到适当的异常。

对于文件I/O,请始终确保将花哨的编码器参数用作OutputStreamWriterInputStreamReader的第二个参数:

  Charset.forName("UTF-8").newEncoder()

还有其他更花哨的可能性,但是这三种简单的可能性都不能用于异常处理。这三种方法可以:

 OutputStreamWriter char_output = new OutputStreamWriter(
     new FileOutputStream("some_output.utf8"),
     Charset.forName("UTF-8").newEncoder() 
 );

 InputStreamReader char_input = new InputStreamReader(
     new FileInputStream("some_input.utf8"),
     Charset.forName("UTF-8").newDecoder() 
 );

就运行而言,

 $ java -Dfile.encoding=utf8 SomeTrulyRemarkablyLongcLassNameGoeShere

问题在于这样做不会对字符流使用完整的编码器参数形式,因此您将再次错过编码问题。

更长的示例

这里有一个更长的示例,它管理进程而不是文件,我们将两个不同的输入字节流和一个输出字节流全部提升为UTF-8字符流,并进行全面的异常处理

 // this runs a perl script with UTF-8 STD{IN,OUT,ERR} streams
 Process
 slave_process = Runtime.getRuntime().exec("perl -CS script args");

 // fetch his stdin byte stream...
 OutputStream
 __bytes_into_his_stdin  = slave_process.getOutputStream();

 // and make a character stream with exceptions on encoding errors
 OutputStreamWriter
   chars_into_his_stdin  = new OutputStreamWriter(
                             __bytes_into_his_stdin,
         /* DO NOT OMIT! */  Charset.forName("UTF-8").newEncoder()
                         );

 // fetch his stdout byte stream...
 InputStream
 __bytes_from_his_stdout = slave_process.getInputStream();

 // and make a character stream with exceptions on encoding errors
 InputStreamReader
   chars_from_his_stdout = new InputStreamReader(
                             __bytes_from_his_stdout,
         /* DO NOT OMIT! */  Charset.forName("UTF-8").newDecoder()
                         );

// fetch his stderr byte stream...
 InputStream
 __bytes_from_his_stderr = slave_process.getErrorStream();

 // and make a character stream with exceptions on encoding errors
 InputStreamReader
   chars_from_his_stderr = new InputStreamReader(
                             __bytes_from_his_stderr,
         /* DO NOT OMIT! */  Charset.forName("UTF-8").newDecoder()
                         );

现在你有三个字符流,分别称为chars_into_his_stdinchars_from_his_stdoutchars_from_his_stderr, 它们都会在编码错误时引发异常。

这比我在这个答案的前半部分提供的解决方案稍微复杂一些。关键点是这是检测编码错误的唯一方法。

只是不要让我开始谈论PrintStream吃掉异常的问题。


1
很棒的回答,但我认为它有一个小错误- InputStreamReader char_input = new InputStreamWriter 应该改为:InputStreamReader char_input = new InputStreamReader,并且 InputStreamReader 构造函数需要使用 CharsetDecoder 而不是 CharsetEncoder - Mark Rhodes
但这是一个真正的问题吗?UTF-8不能表示什么,我认为它可以编码任何东西。 - Paul Taylor
如果你想抱怨流吞噬异常,那么请尝试使用CipherInputStream,它可以移除BadPaddingException,即使它们是由经过身份验证的加密流创建的 :( - Maarten Bodewes
我发现你的代码中有一个小错误: “InputStreamReader”的“Charset.forName(“UTF-8”).newEncoder()”应该改为“Charset.forName(“UTF-8”).newDecoder()”。所以用“decoder”代替“encoder”。不管怎样,感谢这个好答案和+1。 :) - codepleb
关闭OutputStreamWriter是否也会关闭FileOutputStream? - Tuntable
2
整个Java IO系统一直都很混乱。应该像Joda Time重构日期那样进行完全的重新设计。 - Tuntable

67
放弃使用无法指定编码的无用的FileWriterFileReader,改用 new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);。请注意保留HTML标记。

13
如果你不使用非常冗长的Charset.forName("UTF-8").newDecoder()参数(或更高级的结构),而是只用"UTF-8",你将无法正确地收到编码错误的通知(即:异常将被抑制,它将神秘地隐藏编码错误)。 - tchrist
3
使用 StandardCharsets.UTF_8 参数创建一个新的输出流写入器,将数据写入到指定的文件中。 - Abdull
这是正确的方法,它非常有效... - Ola Ström

50
你需要使用OutputStreamWriter类作为BufferedWriter的writer参数。它可以接受编码方式。请查看javadocs中的相关信息。
有些类似于这样:
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
    new FileOutputStream("jedis.txt"), "UTF-8"
));

或者你可以通过设置系统属性file.encoding为UTF-8来设置当前系统的编码。

java -Dfile.encoding=UTF-8 com.jediacademy.Runner arg1 arg2 ...
您还可以通过System.setProperty(...)方法在运行时将其设置为系统属性,如果您只需要针对此特定文件使用它,但在这种情况下,我认为我更喜欢OutputStreamWriter
通过设置系统属性,您可以使用FileWriter,并期望它将UTF-8作为文件的默认编码。在这种情况下,用于读取和写入的所有文件都是如此。
编辑: 1. 从API 19开始,您可以将字符串"UTF-8"替换为StandardCharsets.UTF_8 2. 正如tchrist在下面的评论中建议的那样,如果您打算检测文件中的编码错误,则必须使用OutputStreamWriter方法,并使用接收字符集编码器的构造函数。
CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
encoder.onMalformedInput(CodingErrorAction.REPORT);
encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("jedis.txt"),encoder));

你可以在以下三种行动之间进行选择:忽略 | 替换 | 报告

此外,这个问题已经在这里得到了解答。


这还不够。你还需要一个 InputStreamReader(InputStream in, CharsetDecoder dec),其中最后一个参数是 Charset.forName("UTF-8").newDecoder() - tchrist
1
如果您这样做,输入编码错误将会被静默丢弃。 - tchrist
不需要编码器。构造函数在输入/输出类中接受String、Charset或Encoder。不确定您的评论是什么意思。您能详细说明一下吗? - Edwin Dalorzo
4
如果您测试四个不同的 {In,Out}putStream{Reader,Writer} 构造函数时使用有误数据,您会发现其中三个会掩盖应该引起编码错误的所有异常,只有第四个能够正确地将它们传递给您。这是涉及 Charset.forName("UTF-8").newDecoder() 的那个构造函数。我在我的回答中有一些解释。 - tchrist
@tchist,我刚刚根据你的评论改进了答案 b(^_^)d - Edwin Dalorzo
1
是的,这样会好得多。输入编码错误比输出错误更常见(至少如果它是UTF形式:8位输出编码在Unicode中总是输不起的)。然而,在理论上,您仍然可能在输出时遇到它们,因为Java允许未配对代理存在于内存中的字符串中(它必须这样做;这不是一个错误!),但是没有符合规范的UTF-{8,16,32}输出编码器被允许在输出时产生它们。 - tchrist

12

从Java 11开始,你可以这样做:

FileWriter fw = new FileWriter("filename.txt", Charset.forName("utf-8"));

9
自Java 7开始,处理BufferedWriter和BufferedReader的字符编码有了一种简单的方法。您可以直接使用Files类创建BufferedWriter,而不是创建多个Writer实例。您只需调用以下语句即可创建一个考虑字符编码的BufferedWriter:
Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8);

您可以在JavaDoc中找到更多相关信息:

6

使用中文文本时,我尝试使用字符集UTF-16,幸运的是它起作用了。

希望这能帮到您!

PrintWriter out = new PrintWriter( file, "UTF-16" );

可以尝试使用UTF-32。 - anson

1

使用OutputStream而不是FileWriter来设置编码类型

// file is your File object where you want to write you data 
OutputStream outputStream = new FileOutputStream(file);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
outputStreamWriter.write(json); // json is your data 
outputStreamWriter.flush();
outputStreamWriter.close();

1

现在是2019年,从Java 11开始你可以使用带有Charset参数的构造函数:

FileWriter​(String fileName, Charset charset)

很遗憾,我们仍然无法修改字节缓冲区大小,它被设置为8192。(https://www.baeldung.com/java-filewriter

-3

在我看来

如果你想要编写遵循 UTF-8 的代码,你应该创建一个字节数组。然后,你可以按照以下方式进行操作: byte[] by=("<?xml version=\"1.0\" encoding=\"utf-8\"?>"+"Your string".getBytes();

接下来,你可以将每个字节写入你创建的文件中。 例如:

OutputStream f=new FileOutputStream(xmlfile);
    byte[] by=("<?xml version=\"1.0\" encoding=\"utf-8\"?>"+"Your string".getBytes();
    for (int i=0;i<by.length;i++){
    byte b=by[i];
    f.write(b);

    }
    f.close();

欢迎来到Stack Overflow!虽然这段代码可能解决了问题,但包括解释真的有助于提高您的帖子质量。请记住,您正在为未来的读者回答问题,而这些人可能不知道您提出代码建议的原因。请尽量不要在代码中加入过多的解释性注释,这会降低代码和解释的可读性! - Claudia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接