Java中for循环增强的最后一次迭代


140

有没有一种方法可以确定循环是否在最后一次迭代。我的代码如下所示:

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for(int i : array)
{
    builder.append("" + i);
    if(!lastiteration)
        builder.append(",");
}

现在,我不想在最后一次迭代中添加逗号。现在有一种方法可以确定是最后一次迭代,还是我陷入了for循环还是使用外部计数器来跟踪。


1
是的 很好笑,我只想问同样的问题!
PhiLho

同样的问题也会返回(并且也会如此)。现在,如果一个元素需要不同的处理,为什么还要创建一个循环? stackoverflow.com/questions/156650/…–
xtofl

既然您有固定的数组,为什么要使用增强型?for(int i = 0; i <array.length; i ++ if(i <array.lenth),,,
AnthonyJClink

Answers:


223

另一种选择是在添加i之前添加逗号,只是不在第一次迭代中添加。("" + i顺便说一句,请不要使用,因为您实际上并不需要串联,并且StringBuilder具有非常好的append(int)重载。)

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();

for (int i : array) {
    if (builder.length() != 0) {
        builder.append(",");
    }
    builder.append(i);
}

这样做的好处是,它可以与任何对象一起使用Iterable-您不能总是对事物进行索引。(当您真正使用StringBuilder时,“添加逗号,然后最后将其删除”是一个不错的建议-但它不适用于写入流之类的东西。尽管如此,这可能是解决此确切问题的最佳方法。 )


2
良好的模式,但是builder.length()!= 0很脆弱-假设在循环之前将某些东西(有条件地!)添加到了缓冲区中。而是使用isFirst布尔标志。奖励:也更快。
杰森·科恩

2
@Jason:我当然使用了“ isFirst”模式,其中构建器在第一次迭代中不会为空。但是,根据我的经验,当不需要它时,会给实现带来很大的膨胀。
乔恩·斯基特

3
@Liverpool(等):非常非常不可能对性能进行任何1000次不必要的检查。我可能同样会指出,由Dinah解决方案添加的额外字符可能会导致StringBuilder不得不扩展,从而使最终字符串的大小加倍(续)
Jon Skeet

2
不必要的缓冲区空间。但是,重点是可读性。我碰巧发现我的版本比Dinah的版本更具可读性。如果您觉得相反,那就很好。在发现瓶颈之后,我只会考虑不必要的“ if”对性能的影响。
乔恩·斯基特

3
另外,我的解决方案是一种更普遍适用的解决方案,因为它仅依赖于能够编写。您可以采用我的解决方案并将其更改为写入流等-之后,您可能无法收回不必要的数据。我喜欢通常适用的模式。
乔恩·斯基特

145

另一种方法是:

String delim = "";
for (int i : ints) {
    sb.append(delim).append(i);
    delim = ",";
}

更新:对于Java 8,您现在有了收集器


1
必须删除我的类似答案-这会教我不要在更仔细地查看其他答案之前不发布。
Michael Burr

1
还要注意,这可以解决Robert Paulson在另一个注释线程中提到的潜在问题-此技术不依赖于在附加数组值时StringBuilder为空。
Michael Burr

1
尽管代码更加流畅/简洁/有趣,但是它比if版本更不明显。始终使用更明显/更易读的版本。
Bill K

8
@Bill:这不是一成不变的规则;有时“聪明”的解决方案就是“更正确”的解决方案。更重要的是,您真的认为这个版本很难阅读吗?无论哪种方式,维护人员都需要逐步解决-我认为差异并不明显。
Greg Case

即使您写入其他资源(例如文件系统的write调用),此方法仍然有效。好主意。
Tuxman

39

总是附加可能会更容易。然后,当您完成循环时,只需删除最后一个字符即可。这样,也就减少了附加条件。

您可以使用StringBuilderdeleteCharAt(int index)索引之中length() - 1


5
解决此问题的最有效方法。+1。
真正的红色。

9
也许是我,但我真的不喜欢您要删除刚刚添加的内容的事实……
Fortega,2013年

2
Fortega:我不喜欢每次都检查我99%会做的事情。应用Dinah或Sblundy的解决方案似乎更合乎逻辑(而且速度更快)。
Buffalo

1
我认为最优雅的解决方案。谢谢!
查尔斯·莫林

32

也许您为作业使用了错误的工具。

这比您的操作要手动得多,但即使没有一点“老派”,也可以说更优雅

 StringBuffer buffer = new StringBuffer();
 Iterator iter = s.iterator();
 while (iter.hasNext()) {
      buffer.append(iter.next());
      if (iter.hasNext()) {
            buffer.append(delimiter);
      }
 }


14

另一个解决方案(也许是最有效的)

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();

    if (array.length != 0) {
        builder.append(array[0]);
        for (int i = 1; i < array.length; i++ )
        {
            builder.append(",");
            builder.append(array[i]);
        }
    }

这是一个自然的解决方案,只是它取决于数组不为空。
orcmid

5
@orcmid:如果数组为空,则仍会给出正确的输出-空字符串。我不确定你的意思是。
Eddie


6

显式循环总是比隐式循环更好地工作。

builder.append( "" + array[0] );
for( int i = 1; i != array.length; i += 1 ) {
   builder.append( ", " + array[i] );
}

您应该将整个内容包装在if语句中,以防万一您要处理零长度的数组。


我喜欢这种方法,因为它不需要在每次迭代时都进行测试。我唯一要补充的是,构建器可以直接附加整数,因此无需使用“” + int,只需添加append(array [0])。
乔什

如果需要处理整个批次,则需要再添加一个以确保数组至少包含一个元素。
汤姆·莱斯

2
为什么每个人都继续使用带有附加的字符串串联的示例?“,” + x编译为新的StringBuilder(“,”)。append(x).toString()...
John Gardner

@Josh和@John:仅遵循n00b示例。不想一次介绍太多东西。但是,您的观点很好。
S.Lott

@汤姆:对。我想我已经说过了。无需编辑。
S.Lott

4

如果将其转换为经典索引循环,则可以。

或者,您也可以在完成后删除最后一个逗号。像这样:

int[] array = {1, 2, 3...};
StringBuilder

builder = new StringBuilder();

for(int i : array)
{
    builder.append(i + ",");
}

if(builder.charAt((builder.length() - 1) == ','))
    builder.deleteCharAt(builder.length() - 1);

我,我只是StringUtils.join()commons-lang使用


3

您需要Class Separator

Separator s = new Separator(", ");
for(int i : array)
{
     builder.append(s).append(i);
}

类的实现Separator很简单。它包装一个字符串,该字符串在toString()除第一个调用之外的每个调用中返回,该第一个调用返回一个空字符串。


3

基于java.util.AbstractCollection.toString(),它提早退出以避免使用分隔符。

StringBuffer buffer = new StringBuffer();
Iterator iter = s.iterator();
for (;;) {
  buffer.append(iter.next());
  if (! iter.hasNext())
    break;
  buffer.append(delimiter);
}

它高效,优雅,但不像其他一些答案那样不言而喻。


您忘记了在时包含早期回报(! i.hasNext()),这是整体方法的鲁棒性的重要组成部分。(这里的其他解决方案可以优雅地处理空集合,所以您也应该这样做!:)
Mike Clark

3

这是一个解决方案:

int[] array = {1, 2, 3...};
StringBuilder builder = new StringBuilder();
bool firstiteration=true;

for(int i : array)
{
    if(!firstiteration)
        builder.append(",");

    builder.append("" + i);
    firstiteration=false;
}

寻找第一次迭代:)  


3

如工具包所述,在Java 8中,我们现在有了Collector。代码如下所示:

String joined = array.stream().map(Object::toString).collect(Collectors.joining(", "));

我认为这确实可以满足您的需求,并且可以将其用于许多其他事情。


1

另一个选择。

StringBuilder builder = new StringBuilder();
for(int i : array)
    builder.append(',').append(i);
String text = builder.toString();
if (text.startsWith(",")) text=text.substring(1);

我在另一个问题上做了一个
改动

1

此处描述的许多解决方案有点过头,恕我直言,尤其是那些依赖于外部库的解决方案。有一个很好的干净清晰的习惯用法,用于实现我一直使用的逗号分隔列表。它依赖于条件(?)运算符:

编辑:原始解决方案正确,但根据评论不是最佳的。再次尝试:

    int[] array = {1, 2, 3};
    StringBuilder builder = new StringBuilder();
    for (int i = 0 ;  i < array.length; i++)
           builder.append(i == 0 ? "" : ",").append(array[i]); 

您只需4行代码,包括数组的声明和StringBuilder,就可以了。


但是,您需要在每次迭代中创建一个新的StringBuilder(这要感谢+运算符)。
迈克尔·迈尔斯

是的,如果您查看字节码,那是正确的。我不敢相信如此简单的问题会变得如此复杂。我想知道编译器是否可以在这里进行优化。
Julien Chastang

提供了第二个,希望是更好的解决方案。
朱利安·查斯顿

1

这是我通过这些结果运行的SSCCE基准测试(与我必须实施的测试有关):

elapsed time with checks at every iteration: 12055(ms)
elapsed time with deletion at the end: 11977(ms)

至少在我的例子,在每次迭代跳过检查并无明显改善尤其是对数据的健全卷,但它速度更快。

import java.util.ArrayList;
import java.util.List;


public class TestCommas {

  public static String GetUrlsIn(int aProjectID, List<String> aUrls, boolean aPreferChecks)
  {

    if (aPreferChecks) {

      StringBuffer sql = new StringBuffer("select * from mytable_" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {

        if (inHashes.length() > 0) {
        inHashes.append(",");
        inURLs.append(",");
        }

        inHashes.append(url.hashCode());

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"");//.append(",");

      }

      }

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

    else {

      StringBuffer sql = new StringBuffer("select * from mytable" + aProjectID + " WHERE hash IN ");

      StringBuffer inHashes = new StringBuffer("(");
      StringBuffer inURLs = new StringBuffer("(");

      if (aUrls.size() > 0)
      {

      for (String url : aUrls)
      {
        inHashes.append(url.hashCode()).append(","); 

        inURLs.append("\"").append(url.replace("\"", "\\\"")).append("\"").append(",");
      }

      }

      inHashes.deleteCharAt(inHashes.length()-1);
      inURLs.deleteCharAt(inURLs.length()-1);

      inHashes.append(")");
      inURLs.append(")");

      return sql.append(inHashes).append(" AND url IN ").append(inURLs).toString();
    }

  }

  public static void main(String[] args) { 
        List<String> urls = new ArrayList<String>();

    for (int i = 0; i < 10000; i++) {
      urls.add("http://www.google.com/" + System.currentTimeMillis());
      urls.add("http://www.yahoo.com/" + System.currentTimeMillis());
      urls.add("http://www.bing.com/" + System.currentTimeMillis());
    }


    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, true);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("elapsed time with checks at every iteration: " + (endTime-startTime) + "(ms)");

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; i++) {
      GetUrlsIn(5, urls, false);
    }
    endTime = System.currentTimeMillis();
    System.out.println("elapsed time with deletion at the end: " + (endTime-startTime) + "(ms)");
  }
}

1
请不要发布自己的基准测试,并使用OpenJDK自己的微型基准测试工具(jmh)。它将预热您的代码,并避免最有问题的陷阱。
勒内

0

另一种方法是将数组的长度(如果可用)存储在单独的变量中(比每次重新检查长度都更有效)。然后,您可以将索引与该长度进行比较,以确定是否添加最终逗号。

编辑:另一个考虑因素是权衡删除最终字符(可能会导致字符串复制)的性能成本,而不是在每次迭代中都要检查条件。


删除最后一个字符不应导致复制字符串。StringBuffer的delete / deleteCharAt方法最终在System类内部调用相同的源数组和目标数组的以下方法:System-> public static native void arraycopy(Object src,int srcPos,Object dest,int destPos,int length);
Buffalo

0

如果您只是将一个数组变成一个逗号分隔的数组,那么许多语言都具有连接功能。它将数组转换为字符串,每个元素之间都有定界符。


0

在这种情况下,实际上不需要知道是否是最后一次重复。我们有很多方法可以解决这个问题。一种方法是:

String del = null;
for(int i : array)
{
    if (del != null)
       builder.append(del);
    else
       del = ",";
    builder.append(i);
}

0

这里有两个备用路径:

1:Apache Commons字符串实用程序

2:保留一个名为的布尔值first,设置为true。在每次迭代中,如果first为false,请附加逗号;否则为false。之后,将其设置first为false。


1)链接已失效2)我花了较长的时间而不是代码来阅读描述:)
Buffalo

0

由于它是固定数组,因此避免使用for ...如果对象是一个集合,则迭代器会更容易。

int nums[] = getNumbersArray();
StringBuilder builder = new StringBuilder();

// non enhanced version
for(int i = 0; i < nums.length; i++){
   builder.append(nums[i]);
   if(i < nums.length - 1){
       builder.append(",");
   }   
}

//using iterator
Iterator<int> numIter = Arrays.asList(nums).iterator();

while(numIter.hasNext()){
   int num = numIter.next();
   builder.append(num);
   if(numIter.hasNext()){
      builder.append(",");
   }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.