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 LangStringUtils
也具有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
评论
您没有自己检查错误吗?我们不是调试服务。我们会审查工作代码。是的,它通过了一些简单的测试。
好的,只要确定即可。第二个问题很好,我们可以为您提供帮助。