String#substring()
Java 에서 메소드 의 시간 복잡성은 무엇입니까 ?
String#substring()
Java 에서 메소드 의 시간 복잡성은 무엇입니까 ?
답변:
새로운 답변
자바 7의 수명 내에서 업데이트를 6으로의 행동 substring
변화는 복사본을 생성합니다 - 모든 그래서 String
A를 의미 char[]
하는 하지 다른 객체와 공유, 지금까지 내가 알고 있어요한다. 그래서 그 시점 substring()
에서 n이 부분 문자열의 숫자 인 O (n) 연산이되었습니다.
이전 답변 : Java 7 이전
문서화되지 않음-그러나 실제로 가비지 수집이 필요하지 않다고 가정하는 경우 O (1) 등.
단순히 String
동일한 기본을 참조 char[]
하지만 오프셋 및 개수 값이 다른 새 개체 를 만듭니다 . 따라서 비용은 유효성 검사를 수행하고 하나의 새로운 (합리적으로 작은) 개체를 생성하는 데 걸리는 시간입니다. 가비지 수집, CPU 캐시 등에 따라 시간에 따라 달라질 수있는 작업의 복잡성에 대해 이야기하는 한 O (1)입니다. 특히 원래 문자열 또는 하위 문자열의 길이에 직접적으로 의존하지 않습니다. .
이전 버전의 Java에서는 O (1)이었습니다. Jon이 말했듯이 동일한 기본 char []과 다른 오프셋 및 길이를 가진 새 문자열을 만들었습니다.
그러나 이것은 실제로 Java 7 업데이트 6부터 변경되었습니다.
char [] 공유가 제거되고 오프셋 및 길이 필드가 제거되었습니다. substring ()은 이제 모든 문자를 새 문자열로 복사합니다.
Ergo, Java 7 업데이트 6에서 하위 문자열은 O (n)입니다.
char[]
.
이제 선형 복잡성입니다. 이것은 부분 문자열에 대한 메모리 누수 문제를 수정 한 후입니다.
따라서 Java 1.7.0_06에서 String.substring은 이제 상수 대신 선형 복잡성을가집니다.
Jon의 대답에 증거를 추가합니다. 나는 같은 의심이 있었고 문자열 길이가 부분 문자열 기능에 영향을 미치는지 확인하고 싶었습니다. 실제로 어떤 매개 변수 하위 문자열이 의존하는지 확인하기 위해 다음 코드를 작성했습니다.
import org.apache.commons.lang.RandomStringUtils;
public class Dummy {
private static final String pool[] = new String[3];
private static int substringLength;
public static void main(String args[]) {
pool[0] = RandomStringUtils.random(2000);
pool[1] = RandomStringUtils.random(10000);
pool[2] = RandomStringUtils.random(100000);
test(10);
test(100);
test(1000);
}
public static void test(int val) {
substringLength = val;
StatsCopy statsCopy[] = new StatsCopy[3];
for (int j = 0; j < 3; j++) {
statsCopy[j] = new StatsCopy();
}
long latency[] = new long[3];
for (int i = 0; i < 10000; i++) {
for (int j = 0; j < 3; j++) {
latency[j] = latency(pool[j]);
statsCopy[j].send(latency[j]);
}
}
for (int i = 0; i < 3; i++) {
System.out.println(
" Avg: "
+ (int) statsCopy[i].getAvg()
+ "\t String length: "
+ pool[i].length()
+ "\tSubstring Length: "
+ substringLength);
}
System.out.println();
}
private static long latency(String a) {
long startTime = System.nanoTime();
a.substring(0, substringLength);
long endtime = System.nanoTime();
return endtime - startTime;
}
private static class StatsCopy {
private long count = 0;
private long min = Integer.MAX_VALUE;
private long max = 0;
private double avg = 0;
public void send(long latency) {
computeStats(latency);
count++;
}
private void computeStats(long latency) {
if (min > latency) min = latency;
if (max < latency) max = latency;
avg = ((float) count / (count + 1)) * avg + (float) latency / (count + 1);
}
public double getAvg() {
return avg;
}
public long getMin() {
return min;
}
public long getMax() {
return max;
}
public long getCount() {
return count;
}
}
}
Java 8에서 실행시 출력은 다음과 같습니다.
Avg: 128 String length: 2000 Substring Length: 10
Avg: 127 String length: 10000 Substring Length: 10
Avg: 124 String length: 100000 Substring Length: 10
Avg: 172 String length: 2000 Substring Length: 100
Avg: 175 String length: 10000 Substring Length: 100
Avg: 177 String length: 100000 Substring Length: 100
Avg: 1199 String length: 2000 Substring Length: 1000
Avg: 1186 String length: 10000 Substring Length: 1000
Avg: 1339 String length: 100000 Substring Length: 1000
부분 문자열 기능 증명은 문자열 길이가 아니라 요청 된 부분 문자열의 길이에 따라 다릅니다.
다음에서 스스로 판단하십시오. 그러나 Java의 성능 단점은 여기 문자열의 하위 문자열이 아닌 다른 곳에 있습니다. 암호:
public static void main(String[] args) throws IOException {
String longStr = "asjf97zcv.1jm2497z20`1829182oqiwure92874nvcxz,nvz.,xo" +
"aihf[oiefjkas';./.,z][p\\°°°°°°°°?!(*#&(@*&#!)^(*&(*&)(*&" +
"fasdznmcxzvvcxz,vc,mvczvcz,mvcz,mcvcxvc,mvcxcvcxvcxvcxvcx";
int[] indices = new int[32 * 1024];
int[] lengths = new int[indices.length];
Random r = new Random();
final int minLength = 6;
for (int i = 0; i < indices.length; ++i)
{
indices[i] = r.nextInt(longStr.length() - minLength);
lengths[i] = minLength + r.nextInt(longStr.length() - indices[i] - minLength);
}
long start = System.nanoTime();
int avoidOptimization = 0;
for (int i = 0; i < indices.length; ++i)
//avoidOptimization += lengths[i]; //tested - this was cheap
avoidOptimization += longStr.substring(indices[i],
indices[i] + lengths[i]).length();
long end = System.nanoTime();
System.out.println("substring " + indices.length + " times");
System.out.println("Sum of lengths of splits = " + avoidOptimization);
System.out.println("Elapsed " + (end - start) / 1.0e6 + " ms");
}
산출:
부분 문자열 32768 회 분할 길이의 합계 = 1494414 2.446679ms 경과
O (1) 여부는 다릅니다. 메모리에서 동일한 문자열을 참조하는 경우 매우 긴 문자열 을 상상 하면 하위 문자열을 만들고 긴 문자열 참조를 중지합니다. 오랫동안 메모리를 해제하는 것이 좋지 않습니까?
전 O (1) : 자바 1.7.0_06.
Java 1.7.0_06 이후 : O (n). 이것은 메모리 누수로 인해 변경되었습니다. 필드 offset
와 count
문자열에서 제거 된 후 하위 문자열 구현은 O (n)이되었습니다.
자세한 내용은 http://java-performance.info/changes-to-string-java-1-7-0_06/을 참조하십시오.