Có bao nhiêu chuỗi được tạo trong bộ nhớ khi nối các chuỗi trong Java?


17

Tôi đã được hỏi về các chuỗi bất biến trong Java. Tôi được giao nhiệm vụ viết một hàm nối một số "a" thành một chuỗi.

Những gì tôi đã viết:

public String foo(int n) {
    String s = "";
    for (int i = 0; i < n; i++) {
        s = s + "a"
    }
    return s;
}

Sau đó tôi được hỏi có bao nhiêu chuỗi chương trình này sẽ tạo ra, giả sử việc thu gom rác không xảy ra. Suy nghĩ của tôi cho n = 3 là

  1. ""
  2. "một"
  3. "một"
  4. "aa"
  5. "một"
  6. "aaa"
  7. "một"

Về cơ bản, 2 chuỗi được tạo trong mỗi lần lặp của vòng lặp. Tuy nhiên, câu trả lời là n 2 . Chuỗi nào sẽ được tạo trong bộ nhớ bởi hàm này và tại sao lại như vậy?


15
Nếu bạn được đề nghị công việc này, hãy chạy đi, chạy thật nhanh .......
mattnz

@mattnz vì nhiều lý do (và không chỉ vì mã viết).

3
Điều này cần thời gian chạy O (n ^ 2) trừ khi JIT tối ưu hóa vòng lặp, nhưng nó không tạo ra n ^ 2 chuỗi.
user2357112 hỗ trợ Monica

Câu trả lời:


26

Sau đó tôi được hỏi có bao nhiêu chuỗi chương trình này sẽ tạo ra, giả sử việc thu gom rác không xảy ra. Suy nghĩ của tôi cho n = 3 là (7)

Chuỗi 1 ( "") và 2 ( "a") là các hằng số trong chương trình, chúng không được tạo như một phần của sự vật mà là 'được thực hiện' bởi vì chúng là hằng số mà trình biên dịch biết. Đọc thêm về điều này tại String interning trên Wikipedia.

Điều này cũng loại bỏ chuỗi 5 và 7 khỏi số đếm vì chúng giống "a"như Chuỗi số 2. Điều này để lại các chuỗi # 3, # 4 và # 6. Câu trả lời là "3 chuỗi được tạo cho n = 3" bằng mã của bạn.

Tổng số n 2 rõ ràng là sai bởi vì tại n = 3, đây sẽ là 9 và thậm chí theo câu trả lời trong trường hợp xấu nhất của bạn, đó chỉ là 7. Nếu các chuỗi không được thực hiện của bạn là chính xác, câu trả lời nên là 2n + 1.

Vì vậy, câu hỏi làm thế nào bạn nên làm điều này?

Chuỗi là bất biến , bạn muốn một thứ có thể thay đổi - thứ bạn có thể thay đổi mà không cần tạo đối tượng mới. Đó là StringBuilder .

Điều đầu tiên cần xem xét là các nhà xây dựng. Trong trường hợp này, chúng ta biết chuỗi sẽ dài bao nhiêu và có một hàm tạo StringBuilder(int capacity) có nghĩa là chúng ta phân bổ chính xác bao nhiêu chúng ta cần.

Tiếp theo, "a"không cần phải là String , mà thay vào đó, nó có thể là một ký tự 'a'. Điều này có một số tăng hiệu suất nhỏ khi gọi append(String)vs append(char)- với append(String), phương thức cần tìm hiểu Chuỗi dài bao nhiêu và thực hiện một số công việc trên đó. Mặt khác, charluôn luôn chính xác một ký tự dài.

Sự khác biệt về mã có thể được nhìn thấy tại StringBuilder.append (String) so với StringBuilder.append (char) . Nó không phải là một cái gì đó quá quan tâm, nhưng nếu bạn đang cố gắng gây ấn tượng với nhà tuyển dụng, tốt nhất là sử dụng các thực tiễn tốt nhất có thể.

Vì vậy, làm thế nào điều này trông khi bạn đặt nó lại với nhau?

public String foo(int n) {
    StringBuilder sb = new StringBuilder(n);
    for (int i = 0; i < n; i++) {
        sb.append('a');
    }
    return sb.toString();
}

Một StringBuilder và một String đã được tạo. Không có chuỗi bổ sung cần thiết để được thực tập.


Viết một số chương trình đơn giản khác trong Eclipse. Cài đặt pmd và chạy nó trên mã bạn viết. Lưu ý những gì nó phàn nàn và sửa chữa những điều đó. Nó sẽ tìm thấy sự sửa đổi của Chuỗi có + trong một vòng lặp và nếu bạn thay đổi chuỗi đó thành StringBuilder, nó có thể đã tìm thấy dung lượng ban đầu, nhưng chắc chắn nó sẽ nắm bắt được sự khác biệt giữa .append("a").append('a')


9

Trên mỗi lần lặp, một cái mới Stringđược tạo bởi +toán tử và được gán cho s. Sau khi trở về, tất cả chúng nhưng cuối cùng đều được thu gom rác.

Các hằng chuỗi thích """a"không được tạo mỗi lần, đây là các chuỗi được đặt nội bộ . Vì các chuỗi là bất biến, chúng có thể được chia sẻ tự do; điều này xảy ra với hằng chuỗi.

Để nối chuỗi hiệu quả, sử dụng StringBuilder.


Những người trong cuộc phỏng vấn thực sự đã tranh luận về việc liệu chữ có hay không, và quyết định rằng chữ được tạo ra mỗi lần. Nhưng điều này có ý nghĩa hơn.
ahalbert

6
Làm thế nào để bạn "tranh luận" những gì một ngôn ngữ làm, chắc chắn bạn đã đọc thông số kỹ thuật và biết chắc chắn, hoặc nó không được xác định và do đó, không có câu trả lời chính xác .....
mattnz

@mattnz Có thể rất thú vị khi biết trình biên dịch / thời gian chạy mà bạn đang sử dụng làm gì, ngay cả khi nói đến chi tiết triển khai. Điều này đặc biệt áp dụng cho hiệu suất.
Svick

1
@svick: Bạn có thể đạt được rất nhiều bằng cách đưa ra các giả định, sau đó trình biên dịch được nâng cấp, tối ưu hóa thay đổi, v.v ... Hành vi thay đổi gây ra lỗi vì bạn dựa vào hành vi không xác định thay vì hành vi được xác định. Bạn biết những gì họ nói về tối ưu hóa - a) để lại cho các chuyên gia và b) bạn chưa phải là chuyên gia. :) Nếu sự phụ thuộc chỉ dựa trên hiệu suất, nhưng vẫn theo đặc tả ngôn ngữ, thì bạn chỉ mất hiệu suất. Nhiều lần tôi đã thấy mã dựa trên hành vi cụ thể của trình biên dịch không xác định hoặc trình biên dịch phá vỡ theo những cách không mong muốn (chủ yếu là C và C ++).
mattnz

@mattnz Vậy làm thế nào để bạn đề xuất đưa ra quyết định liên quan đến hiệu suất? Thông thường, thứ tốt nhất bạn có thể nhận được từ đặc tả / tài liệu là độ phức tạp lớn, nhưng điều đó là không đủ. Trong mọi trường hợp, hiệu suất sẽ luôn phụ thuộc vào việc triển khai, vì vậy tôi nghĩ sẽ ổn nếu dựa vào chi tiết triển khai khi nói đến hiệu suất.
Svick

4

Như MichaelT giải thích trong câu trả lời của mình, mã của bạn phân bổ các chuỗi O (n). Nhưng nó cũng phân bổ O (n 2 ) byte bộ nhớ và chạy trong thời gian O (n 2 ).

Nó phân bổ các byte O (n 2 ), bởi vì các chuỗi bạn phân bổ có độ dài 0, 1, 2, CÁCH, n-1, n, tính tổng cho (n 2 + n) / 2 = O (n 2 ).

Thời gian cũng là O (n 2 ), vì việc phân bổ chuỗi thứ i yêu cầu sao chép chuỗi thứ (i-1), có độ dài i-1. Điều này có nghĩa là mỗi byte được phân bổ phải được sao chép, sẽ mất thời gian O (n 2 ).

Có lẽ đây là những gì người phỏng vấn có nghĩa?


Không nên phương trình là (n ^ 2 + n) / 2, như ở đây ?
HeyJude
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.