【问题标题】:How do I optimize this method for breaking a string in chunks?如何优化此方法以将字符串分成块?
【发布时间】:2010-10-27 21:04:10
【问题描述】:

方法如下。我想知道我是否违反了这里的任何最佳实践,或者就语言而言我是否做错了什么。

private List<String> breakStringInChunks(String text, int chunkSize) {
        List<String> chunks = new ArrayList<String>();
        String temporary = "";
        int numberOfChunks = text.length() / chunkSize;
        int beginIndex = 0;
        int endIndex = 0;

        // Add one iteration if numberOfChunks*chunkSize is less than the length of text.
        if ((numberOfChunks * chunkSize) < text.length()) {
            numberOfChunks++;
        }

        // Cut strings and add in the list.
        for (int i = 0; i < numberOfChunks; i++) {
            endIndex+=chunkSize;
            if ((i + 1) == numberOfChunks) {
                temporary = text.substring(beginIndex);
            }
            else {
                temporary = text.substring(beginIndex, endIndex);
            }
            beginIndex=endIndex;
            chunks.add(temporary);
        }

        return chunks;
    }

【问题讨论】:

  • 出于好奇,你为什么要分块字符串?

标签: java algorithm optimization string


【解决方案1】:

更简单,避免对结果列表进行潜在的调整。

private static List<String> breakStringInChunks(final String text, final int chunkSize) {
    final int numChunks = 0 == (text.length() % chunkSize) ? text.length() / chunkSize : 1 + (text.length() / chunkSize);
    final List<String> chunks = new ArrayList<String>(numChunks);
    for (int startIndex = 0; startIndex < text.length(); startIndex += chunkSize) {
        final int endIndex = Math.min(text.length(), startIndex + chunkSize);
        chunks.add(text.substring(startIndex, endIndex));
    }
    return chunks;
}

【讨论】:

  • 尼克,这是一个不错的解决方案。
  • 实际上,我是尼克原始答案的评论者。在我意识到他已经看到我的评论之前,我开始修改他的解决方案。
  • 我认为如果 text.length() 可以直接被 chunksize() 整除,这会分配一个长度为 1 个元素的数组,并将一个空字符串 ("") 作为最后一个块
  • 是的,尼克。相应更新。
  • 清洁器直接在 startIndex 上循环? for (int startIndex = 0; startIndex
【解决方案2】:

除非我误解了你的意图,否则这似乎是一个巨大的矫枉过正,并且多次使用字符串创建,使得算法在 java 中效率很低,因为字符串是不可变的。

试试这个:

public List<String> breakStringsInChunks(String text,int chunkSize) {
    if (chunkSize<=1) {
        throw new IllegalArgumentException("Chunk size must be positive");
    }
    if (text==null || text.isEmpty()) {
        return Collections.emptyList();
    }

    List<String> chunks= new LinkedList<String>();

    int index=0;
    int len = text.length();

    //guaranteed to succeed at least once since 0 length strings we're taken care of
    do {
        chunks.add(text.substring(index, Math.min(index + chunkSize, len)));
        index+=chunkSize;
    } while (index<len);

    return chunks;
}

【讨论】:

  • 这也不错。为什么选择 LinkedList 而不是 ArrayList?
  • 肯定它/is/相当有效/因为/字符串是不可变的? String.substring() 可以返回一个指向原始数据的新字符串,而无需复制它。此外,您的答案对 substring 的调用次数相同。
  • LinkedList 通过在创建/添加节点时为节点分配内存来避免预先分配大量内存(尽管由于引用的局部性差,这可能会减慢稍后的读取速度),并避免添加时不可预测的减速到列表(因为有时 ArrayList 需要增加它的支持数组,这涉及将已经存在的所有内容复制到新的更大的位置)。在general中,如果您知道要分配多少空间或者您有理由确定它不需要经常调整大小,则 ArrayList 会更好。
  • 由于我对 list 所做的 90% 以上是使用它们作为默认集合类型进行迭代,因此我无论如何都不使用 get(i)。当任务相当本地化并且我需要良好的性能时,我倾向于使用 ArrayLst,从那时起我转储 for..each 模式并直接在索引上迭代。
  • 即使只是对结果进行迭代也可能在 ArrayList 中更快(内存是连续的,而 LinkedList 引入了碎片的可能性)。在我不知道列表最终会有多大的情况下,我个人更喜欢 LinkedList,但在这种情况下我确实知道,所以我更喜欢 ArrayList。
【解决方案3】:

这有点冗长,并且不需要在方法的开头声明temporary 字符串,这可能会使垃圾收集速度变慢。以下内容会更简洁:

private List<String> breakStringInChunks(String text, int chunkSize) {
    int nChunks = (int)Math.ceil(((double)text.length())/chunkSize));
    List<String> chunks = new ArrayList<String>(nChunks);
    // Cut strings and add in the list.
    for (int i = 0; i < text.length(); i+=chunkSize) {
        int endIndex=i+chunksize;
        if (endIndex >= text.length()) {
            chunks.add(text.substring(i));
        } else {
            chunks.add(text.substring(i, endIndex));
        }
    }
    return chunks;
}

你的方法和上面的文字有一个好处是,因为你总是在原始字符串上调用 substring(),Java 只会引用原始字符数组,所以它会为你节省一些内存分配。

我认为} else { 是更常见的 Java 编码标准。

【讨论】:

  • 如果会有很多块,分配给 ArrayList 的初始容量我们知道就足够了,例如 final List chunks = new ArrayList(1 + (text.长度()/块大小));
  • 同意,我会编辑。严格来说,我猜它应该是一个细胞
  • 我认为在 for 循环中应该是 i += chunkSize 而不是 i++ ,否则字符串中的每个字符都会有一个块。我同意 beginIndex 变量不是必需的。
  • 相应地,测试应该是 i
  • 如果长度不能被chunkSize整除,则最后一个chunk是不正确的。
【解决方案4】:
public static final List<String> chunk(final String text, final int chunkSize) {
    // Figure out how many chunks we are going to make.
    final int textLength = text.length();
    final int numberOfChunks =
        textLength % chunkSize == 0
        ? textLength / chunkSize
        : textLength / chunkSize + 1;

    // Create an array list of just the right size.
    final ArrayList<String> chunks = new ArrayList<String>(numberOfChunks);

    // Do all the chunking but the last one - here we know that all chunks
    // are exactly chunkSize long.
    for (int i = 0; i < numberOfChunks - 1; i++) {
        chunks.add(text.substring(i * chunkSize, (i + 1) * chunkSize));
    }

    // Add final chunk, which may be shorter than chunkSize, so we use textLength
    // as the end index.
    chunks.add(text.substring((numberOfChunks - 1) * chunkSize, textLength));

    return chunks;
}

【讨论】:

    【解决方案5】:

    这是我的解决方案。我试图以非常有效的方式实现这一点:

    public static List<String> breakStringInChunks(String text, int chunkSize) {
        if (chunkSize < 2) {
            throw new IllegalArgumentException("Chunk size must be > 1");
        }
        if (null == text || text.isEmpty()) {
            return Collections.emptyList();
        }
    
        List<String> chunks = new ArrayList<String>(1 + (text.length() / chunkSize));
    
        int length = text.length() - (text.length() % chunkSize);
    
        for (int i = 0; i < length;) {
            chunks.add(text.substring(i, i += chunkSize));
        }
        if (length < text.length())
            chunks.add(text.substring(length));
    
        return chunks;
    }
    

    【讨论】:

      【解决方案6】:

      这样的事情怎么样?

      private List<String> breakStringInChunks(String text, int chunkSize)
      {
          List<String> chunks = new ArrayList<String>();
          while (text.length() > 0)
          {
              if (chunkSize > text.length())
              {
                  chunkSize = text.length();
              }
              chunks.add(text.substring(0, chunkSize));
              text = text.substring(chunkSize);
          }
          return chunks;
      }
      

      【讨论】:

      • 这将锁定在无限循环中,一遍又一遍地添加第一个块 - String.length() 不会改变。我认为您正在考虑一个流接口,由于实际执行原始数据的副本,我怀疑它会比较慢。
      • 字符串长度确实改变。循环的最后一行实质上是从“text”变量中删除已处理的字符。
      • 这里的问题是,正在修改参数。
      • 不!参数仅在方法内被修改。此方法的调用者的值不会改变! “String”是不可变的,“int”是按值传递的。不过感谢您的关注:)
      • 这是一个清晰而简单的算法,但是分配给“文本”的新字符串的创建在时间和内存上可能非常昂贵。如果字符串很大,块大小很小,和/或方法被调用很多,我会选择 substring() 方法。
      【解决方案7】:

      这是我的。与其他一些答案没有太大区别,但是测试驱动的,fwiw。

      public class ChunkTest extends TestCase {
          public void testEmpty() throws Exception {
              assertEquals(0, breakStringInChunks("", 1).size());
          }
      
          public void testOneChunk() throws Exception {
              String s = "abc";
              List<String> chunks = breakStringInChunks(s, s.length());
              assertEquals(s, chunks.get(0));
              assertEquals(1, chunks.size());
          }
      
          public void testPartialChunk() throws Exception {
              String s = "abc";
              List<String> chunks = breakStringInChunks(s, s.length() + 1);
              assertEquals(s, chunks.get(0));
              assertEquals(1, chunks.size());
          }
      
          public void testTwoChunks() throws Exception {
              String s = "abc";
              List<String> chunks = breakStringInChunks(s, 2);
              assertEquals("ab", chunks.get(0));
              assertEquals("c", chunks.get(1));
              assertEquals(2, chunks.size());
          }
      
          public void testTwoEvenChunks() throws Exception {
              String s = "abcd";
              List<String> chunks = breakStringInChunks(s, 2);
              assertEquals("ab", chunks.get(0));
              assertEquals("cd", chunks.get(1));
          }
      
          private List<String> breakStringInChunks(String text, int chunkSize) {
              if (text.isEmpty())
                  return Collections.emptyList();
              int n = (text.length() + chunkSize - 1) / chunkSize;
              List<String> chunks = new ArrayList<String>(n);
              for (int i = 0; i < n; ++i)
                  chunks.add(text.substring(i * chunkSize, Math.min((i + 1) * chunkSize, text.length())));
              return chunks;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2013-04-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-07
        • 2013-11-16
        • 2010-11-29
        • 2021-03-14
        相关资源
        最近更新 更多