具体问题是这样的:我正在写一个文件,并希望在每行写完后输出新行。如果我使用普通循环而不作任何进一步检查,这将无缘无故在文件的末尾创建一个空行。因此,除了最后一个问题,我每次都需要这样做。

尽管这是一个特定的问题,但实际上我正在寻找一种更好的通用编码实践来处理这种情况,因为这不是我第一次也不会是最后一次。

这是我不满意的解决方案:

//Write contents to the file
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for(int i = 0; i < lines.size(); i++) {
    writer.write(lines.get(i));
    if(i < lines.size() - 1) writer.newLine();
}


在循环的每次迭代中两次检查条件似乎很浪费,我觉得应该有一种更好的方法来完成我想要的而没有模糊的代码味道。任何人都可以使用任何有趣的技巧或窍门来使其更优雅?

它还阻止我使用增强的for循环,这使我感到难过。

另外,对于那些说我应该将所有linesList<String>一起加入的人,这不是一个适当的解决方案。首先,它实际上并未解决一般的编码实践。其次,当使用String写入文件时,使用\n而不是编写BufferedWriter很重要。

评论

*有人不得不说这...一个文本文件是一系列的行。一行包含换行符终止符。没有换行符作为其最终字符的文件不是文本文件。以空行结尾的文件的最后2个字符具有2个连续的换行符:一个用于终止倒数第二行,另一个用于终止空行。最后一行未终止的“文本文件”是一种疾病。不要抓住它!

@ WumpusQ.Wumbley在什么情况下有必要?我并不是说没有,我只是什么都不知道。任意声明所有文本文件都必须遵循的格式似乎过于雄心勃勃。

@ WumpusQ.Wumbley可能是Windows的事情,来自维基百科。文本文件:“ MS-DOS和Windows使用通用的文本文件格式,每一行文本由两个字符的组合分隔:CR和LF,它们具有ASCII码13和10。通常情况下,文本的最后一行不以CR-LF标记结尾,并且许多文本编辑器(包括记事本)不会自动在最后一行插入一行。“

@DanielCook不仅仅是Windows。如果最后一行不是以LF结尾,则许多Unix Shell实用程序(尤其是较旧的Unix实用程序)的行为将无法预测。另一方面,有时缺少最终的LF是必不可少的,因为使用PHP生成XML时-如果PHP源文件在最终的?>之后具有LF,它将被复制到输出中,并且可能会在最后XML解析器不喜欢它的地方。因此,这绝不是硬性规定。

@ WumpusQ.Wumbley-16位Visual C ++编译器之一存在一个错误,该错误会在源文件未以CR / LF结尾时显示。但是在过去的15年中,我再也没有遇到过此类问题。

#1 楼

我假设lines是某种形式的集合。一种气味稍少(尽管它仍然是有气味的)的选择是使用迭代器,该迭代器基本上可以完成相同的工作,但可读性更高:
正如我所说,所有这些都是为了使其更具可读性。

for (Iterator<String> it = lines.iterator(); it.hasNext();) {
    writer.write(it.next());
    if (it.hasNext()) {
        writer.newline();
    }
}


编辑:@tomdemuyt建议仅在第一行之后反转换行,如下所示:

if (!lines.isEmpty()) {
    int limit = lines.size() - 1;
    for (int i = 0; i < limit; i++) {
        ....
    }
    writer.write(lines.get(limit));
}


评论


\ $ \ begingroup \ $
是的,行是List 。真好我喜欢在循环外部执行最后一次写操作而不是不断检查循环内部的想法。在我看来仍然有些模糊,但比我现在所拥有的要好。谢谢。 :)
\ $ \ endgroup \ $
–asteri
2013年12月13日15:15

\ $ \ begingroup \ $
我倾向于写没有换行符的第一个,然后为其余的写换行符+ nextline,从1开始循环。
\ $ \ endgroup \ $
– konijn
2013年12月13日15:22

\ $ \ begingroup \ $
@tomdemuyt-回答一下-这是一个很好的解决方案。
\ $ \ endgroup \ $
–rolfl
2013年12月13日15:23在

\ $ \ begingroup \ $
@tomdemuyt既然您提到了,那实际上可能就是我最终要做的事情。您绝对应该将其发布为答案。
\ $ \ endgroup \ $
–asteri
13年12月13日在15:34

\ $ \ begingroup \ $
我之所以没有这样做,是因为在我的情况下,我总是至少有2行,现在如果只有1行,则代码可能会失败,然后再次变得难看。
\ $ \ endgroup \ $
– konijn
2013年12月13日在21:03

#2 楼

这种情况通常与连接字符串有关。在Apache Commons Lang中有一种方法可以实现以下目的: />

评论


\ $ \ begingroup \ $
我不确定该代码是否可以解决问题……!lines.isEmpty()在每次循环运行时都将返回true。
\ $ \ endgroup \ $
–rolfl
2013年12月13日15:39

\ $ \ begingroup \ $
我的错误:我的意思是buf.isEmpty()
\ $ \ endgroup \ $
–rzymek
2013年12月13日15:46

#3 楼

完成后,只需删除最后一个分隔符即可完成此操作:多个字符。

您的情况:separator

评论


\ $ \ begingroup \ $
如果lines.Length == 0,则此方法不起作用。
\ $ \ endgroup \ $
–马特
2013年12月14日下午5:59

\ $ \ begingroup \ $
@Matt固定,也将它们放在函数中。
\ $ \ endgroup \ $
– AJMansfield
13年12月14日在17:23

#4 楼

在一般情况下,您可以将第一行或最后一行从循环中拉出,或使用break语句将循环出口移至循环的中间-修改rolfl的示例:

Iterator<String> it = lines.iterator()
if (it.hasNext()) {
    while (true) {
        writer.write(it.next());
        if (!it.hasNext()) 
            break;
        writer.newline();
    }
}


“使用goto语句进行结构化编程”是处理非标准循环情况的经典文章。

评论


\ $ \ begingroup \ $
我最喜欢这种模式,因为:它不需要您重复代码-“ writer.write”仅出现一次。 b。它不需要您在循环内重复测试-循环退出测试仅在循环中间执行一次。
\ $ \ endgroup \ $
– Erel Segal-Halevi
2013年12月15日15:24

\ $ \ begingroup \ $
为此,需要在Java中添加一个更好的习惯用法:只需让用户在同一时间之前和之后都有一个块。像这样:do {doSomething(); } while(condition()){doSomethingElse(); }
\ $ \ endgroup \ $
– AJMansfield
2013年12月17日下午2:39

#5 楼

在@rolfls的答案上稍作改动:

评论


\ $ \ begingroup \ $
嗯...您来自JavaScript吗?我不认为if(lines.size())会编译。
\ $ \ endgroup \ $
–asteri
2013年12月13日15:51

\ $ \ begingroup \ $
确实-这是我想表达的逻辑,而不是语法;)
\ $ \ endgroup \ $
–masssey
2013年12月13日在16:05

\ $ \ begingroup \ $
FWIW,我个人认为这可能是最好的方法。当我终于注意到有人已经有这个想法时,我正打算发表同样的想法。我更喜欢这样的原因是,如果一开始就进行检查,则只有一个。大多数其他解决方案在每次运行时都需要进行某种比较。在这种情况下,我们可以通过for循环消除不必要的比较。我的两分钱
\ $ \ endgroup \ $
– Charlie74
2013年12月13日在21:12

\ $ \ begingroup \ $
lines.size()> 0很la脚,并且可能有害(如果line是单链表,则知道它的大小可能是O(n))。如果较早版本不可用,请使用lines.nonEmpty()或!lines.isEmpty()。
\ $ \ endgroup \ $
– scand1sk
2013年12月14日15:44

#6 楼

稍微简单一点:

BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for(int i = 0; i < lines.size(); i++) {
    if(i > 0) writer.newLine();
    writer.write(lines.get(i));
}


#7 楼

为什么不扭转它:除了第一行外,先写一个换行符:

boolean newline = false;
for(int i = 0; i < lines.size(); i++) {
    if(newline) writer.newLine();
    else newline = true;
    writer.write(lines.get(i));
}


#8 楼

您的代码中有两个问题:正如您所注意到的,尽管您知道if仅在最后一个循环中无效,但在每个循环中都会对其进行检查。避免此问题的一种好方法是在进入循环之前(或之后)特别对待列表的第一个或最后一个元素。单独处理第一个元素要容易得多。
请注意,如果您假设计算if是恒定时间的,则size检查可能会非常便宜。 (请注意,大小可能在循环之前计算一次。)优化可能完全可以忽略(特别是因为您正在循环中执行昂贵的I / O)。 :如果linesList,则可能不会被索引(例如,linesLinkedList)。然后调用lines.get(i)可能是O(i),尽管可以执行O(n),但整个循环是O(n²)。建议使用Iterator作为@rolfl,是避免此问题的最佳方法。根据您的经验,它可能会或可能不会提高可读性,但是根据您的List的性质,它肯定会大大提高性能。


BTW,此问题通常在Java中解决。 API:在toString中查找AbstractCollection的实现(只需用自己的分隔符替换,并删除对e == this的测试就很具体了):可定义的分隔符将在Apache Commons或Google Guava中找不到。
无论如何,这是最终代码,使用BufferedWriter而不是StringBuilder
public String toString() {
    Iterator<E> it = iterator();
    if (! it.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = it.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! it.hasNext())
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}


评论


\ $ \ begingroup \ $
是的,我知道我可以按照您的建议将各种字符串与\ n一起加入。但是,当使用BufferedWriter写入文件时,应该改用newLine(),因为它与系统无关。
\ $ \ endgroup \ $
–asteri
2013年12月14日17:16



\ $ \ begingroup \ $
好了,您仍然可以使用上面的结构,用BufferedWriter替换对StringBuilder的引用。我在上面编辑了答案,以包含最终代码。
\ $ \ endgroup \ $
– scand1sk
2013年12月14日在21:58



#9 楼

BufferedWriter writer = new BufferedWriter(new FileWriter(file));
   int i =0;
   for(;i < lines.size()-1; i++)
   {
     writer.write(lines.get(i));
     writer.newLine();
   }
   if(lines.size()>0)
   {
   writer.write(lines.get(i));
   }


这样,您可以每次都避免使用条件语句,从而使您的代码保持相同。

#10 楼

绝对,绝对要使if语句脱离循环。人们总是谈论“优化器”,但是优化器是不同的,您可以采取任何措施来帮助它可能是一个好主意。

//Write contents to the file
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
for(int i = 0; i < lines.size() - 1; i++) {
    writer.write(lines.get(i));
    writer.newLine();
}

// Write the last one without extra newline
if( lines.size() )
  writer.write(lines.get(lines.size()-1));


评论


\ $ \ begingroup \ $
如果lines.Length == 0,则此方法不起作用。
\ $ \ endgroup \ $
–马特
2013年12月14日下午6:01

\ $ \ begingroup \ $
@Matt好点。固定。
\ $ \ endgroup \ $
– bobobobo
2013年12月14日14:03在

#11 楼

如果将回路限制从lines.size()更改为lines.size() -1,则不需要条件检查。这样可确保跳过lines中的最后一个条目。接下来,在循环之后,编写line的最后内容。

//Write contents to the file
 BufferedWriter writer = new BufferedWriter(new FileWriter(file));
 int last = lines.size()-1;
 for(int i = 0; i < last; i++) {
     writer.write(lines.get(i));
     writer.newLine();  //no condition check anymore
  }

 //write last line content
  writer.write(lines.get(last));