2012-12-10 20 views
10

Vấn đề: Tệp rất lớn, tôi cần phân tích từng dòng để nhận 3 giá trị từ mỗi dòng. Tất cả mọi thứ hoạt động nhưng phải mất một thời gian dài để phân tích cú pháp thông qua toàn bộ tệp. Có thể thực hiện việc này trong vài giây không? Thời gian tiêu biểu của nó là từ 1 phút đến 2 phút.Có cách nào nhanh chóng để phân tích cú pháp thông qua một tệp lớn với regex không?

kích thước tập tin Ví dụ là 148,208KB

Tôi đang sử dụng regex để phân tích qua từng dòng:

Đây là C# mã của tôi:

private static void ReadTheLines(int max, Responder rp, string inputFile) 
{ 
    List<int> rate = new List<int>(); 
    double counter = 1; 
    try 
    { 
     using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 1024)) 
     { 
      string line; 
      Console.WriteLine("Reading...."); 
      while ((line = sr.ReadLine()) != null) 
      { 
       if (counter <= max) 
       { 
        counter++; 
        rate = rp.GetRateLine(line); 
       } 
       else if (max == 0) 
       { 
        counter++; 
        rate = rp.GetRateLine(line); 
       } 
      } 
      rp.GetRate(rate); 
      Console.ReadLine(); 
     } 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine("The file could not be read:"); 
     Console.WriteLine(e.Message); 
    } 
} 

Đây là regex của tôi:

public List<int> GetRateLine(string justALine) 
{ 
    const string reg = @"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$"; 
    Match match = Regex.Match(justALine, reg, 
           RegexOptions.IgnoreCase); 

    // Here we check the Match instance. 
    if (match.Success) 
    { 
     // Finally, we get the Group value and display it. 

     string theRate = match.Groups[3].Value; 
     Ratestorage.Add(Convert.ToInt32(theRate)); 
    } 
    else 
    { 
     Ratestorage.Add(0); 
    } 
    return Ratestorage; 
} 

Dưới đây là một dòng ví dụ để phân tích cú pháp, thường là khoảng 200.000 dòng:

10.10.10.10 - - [27/tháng mười một/2002: 16: 46: 20 -0500] "GET/Solr/HTTP/1.1" 200 4926 789

+0

Tôi không thực sự là chuyên gia, nhưng tôi không thấy bất cứ điều gì không đúng chỗ. – Almo

+0

câu trả lời ngắn gọn: không, bạn không thể phân tích cú pháp mỗi dòng 150 mb dữ liệu chỉ trong vài giây –

+0

Vâng, đó là những gì tôi nghĩ, nhưng không chắc liệu tôi có đủ thông minh để nghĩ về một số ký hiệu O lớn để làm cho điều này nhanh hơn. – Rayshawn

Trả lời

16

Memory Mapped FilesTask Parallel Library để được giúp đỡ.

  1. Tạo MMF liên tục với nhiều chế độ xem truy cập ngẫu nhiên. Mỗi điểm tương ứng với một phần cụ thể của một tập tin
  2. Xác định phương pháp phân tích với thông số như IEnumerable<string>, về cơ bản để trừu tượng một tập các dòng không phân tích cú pháp
  3. Tạo và bắt đầu một TPL nhiệm vụ cho mỗi một MMF xem với Parse(IEnumerable<string>) là một hành động công tác
  4. Mỗi nhiệm vụ công nhân cho biết thêm một dữ liệu phân tích vào hàng đợi được chia sẻ của BlockingCollection loại
  5. một nhiệm vụ khác nghe BC (GetConsumingEnumerable()) và xử lý tất cả dữ liệu mà đã được phân tích bởi nhiệm vụ lao động

Xem 01.trên MSDN

Phải nói giải pháp này là dành cho .NET Framework >=4

4

Ngay bây giờ, bạn tạo lại của bạn Regex mỗi khi bạn gọi GetRateLine, xảy ra mỗi khi bạn đọc một dòng.

Nếu bạn create a Regex instance trước và sau đó sử dụng phương pháp không tĩnh Match, bạn sẽ tiết kiệm thời gian biên dịch regex, có khả năng giúp bạn tăng tốc.

Điều đó đang được nói, nó có thể sẽ không đưa bạn từ phút giây ...

1

Thay vì tái tạo một regex cho mỗi cuộc gọi đến GetRateLine, tạo ra nó trước, đi qua các tùy chọn RegexOptions.Compiled để các nhà xây dựng Regex(String,RegexOptions).

Bạn cũng có thể muốn thử đọc toàn bộ tệp trong bộ nhớ, nhưng tôi nghi ngờ đó là nút cổ chai của bạn. Nó sẽ không mất một phút để đọc trong ~ 100MB từ đĩa.

1

Tóm lại, có một vài điều tôi sẽ thử ...

Thứ nhất, Tăng tập tin dòng đệm của bạn ít nhất 64Kb:

using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 65536)) 

Thứ hai, Xây dựng các Regex một lần thay vì sử dụng một chuỗi bên trong vòng lặp:

static readonly Regex rateExpression = new Regex(@"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$", RegexOptions.IgnoreCase); 
//In GetRateLine() change to: 
Match match = rateExpression.Match(justALine); 

Thứ ba, Sử dụng một đĩa đơn Ví dụ danh sách bằng cách có Responder.GetRate() trả về một danh sách hoặc mảng.

// replace: 'rp.GetRate(rate)', with: 
rate = rp.GetRate(); 

tôi sẽ preallocate danh sách để giới hạn 'hợp lý':

List<int> rate = new List<int>(10000); 

Bạn cũng có thể xem xét thay đổi mã hóa của bạn từ UTF-8 để ASCII nếu có và áp dụng đối với nhu cầu cụ thể của bạn.

Comments

Thông thường, nếu điều này thực sự sẽ là một yêu cầu để có được thời gian phân tích xuống, bạn sẽ muốn xây dựng một tokenizer và bỏ qua Regex hoàn toàn. Kể từ khi định dạng đầu vào của bạn trông có vẻ là tất cả ascii và khá đơn giản, điều này sẽ dễ dàng đủ để làm, nhưng có lẽ giòn hơn một chút so với regex. Cuối cùng, bạn sẽ cần phải cân nhắc và cân bằng nhu cầu về tốc độ so với độ tin cậy và khả năng bảo trì của mã.

Nếu bạn cần một số ví dụ về phân tích cú pháp bằng tay tại answer to this question

Các vấn đề liên quan