2010-12-10 10 views
6

getline chức năng sử dụng fread (khối I/O) thay vì fgetc (ký tự I/O)?C: Đọc một tệp văn bản (có các dòng có độ dài thay đổi) theo dòng fread()/fgets() thay vì fgetc() (khối I/O so với ký tự I/O)

Có hình phạt về hiệu suất để đọc ký tự tệp theo ký tự qua fgetc. Chúng tôi nghĩ rằng để cải thiện hiệu suất, chúng tôi có thể sử dụng lượt đọc khối qua fread trong vòng lặp bên trong của getline. Tuy nhiên, điều này giới thiệu hiệu ứng không mong muốn của việc đọc qua cuối dòng. Ít nhất, điều này sẽ yêu cầu thực hiện getline để theo dõi phần "chưa đọc" của tệp, yêu cầu sự trừu tượng vượt ra ngoài ngữ nghĩa ANSI C FILE. Đây không phải là điều chúng tôi muốn thực hiện!

Chúng tôi đã lược tả ứng dụng của chúng tôi và hiệu suất chậm bị cô lập với thực tế là chúng tôi đang sử dụng ký tự tệp lớn theo ký tự qua fgetc. Phần còn lại của chi phí thực sự có một chi phí tầm thường bằng cách so sánh. Chúng tôi luôn đọc tuần tự mọi dòng của tệp, từ đầu đến cuối và chúng tôi có thể khóa toàn bộ tệp trong suốt thời gian đọc. Điều này có thể làm cho fread dựa trên getline dễ triển khai hơn.

Vì vậy, hãy thực hiện chức năng getline sử dụng fread (chặn I/O) thay vì fgetc (ký tự I/O) tồn tại? Chúng tôi khá chắc chắn nó, nhưng nếu không, làm thế nào chúng ta nên thực hiện nó?

Cập nhật Tìm thấy một bài viết hữu ích, Handling User Input in C, bởi Paul Hsieh. Đó là một cách tiếp cận dựa trên fgetc, nhưng nó có một cuộc thảo luận thú vị của giải pháp thay thế (bắt đầu với cách xấu gets là, sau đó thảo luận fgets):

Mặt khác vặn lại phổ biến từ các lập trình viên C (ngay cả những người được coi là có kinh nghiệm) là để nói rằng fgets() nên được sử dụng thay thế. Tất nhiên, tự nó, fgets() không thực sự xử lý đầu vào của người dùng mỗi lần. Bên cạnh việc có điều kiện kết thúc chuỗi kỳ lạ (khi gặp \ n hoặc EOF, nhưng không \ 0) cơ chế được chọn để kết thúc khi bộ đệm đã đạt đến công suất chỉ đơn giản là dừng hoạt động fgets() và \ 0 chấm dứt nó. Vì vậy, nếu đầu vào của người dùng vượt quá độ dài của bộ đệm được phân bổ, fgets() trả về một phần kết quả. Để đối phó với các lập trình viên này có một vài lựa chọn; 1) chỉ đơn giản là đối phó với đầu vào người dùng cắt ngắn (không có cách nào để cung cấp cho người dùng rằng đầu vào đã bị cắt bớt, trong khi họ đang cung cấp đầu vào) 2) Mô phỏng một mảng ký tự có thể phát triển và điền nó với các cuộc gọi liên tiếp đến fgets (). Giải pháp đầu tiên, hầu như luôn luôn là một giải pháp rất nghèo cho đầu vào người dùng có độ dài thay đổi vì bộ đệm chắc chắn sẽ là quá lớn phần lớn thời gian vì nó cố gắng chụp quá nhiều trường hợp thông thường và quá nhỏ đối với các trường hợp bất thường. Các giải pháp thứ hai là tốt, ngoại trừ việc nó có thể phức tạp để thực hiện một cách chính xác. Không giao dịch với fgets ' hành vi kỳ lạ đối với' \ 0 '.

Tập thể dục còn lại để người đọc: Để xác định có bao nhiêu byte đã thực sự đọc bởi một cuộc gọi đến fgets(), người ta có thể thử bằng cách quét, giống như nó, cho một '\ n' và bỏ qua trên bất kỳ '\ 0' nào trong khi không vượt quá kích thước được chuyển đến fgets(). Giải thích lý do tại sao điều này không đủ cho dòng cuối cùng của luồng.Điểm yếu của ftell() ngăn không cho nó giải quyết vấn đề này hoàn toàn?

Tập thể dục còn lại để người đọc: Giải quyết vấn đề xác định độ dài của dữ liệu tiêu thụ bởi fgets() bằng cách ghi đè toàn bộ đệm với một giá trị khác không giữa mỗi cuộc gọi đến fgets().

Vì vậy, với fgets() chúng tôi là trái với sự lựa chọn của văn bản rất nhiều mã và sống chung với một dòng điều kiện chấm dứt mà không phù hợp với phần còn lại của thư viện C, hoặc có một tùy ý cắt. Nếu điều này không đủ tốt, thì chúng ta còn lại cái gì? scanf() trộn lẫn với việc đọc theo cách không thể tách rời và fread() sẽ đọc qua cuối chuỗi. Trong ngắn hạn, thư viện C để lại cho chúng tôi không có gì. Chúng tôi buộc phải tự mình cuộn dựa trên đầu trang của fgetc() trực tiếp. Vì vậy, hãy cho nó một shot.

Vì vậy, thực hiện một chức năng getline đó là dựa trên fgets (và không cắt cụt các đầu vào) tồn tại?

+0

Đến câu hỏi mới của bạn ở cuối, có, nó tồn tại. Tôi vạch ra nó trong câu trả lời của tôi. Bài viết bạn đã trích dẫn đề cập đến một vấn đề với dòng cuối cùng không phải là dòng mới được kết thúc; Tôi đã thực hiện điều này một vấn đề không bằng cách điền sẵn bộ đệm bằng ''\ n'' và cung cấp cách để phát hiện tình trạng. –

+1

Cũng lưu ý rằng giải pháp của Paul Hsieh để sử dụng 'fgetc' rất tệ. Về triển khai hiện đại, do yêu cầu hỗ trợ khóa trong trường hợp nhiều luồng truy cập cùng một đối tượng 'FILE', sử dụng' fgetc' sẽ rất chậm. Bạn có thể sử dụng 'getc_unlocked' (nhưng đây là một hàm POSIX, không phải là hàm C chuẩn), nhưng ngay cả với việc mở rộng macro tối ưu của' getc_unlocked', cách 'fgets' tìm kiếm bộ đệm cho' '\ n'' (tức là sử dụng 'memchr') sẽ nhanh gấp nhiều lần so với bất cứ thứ gì bạn có thể làm mà không cần truy cập vào bộ đệm trong. Cũng lưu ý rằng nếu bạn có POSIX (2008), bạn đã có 'getline' rồi. –

Trả lời

5

Không sử dụng fread. Sử dụng fgets. Tôi lấy nó đây là một bài tập về nhà/vấn đề lớp nên tôi không cung cấp câu trả lời hoàn chỉnh, nhưng nếu bạn nói không, tôi sẽ đưa ra thêm lời khuyên. Nó chắc chắn có thể cung cấp 100% ngữ nghĩa của kiểu getline theo kiểu GNU, bao gồm các byte rỗng được nhúng, sử dụng hoàn toàn fgets, nhưng nó đòi hỏi một số suy nghĩ thông minh.

OK, cập nhật vì đây không phải là bài tập về nhà:

  • memset đệm của bạn để '\n'.
  • Sử dụng fgets.
  • Sử dụng memchr để tìm số '\n' đầu tiên.
  • Nếu không tìm thấy '\n', dòng này dài hơn bộ đệm của bạn. Chạm vào bộ đệm, điền phần mới với '\n'fgets vào phần mới, lặp lại khi cần thiết.
  • Nếu ký tự sau '\n''\0', thì fgets bị chấm dứt do đến cuối dòng.
  • Nếu không, fgets bị chấm dứt do đạt EOF, thì '\n' bị bỏ lại từ memset, ký tự trước đó là null kết thúc là fgets đã viết và ký tự trước đó là ký tự cuối cùng của dữ liệu thực.

Bạn có thể loại bỏ các memset và sử dụng strlen ở vị trí của memchr nếu bạn không quan tâm đến việc hỗ trợ dòng với null nhúng (một trong hai cách, null sẽ không chấm dứt việc đọc, nó sẽ chỉ là một phần của Read- của bạn trong dòng).

Ngoài ra còn có một cách khác để làm điều tương tự với fscanf"%123[^\n]" specifier (nơi 123 là giới hạn bộ đệm của bạn), mang đến cho bạn sự linh hoạt để dừng lại ở các ký tự không newline (ala GNU getdelim).Tuy nhiên nó có thể chậm, trừ khi hệ thống của bạn có một triển khai thực hiện rất thích hợp scanf.

+0

Đây không phải là bài tập về nhà ... :) Bạn đề nghị sử dụng 'fgets' như thế nào? Sử dụng một mảng ký tự có khả năng phát triển và lấp đầy nó bằng các cuộc gọi liên tiếp đến 'fgets' có vẻ phức tạp để thực hiện chính xác. Ngoài ra, tôi hiểu rằng 'fgets' chấm dứt khi gặp phải '\ n' hoặc EOF, nhưng không phải '\ 0'. Tuy nhiên, đây không phải là vấn đề đối với các tệp của chúng tôi. –

+1

@R .. Một lỗ nhỏ: Sau khi sử dụng 'char s [5]; memset (s, '\ n', sizeof s); fgets (s, sizeof s, ...); 'trên một tệp có 3 byte" xyz "dẫn đến" xyz \ 0 \ n "trong' s'. Tìm kiếm ''\ n'' đầu tiên là OK, nhưng việc kiểm tra ký tự sau là UB. Đề xuất thêm "If '\ n" ở vị trí cuối cùng, sau đó 'fgets' bị chấm dứt do tiếp cận dòng cuối cùng trong tệp." sau đó đi đến "Nếu nhân vật sau đây ..." – chux

+0

Tôi tự hỏi tại sao rất nhiều hàm liên quan đến chuỗi có giá trị trả về tương đối vô dụng? Mã lệnh gọi 'strcat' và' fgets' thường sẽ cần phải tìm ký tự cuối cùng được viết - một cái gì đó mã cho các hàm đó đã biết rồi. Tôi không thể nghĩ ra bất kỳ tính hữu ích nào cho giá trị trả về của các hàm đó khi được triển khai. – supercat

1

Không có sự khác biệt lớn về hiệu suất giữa fgets và fgetc/setvbuf. Thử:

int c; 
FILE *f = fopen("blah.txt","r"); 
setvbuf(f,NULL,_IOLBF,4096); /* !!! check other values for last parameter in your OS */ 
while((c=fgetc(f))!=EOF) 
{ 
    if(c=='\n') 
    ... 
    else 
    ... 
} 
Các vấn đề liên quan