Chương 16: Chuỗi
Tác giả: Sưu tầm
Khái quát
Tất cả các chuỗi trong C# là các thể hiện của kiểu System.String trong Common Language Runtime. Bởi vì điều này, nên có nhiều thao tác sẵn có cho phép làm việc với các chuỗi. Ví dụ, lớp String định nghĩa một hàm chỉ mục có thể được sử dụng để duyệt qua các ký tự của chuỗi:
using System;
class Test {
public static void Main() {
string s = “Test String”;
for (int index = 0; index < s.Length; index++)
Console.WriteLine(“Char: {0}”, s[index]);
}
}
Các thao tác
Lớp string là một ví dụ của một kiểu không thay đổi, có nghĩa là các ký tự là các ký tự chứa bên trong chuỗi không thể sửa đổi bởi người sử dụng chuỗi. Tất cả các thao tác được thực hiện bởi lớp string trả ra một phiên bản chuỗi đã sửa đổi hơn là sửa đổi ngay trên phiên bản gọi phương thức.
Lớp String hỗ trợ các phương thức so sánh và tìm kiếm sau:
HÀM |
MÔ TẢ |
So sánh hai chuỗi | |
CompareOrdinal() | So sánh hai miền chuỗi bằng cách sử dụng phép so sánh theo thứ tự |
CompareTo() | So sánh thể hiện hiện thời với một thể hiện khác |
EndsWith() | Xác định xem có một chuỗi con tồn tại tại cuỗi chuỗi không |
StartsWith() | Xác định xem có một chuỗi con tồn tại tại đầu chuỗi không |
IndexOf() | Trả ra vị trí đầu của một chuỗi con |
LastIndexOf() | Trả ra vị trí cuỗi của một chuỗi con |
Lớp String hỗ trợ các phương thức sửa đổi sau:
HÀM |
MÔ TẢ |
Concat() |
Nối hai hay nhiều chuỗi hay các đối tượng khác nhau. Nếu các đối tượng được truyền vào, hàm ToString() được gọi cho chúng |
CopyTo() |
Sao chép số lượng xác định các ký tự từ một vị trí trong chuỗi đó vào một mảng |
Insert() |
Trả ra một chuỗi mới với một chuỗi con được chèn vào tại một vị trí xác định |
Join() |
Nối một mảng các chuỗi lại với nhau với một ký tự chia cắt giữa mỗi thành phần mảng |
PadLeft() |
Canh trái một chuỗi trong trường |
PadRight() |
Canh phải một chuỗi trong trường |
Remove() |
Xoá các ký tự trong một chuỗi |
Replace() |
Thay thế tất cả các thể hiện của một ký tự bằng một ký tự khác |
Split() |
Tạo ra một mảng các chuỗi bằng cách tách một chuỗi tại nơi xuất hiện bất kỳ của một hay nhiều ký tự |
Substrng() |
Trích một chuỗi con từ một chuỗi |
ToLower() |
Trả ra một phiên bản in thường của chuỗi |
ToUpper() |
Trả ra một phiên bản in hoa của chuỗi |
Trim() |
Xoá ký tự trắng trong một chuỗi |
TrimEnd() |
Xoá một chuỗi các ký tự tính từ cuỗi chuỗi |
TrimStart() |
Xoá mọt chuối các ký tự tính từ đầu chuỗi |
Chuyển đổi đối tượng thành chuỗi
Hàm object.ToString() được chồng bởi các kiểu có sẵn để cung cấp cách chuyển đổi dễ dàng từ một giá trị thành một thể hiện chuỗi của giá trị đó. Gọi ToString() sẽ tạo ra một thể hiện mặc nhiên của một giá trị; một thể hiện khác có thể thu được bằng cách gọi String.Format(). Hãy xem phần về định dạng trong Chương 30, “Khái quát” .NET Frameworks, để biết thêm thông tin.
Một ví dụ
Hàm Split() có thể được sử dụng để tách một chuỗi thành các chuỗi con bởi các ký tự chia cắt.
using System;
class Test {
public static void Main() {
string s = “Oh, I hadn’t thought of that”;
char[] separators = new char[] {‘ ‘, ‘,’};
foreach (string sub in s.Split(separators)) {
Console.WriteLine(“Word: {0}”, sub);
}
}
}
Ví dụ này cho ra kết quả sau:
Word: Oh
Word:
Word: I
Word: hadn’t
Word: thought
Word: of
Word: that
Mảng ký tự chia cắt định nghĩa các ký tự mà tại đó chuỗi sẽ bị tách. Hàm Split() trả ra một mảng các chuỗi, và câu lệnh foreach duyệt qua mảng và in nó ra.
Trong trường hợp này, kết quả đặc biệt không hữu ích bởi vì chuỗi “, ” bị tách hai lần. Điều này có thể được sửa lại bằng cách sử dụng các lớp biểu thức bình thường.
StringBuilder
Mặc dù String.Format() có thể được sử dụng để tạo ra một chuỗi dựa trên các giá trị của các chuỗi khác, nhưng nó không phải là cách hiệu quả nhất để tập hợp các chuỗi. Thời gian chạy cung cấp lớp StringBuilder để làm quá trình này dễ dàng hơn.
Lớp StringBuilder hỗ trợ các thuộc tính và phương thức sau:
THUỘC TÍNH |
MÔ TẢ |
Capacity |
Trả ra hay thiết đặt số lượng ký tự StringBuilder có thể lưu giữ |
[] |
Chỉ mục StringBuilder được sử dụng để lấy hay thiết đặt một ký tự tại một vị trí xác định |
Length |
Trả ra hay thiết đặt độ dài |
MaxCapacity |
Trả ra hay thiết đặt sức chứa lớn nhất của StringBuilder |
PHƯƠNG THỨC |
MÔ TẢ |
Append() |
Thêm một thể hiện chuỗi của một đối tượng |
AppendFormat() |
Thêm một thể hiện chuỗi của mọt đối tượng, có sử dụng một chuỗi định dạng xác định cho đối tượng |
EnsureCapacity() |
Đảm bảo StringBuilder có đủ chỗ cho số lượng ký tự nhất định |
Insert() |
Chèn một thể hiện chuỗi của một đối tượng nhất định tại một ví trí xác định |
Remove() |
Xoá các ký tự xác định |
Replace() |
Thay thế tất cả các thể hiện của một ký tự bằng một ký tự mới |
Ví dụ sau đây trình bày cách lớp StringBuilder có thể được sử dụng để tạo một chuỗi từ các chuỗi độc lập.
using System;
using System.Text;
class Test {
public static void Main() {
string s = “I will not buy this record, it is scratched”;
char[] separators = new char[] {‘ ‘, ‘,’};
StringBuilder sb = new StringBuilder();
int number = 1;
foreach (string sub in s.Split(separators)) {
sb.AppendFormat(“{0}: {1} “, number++, sub);
}
Console.WriteLine(“{0}”, sb);
}
}
Mã này sẽ tạo ra một chuỗi với các từ đếm, và sẽ cho ra kết quả sau:
1: I 2: will 3: not 4: buy 5: this 6: record 7: 8: it 9: is 10: scratched
Bởi vì lời gọi đến split() xác định cả khoảng trống và dấu phẩy như các ký tự chia cắt, nên nó cho rằng có một từ giữa dấu phẩy và khoảng trống, mà kết quả là một phần tử rỗng.
Các biểu thức bình thường
Nếu các hàm tìm kiếm có trong lớp String không đủ khả năng, thì không gian tên System.Text chứa đựng một lớp biểu thức bình thường tên là Regex. Các biểu thức bình thường cung cấp một phương thức rất mạnh để thực hiện tìm kiếm và/hoặc thay thế.
Trong khi có một vài ví dụ về cách sử dụng các biểu thức bình thường trong phần này, một mô tả chi tiết là vượt quá phạm vi của quyển sách. Có một vài sách về biểu thức bình thường là đáp ứng điều đó, và chủ đề này cũng được trình bày trong đa số sách về Perl.
Lớp biểu thức bình thường sử dụng một kỹ thuật khá thú vị để có hiệu quả cao nhất. Thay vì thể hiện một biểu thức bình thường cho mỗi so khớp, nó viết một chương trình ngắn để cài đặt một so khớp biểu thức bình thường, và mã này sau đó được thực hiện.
Ví dụ trước sử dụng Split() có thể được xem lại để sử dụng một biểu thức bình thường, hơn là các ký tự đơn lẻ, để chỉ rõ cách tách chuỗi sẽ xảy ra. Điều này sẽ loại bỏ từ trống trong ví dụ trước.
// file: regex.cs
// compile with: csc /r:system.text.regularexpressions.dll regex.cs
using System;
using System.Text.RegularExpressions;
class Test {
public static void Main() {
string s = “Oh, I hadn’t thought of that”;
Regex regex = new Regex(@”( |, )”);
char[] separators = new char[] {‘ ‘, ‘,’};
foreach (string sub in regex.Split(s)) {
Console.WriteLine(“Word: {0}”, sub);
}
}
}
Ví dụ này cho ra kết quả sau:
Word: Oh
Word: I
Word: hadn’t
Word: thought
Word: of
Word: that
Trong một biểu thức bình thường, chuỗi được tách hoặc trên một khoảng trắng hoặc trên một dấu phẩy theo sau là một khoảng trống.
Phân tách phức tạp hơn
Sử dụng các biểu thức bình thường để cải thiện chức năng của Split() không thật sự thể hiện sức mạnh của chúng. Ví dụ sau sử dụng các biểu thức bình thường để phân tích một tập tin IIS. Tập tin log này như sau:
#Software: Microsoft Internet Information Server 4.0
#Version: 1.0
#Date: 1999-12-31 00:01:22
#Fields: time c-ip cs-method cs-uri-stem sc-status
00:01:31 157.56.214.169 GET /Default.htm 304
00:02:55 157.56.214.169 GET /docs/project/overview.htm 200
Đoạn mã sau sẽ phân tích nó thành một hình thức hữu dụng hơn.
// file: logparss e.cs
// compile with: csc logparse.cs /r:system.net.dll /r:system.text.regularexpressions.dll
using System;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections;
class Test {
public static void Main(string[] args) {
if (args.Length == 0){ //we need a file to parse
Console.WriteLine(“No log file specified.”);
}
else
ParseLogFile(args[0]);
}
public static void ParseLogFile(string filename) {
if (!System.IO.File.FileExists(filename)) {
Console.WriteLine (“The file specified does not exist.”);
}
else {
FileStream f = new FileStream(filename, FileMode.Open);
StreamReader stream = new StreamReader(f);
string line;
line = stream.ReadLine(); // header line
line = stream.ReadLine(); // version line
line = stream.ReadLine(); // Date line
Regex regexDate= new Regex(@”\:\s(?<date>[^\s]+)\s”);
Match match = regexDate.Match(line);
string date = “”;
if (match.Length != 0)
date = match.Group(“date”).ToString();
line = stream.ReadLine(); // Fields line
Regex regexLine = new Regex( // match digit or :
@”(?<time>(\d|\:)+)\s” +
// match digit or .
@”(?<ip>(\d|\.)+)\s” +
// match any non-white
@”(?<method>\S+)\s” +
// match any non-white
@”(?<uri>\S+)\s” +
// match any non-white
@”(?<status>\d+)”);
// read through the lines, add an
// IISLogRow for each line
while ((line = stream.ReadLine()) != null) {
//Console.WriteLine(line);
match = regexLine.Match(line);
if (match.Length != 0) {
Console.WriteLine(“date: {0} {1}”, date, match.Group(“time”));
Console.WriteLine(“IP Address: {0}”, match.Group(“ip”));
Console.WriteLine(“Method: {0}”, match.Group(“method”));
Console.WriteLine(“Status: {0}”, match.Group(“status”));
Console.WriteLine(“URI: {0}\n”, match.Group(“uri”));
}
}
f.Close();
}
}
}
Cấu trúc chung của đoạn mã này là quen thuộc. Có hai biểu thức bình thường được sử dụng trong ví dụ này. Chuỗi ngày và biểu thức bình thường được sử dụng để đối sánh như sau:
#Date: 1999-12-31 00:01:22
\:\s(?<date>[^\s]+)\s
Trong mã , các biểu thức bình thường là thường được viết bằng cách sử dụng cú pháp chuỗi đúng nguyên văn, vì cú pháp biểu thức bình thường cũng sử dụng ký tự sổ ngược (\). Các biểu thức bình thường là được đọc đơn giản nhất nếu chúng được chia thành các thành phần tách biệt. Phần
\:
đối sánh với dấu hai chấm (:). Dấu sổ ngược (\) được đòi hỏi bởi vì dấu hai chaấmtự bản thân nó có nhiều nghĩa khác nữa. Phần
\s
đối sánh với một ký tự đơn lẻ của khoảng trắng (tab hoặc khoảng trống). Trong phần tiếp theo
(?<date>[^\s]+)
?<date> định rõ một giá trị sẽ được đối sánh, nên nó có thể trích rút sau này. [^\s] được gọi là một nhóm ký tự, với ký tự ^ có nghĩa là “không có các ký tự sau”. Do đó nhóm này đối sánh bất kỳ ký tự nào trừ khoảng trắng. Cuối cùng, ký tự + có nghĩa là đối sánh một hay nhiều sự xuất hiện được mô tả ở trên (trừ khoảng trắng). Dấu ngoặc được sử dụng để giới hạn với 1999-12-31. Để đối sánh cẩn thận hơn, chỉ định /d (chữ số) có thể được sử dụng, với toàn bộ biểu thức được viết như sau:
\:\s(?<date>\d\d\d\d-\d\d-\d\d)\s
Biểu thức này là một biểu thức bình thường đơn giản. Một biểu thức bình thường phức tạp hơn được sử dụng để đối sánh mỗi dòng của tập tin log. Do tính quy tắc của mỗi dòng, Split() cũng có thể được sử dụng, nhưng điều này sẽ không như minh hoạ. Các mệnh đề của biểu thức bình thường như sau:
<time>(\d|\:)+)\s // match digit or : to extract time
(?<ip>(\d|\.)+)\s // match digit or . to get IP address
(?<method>\S+)\s // any non-whitespace for method
(?<uri>\S+)\s // any non-whitespace for uri
(?<status>\d+) // any digit for status