Tại sao các chuỗi không thể thay đổi trong Java và .NET?


189

Tại sao họ quyết định làm cho Stringbất biến trong Java và .NET (và một số ngôn ngữ khác)? Tại sao họ không biến nó thành đột biến?


13
Tôi đã có cùng suy nghĩ, nhưng kiểm tra vị trí áp phích ban đầu và thấy rằng họ đến từ Bỉ. Cho rằng điều này có nghĩa là họ không có khả năng là một người nói tiếng Anh bản ngữ. Cùng với thực tế là hầu hết người bản địa có khả năng nắm bắt ngôn ngữ một cách lỏng lẻo, tôi quyết định cắt giảm cho cô ấy một chút chậm chạp.
belugabob 20/03/2016

8
Cảm ơn bạn belugabob, nhưng tôi không phải là cô ấy Tôi là anh ấy. Rõ ràng mọi người không xem xét sự khác biệt văn hóa.
chrissie1

7
Lời xin lỗi của tôi - chrissie là (nói chung) tên của một cô gái ở Anh - khiến tôi trở thành nạn nhân của một sự khác biệt văn hóa khác :-)
belugabob

Chỉ cần một lưu ý, trong .NET Stringlà thực sự có thể thay đổi trong nội bộ. StringBuildertrong .NET 2.0 đột biến một chuỗi . Tôi sẽ chỉ để nó ở đây.
Alvin Wong

Trên thực tế, các chuỗi .NET thể thay đổi. Và nó thậm chí không phải là một hack.
Bitterblue

Câu trả lời:


203

Theo Java hiệu quả , chương 4, trang 73, ấn bản 2:

"Có nhiều lý do chính đáng cho việc này: Các lớp bất biến dễ thiết kế, thực hiện và sử dụng hơn các lớp có thể thay đổi. Chúng ít bị lỗi và an toàn hơn.

[...]

"Các đối tượng bất biến rất đơn giản. Một đối tượng bất biến có thể ở chính xác một trạng thái, trạng thái mà nó được tạo. Nếu bạn chắc chắn rằng tất cả các hàm tạo thiết lập bất biến lớp, thì nó được đảm bảo rằng các bất biến này sẽ luôn đúng với mọi thời gian, với không có nỗ lực từ phía bạn.

[...]

Đối tượng bất biến vốn đã an toàn chủ đề; họ không yêu cầu đồng bộ hóa. Chúng không thể bị hỏng bởi nhiều luồng truy cập đồng thời. Đây là cách tiếp cận dễ dàng nhất để đạt được an toàn luồng. Trong thực tế, không có luồng nào có thể quan sát bất kỳ tác động nào của một luồng khác lên một vật thể bất biến. Do đó, các đối tượng bất biến có thể được chia sẻ tự do

[...]

Các điểm nhỏ khác từ cùng một chương:

Bạn không chỉ có thể chia sẻ các đối tượng bất biến, mà bạn có thể chia sẻ nội bộ của họ.

[...]

Các đối tượng bất biến tạo ra các khối xây dựng tuyệt vời cho các đối tượng khác, cho dù có thể thay đổi hoặc bất biến.

[...]

Nhược điểm thực sự duy nhất của các lớp bất biến là chúng yêu cầu một đối tượng riêng cho mỗi giá trị riêng biệt.


22
Đọc câu thứ hai trong câu trả lời của tôi: Các lớp bất biến dễ thiết kế, thực hiện và sử dụng hơn các lớp có thể thay đổi. Họ ít bị lỗi và an toàn hơn.
FLUFF PRINCESS

5
@PRINCESSFLUFF Tôi sẽ thêm rằng chia sẻ chuỗi có thể thay đổi là nguy hiểm ngay cả trên một chuỗi. Ví dụ: sao chép báo cáo : report2.Text = report1.Text;. Sau đó, ở một nơi khác, sửa đổi văn bản : report2.Text.Replace(someWord, someOtherWord);. Điều này sẽ thay đổi báo cáo đầu tiên cũng như thứ hai.
phoog

10
@Sam anh ấy đã không hỏi "tại sao họ không thể biến đổi được", anh ấy hỏi "tại sao họ quyết định làm cho bất biến" mà câu trả lời này hoàn hảo.
James

1
@PRINCESSFLUFF Câu trả lời này không giải quyết các chuỗi cụ thể. Đó là câu hỏi của OP. Thật là bực bội - điều này xảy ra mọi lúc trên SO và với các câu hỏi bất biến String. Câu trả lời ở đây nói về lợi ích chung của sự bất biến. Vậy tại sao tất cả các loại không thay đổi? Bạn có thể vui lòng quay lại và giải quyết Chuỗi không?
Howiecamp

@Howiecamp Tôi nghĩ rằng nó ẩn ý bởi câu trả lời rằng các chuỗi có thể có thể thay đổi được (không có gì ngăn cản một lớp chuỗi có thể thay đổi giả định tồn tại). Họ chỉ quyết định không làm theo cách đó vì đơn giản, và vì nó bao gồm 99% trường hợp sử dụng. Họ vẫn cung cấp StringBuilder cho các trường hợp 1% khác.
Daniel García Rubio

102

Có ít nhất hai lý do.

Đầu tiên - bảo mật http://www.javafaq.nu/java-article1060.html

Lý do chính khiến String trở nên bất biến là bảo mật. Nhìn vào ví dụ này: Chúng tôi có một phương pháp mở tệp với kiểm tra đăng nhập. Chúng tôi chuyển một Chuỗi cho phương thức này để xử lý xác thực cần thiết trước khi cuộc gọi sẽ được chuyển đến HĐH. Nếu String có thể thay đổi, bằng cách nào đó có thể sửa đổi nội dung của nó sau khi kiểm tra xác thực trước khi HĐH nhận được yêu cầu từ chương trình thì có thể yêu cầu bất kỳ tệp nào. Vì vậy, nếu bạn có quyền mở tệp văn bản trong thư mục người dùng nhưng sau đó, khi bạn bằng cách nào đó thay đổi tên tệp, bạn có thể yêu cầu mở tệp "passwd" hoặc bất kỳ tệp nào khác. Sau đó, một tập tin có thể được sửa đổi và nó sẽ có thể đăng nhập trực tiếp vào hệ điều hành.

Thứ hai - Hiệu quả bộ nhớ http://hikrish.blogspot.com/2006/07/why-opes- class-is-immutable.html

JVM bên trong duy trì "Chuỗi nhóm". Để đạt được hiệu quả bộ nhớ, JVM sẽ tham chiếu đối tượng String từ pool. Nó sẽ không tạo các đối tượng String mới. Vì vậy, bất cứ khi nào bạn tạo một chuỗi ký tự mới, JVM sẽ kiểm tra trong nhóm xem nó đã tồn tại hay chưa. Nếu đã có trong nhóm, chỉ cần đưa tham chiếu đến cùng một đối tượng hoặc tạo đối tượng mới trong nhóm. Sẽ có nhiều tham chiếu trỏ đến cùng các đối tượng String, nếu ai đó thay đổi giá trị, nó sẽ ảnh hưởng đến tất cả các tham chiếu. Vì vậy, mặt trời quyết định làm cho nó bất biến.


Đây là một điểm tốt về tái sử dụng và đặc biệt đúng nếu bạn sử dụng String.i INTERN (). Nó đã có thể tái sử dụng mà không làm cho tất cả các chuỗi bất biến, nhưng cuộc sống có xu hướng trở nên phức tạp tại thời điểm đó.
jsight

3
Không ai trong số chúng dường như là những lý do có giá trị khủng khiếp đối với tôi trong thời đại ngày nay.
Brian Knoblauch

1
Tôi không bị thuyết phục bởi đối số hiệu quả bộ nhớ (nghĩa là khi hai hoặc nhiều đối tượng Chuỗi chia sẻ cùng một dữ liệu và một đối tượng được sửa đổi, thì cả hai đều được sửa đổi). Các đối tượng CString trong MFC có được xung quanh đó bằng cách sử dụng tính tham chiếu.
RobH

7
bảo mật không thực sự là một phần của Raison đối với các chuỗi bất biến - HĐH của bạn sẽ sao chép các chuỗi vào bộ đệm chế độ kernel và kiểm tra truy cập ở đó, để tránh các cuộc tấn công thời gian. Đó thực sự là tất cả về an toàn & hiệu suất của luồng :)
snemarch

1
Đối số hiệu quả bộ nhớ cũng không hoạt động. Trong một ngôn ngữ bản địa như C, các hằng chuỗi chỉ đơn giản là con trỏ tới dữ liệu trong phần dữ liệu được khởi tạo - dù sao chúng chỉ đọc / không thay đổi. "Nếu ai đó thay đổi giá trị" - một lần nữa, các chuỗi từ nhóm sẽ chỉ đọc.
wj32

57

Trên thực tế, chuỗi lý do là bất biến trong java không liên quan nhiều đến bảo mật. Hai lý do chính là:

An toàn:

Chuỗi là loại đối tượng được sử dụng rất rộng rãi. Do đó, nó ít nhiều được đảm bảo để được sử dụng trong môi trường đa luồng. Chuỗi là bất biến để đảm bảo rằng an toàn để chia sẻ chuỗi giữa các chuỗi. Có một chuỗi bất biến đảm bảo rằng khi chuyển các chuỗi từ luồng A sang luồng B khác, luồng B không thể sửa đổi chuỗi A của chuỗi một cách bất ngờ.

Điều này không chỉ giúp đơn giản hóa nhiệm vụ vốn đã khá phức tạp của lập trình đa luồng mà còn giúp thực hiện các ứng dụng đa luồng. Truy cập vào các đối tượng có thể thay đổi bằng cách nào đó phải được đồng bộ hóa khi chúng có thể được truy cập từ nhiều luồng, để đảm bảo rằng một luồng không cố đọc giá trị của đối tượng của bạn trong khi nó đang được sửa đổi bởi một luồng khác. Đồng bộ hóa phù hợp vừa khó thực hiện chính xác cho lập trình viên, vừa tốn kém khi chạy. Các đối tượng bất biến không thể được sửa đổi và do đó không cần đồng bộ hóa.

Hiệu suất:

Mặc dù thực tập String đã được đề cập, nó chỉ thể hiện mức tăng nhỏ về hiệu quả bộ nhớ cho các chương trình Java. Chỉ có chuỗi ký tự được thực tập. Điều này có nghĩa là chỉ các chuỗi giống nhau trong mã nguồn của bạn mới chia sẻ cùng một Đối tượng chuỗi. Nếu chương trình của bạn tự động tạo chuỗi giống nhau, chúng sẽ được biểu diễn trong các đối tượng khác nhau.

Quan trọng hơn, chuỗi bất biến cho phép họ chia sẻ dữ liệu nội bộ của họ. Đối với nhiều hoạt động chuỗi, điều này có nghĩa là mảng ký tự bên dưới không cần phải sao chép. Ví dụ: giả sử bạn muốn lấy năm ký tự đầu tiên của Chuỗi. Trong Java, bạn sẽ gọi myString.subopes (0,5). Trong trường hợp này, phương thức chuỗi con () thực hiện chỉ đơn giản là tạo một đối tượng Chuỗi mới chia sẻ char [] cơ bản của myString nhưng ai biết rằng nó bắt đầu ở chỉ số 0 và kết thúc tại chỉ mục 5 của char []. Để đặt cái này ở dạng đồ họa, bạn sẽ kết thúc như sau:

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

Điều này làm cho loại hoạt động này cực kỳ rẻ và O (1) vì hoạt động không phụ thuộc vào độ dài của chuỗi gốc, cũng như độ dài của chuỗi con mà chúng ta cần trích xuất. Hành vi này cũng có một số lợi ích về bộ nhớ, vì nhiều chuỗi có thể chia sẻ char [] bên dưới của chúng.


6
Việc thực hiện các chuỗi con như các tài liệu tham khảo chia sẻ cơ bản char[]là một quyết định thiết kế khá nghi vấn. Nếu bạn đọc toàn bộ tệp thành một chuỗi và duy trì tham chiếu đến chỉ một chuỗi con 1 ký tự, toàn bộ tệp sẽ phải được lưu trong bộ nhớ.
Gabe

5
Chính xác, tôi đã chạy vào gotcha cụ thể đó trong khi tạo trình thu thập dữ liệu trang web chỉ cần trích xuất một vài từ trong toàn bộ trang. Toàn bộ mã HTML của trang nằm trong bộ nhớ và do chuỗi con chia sẻ char [], tôi đã giữ toàn bộ mã HTML mặc dù tôi chỉ cần một vài byte. Một cách giải quyết khác là sử dụng Chuỗi mới (gốc.sub chuỗi (.., ..)), hàm tạo Chuỗi (Chuỗi) tạo một bản sao của phạm vi có liên quan của mảng bên dưới.
LordOfThePigs

1
Một phụ lục để bao gồm các thay đổi tiếp theo: Kể từ Jave 7, String.substring()thực hiện một bản sao đầy đủ, để ngăn chặn các vấn đề được đề cập trong các nhận xét ở trên. Trong Java 8, hai trường cho phép char[]chia sẻ, cụ thể countoffset, được loại bỏ, do đó làm giảm dung lượng bộ nhớ của các thể hiện Chuỗi.
Christian Semrau

Tôi đồng ý với phần An toàn của Thead, nhưng nghi ngờ trường hợp chuỗi con.
Gqqnbig

@LoveRight: Sau đó, hãy kiểm tra mã nguồn của java.lang.String ( grepcode.com/file/reposective.grepcode.com/java/root/jdk/openjdk/ .) là hiện tại khi câu trả lời này được viết). Tôi rõ ràng đã thay đổi trong Java 7.
LordOfThePigs

28

Chủ đề an toàn và hiệu suất. Nếu một chuỗi không thể được sửa đổi, nó an toàn và nhanh chóng để chuyển một tham chiếu xung quanh giữa nhiều luồng. Nếu các chuỗi có thể thay đổi, bạn sẽ luôn phải sao chép tất cả các byte của chuỗi sang một thể hiện mới hoặc cung cấp đồng bộ hóa. Một ứng dụng thông thường sẽ đọc một chuỗi 100 lần cho mỗi lần chuỗi đó cần được sửa đổi. Xem wikipedia về bất biến .


11

Người ta thực sự nên hỏi, "tại sao X phải biến đổi?" Tốt hơn hết là mặc định là bất biến, vì những lợi ích đã được đề cập bởi Princess Fluff . Nó nên là một ngoại lệ rằng một cái gì đó có thể thay đổi.

Thật không may, hầu hết các ngôn ngữ lập trình hiện tại đều mặc định là có thể thay đổi, nhưng hy vọng trong tương lai, mặc định sẽ có nhiều tính bất biến hơn (xem Danh sách mong muốn cho Ngôn ngữ lập trình chính tiếp theo ).


7

Ồ Tôi không thể tin những thông tin sai lệch ở đây. StringS là bất biến không có gì với an ninh. Nếu ai đó đã có quyền truy cập vào các đối tượng trong một ứng dụng đang chạy (điều này sẽ phải được giả định nếu bạn đang cố gắng bảo vệ chống lại ai đó 'hack' a Stringtrong ứng dụng của bạn), họ chắc chắn sẽ có rất nhiều cơ hội khác để hack.

Đó là một ý tưởng khá mới lạ rằng tính bất biến của Stringviệc giải quyết các vấn đề luồng. Hmmm ... Tôi có một đối tượng đang được thay đổi bởi hai luồng khác nhau. Làm thế nào để tôi giải quyết điều này? đồng bộ hóa truy cập vào đối tượng? Naawww ... chúng ta đừng để ai thay đổi đối tượng - điều đó sẽ khắc phục tất cả các vấn đề tương tranh lộn xộn của chúng ta! Trong thực tế, chúng ta hãy làm cho tất cả các đối tượng trở nên bất biến, và sau đó chúng ta có thể loại bỏ sự tương phản được đồng bộ hóa khỏi ngôn ngữ Java.

Lý do thực sự (được chỉ ra bởi những người khác ở trên) là tối ưu hóa bộ nhớ. Nó là khá phổ biến trong bất kỳ ứng dụng cho cùng một chuỗi ký tự được sử dụng nhiều lần. Trên thực tế, nó phổ biến đến mức nhiều thập kỷ trước, nhiều trình biên dịch đã tối ưu hóa việc lưu trữ chỉ một thể hiện duy nhất của một Stringnghĩa đen. Hạn chế của việc tối ưu hóa này là mã thời gian chạy sửa đổi một Stringnghĩa đen giới thiệu một vấn đề bởi vì nó đang sửa đổi thể hiện cho tất cả các mã khác chia sẻ nó. Ví dụ, sẽ không tốt cho một chức năng ở đâu đó trong một ứng dụng để thay đổi Stringnghĩa đen "dog"thành "cat". Một printf("dog")kết quả sẽ "cat"được viết vào thiết bị xuất chuẩn. Vì lý do đó, cần phải có một cách bảo vệ chống lại mã cố gắng thay đổiStringnghĩa đen (nghĩa là làm cho chúng bất biến). Một số trình biên dịch (với sự hỗ trợ từ HĐH) sẽ thực hiện điều này bằng cách đặt Stringchữ vào một phân đoạn bộ nhớ chỉ đọc đặc biệt sẽ gây ra lỗi bộ nhớ nếu thực hiện ghi.

Trong Java, điều này được gọi là thực tập. Trình biên dịch Java ở đây chỉ tuân theo tối ưu hóa bộ nhớ tiêu chuẩn được thực hiện bởi các trình biên dịch trong nhiều thập kỷ. Và để giải quyết vấn đề tương tự của những Stringchữ này được sửa đổi trong thời gian chạy, Java chỉ đơn giản làm cho Stringlớp không thay đổi (tức là, cung cấp cho bạn không có setters nào cho phép bạn thay đổi Stringnội dung). Strings sẽ không phải là bất biến nếu việc thực hiện nghĩa Stringđen không xảy ra.


3
Tôi hoàn toàn không đồng ý về sự bất biến và nhận xét luồng, có vẻ như tôi không hoàn toàn nhận được điểm đó. Và nếu Josh Bloch, một trong những người triển khai Java, nói rằng đó là một trong những vấn đề thiết kế, thì đó có thể là thông tin sai lệch như thế nào?
javashlook

1
Đồng bộ hóa là tốn kém. Tài liệu tham khảo cho các đối tượng có thể thay đổi cần phải được đồng bộ hóa, không phải như vậy cho bất biến. Đó là một lý do để làm cho tất cả các đối tượng bất biến trừ khi chúng phải có thể thay đổi. Chuỗi có thể là bất biến, và do đó làm điều đó làm cho chúng hiệu quả hơn trong nhiều luồng.
David Thornley

5
@Jim: Tối ưu hóa bộ nhớ không phải là 'lý do', đó là lý do 'A'. An toàn luồng cũng là lý do 'A', bởi vì các đối tượng bất biến vốn đã an toàn theo luồng và không yêu cầu đồng bộ hóa đắt tiền, như David đã đề cập. An toàn chủ đề thực sự là một tác dụng phụ của một đối tượng là bất biến. Bạn có thể nghĩ về việc đồng bộ hóa như một cách để làm cho đối tượng "tạm thời" không thể thay đổi (ReaderWriterLock sẽ làm cho nó chỉ đọc và một khóa thông thường sẽ khiến nó không thể truy cập hoàn toàn, điều này tất nhiên cũng khiến nó không thể thay đổi được).
Triynko

1
@DavidThornley: Việc tạo ra nhiều đường dẫn tham chiếu độc lập đến một người giữ giá trị có thể thay đổi có hiệu quả biến nó thành một thực thể và khiến cho việc suy luận về vấn đề xâu chuỗi trở nên khó khăn hơn nhiều. Nói chung, các đối tượng có thể thay đổi sẽ hiệu quả hơn các đối tượng bất biến trong trường hợp chính xác một đường dẫn tham chiếu sẽ tồn tại cho mỗi đối tượng, nhưng các đối tượng bất biến cho phép chia sẻ nội dung của các đối tượng một cách hiệu quả bằng cách chia sẻ tham chiếu. Mẫu tốt nhất được minh họa bằng StringStringBuffer, nhưng thật không may, một vài loại khác theo mô hình đó.
supercat

7

String không phải là một kiểu nguyên thủy, nhưng bạn thường muốn sử dụng nó với ngữ nghĩa giá trị, tức là giống như một giá trị.

Giá trị là thứ bạn có thể tin tưởng sẽ không thay đổi sau lưng. Nếu bạn viết: String str = someExpr(); Bạn không muốn nó thay đổi trừ khi BẠN làm gì đó với str.

Stringnhư một Objectngữ nghĩa con trỏ tự nhiên, để có được ngữ nghĩa giá trị cũng như nó cần phải bất biến.


7

Một yếu tố là, nếu Strings là có thể thay đổi, các đối tượng lưu trữ Strings sẽ phải cẩn thận để lưu trữ các bản sao, kẻo thay đổi dữ liệu nội bộ của chúng mà không cần thông báo trước. Cho rằng đó Stringlà một loại khá nguyên thủy như số, thật tuyệt khi người ta có thể coi chúng như thể chúng được truyền theo giá trị, ngay cả khi chúng được truyền bằng tham chiếu (cũng giúp tiết kiệm bộ nhớ).


6

Tôi biết đây là một vết sưng, nhưng ... Chúng có thực sự bất biến không? Hãy xem xét những điều sau đây.

public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
    fixed (char* ptr = s)
    {
        *((char*)(ptr + i)) = c;
    }
}

...

string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3

Bạn thậm chí có thể làm cho nó một phương pháp mở rộng.

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

Điều này làm cho công việc sau đây

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

Kết luận: Chúng ở trạng thái bất biến được trình biên dịch biết đến. Trong số các điều khoản trên chỉ áp dụng cho các chuỗi .NET vì Java không có con trỏ. Tuy nhiên, một chuỗi có thể hoàn toàn có thể thay đổi bằng cách sử dụng các con trỏ trong C #. Đó không phải là cách con trỏ được sử dụng, sử dụng thực tế hoặc được sử dụng một cách an toàn; tuy nhiên điều đó là có thể, do đó bẻ cong toàn bộ quy tắc "có thể thay đổi". Bạn thường không thể sửa đổi một chỉ mục trực tiếp của một chuỗi và đây là cách duy nhất. Có một cách mà điều này có thể được ngăn chặn bằng cách không cho phép các trường hợp con trỏ của chuỗi hoặc tạo một bản sao khi một chuỗi được trỏ đến, nhưng không được thực hiện, điều đó làm cho các chuỗi trong C # không hoàn toàn bất biến.


1
+1. Chuỗi .NET không thực sự bất biến; trong thực tế, điều này được thực hiện mọi lúc trong các lớp String và StringBuilder vì những lý do hoàn hảo.
James Ko

3

Đối với hầu hết các mục đích, một "chuỗi" là (được sử dụng / được coi là / nghĩ về / giả định là) một đơn vị nguyên tử có ý nghĩa , giống như một con số .

Hỏi tại sao các ký tự riêng lẻ của một chuỗi không thể thay đổi do đó giống như hỏi tại sao các bit riêng lẻ của một số nguyên không thể thay đổi.

Bạn nên biết tại sao. Nghĩ về nó đi.

Tôi ghét phải nói điều đó, nhưng thật không may, chúng tôi đang tranh luận điều này bởi vì ngôn ngữ của chúng tôi rất tệ và chúng tôi đang cố gắng sử dụng một từ, chuỗi , để mô tả một khái niệm hoặc lớp đối tượng phức tạp, theo ngữ cảnh.

Chúng tôi thực hiện các tính toán và so sánh với "chuỗi" tương tự như cách chúng tôi làm với các số. Nếu các chuỗi (hoặc số nguyên) có thể thay đổi, chúng ta sẽ phải viết mã đặc biệt để khóa các giá trị của chúng thành các dạng cục bộ bất biến để thực hiện bất kỳ loại tính toán nào một cách đáng tin cậy. Do đó, tốt nhất là nghĩ về một chuỗi như một định danh số, nhưng thay vì dài 16, 32 hoặc 64 bit, nó có thể dài hàng trăm bit.

Khi ai đó nói "chuỗi", tất cả chúng ta đều nghĩ về những điều khác nhau. Những người nghĩ về nó đơn giản chỉ là một tập hợp các nhân vật, không có mục đích cụ thể nào, tất nhiên sẽ kinh hoàng khi ai đó quyết định rằng họ không nên thao túng những nhân vật đó. Nhưng lớp "chuỗi" không chỉ là một mảng các ký tự. Đó là một STRING, không phải a char[]. Có một số giả định cơ bản về khái niệm mà chúng ta gọi là "chuỗi" và nói chung nó có thể được mô tả là đơn vị nguyên tử, có ý nghĩa của dữ liệu được mã hóa như một con số. Khi mọi người nói về "thao tác chuỗi", có lẽ họ thực sự đang nói về việc thao túng các nhân vật để xây dựng chuỗi và StringBuilder là điều tuyệt vời cho điều đó.

Hãy xem xét một lúc nó sẽ như thế nào nếu các chuỗi có thể thay đổi. Hàm API sau đây có thể bị lừa trả lại thông tin cho một người dùng khác nếu chuỗi tên người dùng có thể thay đổi được sửa đổi có chủ ý hoặc vô ý bởi một luồng khác trong khi chức năng này đang sử dụng nó:

string GetPersonalInfo( string username, string password )
{
    string stored_password = DBQuery.GetPasswordFor( username );
    if (password == stored_password)
    {
        //another thread modifies the mutable 'username' string
        return DBQuery.GetPersonalInfoFor( username );
    }
}

Bảo mật không chỉ là về 'kiểm soát truy cập', mà còn về 'an toàn' và 'đảm bảo tính chính xác'. Nếu một phương thức không thể dễ dàng được viết và phụ thuộc để thực hiện một phép tính hoặc so sánh đơn giản một cách đáng tin cậy, thì việc gọi nó là không an toàn, nhưng sẽ an toàn khi tự đặt câu hỏi cho ngôn ngữ lập trình.


Trong C #, một chuỗi có thể thay đổi bằng con trỏ của nó (sử dụng unsafe) hoặc đơn giản thông qua sự phản chiếu (bạn có thể lấy trường bên dưới một cách dễ dàng). Điều này làm cho điểm về bảo mật bị vô hiệu, vì bất kỳ ai cố tình muốn thay đổi một chuỗi, đều có thể thực hiện điều đó khá dễ dàng. Tuy nhiên, nó cung cấp bảo mật cho các lập trình viên: trừ khi bạn làm điều gì đó đặc biệt, chuỗi được đảm bảo không thay đổi (nhưng nó không an toàn cho chủ đề!).
Abel

Có, bạn có thể thay đổi byte của bất kỳ đối tượng dữ liệu nào (chuỗi, int, v.v.) thông qua các con trỏ. Tuy nhiên, chúng ta đang nói về lý do tại sao lớp chuỗi là bất biến theo nghĩa là nó không có các phương thức công khai được xây dựng để sửa đổi các ký tự của nó. Tôi đã nói rằng một chuỗi rất giống với một số trong đó thao tác các ký tự riêng lẻ không có ý nghĩa gì hơn là thao tác các bit riêng lẻ của một số (khi bạn coi một chuỗi là toàn bộ mã thông báo (không phải là một mảng byte) và một số là một giá trị số (không phải là một trường bit). Chúng ta đang nói ở cấp đối tượng khái niệm, không phải ở cấp đối tượng phụ.
Triynko

2
Và chỉ cần làm rõ, các con trỏ trong mã hướng đối tượng vốn không an toàn, chính xác bởi vì chúng phá vỡ các giao diện công cộng được định nghĩa cho một lớp. Những gì tôi đã nói, là một chức năng có thể dễ dàng bị lừa nếu giao diện chung cho một chuỗi cho phép nó được sửa đổi bởi các luồng khác. Tất nhiên, nó luôn có thể bị lừa bằng cách truy cập dữ liệu trực tiếp bằng con trỏ, nhưng không dễ dàng hoặc vô ý.
Triynko

1
'Con trỏ trong mã hướng đối tượng vốn không an toàn' trừ khi bạn gọi chúng là tham chiếu . Các tham chiếu trong Java không khác với các con trỏ trong C ++ (chỉ số học con trỏ bị tắt). Một khái niệm khác là quản lý bộ nhớ có thể được quản lý hoặc thủ công, nhưng đó là một điều khác. Bạn có thể có ngữ nghĩa tham chiếu (con trỏ không có số học) mà không có GC (ngược lại sẽ khó hơn theo nghĩa ngữ nghĩa của khả năng tiếp cận sẽ khó làm sạch hơn, nhưng không khả thi)
David Rodríguez - dribeas

Một điều khác là nếu các chuỗi gần như bất biến, nhưng không hoàn toàn như vậy, (tôi không biết đủ CLI ở đây), điều đó có thể thực sự tồi tệ vì lý do bảo mật. Trong một số triển khai Java cũ hơn, bạn có thể làm điều đó và tôi đã tìm thấy một đoạn mã sử dụng chuỗi đó để nội hóa chuỗi (cố gắng xác định chuỗi nội bộ khác có cùng giá trị, chia sẻ con trỏ, xóa khối bộ nhớ cũ) và sử dụng cửa sau để viết lại nội dung chuỗi buộc một hành vi không chính xác trong một lớp khác. (Cân nhắc viết lại "CHỌN *" thành "XÓA")
David Rodríguez - dribeas

3

Sự bất biến không gắn chặt với an ninh. Đối với điều đó, ít nhất là trong .NET, bạn có được SecureStringlớp.

Chỉnh sửa sau: Trong Java bạn sẽ tìm thấy GuardedString, một triển khai tương tự.


2

Quyết định có chuỗi đột biến trong C ++ gây ra rất nhiều vấn đề, hãy xem bài viết xuất sắc này của Kelvin Henney về Bệnh Mad COW .

COW = Sao chép khi viết.


2

Đó là một sự đánh đổi. Strings đi vào nhóm Stringvà khi bạn tạo nhiều Strings giống nhau , chúng có chung bộ nhớ. Các nhà thiết kế cho rằng kỹ thuật tiết kiệm bộ nhớ này sẽ hoạt động tốt trong trường hợp phổ biến, vì các chương trình có xu hướng nghiền trên cùng một chuỗi rất nhiều.

Nhược điểm là việc ghép nối tạo ra rất nhiều Strings chỉ chuyển tiếp và trở thành rác, thực sự gây hại cho hiệu năng bộ nhớ. Bạn có StringBufferStringBuilder(trong Java, StringBuildercũng ở .NET) để sử dụng để bảo toàn bộ nhớ trong những trường hợp này.


1
Hãy nhớ rằng "nhóm chuỗi" không được sử dụng tự động cho TẤT CẢ các chuỗi trừ khi bạn sử dụng rõ ràng các chuỗi "inter ()" 'ed.
jsight

2

Strings trong Java không thực sự bất biến, bạn có thể thay đổi giá trị của chúng bằng cách sử dụng sự phản chiếu và tải lớp. Bạn không nên phụ thuộc vào tài sản đó để bảo mật. Ví dụ xem: Magic Trick In Java


1
Tôi tin rằng bạn sẽ chỉ có thể thực hiện các thủ thuật như vậy nếu mã của bạn đang chạy với sự tin tưởng hoàn toàn, do đó không có mất mát bảo mật. Bạn cũng có thể sử dụng JNI để ghi trực tiếp vào vị trí bộ nhớ nơi các chuỗi được lưu trữ.
Antoine Aubry

Trên thực tế tôi tin rằng bạn có thể thay đổi bất kỳ đối tượng bất biến bằng phản xạ.
Gqqnbig

0

Bất biến là tốt. Xem Java hiệu quả. Nếu bạn phải sao chép Chuỗi mỗi lần bạn chuyển nó, thì đó sẽ là rất nhiều mã dễ bị lỗi. Bạn cũng có sự nhầm lẫn về việc sửa đổi nào ảnh hưởng đến tham chiếu nào. Theo cùng một cách mà Integer phải bất biến để hành xử như int, String phải hành xử như bất biến để hành động như người nguyên thủy. Trong C ++, việc chuyển các chuỗi theo giá trị thực hiện điều này mà không đề cập rõ ràng trong mã nguồn.


0

Có một ngoại lệ cho gần như mọi quy tắc:

using System;
using System.Runtime.InteropServices;

namespace Guess
{
    class Program
    {
        static void Main(string[] args)
        {
            const string str = "ABC";

            Console.WriteLine(str);
            Console.WriteLine(str.GetHashCode());

            var handle = GCHandle.Alloc(str, GCHandleType.Pinned);

            try
            {
                Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');

                Console.WriteLine(str);
                Console.WriteLine(str.GetHashCode());
            }
            finally
            {
                handle.Free();
            }
        }
    }
}

-1

Phần lớn là vì lý do bảo mật. Việc bảo mật một hệ thống sẽ khó hơn nhiều nếu bạn không thể tin tưởng rằng hệ thống của mình Stringchống giả.


1
Bạn có thể cho một ví dụ về những gì bạn có nghĩa là "tamperproof". Câu trả lời này cảm thấy thực sự ra khỏi bối cảnh.
Gergely Orosz
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.