STDIO
Tìm kiếm gần đây
    • Nội dung
    • QR Code
    • 0
    • 0
    • Sao chép

    Tối Ưu Xử Lý Chuỗi với StringBuilder - Phần 1

    Bài viết giới thiệu và phân tích hiệu năng khi xử lí chuỗi với đối tượng String và StringBuilder trong C#.
    03/02/2018
    19/09/2020
    8 phút đọc
    Tối Ưu Xử Lý Chuỗi với StringBuilder - Phần 1

    Sự khác nhau giữa việc xử lí chuỗi với StringStringBuilder, phân tích ưu nhược điểm của cả hai nhằm giúp độc giả có cái nhìn tổng quan về hai đối tượng này. Bài viết sử dụng ngôn ngữ lập trình C# (nền tảng .NET 4.0 trở lên) để minh họa cho các ví dụ.

    * Ngoài ra bạn có thể xem như tương tự cho sự khác biệt giữa String và StringBuilder trong Java.

    Nhu cầu xử lí chuỗi

    Trong lập trình nói chung, nhu cầu xử lí thông tin là vô cùng cần thiết. Những thông tin này, còn gọi là Input, đều được người dùng đưa vào dưới dạng các chuỗi kí tự được chúng ta định sẵn. Do đó, việc xử lí chuỗi kí tự trong lập trình đã trở thành bước quan trọng nhưng cơ bản nhất để ứng dụng có thể "hiểu" những yêu cầu từ người dùng.

    Trong các ngôn ngữ phát triển đi trước như C, việc xử lí chuỗi được các lập trình viên xử lí thông qua việc xử lí trên mảng kí tự char[] đi kèm với các phương thức xử lí cần thiết. Tuy nhiên đến thế hệ các ngôn ngữ lập trình hướng đối tượng, chúng ta phát sinh ra nhu cầu, tại sao không hiện thực một đối tượng sao cho bản thân đối tượng ấy có khả năng nhận được dữ liệu dạng chuỗi kí tự và đi kèm theo là các phương thức xử lí mạnh mẽ được tích hợp đồng bộ đi kèm. Như thế, một đối tượng với tên gọi String được ra đời.

    String và StringBuilder

    String

    String là một đối tượng trong C# (tương tự như một số ngôn ngữ lập trình hướng đối tượng khác) dùng để hiện thực một chuỗi kí tự Unicode (rỗng hoặc khác rỗng).

    StringBuilder

    String không tốt cho các trường hợp đồi hỏi hiệu năng, vì vậy StringBuilder ra đời, là một đối tượng chuyên xử lí chuỗi với hiệu năng cao hơn.

    Vấn đề xử lí chuỗi với String và StringBuilder

    Cấu trúc tổ chức String và StringBuilder

    String

    Là một đối tượng kiểu immutable (bất biến) và sealed (bị niêm phong, không có khả năng tạo ra kế thừa), nghĩa là khi bạn tạo mới một đối tượng, các giá trị của đối tượng này hoàn toàn không thể thay đổi.

    Sự thay đổi giá trị của String nếu diễn ra sẽ diễn ra trên một đối tượng mới.

    StringBuilder

    public sealed class StringBuilder : ISerializable
    {
        // Fields
        private const string CapacityField = "Capacity";
        internal const int DefaultCapacity = 0x10;
        internal char[] m_ChunkChars;
        internal int m_ChunkLength;
        internal int m_ChunkOffset;
        internal StringBuilder m_ChunkPrevious;
        internal int m_MaxCapacity;
        private const string MaxCapacityField = "m_MaxCapacity";
        internal const int MaxChunkSize = 0x1f40;
        private const string StringValueField = "m_StringValue";
        private const string ThreadIDField = "m_currentThread";
    }

    Nhìn vào cấu trúc trên ta có vài nhận xét như sau:

    • internal char[] m_ChunkChars: StringBuilder dùng một mảng char để lưu trữ các dữ liệu.
    • internal StringBuilder m_ChunkPrevious: StringBuilder được tổ chức như một danh sách liên kết, do đó việc thao tác chuỗi cơ bản như chèn, xóa, cắt, tìm kiếm chuỗi… sẽ là những thao tác trên danh sách liên kết.
    • internal int m_MaxCapacity: StringBuilder cũng có giới hạn sức chứa dữ liệu.

    Hiệu năng của String và StringBuilder

    Hiệu năng của String

    Trước khi vào hiệu năng của StringBuilder, hãy xem xét một chút vấn đề hiệu năng của String để hiểu rõ hơn sự khác nhau biệt với StringBuilder.

    Tạo một đối tượng String và gán cho nó một giá trị là Hello.

    String MyExampleString = new String();
    MyExampleString = "Hello";
    01
    Bộ nhớ lưu trữ "Hello"

    MyExampleString được khởi tạo và cấp phát một vùng nhớ tại 0x90000.

    Nối thêm chuỗi choMyExampleString:

    MyExampleString = MyExampleString + ", is it me you're looking for";
    02

    MyExampleString tham chiếu đến một đối tượng khác (tạm gọi là UndentifyString) tại một vùng nhớ mới 0x83320 và gán giá trị mới vào.

    Sau khi quá trình gán giá trị hoàn tất tại UndentifyString, MyExampleString sẽ tham chiếu đến UndentifyString. Vùng nhớ cũ 0x90000 sẽ được Garbage Collector dọn dẹp.

    Hiệu năng của StringBuilder

    Minh Họa 1

    Tạo một đối tượng StringBuilder MyExampleStringBuilder và gán giá trị tương tự như ví dụ trên.

    StringBuilder MyExampleStringBuilder = new StringBuilder();
    MyExampleStringBuilder.Append(“Hello”);
    01
    Bộ nhớ lưu trữ "Hello"

    MyExampleStringBuilder được khởi tạo và cấp phát một vùng nhớ là 0x90000 (giả sử vùng nhớ 0x90000 ở ví dụ trên đã được thu hồi).

    Thêm dữ liệu vào để MyExampleStringBuilder thành Hello, is it me you're looking for

    MyExampleStringBuilder.Append(", is it me you're looking for");
    03

    StringBuilder lại có cơ chế hoàn toàn khác. Thay vì tạo mới vùng nhớ như String, StringBuilder có khả năng thao tác trên chính vùng nhớ được cấp phát.

    Minh Họa 2

    Trong minh họa này, tôi sẽ tính toán thời gian thực thi của StringStringBuilder với cùng một yêu cầu.

    Cụ thể tôi sẽ tạo 2 đối tượng StringStringBuilder chứa kí tự rỗng. Lần lượt cộng giá trị “X” vào giá trị của 2 đối tượng trên với số lần cộng đều là 100 ngàn lần. Hãy xem thời gian thực thi phương thức trên của 2 đối tượng StringStringBuilder.

    Khởi tạo các biến cần thiết.

    const int Length = 1;
    const int Loops = 100000;
    DateTime StartTime;
    DateTime EndTime;
    int i;
    string TextSource = new String('X', Lenght);
    string TextDest = "";
    

    Thực thi cộng chuỗi cho TestDest bằng toán tử + của String với số lần là Loops và tính toán thời gian thực thi với StartTimeEndTime.

    StartTime = DateTime.Now;
    for (i = 0; i < Loops; i++)
        TextDest += TextSource;
    EndTime = DateTime.Now;
    Console.WriteLine("String took " + (EndTime - StartTime).TotalSeconds + " seconds.");

    Kết quả nhận được: (EndTime - StartTime).TotalSeconds  =~ 2.5 giây.

    Thực thi cộng chuỗi cho TestDest bằng phương thức Append() của StringBuilder với số lần là Loops và tính toán thời gian thực thi với StartTimeEndTime.

    StartTime = DateTime.Now;
    StringBuilder sb = new StringBuilder((int)(Lenght * Loops * 1.1));
    for (i = 0; i < Loops; i++) 
        sb.Append(TextSource);
    EndTime = DateTime.Now;
    Console.WriteLine("String Builder took " + (EndTime - StartTime).TotalSeconds + " seconds.");

    Kết quả nhận được: (EndTime - StartTime).TotalSeconds  =~ 0.001 giây.

    Kết quả thời gian thực thi lệnh với cùng một điều kiện của StringStringBuilder rất chênh lệch.

    Với các thao tác chuỗi mà có sự thay đổi giá trị với String, cứ mỗi lệnh thao tác chuỗi được thực thi, String sẽ tạo hẳn một đối tượng mới đi kèm là vùng nhớ mới đồng thời sao chép giá trị của đối tượng cũ ở vùng nhớ cũ và kết quả mà lệnh thao tác chuỗi thực thi vào đối tượng mới và vùng nhớ mới. Ví dụ như ở minh họa trên:

    • Khi i = 0, TextDest = "", giả sử vùng nhớ TestDestA
    • Gọi lệnh TextDest += TextSource
    • TextDest tạo ra một đối tượng String khác (tạm gọi là UndentifyString) được cấp vùng nhớ mới là B và giá trị vùng nhớ này là kết quả của việc sao chép giá trị hiện tại của TextDest (vùng nhớ A) là giá trị rỗng "" cộng với giá trị mới được đưa vào của TextSource"X". Sau khi quá trình sao chép hoàn tất, TestDest tham chiếu tới vùng nhớ của UndentifyString.

    Vậy nên giá trị mới của TextDest tại vùng nhớ B“X”. Vùng nhớ cũ TestDest sẽ được Garbage Collector dọn dẹp.

    Việc này được lặp lại cho đến khi vòng lặp kết thúc.

    Một số lượng rất lớn đối tượng phụ đi kèm là những vùng nhớ mới được tạo ra để phục vụ thao tác cộng chuỗi đơn giản. Sau đó là quá trình sao chép dữ liệu sang đối tượng mới, làm tốn tài nguyên máy tính lẫn mang nhiều nguy cơ tiềm ẩn trong quá trình thực thi chương trình, cũng như thời gian thực thi lệnh sẽ rất dài.

    Tuy nhiên, các thao chuỗi của StringBuilder lại chỉ diễn ra trên vùng nhớ chúng được cấp phát do chúng dùng một mảng dữ liệu để lưu trữ. Điều này nghĩa là sự thay đổi về giá trị của StringBuilder hoàn toàn được xử lí “nội bộ”. Do vậy tài nguyên cũng như thời gian thực thi của StringBuilder là tốt hơn String.

    Kết luận

    • Việc thao tác với chuỗi (gán, cắt, thêm…) với StringBuilder diễn ra ngay trên đối tượng được cấp phát.
    • Ngược lại, String sẽ tạo ra đối tượng mới.
    • Về hiệu năng, StringBuilder tỏ ra vượt trội hơn so với String khi nghiệp vụ bạn đang xử lí đòi hỏi việc thao tác chuỗi mà giá trị chuỗi biến động với số lượng lớn.
    • Tuy nhiên StringBuilder không hỗ trợ vấn đề thao tác chuỗi mạnh mẽ như String, như nhiều toán tử thao tác như +, +=, =, … cũng như các phương thức như Contains, ToList, ToArray, ..
    • Nếu nghiệp vụ bạn đang xử lí yêu cầu việc thao tác chuỗi mà giá trị của chuỗi biến động ít, String hoàn toàn là sự lựa chọn hợp lí bởi sự hỗ trợ đa dạng và đầy đủ các toán tử thao tác chuỗi và phương thức tích hợp đi kèm và cả tốc độ lập trình.
    0 Bình luận
    Java

    Java

    Tổng hợp các bài viết Java hữu ích

    Đề xuất

    Xử Lý Ảnh Với OpenCV: Các Phép Toán Hình Thái Học
    Giới thiệu những thuật toán cơ sở trong xử lý hình thái học, những thuật ...
    Xử Lý Chuỗi Trong PHP
    Thao tác xử lý chuỗi và các hàm xử lý chuỗi thông dụng trong ngôn ngữ ...
    28/08/2015

    Khám phá

    Chỉ Thị Tiền Xử Lý trong C/C++
    Chỉ thị tiền xử lý là những chỉ thị cung cấp cho bộ tiền xử lý để xử lý ...
    10/08/2016
    GameObject - Thao Tác với C# Script
    Giới thiệu các thành phần chung của 1 GameObject và các thao tác với C# ...
    Phép Tích Chập Trong Xử Lý Ảnh (Convolution)
    Convolution là kỹ thuật quan trọng trong Xử Lý Ảnh, được sử dụng chính ...
    Tối Ưu Hóa - Tối Ưu Hóa Code C++
    Các mẹo tối ưu hóa code làm tăng hiệu suất trong lập trình C++.
    Box2D - Phần 1: Giới Thiệu - Một Số Thuật Ngữ và Khái Niệm
    Giới thiệu engine xử lý vật lý Box2D, các khái niệm, cách thành phần ...
    Xử Lý Ảnh Với OpenCV: Độ Sáng, Độ Tương Phản Và Biểu Đồ Tần Số Histogram
    Hướng dẫn thay đổi Brightness, Contrast của ảnh, tìm hiểu biểu đồ tần số ...
    std::string và Xử Lý Chuỗi trong C++
    Giới thiệu và hướng dẫn sử dụng thư viện std::string trong C++ xử lý ...
    15/08/2015
    9 Tính Năng Quan Trọng Trong C++11
    C++11 là một phiên bản cải tiến và nâng cấp từ C++98 (hay các bạn vẫn ...
    13/08/2015
    Khi bạn nhấn vào liên kết sản phẩm do STDIO đề xuất và mua hàng, STDIO có thể nhận được hoa hồng. Điều này hỗ trợ STDIO tạo thêm nhiều nội dung hữu ích.. Tìm hiểu thêm.
    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020