类库下载 java类库 Java 문자열 접합 효율성 분석 및 모범 사례

Java 문자열 접합 효율성 분석 및 모범 사례

Oct 13, 2016 am 09:23 AM

이 기사는 "Java 문자열 연결의 모범 사례?"라는 질문에서 나온 것입니다.

+ 연산자, StringBuilder.append 메서드 등 Java에서 문자열을 연결하는 방법에는 여러 가지가 있습니다. 및 각 방법의 단점(적절할 수 있음) 다양한 방법의 구현 세부 사항을 설명합니까?

효율성의 원칙에 따라 Java에서 문자열 연결에 대한 모범 사례는 무엇입니까?

문자열 처리에 대한 다른 모범 사례는 무엇입니까? 모범 사례는 무엇입니까?

더 이상 고민하지 말고 시작해 보겠습니다.

JDK 버전: 1.8.0_65

CPU: i7 4790

메모리: 16G

+접속 직접 사용

다음 코드를 보세요.

@Test 
    public void test() { 
        String str1 = "abc"; 
        String str2 = "def"; 
        logger.debug(str1 + str2); 
    }
로그인 후 복사

위 코드에서, 더하기 기호를 사용하여 네 개의 문자열을 연결합니다. 문자열 접합 방법의 장점은 분명합니다. 코드는 간단하고 직관적이지만 StringBuilder 및 StringBuffer와 비교하면 대부분의 경우 후자보다 낮습니다. 대부분의 경우 javap 도구를 사용하여 위 코드에서 생성된 바이트코드를 수행하고 컴파일러가 이 코드에 어떤 작업을 수행했는지 확인합니다.

public void test(); 
    Code: 
       0: ldc           #5                  // String abc 
       2: astore_1 
       3: ldc           #6                  // String def 
       5: astore_2 
       6: aload_0 
       7: getfield      #4                  // Field logger:Lorg/slf4j/Logger; 
      10: new           #7                  // class java/lang/StringBuilder 
      13: dup 
      14: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V 
      17: aload_1 
      18: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
      21: aload_2 
      22: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
      25: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
      28: invokeinterface #11,  2           // InterfaceMethod org/slf4j/Logger.debug:(Ljava/lang/String;)V 
      33: return
로그인 후 복사

디컴파일 결과에 따르면 + 연산자는 실제로 문자열을 연결하는 데 사용됩니다. 컴파일러는 컴파일 단계에서 StringBuilder 클래스를 사용하도록 코드를 최적화하고 문자열을 연결하기 위해 추가 메서드를 호출합니다. 스플라이싱하고 마지막으로 toString 메서드를 호출하면 일반적으로 +가 직접 사용된다고 볼 수 있나요? 어쨌든 컴파일러가 StringBuilder를 사용하도록 최적화하는 데 도움이 될까요?

StringBuilder 소스 코드 분석

답변 물론 불가능합니다. 그 이유는 StringBuilder 클래스가 내부적으로 수행하는 작업에 있습니다.

StringBuilder 클래스의 생성자를 살펴보겠습니다.

public StringBuilder() { 
        super(16); 
    } 
 
    public StringBuilder(int capacity) { 
        super(capacity); 
    } 
 
    public StringBuilder(String str) { 
        super(str.length() + 16); 
        append(str); 
    } 
 
    public StringBuilder(CharSequence seq) { 
        this(seq.length() + 16); 
        append(seq); 
    }
로그인 후 복사

StringBuilder는 매개변수 없는 생성자 외에도 3개의 다른 오버로드된 버전을 제공합니다. 상위 클래스의 super(int 용량) 구성 메소드는 내부적으로 호출되며 상위 클래스는 AbstractStringBuilder입니다. 구성 메소드는 다음과 같습니다.

AbstractStringBuilder(int capacity) { 
        value = new char[capacity]; 
    }
로그인 후 복사

StringBuilder는 실제로 내부적으로 char 배열을 사용하는 것을 볼 수 있습니다. . Data(String 및 StringBuffer도 포함). 여기서 용량 값은 배열의 크기를 지정합니다. StringBuilder의 매개변수 없는 생성자와 결합하면 기본 크기가 16자임을 알 수 있습니다.

즉, 접합할 문자열의 전체 길이가 16자 이상인 경우 직접 접합과 수동 StringBuilder 사이에는 큰 차이가 없지만 크기를 지정할 수 있습니다. 너무 많은 메모리 할당을 피하기 위해 StringBuilder 클래스를 직접 구성하여 배열을 만듭니다.

이제 StringBuilder.append 메서드 내에서 수행되는 작업을 살펴보겠습니다.

@Override 
   public StringBuilder append(String str) { 
       super.append(str); 
       return this; 
   }
로그인 후 복사

직접 호출되는 상위 클래스의 추가 메서드:

public AbstractStringBuilder append(String str) { 
        if (str == null) 
            return appendNull(); 
        int len = str.length(); 
        ensureCapacityInternal(count + len); 
        str.getChars(0, len, value, count); 
        count += len; 
        return this; 
    }
로그인 후 복사

메서드 내에서 verifyCapacityInternal 메소드가 호출됩니다. 스플라이싱된 문자열의 전체 크기가 내부 배열 값의 크기보다 큰 경우 스플라이싱하기 전에 확장해야 합니다.

void expandCapacity(int minimumCapacity) { 
        int newCapacity = value.length * 2 + 2; 
        if (newCapacity - minimumCapacity < 0) 
            newCapacity = minimumCapacity; 
        if (newCapacity < 0) { 
            if (minimumCapacity < 0) // overflow 
                throw new OutOfMemoryError(); 
            newCapacity = Integer.MAX_VALUE; 
        } 
        value = Arrays.copyOf(value, newCapacity); 
    }
로그인 후 복사

StringBuilder는 확장시 용량을 추가합니다. 현재 용량의 2배 + 2로 용량을 늘리는 것은 정말 끔찍한 일입니다. 구축 시 용량을 지정하지 않으면 많은 양의 메모리 공간이 확보될 가능성이 매우 높습니다. 확장 이후 점유 및 낭비됨. 둘째, 확장 후 Arrays.copyOf 메소드를 호출하는데, 이 메소드는 확장 전의 데이터를 확장된 공간에 복사합니다. 그 이유는 StringBuilder는 내부적으로 char 배열을 사용하여 데이터를 확장할 수 없기 때문입니다. 메모리 공간을 다시 적용하고 기존 데이터를 새 공간에 복사합니다. 여기서는 마지막으로 System.arraycopy 메소드를 호출하여 복사합니다. 이는 기본 메소드이므로 메모리를 사용하는 것보다 낫습니다. 그럼에도 불구하고 많은 양의 메모리 공간을 적용하고 데이터를 복사하는 영향을 무시할 수 없습니다.

+스플라이싱 사용과 StringBuilder 사용 비교

@Test 
public void test() { 
    String str = ""; 
    for (int i = 0; i < 10000; i++) { 
        str += "asjdkla"; 
    } 
}
로그인 후 복사

위 코드는 다음과 동일하도록 최적화되었습니다.

@Test 
   public void test() { 
       String str = null; 
       for (int i = 0; i < 10000; i++) { 
           str = new StringBuilder().append(str).append("asjdkla").toString(); 
       } 
   }
로그인 후 복사

그것도 한 눈에 알 수 있습니다. 많은 StringBuilder 객체가 생성되고 str은 각 사이클마다 점점 더 커지므로 매번 적용되는 메모리 공간이 점점 커지고 str의 길이가 16보다 큰 경우 매번 두 번 확장해야 합니다! 실제로 toString 메소드는 String 객체를 생성할 때 Arrays.copyOfRange 메소드를 호출하여 데이터를 복사하는데, 이는 실행될 때마다 용량을 2번 확장하고 데이터를 3번 복사하는 것과 같습니다.

public void test() { 
        StringBuilder sb = new StringBuilder("asjdkla".length() * 10000); 
        for (int i = 0; i < 10000; i++) { 
            sb.append("asjdkla"); 
        } 
        String str = sb.toString(); 
    }
로그인 후 복사

내 컴퓨터에서 이 코드의 실행 시간은 0ms(1ms 미만)와 1ms인 반면, 위 코드는 약 380ms입니다. 효율성의 차이는 매우 분명합니다.

위와 같은 코드에서 루프 수를 1000000으로 조정하면 내 컴퓨터에서는 용량을 지정했을 때 약 20ms, 용량을 지정하지 않았을 때는 약 29ms 정도 걸리는데 사용하는 것과는 차이가 있다. 직접 + 연산자가 크게 개선되었지만(루프 수가 100배 증가함) 여전히 여러 확장 및 복사를 트리거합니다.

위 코드를 StringBuffer를 사용하도록 변경합니다. 내 컴퓨터에서는 약 33ms가 소요됩니다. 이는 StringBuffer가 스레드 안전성과 실행 효율성을 보장하기 위해 대부분의 메서드에 동기화된 키워드를 추가하기 때문입니다.

String.concat을 사용하여 연결

이제 다음 코드를 살펴보세요.

@Test 
   public void test() { 
       String str = ""; 
       for (int i = 0; i < 10000; i++) { 
           str.concat("asjdkla"); 
       } 
   }
로그인 후 복사

这段代码使用了String.concat方法,在我的机器上,执行时间大约为130ms,虽然直接相加要好的多,但是比起使用StringBuilder还要太多了,似乎没什么用。其实并不是,在很多时候,我们只需要连接两个字符串,而不是多个字符串的拼接,这个时候使用String.concat方法比StringBuilder要简洁且效率要高。

public String concat(String str) { 
        int otherLen = str.length(); 
        if (otherLen == 0) { 
            return this; 
        } 
        int len = value.length; 
        char buf[] = Arrays.copyOf(value, len + otherLen); 
        str.getChars(buf, len); 
        return new String(buf, true); 
    }
로그인 후 복사

上面这段是String.concat的源码,在这个方法中,调用了一次Arrays.copyOf,并且指定了len + otherLen,相当于分配了一次内存空间,并分别从str1和str2各复制一次数据。而如果使用StringBuilder并指定capacity,相当于分配一次内存空间,并分别从str1和str2各复制一次数据,最后因为调用了toString方法,又复制了一次数据。

结论

现在根据上面的分析和测试可以知道:

Java中字符串拼接不要直接使用+拼接。

使用StringBuilder或者StringBuffer时,尽可能准确地估算capacity,并在构造时指定,避免内存浪费和频繁的扩容及复制。

在没有线程安全问题时使用StringBuilder, 否则使用StringBuffer。

两个字符串拼接直接调用String.concat性能最好。

关于String的其他最佳实践

用equals时总是把能确定不为空的变量写在左边,如使用"".equals(str)判断空串,避免空指针异常。

第二点是用来排挤第一点的.. 使用str != null && str.length() != 0来判断空串,效率比第一点高。

在需要把其他对象转换为字符串对象时,使用String.valueOf(obj)而不是直接调用obj.toString()方法,因为前者已经对空值进行检测了,不会抛出空指针异常。

使用String.format()方法对字符串进行格式化输出。

在JDK 7及以上版本,可以在switch结构中使用字符串了,所以对于较多的比较,使用switch代替if-else。


본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)