Đồng thời
Java đã được định nghĩa ngay từ đầu với những cân nhắc về sự tương tranh. Như thường được đề cập chia sẻ đột biến là có vấn đề. Một điều có thể thay đổi khác đằng sau mặt sau của một chủ đề khác mà không nhận ra chủ đề đó.
Có một loạt các lỗi C ++ đa luồng đã xuất hiện do một chuỗi được chia sẻ - trong đó một mô-đun nghĩ rằng nó an toàn để thay đổi khi một mô-đun khác trong mã đã lưu một con trỏ vào nó và hy vọng nó sẽ giữ nguyên.
"Giải pháp" cho vấn đề này là mọi lớp tạo ra một bản sao phòng thủ của các vật thể đột biến được truyền cho nó. Đối với các chuỗi có thể thay đổi, đây là O (n) để tạo bản sao. Đối với các chuỗi bất biến, tạo một bản sao là O (1) vì nó không phải là một bản sao, cùng một đối tượng không thể thay đổi.
Trong một môi trường đa luồng, các đối tượng bất biến luôn có thể được chia sẻ an toàn với nhau. Điều này dẫn đến việc giảm tổng thể sử dụng bộ nhớ và cải thiện bộ nhớ đệm.
Bảo vệ
Nhiều lần các chuỗi được truyền xung quanh dưới dạng đối số cho các nhà xây dựng - các kết nối mạng và các nguyên mẫu là hai thứ dễ dàng xuất hiện nhất trong tâm trí. Có thể thay đổi điều này tại một thời điểm không xác định sau đó trong quá trình thực thi có thể dẫn đến các vấn đề bảo mật (chức năng nghĩ rằng nó đang kết nối với một máy, nhưng đã được chuyển hướng sang một máy khác, nhưng mọi thứ trong đối tượng trông giống như được kết nối với ... nó thậm chí cùng một chuỗi).
Java cho phép một người sử dụng sự phản chiếu - và các tham số cho điều này là các chuỗi. Sự nguy hiểm của việc truyền một chuỗi có thể được sửa đổi thông qua cách này đến một phương thức khác phản ánh. Thật tồi tệ.
Chìa khóa để băm
Bảng băm là một trong những cấu trúc dữ liệu được sử dụng nhiều nhất. Các khóa cho cấu trúc dữ liệu là rất thường xuyên chuỗi. Có các chuỗi bất biến có nghĩa là (như trên) bảng băm không cần tạo một bản sao của khóa băm mỗi lần. Nếu các chuỗi có thể thay đổi và bảng băm không tạo ra điều này, thì có thể có thứ gì đó để thay đổi khóa băm ở khoảng cách xa.
Cách mà Object trong java hoạt động, là mọi thứ đều có khóa băm (được truy cập thông qua phương thức hashCode ()). Có một chuỗi bất biến có nghĩa là hashCode có thể được lưu trữ. Xem xét tần suất sử dụng Chuỗi làm khóa cho hàm băm, điều này mang lại hiệu suất tăng đáng kể (thay vì phải tính toán lại mã băm mỗi lần).
Chất nền
Bằng cách có Chuỗi là bất biến, mảng ký tự bên dưới hỗ trợ cấu trúc dữ liệu cũng là bất biến. Điều này cho phép tối ưu hóa nhất định về substring
phương pháp được thực hiện (chúng không nhất thiết phải được thực hiện - nó cũng giới thiệu khả năng bị rò rỉ bộ nhớ).
Nếu bạn làm:
String foo = "smiles";
String bar = foo.substring(1,5);
Giá trị của bar
là 'dặm'. Tuy nhiên, cả hai foo
và bar
có thể được hỗ trợ bởi cùng một mảng ký tự, làm giảm khả năng khởi tạo của nhiều mảng ký tự hoặc sao chép nó - chỉ sử dụng các điểm bắt đầu và kết thúc khác nhau trong chuỗi.
foo | | (0, 6)
vv
những nụ cười
^ ^
thanh | | (1, 5)
Bây giờ, nhược điểm của điều đó (rò rỉ bộ nhớ) là nếu một người có chuỗi dài 1k và lấy chuỗi con của ký tự thứ nhất và thứ hai, thì nó cũng sẽ được hỗ trợ bởi mảng ký tự dài 1k. Mảng này sẽ vẫn còn trong bộ nhớ ngay cả khi chuỗi ban đầu có giá trị của toàn bộ mảng ký tự là rác được thu thập.
Mọi người có thể thấy điều này trong Chuỗi từ JDK 6b14 (đoạn mã sau lấy từ nguồn GPL v2 và được sử dụng làm ví dụ)
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
Lưu ý cách chuỗi con sử dụng hàm tạo Chuỗi cấp gói không liên quan đến bất kỳ sự sao chép nào của mảng và sẽ nhanh hơn nhiều (với chi phí có thể giữ xung quanh một số mảng lớn - mặc dù không sao chép các mảng lớn).
Xin lưu ý rằng đoạn mã trên dành cho Java 1.6. Cách trình xây dựng chuỗi con được triển khai đã được thay đổi với Java 1.7 như được ghi lại trong biểu diễn bên trong Thay đổi thành Chuỗi được thực hiện trong Java 1.7.0_06
- vấn đề gây ra rò rỉ bộ nhớ mà tôi đã đề cập ở trên. Java có thể không được coi là một ngôn ngữ có nhiều thao tác Chuỗi và vì vậy việc tăng hiệu năng cho một chuỗi con là một điều tốt. Bây giờ, với các tài liệu XML khổng lồ được lưu trữ trong các chuỗi không bao giờ được thu thập, điều này trở thành một vấn đề ... và do đó, việc thay đổi String
không sử dụng cùng một mảng bên dưới với một chuỗi con, để mảng ký tự lớn hơn có thể được thu thập nhanh hơn.
Đừng lạm dụng Stack
Người ta có thể chuyển giá trị của chuỗi xung quanh thay vì tham chiếu đến chuỗi bất biến để tránh các vấn đề có tính biến đổi. Tuy nhiên, với các chuỗi lớn, việc chuyển chuỗi này trên ngăn xếp sẽ ... bị lạm dụng đối với hệ thống (đặt toàn bộ tài liệu xml dưới dạng chuỗi trên ngăn xếp và sau đó gỡ chúng ra hoặc tiếp tục chuyển chúng theo ...).
Khả năng trùng lặp
Được cho rằng, đây không phải là động lực ban đầu cho lý do tại sao Chuỗi nên bất biến, nhưng khi người ta nhìn vào lý do tại sao Chuỗi bất biến là một điều tốt, đây chắc chắn là điều cần xem xét.
Bất cứ ai từng làm việc với String một chút đều biết rằng họ có thể hút bộ nhớ. Điều này đặc biệt đúng khi bạn đang làm những việc như lấy dữ liệu từ cơ sở dữ liệu trong một thời gian ngắn. Nhiều lần với các stings này, chúng là cùng một chuỗi lặp đi lặp lại (một lần cho mỗi hàng).
Nhiều ứng dụng Java quy mô lớn hiện đang bị tắc nghẽn trong bộ nhớ. Các phép đo đã chỉ ra rằng khoảng 25% tập dữ liệu trực tiếp của vùng heap Java trong các loại ứng dụng này được sử dụng bởi các đối tượng String. Hơn nữa, khoảng một nửa trong số các đối tượng Chuỗi đó là trùng lặp, trong đó trùng lặp có nghĩa là chuỗi1.equals (chuỗi2) là đúng. Có các đối tượng String trùng lặp trên heap, về cơ bản, chỉ là một sự lãng phí bộ nhớ. ...
Với bản cập nhật Java 8 20, JEP 192 (động lực được trích dẫn ở trên) đang được triển khai để giải quyết vấn đề này. Không đi sâu vào chi tiết về cách thức sao chép chuỗi hoạt động, điều cần thiết là bản thân các Chuỗi là bất biến. Bạn không thể sao chép StringBuilders vì chúng có thể thay đổi và bạn không muốn ai đó thay đổi thứ gì đó từ bên dưới bạn. Chuỗi không thay đổi (liên quan đến nhóm Chuỗi đó) có nghĩa là bạn có thể đi qua và nếu bạn tìm thấy hai chuỗi giống nhau, bạn có thể trỏ một chuỗi tham chiếu đến chuỗi khác và để trình thu gom rác tiêu thụ chuỗi không sử dụng.
Những ngôn ngữ khác
Mục tiêu C (có trước Java) có NSString
và NSMutableString
.
C # và .NET đã đưa ra các lựa chọn thiết kế giống nhau của chuỗi mặc định là không thay đổi.
Dây Lua cũng bất biến.
Python cũng vậy.
Trong lịch sử, Lisp, Scheme, Smalltalk đều thực hiện chuỗi và do đó, nó là bất biến. Các ngôn ngữ động hiện đại hơn thường sử dụng các chuỗi theo một cách nào đó đòi hỏi chúng phải bất biến (nó có thể không phải là Chuỗi , nhưng nó là bất biến).
Phần kết luận
Những cân nhắc thiết kế này đã được thực hiện lặp đi lặp lại trong vô số ngôn ngữ. Đó là sự đồng thuận chung rằng các chuỗi bất biến, cho tất cả sự lúng túng của họ, tốt hơn so với các lựa chọn thay thế và dẫn đến mã tốt hơn (ít lỗi hơn) và tổng thể thực thi nhanh hơn.