Tải bản đầy đủ (.pdf) (10 trang)

Các giải pháp lập trình CSharp- P7 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.63 MB, 10 trang )

61
Chương 2: Thao tác dữ liệu
ầ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:
H

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ỗ ộ ệ ả
Thao tác chu i m t cách hi u quỗ ộ ệ ả


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

Capacity

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 thậ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

Length
, cần chú
ý các điểm sau:
62
Chương 2: Thao tác dữ liệu


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();

}
2.
2.
Mã hóa chu i b ng các ki u mã hóa ký tỗ ằ ể ự
Mã hóa chu i b ng các ki u mã hóa ký tỗ ằ ể ự


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
).
63
Chương 2: Thao tác dữ liệu


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ự
Kiểu mã hóa Lớp Sử dụng
ASCII
ASCIIEncoding
GetEncoding(20127)

hay thuộc tính
ASCII
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
UTF-7

UTF7Encoding
GetEncoding(65000)

hay thuộc tính
UTF7
UTF-8
UTF8Encoding
GetEncoding(65001)

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
Windows OS
Encoding GetEncoding(1252)
Sau 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 {
64
Chương 2: Thao tác dữ liệu
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.
output.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();
65
Chương 2: Thao tác dữ liệu
output.Close();
}
}
}
Chạy
CharacterEncodingExample
sẽ tạo ra file output.txt. Mở file này trong một trình soạn
thảo có hỗ trợ Unicode, bạn sẽ thấy kết quả như sau:
Source Text : Area = Πr^2
UTF-16 Bytes: 41-00-72-00-65-00-61-00-20-00-3D-00-20-00-A0-03-72-00-5E-00-32-00
UTF-8 Bytes: 41-72-65-61-20-3D-20-CE-A0-72-5E-32
ASCII Bytes: 41-72-65-61-20-3D-20-3F-72-5E-32
UTF-8 Text : Area = Πr^2
ASCII Text : Area = ?r^2
Chú ý rằng, nếu sử dụng UTF-16 thì mỗi ký tự được mã hóa bởi 2 byte, nhưng vì hầu hết các
ký tự đều là ký tự chuẩn nên byte cao là 0 (nếu sử dụng little-endian thì byte thấp viết trước).
Do đó, hầu hết các ký tự đều được mã hóa bởi những số giống nhau trong ba kiểu mã hóa,

ngoại trừ ký hiệu PI được mã hóa khác (được in đậm trong kết quả ở trên). Để mã hóa PI cần
2 byte, đòi hỏi này được UTF-8 hỗ trợ nên thể hiện được Π, trong khi đó ASCII chỉ sử dụng
một byte nên thay PI bằng mã 3F, đây là mã của dấu hỏi (?).

Nếu chuyển các ký tự
Unicode
sang
ASCII
hoặc một kiểu mã hóa khác thì có thể
mất dữ liệu. Bất kỳ ký tự
Unicode
nào có mã ký tự không biểu diễn được trong
kiểu mã hóa đích sẽ bị bỏ qua khi chuyển đổi.
Lớp
Encoding
cũng cung cấp phương thức tĩnh
Covert
để đơn giản hóa việc chuyển một mảng
byte từ kiểu mã hóa này sang kiểu mã hóa khác không phải qua trung gian UTF-16. Ví dụ,
dòng mã sau chuyển trực tiếp các byte trong mảng
asciiString
từ ASCII sang UTF-8:
byte[] utf8String = Encoding.Convert(Encoding.ASCII, Encoding.UTF8,
asciiString);
3.
3.
Chuy n các ki u giá tr c b n thành m ng ki u byteể ể ị ơ ả ả ể
Chuy n các ki u giá tr c b n thành m ng ki u byteể ể ị ơ ả ả ể



Bạn cần chuyển các kiểu giá trị cơ bản thành mảng kiểu byte.


Lớp
System.BitConverter
cung cấp các phương thức tĩnh rất tiện lợi cho việc
chuyển đổi qua lại giữa các mảng kiểu byte và hầu hết các kiểu giá trị cơ bản—
trừ kiểu
decimal
. Để chuyển một giá trị kiểu
decimal
sang mảng kiểu byte, bạn
cần sử dụng đối tượng
System.IO.BinaryWriter
để ghi giá trị đó vào một thể hiện
System.IO.MemoryStream
, sau đó gọi phương thức
Memorystream.ToArray
. Để có
một giá trị
decimal
từ một mảng kiểu byte, bạn cần tạo một đối tượng
MemoryStream
từ mảng kiểu byte, sau đó sử dụng thể hiện
System.IO.BinaryReader
để đọc giá trị này từ
MemoryStream
.
66
Chương 2: Thao tác dữ liệu

Phương thức tĩnh
GetBytes
của lớp
BitConverter
cung cấp nhiều phiên bản nạp chồng cho
phép chuyển hầu hết các kiểu giá trị cơ bản sang mảng kiểu byte. Các kiểu được hỗ trợ là
bool
,
char
,
double
,
short
,
int
,
long
,
float
,
ushort
,
uint
, và
ulong
. Lớp
BitConverter
cũng
cung cấp các phương thức tĩnh cho phép chuyển các mảng kiểu byte thành các kiểu giá trị
chuẩn như

ToBoolean
,
ToUInt32
,
ToDouble
, Ví dụ sau minh họa cách chuyển các giá trị
bool

int
thành mảng kiểu byte, và ngược lại. Đối số thứ hai trong
ToBoolean

ToUInt32
cho
biết vị trí (tính từ 0) trong mảng byte mà
BitConverter
sẽ lấy các byte kể từ đó để tạo giá trị
dữ liệu.
byte[] b = null;
// Chuyển một giá trị bool thành mảng kiểu byte và hiển thị.
b = BitConverter.GetBytes(true);
Console.WriteLine(BitConverter.ToString(b));
// Chuyển một mảng kiểu byte thành giá trị bool và hiển thị.
Console.WriteLine(BitConverter.ToBoolean(b,0));
// Chuyển một giá trị int thành mảng kiểu byte và hiển thị.
b = BitConverter.GetBytes(3678);
Console.WriteLine(BitConverter.ToString(b));
// Chuyển một mảng kiểu byte thành giá trị int và hiển thị.
Console.WriteLine(BitConverter.ToInt32(b,0));
Đối với kiểu

decimal
, lớp
BitConverter
không hỗ trợ, nên bạn phải sử dụng thêm
MemoryStream

BinaryWriter
.
// Tạo mảng kiểu byte từ giá trị decimal.
public static byte[] DecimalToByteArray (decimal src) {
// Tạo một MemoryStream làm bộ đệm chứa dữ liệu nhị phân.
using (MemoryStream stream = new MemoryStream()) {
// Tạo một BinaryWriter để ghi dữ liệu nhị phân vào stream.
using (BinaryWriter writer = new BinaryWriter(stream)) {
// Ghi giá trị decimal vào BinaryWriter/MemoryStream.
writer.Write(src);
67
Chương 2: Thao tác dữ liệu
// Trả về mảng kiểu byte.
return stream.ToArray();
}
}
}
Để chuyển một mảng kiểu byte thành một giá trị
decimal
, sử dụng
BinaryReader
để đọc từ
MemoryStream
.

// Tạo giá trị decimal từ mảng kiểu byte.
public static decimal ByteArrayToDecimal (byte[] src) {
// Tạo một MemoryStream chứa mảng.
using (MemoryStream stream = new MemoryStream(src)) {
// Tạo một BinaryReader để đọc từ stream.
using (BinaryReader reader = new BinaryReader(stream)) {
// Đọc và trả về giá trị decimal từ
// BinaryReader/MemoryStream.
return reader.ReadDecimal();
}
}
}

Lớp
BitConverter
cũng cung cấp phương thức
ToString
để tạo một
String
chứa
giá trị mảng. Gọi
ToString
và truyền đối số là một mảng byte sẽ trả về một
String
chứa giá trị thập lục phân của các byte trong mảng, các giá trị này cách
nhau bởi dấu gạch nối, ví dụ “
34-A7-2C
”. Tuy nhiên, không có phương thức nào
tạo một mảng kiểu byte từ một chuỗi theo định dạng này.
4.

4.
Mã hóa d li u nh phân thành văn b nữ ệ ị ả
Mã hóa d li u nh phân thành văn b nữ ệ ị ả


Bạn cần chuyển dữ liệu nhị phân sang một dạng sao cho có thể được lưu trữ
trong một file văn bản
ASCII
(chẳng hạn file
XML
), hoặc được gởi đi trong e-
mail.


Sử dụng các phương thức tĩnh
ToBase64String

FromBase64String
của lớp
System.Converter
để chuyển đổi qua lại giữa dữ liệu nhị phân và chuỗi được mã
hóa theo
Base64
.
68
Chương 2: Thao tác dữ liệu
Base64 là một kiểu mã hóa cho phép bạn mô tả dữ liệu nhị phân như một dãy các ký tự ASCII
để nó có thể được chèn vào một file văn bản hoặc một e-mail, mà ở đó dữ liệu nhị phân không
được cho phép. Base64 làm việc trên nguyên tắc sử dụng 4 byte để chứa 3 byte dữ liệu nguồn
và đảm bảo mỗi byte chỉ sử dụng 7 bit thấp để chứa dữ liệu. Điều này có nghĩa là mỗi byte dữ

liệu được mã hóa theo Base64 có dạng giống như một ký tự ASCII, nên có thể được lưu trữ
hoặc truyền đi bất cứ nơi đâu cho phép ký tự ASCII.
Lớp
Convert
cung cấp hai phương thức
ToBase64String

FromBase64String
để mã hóa và
giải mã Base64. Tuy nhiên, trước khi mã hóa Base64, bạn phải chuyển dữ liệu thành mảng
kiểu byte; và sau khi giải mã, bạn phải chuyển mảng kiểu byte trở về kiểu dữ liệu thích hợp
(xem lại mục 2.2 và 2.3).
Ví dụ sau minh họa cách sử dụng lớp
Convert
để mã hóa và giải mã Base64 với chuỗi
Unicode, giá trị
int
, giá trị
decimal
. Đối với giá trị
decimal
, bạn phải sử dụng lại các phương
thức
ByteArrayToDecimal

DecimalToByteArray
trong mục 2.3.
// Mã hóa Base64 với chuỗi Unicode.
public static string StringToBase64 (string src) {
// Chuyển chuỗi thành mảng kiểu byte.

byte[] b = Encoding.Unicode.GetBytes(src);
// Trả về chuỗi được mã hóa theo Base64.
return Convert.ToBase64String(b);
}
// Giải mã một chuỗi Unicode được mã hóa theo Base64.
public static string Base64ToString (string src) {
// Giải mã vào mảng kiểu byte.
byte[] b = Convert.FromBase64String(src);
// Trả về chuỗi Unicode.
return Encoding.Unicode.GetString(b);
}
// Mã hóa Base64 với giá trị decimal.
public static string DecimalToBase64 (decimal src) {
// Chuyển giá trị decimal thành mảng kiểu byte.
69
Chương 2: Thao tác dữ liệu
byte[] b = DecimalToByteArray(src);
// Trả về giá trị decimal được mã hóa theo Base64.
return Convert.ToBase64String(b);
}
// Giải mã một giá trị decimal được mã hóa theo Base64.
public static decimal Base64ToDecimal (string src) {
// Giải mã vào mảng kiểu byte.
byte[] b = Convert.FromBase64String(src);
// Trả về giá trị decimal.
return ByteArrayToDecimal(b);
}
// Mã hóa Base64 với giá trị int.
public static string IntToBase64 (int src) {
// Chuyển giá trị int thành mảng kiểu byte.

byte[] b = BitConverter.GetBytes(src);
// Trả về giá trị int được mã hóa theo Base64.
return Convert.ToBase64String(b);
}
// Giải mã một giá trị int được mã hóa theo Base64.
public static int Base64ToInt (string src) {
// Giải mã vào mảng kiểu byte.
byte[] b = Convert.FromBase64String(src);
// Trả về giá trị int.
return BitConverter.ToInt32(b,0);
}
70
Chương 2: Thao tác dữ liệu
5.
5.
S d ng bi u th c chính quy đ ki m tra d li u nh pử ụ ể ứ ể ể ữ ệ ậ
S d ng bi u th c chính quy đ ki m tra d li u nh pử ụ ể ứ ể ể ữ ệ ậ


Bạn cần kiểm tra dữ liệu nhập vào có đúng với cấu trúc và nội dung được quy
định trước hay không. Ví dụ, bạn muốn bảo đảm người dùng nhập địa chỉ
IP
, số
điện thoại, hay địa chỉ e-mail hợp lệ.


Sử dụng biểu thức chính quy để bảo đảm dữ liệu nhập đúng cấu trúc và chỉ chứa
các ký tự được quy định trước đối với từng dạng thông tin.
Khi ứng dụng nhận dữ liệu từ người dùng hoặc đọc dữ liệu từ file, bạn nên giả định dữ liệu
này là chưa chính xác và cần được kiểm tra lại. Một nhu cầu kiểm tra khá phổ biến là xác định

các số điện thoại, số thẻ tín dụng, địa chỉ e-mail có đúng dạng hay không. Việc kiểm tra cấu
trúc và nội dung của dữ liệu không đảm bảo dữ liệu là chính xác nhưng giúp loại bỏ nhiều dữ
liệu sai và đơn giản hóa việc kiểm tra sau này. Biểu thức chính quy (regular expression) cung
cấp một cơ chế rất tốt để kiểm tra một chuỗi có đúng với cấu trúc quy định trước hay không,
do đó bạn có thể lợi dụng tính năng này cho mục đích kiểm tra dữ liệu nhập.
Trước tiên, bạn phải xác định cú pháp của biểu thức chính quy cho phù hợp với cấu trúc và
nội dung của dữ liệu cần kiểm tra, đây là phần khó nhất khi sử dụng biểu thức chính quy. Biểu
thức chính quy được xây dựng trên hai yếu tố: trực kiện (literal) và siêu ký tự
(metacharacter). Trực kiện mô tả các ký tự có thể xuất hiện trong mẫu mà bạn muốn so trùng;
siêu ký tự hỗ trợ việc so trùng các ký tự đại diện (wildcard), tầm trị, nhóm, lặp, điều kiện, và
các cơ chế điều khiển khác. Ở đây không thảo luận đầy đủ về cú pháp biểu thức chính quy
(tham khảo tài liệu .NET SDK để hiểu thêm về biểu thức chính quy), nhưng bảng 2.2 sẽ mô tả
các siêu ký tự thường dùng.
Bảng 2.2
Các siêu ký tự thường dùng
Siêu ký tự Mô tả
.
Mọi ký tự trừ ký tự xuống dòng (
\n
).
\d
Ký tự chữ số thập phân (digit).
\D
Ký tự không phải chữ số (non-digit).
\s
Ký tự whitespace (như khoảng trắng, tab ).
\S
Ký tự non-whitespace.
\w
Ký tự word (gồm mẫu tự, chữ số, và dấu gạch dưới).

\W
Ký tự non-word.
^
Bắt đầu một chuỗi hoặc dòng.
\A
Bắt đầu một chuỗi.
$
Kết thúc một chuỗi hoặc dòng.
\z
Kết thúc một chuỗi.

×