【问题标题】:Is there a better way to traverse a string in java?有没有更好的方法来遍历java中的字符串?
【发布时间】:2013-07-18 22:12:04
【问题描述】:

请多多包涵。我已经一年多没有编程了,我目前正在通过做“家庭作业问题”来审查我的 Java 以进行工作面试。我的函数应该返回一个包含给定字符串中每隔一个字符的字符串。有没有不那么尴尬的方式来做到这一点?

public String stringBits(String str) {
  StringBuffer tmp = new StringBuffer();
  for(int i = 0; i<str.length(); i+=2)
    tmp.append(str.charAt(i));
  String ret = new String(tmp);
  return ret;

【问题讨论】:

  • 你应该包含第一个字符吗?只有跳出的修复是您应该使用return tmp.toString() 而不是使用new String(tmp),并使用StringBuilder 而不是StringBuffer
  • 谢谢。你能解释一下为什么使用 StringBuilder 是一个更好的主意吗?
  • StringBuffer 是同步的,而StringBuilder 不是,因此对于单线程代码,使用 StringBuilder 更有效。事实上,现在使用 StringBuilder 几乎总是更好,因为您很少会在线程之间共享此类的实例。

标签: java string coding-style


【解决方案1】:

我会使用StringBuilder,而不是StringBufferStringBuffer 用于多线程情况,因此比 StringBuilder 慢,因为它不同步。我测试了此线程中各种答案列出的四种基本方法。 不过,请注意我在这里经常做的某些事情;这些应该是你的面试官真正想要的东西:

  • 我从不使用 String += nextCharacter;,因为它比使用 StringBuilder 慢得多。
  • 我设置了initialCapacity,因为这样做总是更快。如果不这样做,如果StringBuilder 已满,它必须重新分配一个新数组并复制过来,这很慢。

还有代码:

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Random;

public class EveryOtherTest {
    public static class StringBenchmark extends SimpleBenchmark {
        private String input;

        protected void setUp() {
            Random r = new Random();
            int length = r.nextInt(1000) + 1000;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < length; i++) {
                sb.append((char) ('A' + r.nextInt(26)));
            }
            input = sb.toString();
        }

        public String timeCharArrayForeach(int reps) {
            String output = "";
            Random r = new Random();
            for (int i = 0; i < reps; i++) {
                StringBuilder sb = new StringBuilder(input.length() / 2 + 1);
                boolean use = false;
                for (char c : input.toCharArray()) {
                    if(use) sb.append(c);
                    use = !use;
                }
                String newOutput = sb.toString();
                if (r.nextBoolean()) output = newOutput; // Trick the JIT
            }

            return output;
        }

        public String timeCharArrayPlusTwo(int reps) {
            String output = "";
            Random r = new Random();
            for (int i = 0; i < reps; i++) {
                StringBuilder sb = new StringBuilder(input.length() / 2 + 1);
                char[] charArray = input.toCharArray();
                for(int j = 0; j < input.length(); j += 2) {
                    sb.append(charArray[j]);
                }
                String newOutput = sb.toString();
                if (r.nextBoolean()) output = newOutput; // Trick the JIT
            }

            return output;
        }

        public String timeCharAt(int reps) {
            String output = "";
            Random r = new Random();
            for (int i = 0; i < reps; i++) {
                StringBuilder tmp = new StringBuilder(input.length() / 2 + 1);
                for (int j = 0; j < input.length(); j += 2) {
                    tmp.append(input.charAt(j));
                }
                String newOutput = tmp.toString();
                if (r.nextBoolean()) output = newOutput; // Trick the JIT
            }

            return output;
        }

        public String timeIterator(int reps) {
            String output = "";
            Random r  = new Random();
            for(int i = 0; i < reps; i++) {
                StringBuilder buf = new StringBuilder(input.length() / 2 + 1);
                StringCharacterIterator iterator = new StringCharacterIterator(input);
                for (char c = iterator.first(); c != CharacterIterator.DONE; c = iterator.next()) {
                    buf.append(c);
                    iterator.next();
                }
                String newOutput = buf.toString();
                if (r.nextBoolean()) output = newOutput; // Trick the JIT
            }

            return output;
        }

        public String timeRegex(int reps) {
            String output = "";
            Random r  = new Random();
            for(int i = 0; i < reps; i++) {
                String newOutput = input.replaceAll("(?<!^).(.)", "$1");
                if (r.nextBoolean()) output = newOutput; // Trick the JIT
            }

            return output;
        }
    }

    public static void main(String... args) {
        Runner.main(StringBenchmark.class, args);
    }
}

结果:

 0% Scenario{vm=java, trial=0, benchmark=CharArrayForeach} 2805.55 ns; ?=688.96 ns @ 10 trials
20% Scenario{vm=java, trial=0, benchmark=CharArrayPlusTwo} 3428.48 ns; ?=475.32 ns @ 10 trials
40% Scenario{vm=java, trial=0, benchmark=CharAt} 2138.68 ns; ?=379.44 ns @ 10 trials
60% Scenario{vm=java, trial=0, benchmark=Iterator} 3963.94 ns; ?=389.53 ns @ 10 trials
80% Scenario{vm=java, trial=0, benchmark=Regex} 58743.66 ns; ?=10850.33 ns @ 10 trials

       benchmark    us linear runtime
CharArrayForeach  2.81 =
CharArrayPlusTwo  3.43 =
          CharAt  2.14 =
        Iterator  3.96 ==
           Regex 58.74 ==============================

vm: java
trial: 0

【讨论】:

  • CharArray 和 CharAt 有这么大的区别,真是出乎意料。如果您使用带索引的循环,即 char[] ca = input.toCharArray(); for (int i = 0; ... i +=2) 而不是 for (char c : input.toCharArray()),那么 CharArray 的时间是多少?
  • @c.s.我不确定,但我希望它会非常相似。我认为原因是.toCharArray() 必须制作一个数组副本(因为Strings 是不可变的,它不能只提供底层数组)而charAt 没有。
  • 您能按照我的建议修改您的测试并重新检查吗?就像现在一样,测试并不等同。在 CharArray 中,for-loop 隐式使用了Iteratable,这就是结果与Iterator 情况相似的原因。如果您使用索引访问数组并使用i += 2,您将只能访问数组的 n/2 个元素,我希望它会更快,类似于 CharAt 案例
  • @c.s.我更新了测试;这样会慢很多。我认为原因是Java中检查了数组边界,所以它不仅复制数组,而且每次都检查数组边界。
  • 感谢您的反馈。这真是出乎意料。 charAt() 需要执行相同的数组索引检查,因为它在内部使用 char[]。对于数组的副本,尽管您有一点。这可能会导致延迟。
【解决方案2】:

如果您更喜欢迭代器方法,可以使用 StringCharacterIterator 类。

【讨论】:

    【解决方案3】:

    你可以使用这个等价的正则表达式

    String newString = str.replaceAll("(?<!^).(.)", "$1");
    

    【讨论】:

    • 也是迄今为止最慢的。看我的回答:)
    【解决方案4】:

    据我所知,您正在做的工作。这是一个简单的测试用例:

    package com.sandbox;
    
    import org.junit.Test;
    
    import static org.junit.Assert.assertEquals;
    
    public class SandboxTest {
    
        @Test
        public void testMocking() {
            assertEquals("foo", stringBits("f1o2o3"));
        }
    
        public String stringBits(String str) {
            StringBuffer tmp = new StringBuffer();
            for (int i = 0; i < str.length(); i += 2) {
                tmp.append(str.charAt(i));
            }
            String ret = new String(tmp);
            return ret;
        }
    }
    

    我认为这是一种非常直接的方式。可能有一种使用正则表达式和组的方法,但我觉得您当前的代码会更容易阅读。


    在看到@Joey 的答案之前,我从未听说过StringCharacterIterator,但它看起来是一个有趣的解决方案。这是使用他的答案的代码:

    package com.sandbox;
    
    import org.junit.Test;
    
    import java.text.CharacterIterator;
    import java.text.StringCharacterIterator;
    
    import static org.junit.Assert.assertEquals;
    
    public class SandboxTest {
    
        @Test
        public void testMocking() {
            assertEquals("foo", stringBits("f1o2o3"));
        }
    
        public String stringBits(String str) {
            StringBuilder buf = new StringBuilder();
            StringCharacterIterator iterator = new StringCharacterIterator(str);
            for (char c = iterator.first(); c != CharacterIterator.DONE; c = iterator.next()) {
                buf.append(c);
                iterator.next();
            }
            return buf.toString();
        }
    }
    

    【讨论】:

      【解决方案5】:

      没有。这一点也不尴尬。对于每一个有用的任务,可能都有更合适的方法,但在这种情况下,你别无选择,只能遍历字符串。

      【讨论】:

        【解决方案6】:

        我相信这也应该有效,而且对我来说看起来更简单。

        public String stringBits(String str) {
            String tmp = "";
            for(int i = 0; i<str.length(); i+=2)
                tmp+=str.charAt(i);
            return tmp;
        

        我编辑说如果你想要第二个、第四个、六个……字符,我应该等于 1。

        【讨论】:

        • 使用 StringBuffer / StringBuilder 比使用 String 连接要好得多,因为使用 += 和 String 会在对象之间生成许多不必要的对象,并且对于大字符串来说效率非常低。不会投反对票,但这不是一个特别好的解决方案。
        【解决方案7】:

        您可以将字符串转换为 CharArray 并使用 for-each 循环:

        for (char c: str.toCharArray()){
        }
        

        当然,那么你可能需要一个计数器或一个标志来获取其他所有字符,所以它可能不会不那么尴尬。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-01-01
          • 1970-01-01
          • 2018-12-12
          • 1970-01-01
          • 2019-09-30
          • 1970-01-01
          • 2013-04-23
          相关资源
          最近更新 更多