Tại sao việc gắn thêm các ứng dụng vào một chuỗi lưu trữ bộ nhớ?


193

Tôi đã sử dụng một biến có rất nhiều dữ liệu trong đó String data. Tôi muốn sử dụng một phần nhỏ của chuỗi này theo cách sau:

this.smallpart = data.substring(12,18);

Sau vài giờ gỡ lỗi (với trình hiển thị bộ nhớ) tôi phát hiện ra rằng trường đối tượng smallpartghi nhớ tất cả dữ liệu từ đó data, mặc dù nó chỉ chứa chuỗi con.

Khi tôi thay đổi mã thành:

this.smallpart = data.substring(12,18)+""; 

..các vấn đề đã được giải quyết! Bây giờ ứng dụng của tôi sử dụng rất ít bộ nhớ!

Làm thế nào là có thể? Bất cứ ai có thể giải thích điều này? Tôi nghĩ this.smallpart tiếp tục tham khảo dữ liệu, nhưng tại sao?

CẬP NHẬT: Làm thế nào tôi có thể xóa Chuỗi lớn sau đó? Dữ liệu = Chuỗi mới (data.sub chuỗi (0,100)) sẽ làm điều đó?


Đọc thêm về ý định cuối cùng của bạn dưới đây: Chuỗi lớn đến từ đâu đầu tiên? Nếu đọc từ một tệp hoặc cơ sở dữ liệu CLOB hoặc một cái gì đó thì chỉ đọc những gì bạn cần trong khi phân tích cú pháp sẽ là tối ưu tất cả các cách.
Trả lời

4
Thật tuyệt vời ... Tôi đã làm việc trong java hơn 4 đến 5 năm, nhưng điều này vẫn còn mới đối với tôi :). cảm ơn vì thông tin bro
Parth

1
Có một sự tinh tế để sử dụng new String(String); xem stackoverflow.com/a/390854/8946 .
Lawrence Dol

Câu trả lời:


159

Làm như sau:

data.substring(x, y) + ""

tạo một đối tượng Chuỗi mới (nhỏ hơn) và loại bỏ tham chiếu đến Chuỗi được tạo bởi chuỗi con (), do đó cho phép thu gom rác này.

Điều quan trọng cần nhận ra là substring()cung cấp một cửa sổ cho Chuỗi hiện có - hay đúng hơn là mảng ký tự nằm dưới Chuỗi ban đầu. Do đó, nó sẽ tiêu thụ cùng bộ nhớ với Chuỗi gốc. Điều này có thể thuận lợi trong một số trường hợp, nhưng có vấn đề nếu bạn muốn lấy một chuỗi con và loại bỏ Chuỗi gốc (như bạn đã tìm ra).

Hãy xem phương thức chuỗi con () trong nguồn Chuỗi JDK để biết thêm thông tin.

EDIT: Để trả lời câu hỏi bổ sung của bạn, việc xây dựng Chuỗi mới từ chuỗi con sẽ giảm mức tiêu thụ bộ nhớ của bạn, miễn là bạn có bất kỳ tham chiếu nào đến Chuỗi ban đầu.

LƯU Ý (tháng 1 năm 2013). Hành vi trên đã thay đổi trong Java 7u6 . Mẫu flykg không còn được sử dụng và substring()sẽ hoạt động như bạn mong đợi.


89
Đó là một trong số rất ít trường hợp hàm String(String)tạo (tức là hàm tạo Chuỗi lấy Chuỗi làm đầu vào) hữu ích: new String(data.substring(x, y))thực hiện điều tương tự như nối thêm "", nhưng nó làm cho ý định rõ ràng hơn.
Joachim Sauer

3
chính xác, chuỗi con sử dụng valuethuộc tính của chuỗi gốc. Tôi nghĩ đó là lý do tại sao tài liệu tham khảo được lưu giữ.
Valentin Rocher

@Bishiboosh - vâng, đúng vậy. Tôi không muốn tiết lộ những đặc điểm của việc thực hiện, nhưng đó chính xác là những gì đang xảy ra.
Brian Agnew

5
Về mặt kỹ thuật đó là một chi tiết thực hiện. Nhưng dù sao nó cũng làm nản lòng và bắt được rất nhiều người.
Brian Agnew

1
Tôi tự hỏi liệu có thể tối ưu hóa điều này trong JDK bằng cách sử dụng các tham chiếu yếu hay không. Nếu tôi là người cuối cùng cần char [] này và tôi chỉ cần một chút về nó, hãy tạo một mảng mới để tôi sử dụng nội bộ.
Thế chiến.

28

Nếu bạn nhìn vào nguồn của substring(int, int), bạn sẽ thấy rằng nó trả về:

new String(offset + beginIndex, endIndex - beginIndex, value);

đâu valuelà bản gốc char[]. Vì vậy, bạn nhận được một Chuỗi mới nhưng với cùng một cơ sở char[].

Khi bạn làm như vậy, data.substring() + ""bạn nhận được một Chuỗi mới với một cơ sở mớichar[] .

Trên thực tế, trường hợp sử dụng của bạn là tình huống duy nhất mà bạn nên sử dụng hàm String(String)tạo:

String tiny = new String(huge.substring(12,18));

1
Có một sự tinh tế để sử dụng new String(String); xem stackoverflow.com/a/390854/8946 .
Lawrence Dol

17

Khi bạn sử dụng substring, nó không thực sự tạo ra một chuỗi mới. Nó vẫn đề cập đến chuỗi ban đầu của bạn, với một ràng buộc về độ lệch và kích thước.

Vì vậy, để cho phép chuỗi gốc của bạn được thu thập, bạn cần tạo một chuỗi mới (sử dụng new Stringhoặc những gì bạn đã có).


5

Tôi nghĩ this.smallpart tiếp tục tham khảo dữ liệu, nhưng tại sao?

Bởi vì các chuỗi Java bao gồm một mảng char, phần bù bắt đầu và độ dài (và mã băm được lưu trữ). Một số hoạt động Chuỗi như substring()tạo một đối tượng Chuỗi mới chia sẻ mảng char ban đầu và chỉ đơn giản là có các trường bù và / hoặc độ dài khác nhau. Điều này hoạt động vì mảng char của Chuỗi không bao giờ được sửa đổi một khi nó đã được tạo.

Điều này có thể tiết kiệm bộ nhớ khi nhiều chuỗi con tham chiếu đến cùng một chuỗi cơ bản mà không cần sao chép các phần chồng chéo. Như bạn đã nhận thấy, trong một số trường hợp, nó có thể giữ cho dữ liệu không cần thiết nữa không bị thu gom rác.

Cách "chính xác" để sửa lỗi này là hàm new String(String)tạo, tức là

this.smallpart = new String(data.substring(12,18));

BTW, giải pháp tốt nhất tổng thể sẽ là tránh có các Chuỗi rất lớn ở vị trí đầu tiên và xử lý bất kỳ đầu vào nào trong các phần nhỏ hơn, một vài KB mỗi lần.


Có một sự tinh tế để sử dụng new String(String); xem stackoverflow.com/a/390854/8946 .
Lawrence Dol

5

Trong các chuỗi Java là các đối tượng không thể thay đổi được và một khi một chuỗi được tạo, nó sẽ lưu lại trên bộ nhớ cho đến khi nó được dọn sạch bởi bộ thu gom rác (và việc dọn dẹp này không phải là điều bạn có thể cho phép).

Khi bạn gọi phương thức chuỗi con, Java không tạo ra một chuỗi mới, mà chỉ lưu trữ một phạm vi các ký tự bên trong chuỗi gốc.

Vì vậy, khi bạn tạo một chuỗi mới với mã này:

this.smallpart = data.substring(12, 18) + ""; 

bạn thực sự đã tạo một chuỗi mới khi bạn kết hợp kết quả với chuỗi trống. Đó là lý do tại sao.


3

Theo tài liệu của jwz năm 1997 :

Nếu bạn có một chuỗi lớn, hãy kéo ra một chuỗi con () của chuỗi đó, giữ chuỗi con đó và cho phép chuỗi dài hơn trở thành rác (nói cách khác, chuỗi con có tuổi thọ dài hơn) các byte bên dưới của chuỗi lớn không bao giờ đi xa.


2

Chỉ cần tóm tắt, nếu bạn tạo nhiều chuỗi con từ một số lượng nhỏ chuỗi lớn, sau đó sử dụng

   String subtring = string.substring(5,23)

Vì bạn chỉ sử dụng không gian để lưu trữ các chuỗi lớn, nhưng nếu bạn đang trích xuất một số ít các chuỗi nhỏ, từ việc mất các chuỗi lớn, thì

   String substring = new String(string.substring(5,23));

Sẽ giữ cho bộ nhớ của bạn sử dụng xuống, vì các chuỗi lớn có thể được thu hồi khi không còn cần thiết.

Rằng bạn gọi new Stringlà một lời nhắc hữu ích rằng bạn thực sự đang nhận được một chuỗi mới, thay vì tham chiếu đến chuỗi gốc.


Có một sự tinh tế để sử dụng new String(String); xem stackoverflow.com/a/390854/8946 .
Lawrence Dol

2

Đầu tiên, gọi java.lang.String.substringtạo cửa sổ mới trên bản gốcString với việc sử dụng phần bù và độ dài thay vì sao chép phần quan trọng của mảng bên dưới.

Nếu chúng ta xem xét kỹ hơn về substringphương thức, chúng ta sẽ nhận thấy một lệnh gọi hàm tạo chuỗiString(int, int, char[]) và chuyển toàn bộ nó char[]đại diện cho chuỗi . Điều đó có nghĩa là chuỗi con sẽ chiếm nhiều bộ nhớ như chuỗi gốc .

Ok, nhưng tại sao + ""dẫn đến nhu cầu về bộ nhớ ít hơn mà không có nó ??

Thực hiện +trên stringsđược thực hiện thông qua StringBuilder.appendcuộc gọi phương thức. Nhìn vào việc thực hiện phương pháp này trong AbstractStringBuilderlớp sẽ cho chúng ta biết rằng cuối cùng nó cũng làm được arraycopyvới phần chúng ta thực sự cần (cái substring).

Bất kỳ cách giải quyết khác ??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();

0

Việc thêm "" vào một chuỗi đôi khi sẽ tiết kiệm bộ nhớ.

Giả sử tôi có một chuỗi lớn chứa cả một cuốn sách, một triệu ký tự.

Sau đó, tôi tạo ra 20 chuỗi chứa các chương của cuốn sách làm chuỗi con.

Sau đó, tôi tạo 1000 chuỗi chứa tất cả các đoạn.

Sau đó, tôi tạo ra 10.000 chuỗi chứa tất cả các câu.

Sau đó, tôi tạo ra 100.000 chuỗi chứa tất cả các từ.

Tôi vẫn chỉ sử dụng 1.000.000 ký tự. Nếu bạn thêm "" vào mỗi chương, đoạn, câu và từ, bạn sử dụng 5.000.000 ký tự.

Tất nhiên nó hoàn toàn khác nếu bạn chỉ trích xuất một từ duy nhất trong toàn bộ cuốn sách và toàn bộ cuốn sách có thể là rác được thu thập nhưng không phải vì một từ đó có liên quan đến nó.

Và nó lại khác nếu bạn có một chuỗi ký tự một triệu và xóa các tab và khoảng trắng ở cả hai đầu, thực hiện 10 cuộc gọi để tạo một chuỗi con. Cách Java hoạt động hoặc làm việc tránh sao chép một triệu ký tự mỗi lần. Có sự thỏa hiệp, và thật tốt nếu bạn biết thỏa hiệp là gì.

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.