如何从 ArrayList 写入文本文件

How to write to a text file from an ArrayList

我正在尝试从 class 的 ArrayList 中获取数据并将其写入文本文件。它创建临时文件,但不对其执行任何操作。它打印出我试图放入文件中的内容,但不会删除临时文件。我做错了什么?

try
{
    File temp = new File("temp.txt");
    File file = new File(prop.getProperty("path"));
    BufferedWriter writer = new BufferedWriter(new FileWriter(temp));
    ArrayList<Contact> contacts = gestor.checkData();
                        
    if(temp.createNewFile())
    {
        for(int i = 0; i < contacts.size(); i++)
        {
            writer.write(contacts.get(i).getName());
            writer.write(contacts.get(i).getLastNames());
                                
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            String date = df.format(contacts.get(i).getBirthday());
                                
            writer.write(date);
            writer.write(contacts.get(i).getEmail());
        }
    writer.close();
    file.delete();
    temp.renameTo(file);
    }
}
catch (IOException e)
{
    e.printStackTrace();
}

checkData() 的代码只是 returns ArrayList<Contact> contactList.

你的代码有很多问题:

资源泄露

类似于 new FileWriter 的内容是 资源。资源 必须 关闭。因此,你永远不应该制作资源(你通常在制作资源时知道这一点:你要么调用 new X ,其中 X 显然代表资源,要么你调用 socket.getInputStreamFiles.newBufferedReader...除非你做到了'right'。只有两种方法可以做到:

try (FileWriter w = new FileWriter(...)) {
   // use it here
}

或者,如果您需要资源成为您的 class 的字段,那么唯一安全的方法是将您的 class 变成资源:将其设为 implements AutoClosable,创建一个 close 方法,现在使用该 try 语法的负担由使用您的 class 的任何人承担。 没有 try-with-resources*.

就没有办法安全地做这些事情

字符集混乱

文件是字节。不是字符。此外,文件系统不知道 'encoding' 是什么。如何将 é 转换为字节?答案取决于字符集编码。 java.nio.file 包中没有的所有将字节转换为字符或反之亦然的方法的问题在于它们使用了 'platform default encoding'。这是一种有趣的说法 'the worst idea ever',因为它会在您的机器上 'work' 并通过所有测试,然后在最糟糕的时刻在生产中失败。解决方案是 永远 永远依赖平台默认设置。如果您真的打算使用它,请明确说明。不幸的是,在 java11 之前,FileWriter 无法 指定字符集编码,这使它成为一个完全无用的 class 如果不编写有缺陷的代码,您实际上永远无法使用。所以不要。我建议你切换到新文件 API,它默认为 UTF-8(相当合理的默认值),并且可以在 one-liners.

中做很多复杂的事情

没有换行符

你只是 .write 所有这些数据。 write 完全按照它说的去做,并准确地写入字符串。它不再打印任何东西。具体来说,它不打印任何换行符。当然,您不打算只将 JoeSteel1990-10-01 写入文件,就像那样一团糟?写 \n 来避免这种情况,或者根据需要预先构造整个字符串(使用换行符),然后写那个。

在意外情况下静静地什么都不做

如果 'temp' 文件已经存在,您的代码将不执行任何操作。听起来设计是一种奇怪的情况(因为临时文件紧随其后 'renamed')。作为一般经验法则,当您 运行 进入您知道不太可能或看似不可能的场景时,'silently do nothing' 完全是错误的本能。正确的本能恰恰相反:尽可能地失败。 (当然,最好的选择是考虑奇怪的场景意味着什么并妥善处理,但这并不总是可能的)。所以,与其这样,不如试试:

if (!temp.creatNewFile()) {
    throw new RuntimeException("That is weird - tempfile already exists: " + temp);
}

这是正确的心态:如果发生奇怪的事情,请尽可能快地炸毁,并提供足够的细节以便您知道出了什么问题。 (最有可能 'correct' 处理这个问题的方法就是删除临时文件。该临时文件的全部意义在于,如果您输入此代码时它仍然存在,则先前的尝试中途失败了,所以只需删除操作失败的产品,没有用。

异常处理。

虽然在示例甚至 IDE 模板中很常见,但您没有做对。你永远不应该通过打印一些东西并继续处理异常。想一想:如果出现问题,继续执行代码要么会导致严重的问题(因为您在编写代码时肯定没有考虑到程序中的一个步骤失败了),要么会导致另一个问题错误。如果所有错误都像这样处理,就会出现一件事,您会在日志中得到 85 个错误跟踪。那没有用。 如果出现问题而您不知道如何处理,请不要继续运行宁。唯一明智的 'I do not know how to handle this' 异常处理是:

catch (IOException e) {
    throw new RuntimeException("unhandled", e);
}

有时可能会出现比 RuntimeException 更好的异常(例如 UncheckedIOException 或 ServletException,视情况而定)。此外,有时正确的答案是 throws 之后的异常。请记住,public static void main 可以(通常应该!)声明为 throws Exception.

综合起来

try {
    Path temp = Paths.get("temp.txt");
    Path file = Paths.get(prop.getProperty("path"));
    ArrayList<Contact> contacts = gestor.checkData();

    try (BufferedWriter writer = Files.newBufferedWriter(temp)) {
      for (Contact contact : contacts) {                        
          writer.write(contact.getName());
          writer.write("\n");
          writer.write(contact.getLastNames());
          writer.write("\n");
          DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
          String date = df.format(contact.getBirthday());
          writer.write(date);
          writer.write("\n");
          writer.write(contact.getEmail());
          writer.write("\n");
      }
    }
    Files.delete(file);
    Files.move(temp, file);
} catch (IOException e) {
    throw new UncheckedIOException(e);
}

以上可能会失败,但它不会告诉你失败的确切原因。例如,它可能会告诉您您尝试写入的目录不存在,或者您没有写入权限。而您的代码随后将静静地什么也不做。

*) 对于 java 专业人士来说,当然,您可以手动滚动自己的 try/finally 循环。让我知道有多少 java 的新程序员在您这样做时设法躲过了雷区中的每一个地雷。直到你相当冬天,这条经验法则实际上变成了一条法律规则:没有 t-w-r,你不能安全地做到这一点。让我们保持简单。