【问题标题】:Is there a way to make PrintWriter output to UNIX format?有没有办法让 PrintWriter 输出为 UNIX 格式?
【发布时间】:2009-06-18 18:08:29
【问题描述】:

当然是在 Java 中。我正在编写一个程序并在 Windows 环境下运行它,但我需要以 Unix 格式完成输出(.csv)。有什么简单的解决办法吗?谢谢!

【问题讨论】:

  • unix 格式是什么意思?行尾格式?

标签: java unix


【解决方案1】:

要编写带有 unix 行结尾的文件,请在派生自 PrintWriter 的类中覆盖 println,并将 print 与 \n 一起使用。

PrintWriter out = new PrintWriter("testFile") {
    @Override
    public void println() {
        write('\n');
    }
};
out.println("This file will always have unix line endings");
out.println(41);
out.close();

这避免了必须触及代码中任何现有的 println 调用。

【讨论】:

  • 不,你应该覆盖println()。所有其他 println 方法(例如 println(String)println(int) 等都记录为打印数据然后调用 println()。此外,您应该在受保护的变量 lock 上进行同步。
  • 公平点,覆盖 println() 是一个更好的主意。我已经更新了我的代码。对write 的调用处理锁。
  • 万一其他人和我落入同一个陷阱,上面的方法对PrintWriter有效,但对于PrintStream
【解决方案2】:

注意:正如 cmets 中所报告的,下面给出的方法在 JDK 9+ 中中断。使用 James H. 的回答中的方法。


“Unix 格式”是指使用“\n”作为行终止符而不是“\r\n”吗?只需在创建 PrintWriter 之前设置 line.separator 系统属性即可。

仅作为演示:

import java.io.*;

public class Test
{
    public static void main(String[] args)
        throws Exception // Just for simplicity
    {
        System.setProperty("line.separator", "xxx");
        PrintWriter pw = new PrintWriter(System.out);
        pw.println("foo");
        pw.println("bar");
        pw.flush();
    }
}

当然,这为整个 JVM 设置了它,这并不理想,但它可能就是你所需要的。

【讨论】:

  • @Abex:感谢您的报告。我在答案的顶部添加了一条注释。我希望设计出一种比子类化更简单的方法,但总比没有好。
  • 请记住,系统属性会影响 JVM 中的每个PrintWriter,甚至可能会影响面向行的阅读器。如果您只希望特定的 PrintWriter 实例输出 unix 格式,您应该考虑其他选项。
  • @RenéLink:我确实特别说过“当然,这为整个 JVM 设置了它,这并不理想”
  • @JonSkeet 不,问题。我只想提一下以防止有人这样做,例如在 servlet 请求中;)
【解决方案3】:

假设您提到的格式问题是 Windows 换行符是回车换行符 ("\r\n") 而 Unix 换行符只是换行符 ("\n"),这是确保文件使用 LF 和不是 CRLF 是避开 println 而是使用 print("\n") 来终止行。

所以而不是:

writer.println("foo,bar,88");

使用

writer.print("foo,bar,88\n");

您可以在相关文件中搜索println 以确保您能全部找到。

【讨论】:

  • 这种方法与 Jon Skeet 的不同之处在于他的方法是全局的,但需要的更改更少。只要您确定您不在乎程序中的所有内容都将使用 \n 或 \r\n,那么设置 line.separator 可能会更容易。
  • 啊,我明白了。好主意也。虽然就我而言(尽管我应该指定),但我正在寻找一种全局方法。谢谢!
【解决方案4】:

我看到另外 2 个选项不会影响整个系统或导致并发问题,例如设置 lineSeparator。我还会声明一个表示行尾的枚举。

 enum LineEnding {
  UNIX("\n"), DOS("\r\n");

  private String lineSeparator;

  LineEnding(String lineSeparator) {
    this.lineSeparator = lineSeparator;
  }

  public String getLineSeparator() {
    return lineSeparator;
  }
}
  1. 使用反射设置lineSeperator

    您应该创建一个封装访问的工厂,使用 反射以向客户端隐藏 PrintWriter 内部。例如。客户端代码应如下所示:

    PrintWriterFactory.newPrintWriter(someWriter, LineEnding.UNIX);
    

    虽然实现可能如下所示:

    public class PrintWriterFactory {
    
      private static final Field lineSeparatorField;
    
      static {
        try {
          lineSeparatorField = PrintWriter.class.getDeclaredField("lineSeparator");
          lineSeparatorField.setAccessible(true);
        } catch (NoSuchFieldException e) {
          throw new IllegalStateException("java.io.PrintWriter implementation changed. Unable to determine lineSeparator field.", e);
        }
      }
    
      public static PrintWriter newPrintWriter(Writer writer, LineEnding lineEnding) {
        PrintWriter printWriter = new PrintWriter(writer);
    
        try {
          lineSeparatorField.set(printWriter, lineEnding.getLineSeparator());
        } catch (IllegalAccessException e) {
          throw new IllegalStateException("Can't set line ending", e);
        }
    
        return printWriter;
      }
    }
    

    PS:工厂不能是静态的。如果 PrintWriter 实现从一个 JDK 更改为另一个,您可以使用一个接口和多个实现,因此您必须使用另一种反射策略。

  2. 扩展 PrintWriter 并覆盖 println() 方法

    public class LineEndingPrintWriter extends PrintWriter {
    
      protected boolean autoFlush = false;
      private LineEnding lineEnding;
    
      public LineEndingPrintWriter(Writer out, LineEnding lineEnding) {
        this(out, false, lineEnding);
      }
    
      public LineEndingPrintWriter(Writer out, boolean autoFlush, LineEnding lineEnding) {
        super(out, autoFlush);
        this.autoFlush = autoFlush;
        this.lineEnding = lineEnding;
      }
    
      public LineEndingPrintWriter(OutputStream out, LineEnding lineEnding) {
        this(out, false, lineEnding);
      }
    
      public LineEndingPrintWriter(OutputStream out, boolean autoFlush, LineEnding lineEnding) {
        super(out, autoFlush);
        this.autoFlush = autoFlush;
        this.lineEnding = lineEnding;
      }
    
      protected void ensureOpen() throws IOException {
        if (out == null)
          throw new IOException("Stream closed");
      }
    
      public void println() {
        // Method body taken from java.io.PrintWriter.println();
        try {
          synchronized (lock) {
            ensureOpen();
    
            out.write(lineEnding.getLineSeparator());
    
            if (autoFlush) {
              out.flush();
            }
          }
        } catch (InterruptedIOException e) {
          Thread.currentThread().interrupt();
        } catch (IOException e) {
          setError();
        }
      }
    }
    

【讨论】:

  • 被低估的评论!
【解决方案5】:

一个偏执的程序员会在系统属性对象上进行同步,至少在不同的 Printwriters 需要不同类型的行终止符时是这样。

public static PrintWriter createPrintWriter(OutputStreamWriter out, boolean autoflush){
    Properties props = System.getProperties();
    synchronized (props) {
        Object old = null;
        try {
        old = props.setProperty("line.separator", "\n");
        return  new PrintWriter(out, autoflush);
        } finally {
            if( old != null ) {
                props.put("line.separator", old);
            }
        }
    }
}

【讨论】:

  • 这里的推理是什么 :) ?
  • 为了确保你真的会得到你刚刚设置的属性,除非你在一个线程中运行,或者总是使用相同的行终止符,这永远不会改变。跨度>
  • 不幸的是,这并不能阻止另一个方法在不使用此锁的情况下同时更改属性。
  • 实际上,Properties.setProperty() 是同步的,所以如果线程持有属性锁,其他人应该无法更改它。
猜你喜欢
  • 2012-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-03
  • 1970-01-01
  • 2019-08-27
  • 2021-08-31
  • 1970-01-01
相关资源
最近更新 更多