1.1 Thao tác chuỗi một cách hiệu quả V Bạn cần thao tác trên nội dung của một đối tượng String và tránh chi phí của việc tự động tạo các đối tượng String mới do tính không đổi của đối
Trang 1Chương 2 : Thao tác Dữ liệu
Hầu hết các ứng dụng đều cần thao tác trên một loại dữ liệu nào đó Microsoft NET Framework cung cấp nhiều kỹ thuật để đơn giản hóa hay nâng cao hiệu quả các thao tác
dữ liệu thông dụng Chương này sẽ đề cập các kỹ thuật sau:
Thao tác chuỗi một cách hiệu quả (mục 2.1)
Mô tả các kiểu dữ liệu cơ sở bằng các kiểu mã hóa khác nhau (mục 2.2, 2.3, và 2.4)
Sử dụng biểu thức chính quy để xác nhận tính hợp lệ và thao tác chuỗi (mục 2.5 và 2.6)
Làm việc với ngày và giờ (mục 2.7 và 2.8)
Làm việc với mảng và tập hợp (mục 2.9, 2.10, và 2.11)
Tuần tự hóa trạng thái đối tượng và lưu nó vào file (mục 2.12)
1.1 Thao tác chuỗi một cách hiệu quả
V Bạn cần thao tác trên nội dung của một đối tượng String và tránh chi phí của việc tự động tạo các đối tượng String mới do tính không đổi của đối tượng String
# Sử dụng lớp System.Text.StringBuilder để thực hiện các thao tác, sau đó chuyển kết quả thành String bằng phương thức StringBuilder.ToString
Các đối tượng String trong NET là không đổi, nghĩa là một khi đã được tạo thì chúng
không thể bị thay đổi Ví dụ, nếu bạn tạo một String bằng cách nối một số ký tự hoặc chuỗi, thì khi thêm một phần tử mới vào cuối String hiện có, bộ thực thi sẽ tạo ra một String mới chứa kết quả (chứ không phải String cũ bị thay đổi) Do đó sẽ nảy sinh chi phí đáng kể nếu ứng dụng của bạn thường xuyên thao tác trên String
Lớp StringBuilder khắc phục vấn đề này bằng cách cung cấp một bộ đệm ký tự, và cho phép thao tác trên nội dung của nó mà bộ thực thi không phải tạo đối tượng mới để chứa kết quả sau mỗi lần thay đổi Bạn có thể tạo một đối tượng StringBuilder rỗng hoặc được khởi tạo là nội dung của một String hiện có Sau đó, thao tác trên nội dung của StringBuilder này bằng các phương thức nạp chồng (cho phép bạn chèn, thêm dạng chuỗi của các kiểu dữ liệu khác nhau) Cuối cùng, gọi StringBuilder.ToString để chuyển nội dung hiện tại của StringBuilder thành một String
Khi bạn thêm dữ liệu mới vào chuỗi, có hai thuộc tính quan trọng ảnh hưởng đến hoạt động của StringBuilder là Capacity và Length Capacity mô tả kích thước của bộ đệm StringBuilder, còn Length mô tả kích thước của chuỗi ký tự trong bộ đệm Nếu việc thêm
dữ liệu mới vào StringBuilder làm kích thước chuỗi (Length) vượt quá kích thước bộ đệm (Capacity) thì StringBuilder sẽ cấp phát bộ đệm mới để chứa chuỗi Nếu thiếu cẩn
Trang 2thận, việc cấp phát bộ đệm này có thể phủ định lợi ích của việc sử dụng StringBuilder
Do đó, nếu biết chính xác kích thước của chuỗi, hoặc biết kích thước tối đa của chuỗi, bạn có thể tránh việc cấp phát bộ đệm quá mức cần thiết bằng cách thiết lập thuộc tính Capacity hoặc chỉ định kích thước bộ đệm lúc tạo StringBuilder Khi thiết lập các thuộc tính Capacity và Length, cần chú ý các điểm sau:
• Nếu bạn thiết lập giá trị Capacity nhỏ hơn giá trị Length, thuộc tính Capacity sẽ ném ngoại lệ System.ArgumentOutOfRangeException
• Nếu bạn thiết lập giá trị Length nhỏ hơn kích thước của chuỗi hiện có trong bộ đệm, chuỗi sẽ bị cắt bớt phần lớn hơn
• Nếu bạn thiết lập giá trị Length lớn hơn kích thước của chuỗi, bộ đệm sẽ được "lấp" thêm các khoảng trắng cho bằng với Length Việc thiết lập giá trị Length lớn hơn giá trị Capacity sẽ tự động điều chỉnh Capacity cho bằng với Length
Phương thức ReverseString dưới đây minh họa cách sử dụng lớp StringBuilder để đảo một chuỗi Nếu không sử dụng lớp StringBuilder để thực hiện thao tác này thì sẽ tốn chi phí đáng kể, đặc biệt khi chuỗi nguồn dài Việc khởi tạo StringBuilder với kích thước bằng chuỗi nguồn bảo đảm không cần phải cấp phát lại bộ đệm trong quá trình đảo chuỗi public static string ReverseString(string str) {
// Kiểm tra các trường hợp không cần đảo chuỗi
if (str == null || str.Length == 1) {
return str;
}
// Tạo một StringBuilder với sức chứa cần thiết
System.Text.StringBuilder revStr =
new System.Text.StringBuilder(str.Length);
// Duyệt ngược chuỗi nguồn từng ký tự một
// và thêm từng ký tự đọc được vào StringBuilder
for (int count = str.Length-1; count > -1; count ) {
revStr.Append(str[count]);
}
// Trả về chuỗi đã được đảo
return revStr.ToString();
}
1.2 Mã hóa chuỗi bằng các kiểu mã hóa ký tự
Trang 3V Bạn cần trao đổi dữ liệu dạng ký tự với các hệ thống sử dụng kiểu mã hóa khác
với UTF-16 (kiểu mã hóa này được sử dụng bởi CRL)
# Sử dụng lớp System.Text.Encoding và các lớp con của nó để chuyển đổi ký tự giữa các kiểu mã hóa khác nhau
Unicode không phải là kiểu mã hóa duy nhất, cũng như UTF-16 không phải cách duy nhất biểu diễn ký tự Unicode Khi ứng dụng cần trao đổi dữ liệu ký tự với các hệ thống bên ngoài (đặc biệt là các hệ thống cũ), dữ liệu cần phải được chuyển đổi giữa UTF-16 và
kiểu mã hóa mà hệ thống đó hỗ trợ
Lớp trừu tượng Encoding, và các lớp con của nó cung cấp các chức năng để chuyển ký tự qua lại giữa nhiều kiểu mã hóa khác nhau Mỗi thể hiện của lớp con hỗ trợ việc chuyển
đổi giữa UTF-16 và một kiểu mã hóa khác Phương thức tĩnh Encoding.GetEncoding nhận vào tên hoặc số hiệu trang mã (code page number) của một kiểu mã hóa và trả về
thể hiện của lớp mã hóa tương ứng
Bảng 2.1 liệt kê một vài kiểu mã ký tự và số hiệu trang mã mà bạn phải truyền cho
phương thức GetEncoding để tạo ra thể hiện của lớp mã hóa tương ứng Bảng này cũng cung cấp các thuộc tính tĩnh của lớp Encoding đại diện cho phương thức GetEncoding tương ứng
Bảng 2.1 Các lớp mã hóa ký tự
ASCII ASCIIEncoding hay thuộc tính ASCII GetEncoding(20127)
Mặc định (kiểu mã hóa
hiện hành trên hệ thống) Encoding
GetEncoding(0) hay thuộc tính Default
hay thuộc tính UTF7
hay thuộc tính UTF8
UTF-16 (Big Endian) UnicodeEncoding GetEncoding(1201)
hay thuộc tính BigEndianUnicode
UTF-16 (Little Endian) UnicodeEncoding GetEncoding(1200)
hay thuộc tính Unicode
Trang 4Sau khi đã lấy được đối tượng lớp Encoding hỗ trợ kiểu mã hóa thích hợp, sử dụng
phương thức GetBytes để chuyển chuỗi nguồn (được mã hóa theo UTF-16) thành mảng
kiểu byte chứa các ký tự được mã hóa theo kiểu cần chuyển, và sử dụng GetString để chuyển mảng byte thành chuỗi đích Ví dụ dưới đây trình bày cách sử dụng một vài lớp
mã hóa:
using System;
using System.IO;
using System.Text;
public class CharacterEncodingExample {
public static void Main() {
// Tạo file giữ các kết quả
using (StreamWriter output = new StreamWriter("output.txt")) {
// Tạo và ghi ra file một chuỗi chứa ký hiệu của số PI
string srcString = "Area = \u03A0r^2";
output.WriteLine("Source Text : " + srcString);
// Ghi các byte được mã hóa theo UTF-16
// của chuỗi nguồn ra file
byte[] utf16String = Encoding.Unicode.GetBytes(srcString);
output.WriteLine("UTF-16 Bytes: {0}",
BitConverter.ToString(utf16String));
// Chuyển chuỗi nguồn được mã hóa theo UTF-16
// thành UTF-8 và ASCII
byte[] utf8String = Encoding.UTF8.GetBytes(srcString);
byte[] asciiString = Encoding.ASCII.GetBytes(srcString);
// Ghi mảng các byte được mã hóa theo UTF-8 và ASCII ra file
output.WriteLine("UTF-8 Bytes: {0}",
BitConverter.ToString(utf8String));
output.WriteLine("ASCII Bytes: {0}",
BitConverter.ToString(asciiString));
// Chuyển các byte được mã hóa theo UTF-8 và ASCII
// thành chuỗi được mã hóa theo UTF-16 và ghi ra file
Trang 5output.WriteLine("UTF-8 Text : {0}", Encoding.UTF8.GetString(utf8String)); output.WriteLine("ASCII Text : {0}", Encoding.ASCII.GetString(asciiString));
// Ghi dữ liệu xuống file và đóng file output.Flush();
output.Close();
}
}
}