这是我创建的CString类的代码。

public class CString {

    public String reverse(String s) {
        char[] array = new char[s.length()];
        array = s.toCharArray();
        for(int i=0; i<array.length/2; i++) {
            char tmp = array[i];
            array[i] = array[array.length-1-i];
            array[array.length-1-i] = tmp;
        }
        return charArrayToString(array);
    }

    public String charArrayToString(char[] array) {
        StringBuffer sb = new StringBuffer();
        sb.append(array);
        return sb.toString();
    }
}


代码中是否有错误?有更有效的方法来反转字符串吗?

评论

您没有自己检查错误吗?我们不是调试服务。我们会审查工作代码。

是的,它通过了一些简单的测试。

好的,只要确定即可。第二个问题很好,我们可以为您提供帮助。

#1 楼

准确有效地反转弦乐。

是的,您做的很准确。但是,效率不是很高(尽管有更差的处理方法)。

注意:: Palacsint是正确的,因为您不处理输入数据中的空输入和代理对。您的解决方案并不完全准确。考虑此答案仅是关于效率的讨论...。尽管我也更新了此答案,以包括一个有效处理代理对的解决方案

要了解事物的效率方面,您必须了解Java String内部。


字符串是不可变的,不能更改。
Java在分配/清除内存方面相对较慢,请尽量避免。
Java可以'内联方法调用并使make方法真正快速。

因此,使Java高效的最佳方法是创建/使用尽可能少的内存,并且仍然以新的String结尾(而不是更改现有字符串)。

您的代码已经有1个字符串,您正在创建另一个字符串。您无法避免。

您可以避免的是中间发生的事情。

您的“介于两者之间”

您正在做什么两者之间有点杂乱且浪费:


    char[] array = new char[s.length()];
    array = s.toCharArray();



创建一个新数组,然后将其丢弃并替换为s.toCharArray() ...为什么?

可能只是:

    char[] array = s.toCharArray();


此外,您还有一个循环,可以交换字符...这的实现方式如下:


for(int i=0; i<array.length/2; i++) {



如果向后进行操作,这可能会更快(可能取决于所使用的Java)。

for (int i = array.length / 2; i >= 0; i--) {


这样,它只需要执行一次array.length / 2(尽管正如我所说,某些Java实现可能会编译它为您工作)。 br />
最后,charArrayToString()是严重的过度杀伤力....

转换为StringBuilder然后转换为String浪费时间/资源...


return charArrayToString(array);





return new String(array);


编辑:另外,正如理查德·米斯金(Richard Miskin)所提到的...我以为你是已经使用StringBuilder...。在单线程情况下,StringBuilder比StringBuffer效率更高。除非有充分的理由,否则始终应使用StringBuilder。有效的中间值....

public String reverse(String s) {
    char[] array = s.toCharArray();
    char tmp;
    for(int i = (array.length - 1) / 2; i >= 0; i--) {
        tmp = array[i];
        array[i] = array[array.length-1-i];
        array[array.length-1-i] = tmp;
    }
    return new String(array);
}


编辑:采用另一种方法:

这里有一些性能指标...


String Reverse                           => 4473870 (hot 19.74881ms)
String Reverse Surrogate Aware           => 4473870 (hot 22.73488ms)
String Reverse B                         => 4473870 (hot 25.16192ms)
String Reverse StringBuilder             => 4473870 (hot 31.60709ms)
String Reverse StringBuilder NoNullCheck => 4473870 (hot 31.72952ms)
String Reverse Orig                      => 4473870 (hot 36.83827ms)



对于每个“热”运行,我颠倒了479829个单词(linux.words)的顺序(数据中有4473870个字符(不包括换行符)。)


我在上面建议的代码是“高效的-之间的间隔是在20毫秒内完成的

基于对null和代理对的讨论,下面的代码做到了这一“正确”,并在23毫秒内运行:

public String reverse(final String s) {
    if (s == null) {
        return null;
    }
    final char[] array = s.toCharArray();
    char tmp;
    for(int i=array.length/2; i >= 0; i--) {
        tmp = array[i];
        array[i] = array[array.length-1-i];
        array[array.length-1-i] = tmp;
    }
    //surrogate pairs will have been swapped.
    //identify, and un-swap them.
    for (int i = 1; i < array.length; i++) {
        if (Character.isHighSurrogate(array[i]) && Character.isLowSurrogate(array[i - 1])) {
            tmp = array[i];
            array[i] = array[i - 1];
            array[i - 1] = tmp;
        }
    }
    return new String(array);
}



以下代码在25毫秒内完成操作

public String reverse(String s) {
    char[] array = new char[s.length()];
    for(int i=array.length - 1, j = 0; i >= 0; i--, j++) {
        array[i] = s.charAt(j);
    }
    return new String(array);
}



@palacsint的建议在31毫秒内完成操作:

public static String reverse(final String str) {
    if (str == null) {
        return null;
    }
    return new StringBuilder(str).reverse().toString();
} 



@palacsint的建议(不进行空检查)在31毫秒内完成:

public static String reverse(final String str) {
    return new StringBuilder(str).reverse().toString();
} 


您的代码可以它只需37毫秒。

如果您看一下代码,我的代码将创建三个对象(char []和新的String()(也创建char []))

s.charAt()代码还创建了三个对象,但是对String.charAt()进行了很多调用。

@palacsint建议创建4个对象(StringBuffer,StringBuffer的内部char [],String和String的内部char [] );

有无null检查都是如此。

您的代码创建5个对象(如果算出第一个可能被编译出的数组,则为6个对象。 ..):( char []数组,新的StringBuffer,StringBuffer的char [],String和String的char [])

我的猜测是,我们的时间之间存在紧密的联系,只是因为在堆上创建480,000个对象需要5毫秒,加上实际工作的一些开销。

评论


\ $ \ begingroup \ $
“ Java在分配/清除内存方面相对较慢,请尽量避免使用它。”通常,快速内存分配(即由VM检索内存)不是基于GC的系统和由VM管理的内存系统的重点之一吗?据我所知,以Java类语言“分配”内存非常快,因为没有“真实”分配(即调用堆分配器来检索可用内存的新块/页面),而VM只是在找回您一块之前分配的大量内存,以避免连续调用缓慢的“ malloc”。
\ $ \ endgroup \ $
–Manu343726
2014年2月27日在22:24



\ $ \ begingroup \ $
另外,GC的收集速度并不慢,如今非常快。唯一的问题是它们的工作方式不确定(即“我不知道GC何时会停止我的程序来执行收集工作”),这在实时系统(例如视频游戏)中可能是个问题。
\ $ \ endgroup \ $
–Manu343726
2014年2月27日在22:25



\ $ \ begingroup \ $
@ Manu343726-您的观点都是正确的,但在本讨论中不涉及上下文。我的意思是“使用堆(无论是否进行了预分配)比不使用堆要慢”。不必GC一些内存比其他方法更快。
\ $ \ endgroup \ $
–rolfl
2014-2-27在23:20

\ $ \ begingroup \ $
编辑了答案,以包括一些比较性能结果。
\ $ \ endgroup \ $
–rolfl
2014年2月27日23:21

\ $ \ begingroup \ $
也许我遗漏了一些东西,但看来您有效的中间方法实际上并未完全反转字符串。输入具有偶数个字符的任何字符串。让我们以“ Java”为例。当传递到您的方法中时,您会期望“ avaJ”作为输出,但是会得到“ aavJ”。
\ $ \ endgroup \ $
– IZI_Shadow_IZI
19-2-14在22:11



#2 楼

从现有的实施中学习,它们通常具有针对极端情况和常见陷阱的解决方案。例如,Apache Commons Lang StringUtils也具有reverse功能。它的实现非常简单:

public static String reverse(final String str) {
    if (str == null) {
        return null;
    }
    return new StringBuilder(str).reverse().toString();
} 


它使用StringBuilder.reverse,其javadoc提到了一些特殊情况:


如果存在任何替代序列中包含的对
,它们被视为反向操作的单个字符。
因此,高低代孕的顺序永远不会颠倒。


以下是一些测试:

@Test
public void testCstring() {
    assertEquals("\uD800\uDC00", CString.reverse("\uD800\uDC00")); // fails
    assertEquals("\uD800\uDC00", CString.reverse("\uDC00\uD800")); // OK
}

@Test
public void testStringUtils() throws Exception {
    assertEquals("\uD800\uDC00", StringUtils.reverse("\uD800\uDC00"));
    assertEquals("\uD800\uDC00", StringUtils.reverse("\uDC00\uD800"));
}


我不太了解这些替代,但是您应该检查一下并在代码中处理它们。我想JDK的实现更可靠,并且以它们在javadoc中提到的方式来处理它们并不是巧合。

关于Stack Overflow的替代品有一个很好的问题(有很好的答案):是Java中的代理对吗?

(另请参见:有效的Java,第二版,第47项:了解和使用库)

评论


\ $ \ begingroup \ $
仅处理波纹是不够的。您还需要处理组合字符。
\ $ \ endgroup \ $
– CodesInChaos
2014-02-28 20:06



#3 楼

该代码在功能上似乎是正确的,尽管您可以选择使用StringBuffer.reverse()。这里有一些讨论。

如果您确实需要滚动自己的反转方法,则可以完全避免使用StringBuffer / StringBuilder,而只需执行new String(array)

通常,如果您不处理多线程代码,则应优先使用StringBuilder而不是StringBuffer

#4 楼

您可以使用Java 8 lambda函数来反转字符串,而无需更改或使用任何局部变量。该代码将说明反向功能:

public static void reverse(String myString) {
    return myString
                .chars()
                .mapToObj(c -> String.valueOf((char) c))
                .reduce("", (sb, str) -> str + sb);     
}


此代码从String对象获取整数流:

myString.chars()


一旦有了整数流,就可以使用map函数将整数转换为字符:

mapToObj(c -> String.valueOf((char) c))


最后,您可以按照自己的方式减少字符。在这里,我已将字符添加到最终输出字符串中:

reduce("", (sb, str) -> str + sb);


评论


\ $ \ begingroup \ $
对于大型字符串,此代码非常慢。您是否曾经尝试使用该代码来撤销Wikipedia文章?可能需要几分钟,但应该只需要几微秒。
\ $ \ endgroup \ $
–罗兰·伊利格(Roland Illig)
18年4月29日在7:26



\ $ \ begingroup \ $
绝对可以优化此代码,我给出了编写更简洁的代码的基本方法。
\ $ \ endgroup \ $
–Joydeep Bhattacharya
18年4月30日在3:03