Rust: عمليات الإدخال/الإخراج للملفات في Rust
آخر تحديث: 2026-08-26
تعمل عمليات الإدخال/الإخراج للملفات كـ«جسر» بين البرنامج والعالم الخارجي — فقراءة ملف ما تشبه «الاستماع» إلى رسالة تركها شخص آخر، بينما الكتابة في ملف تشبه «التحدث» إلى نفسك في المستقبل أو إلى الآخرين.
إذا قارنا البرنامج بشخص ما، فإن المتغيرات وهياكل البيانات تمثل «ذاكرته قصيرة المدى» (الدماغ)، ونظام الملفات يمثل «ذاكرته طويلة المدى» (دفتر الملاحظات). وعندما يتم إغلاق البرنامج، تختفي ذاكرته قصيرة المدى، لكن محتويات دفتر الملاحظات تبقى إلى الأبد. تعد عمليات الإدخال/الإخراج للملفات (File I/O) هي القدرة على «القراءة والكتابة في دفتر الملاحظات» — فبدونها، سيبدأ البرنامج بصفحة فارغة في كل مرة يتم تشغيله فيها.
1. ما ستتعلمه
std::fs::read_to_stringقراءة الملف بأكمله في سلسلة نصية دفعة واحدةstd::fs::writeكتابة سلسلة أحرف إلى ملف دفعة واحدةFile::openفتح ملف +File::createإنشاء/الكتابة فوق ملفBufReader/BufWriterتعمل عمليات القراءة/الكتابة ذات التخزين المؤقت على تحسين الأداء عند التعامل مع الملفات الكبيرة- عمليات المسار الخاصة بـ
PathوPathBuf(التسلسل، والتحليل، والتقييم) ?تبسيط انتشار الأخطاء في العوامل الحسابية والتعامل مع أخطاء الإدخال/الإخراج بطريقة سلسة
2. قصة محلل السجلات
(1) عناء تصفح السجلات يدويًّا
شياو لين هو مهندس عمليات في الشركة، وأول ما يفعله كل صباح هو فحص سجلات الخادم يدويًّا:
- فتح 10 ملفات سجل، يحتوي كل منها على عدة آلاف من الأسطر
- استخدم Ctrl+F للبحث عن كلمة "ERROR" وحسب عدد مرات ظهورها.
- قم بحساب عدد كل رمز حالة (200، 404، 500) يدويًّا باستخدام برنامج Excel
- يستغرق الأمر 30 دقيقة في كل مرة، وغالبًا ما أخطئ في العد.
- سأل المدير: «هل يمكنك إنشاء تقرير تلقائيًّا؟» — فشعر شياو لين بأنه وصل إلى نهاية حيلته.
«سيكون من الرائع لو تمكنت من كتابة برنامج يقوم تلقائيًا بقراءة السجلات، وحصر رموز الحالة، وإنشاء التقارير...»
(2) طرق التعامل مع عمليات الإدخال/الإخراج للملفات في لغة Rust
توفر المكتبة القياسية لـ Rust مجموعة كاملة من أدوات الإدخال/الإخراج للملفات، تشبه إلى حد كبير «خط أنابيب تحليل السجلات»:
Log Files(Server) → Raw Materials
File::open / read_to_string → Conveyor Feed
BufReader → Buffering(Assembly Line Buffer)
Line-by-Line Analysis → Quality Inspection Station
HashMap Statistical Status Codes → Categorical Statistics
File::create / write → Generate and Export Report
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
fn analyze_log(path: &str) -> std::io::Result<()> {
// Open the file and create a buffer reader
let file = File::open(path)?;
let reader = BufReader::new(file);
// Count the number of occurrences for each status code
let mut counts: std::collections::HashMap<String, u32> =
std::collections::HashMap::new();
for line in reader.lines() {
let line = line?;
// Assumed Log Format: "2026-07-03 10:30:45 GET /api/users 200"
if let Some(status) = line.split_whitespace().last() {
*counts.entry(status.to_string()).or_insert(0) += 1;
}
}
// Write to the report file
let mut report = File::create("report.txt")?;
writeln!(report, "=== Status Code Statistics Report ===")?;
for (code, count) in &counts {
writeln!(report, " {}: {}", code, count)?;
}
println!("The report has been generated: report.txt");
Ok(())
}
يتبع تصميم عمليات الإدخال/الإخراج للملفات في لغة Rust مبدأ «التجريد بدون تكلفة»: يعمل
BufReaderكذاكرة مؤقتة في مسار البيانات، مما يقلل من عدد استدعاءات النظام ذات المستوى المنخفض؛ ويوفر المشغل?«الإبلاغ التلقائي عن الأخطاء» — حيث يعود فور مواجهة أي خطأ في الإدخال/الإخراج، مما يلغي الحاجة إلى كتابةmatchيدويًّا. يعمل كل منPathوPathBufكـ«موجه آمن» لمسارات الملفات، حيث يتعاملان تلقائيًا مع الاختلافات في فواصل المسارات عبر أنظمة التشغيل المختلفة.
3. المفاهيم الأساسية
(1) نظام إدخال/إخراج الملفات
graph TB
A[Rust Document I/O] --> B[Read a File]
A --> C[Write to a file]
A --> D[Path Operations]
A --> E[Error Handling]
B --> B1["std::fs::read_to_string"]
B --> B2["File::open + read_to_string"]
B --> B3["BufReader Read line by line"]
C --> C1["std::fs::write"]
C --> C2["File::create + write_all"]
C --> C3["BufWriter Buffered Write"]
D --> D1["Path(Immutable Slices)"]
D --> D2["PathBuf(Variable strings)"]
D --> D3["Path Concatenation .join()"]
E --> E1["Result<T, io::Error>"]
E --> E2["? Operator Propagation Error"]
E --> E3["match Precision Processing"]
(2) مقارنة بين أساليب القراءة
| الطريقة | الوظيفة/النوع | حالة الاستخدام | استهلاك الذاكرة | الأداء |
|---|---|---|---|---|
| القراءة في مسار واحد | fs::read_to_string |
الملفات الصغيرة (< 100 ميغابايت) | محتويات الملف بالكامل | الأسرع (استدعاء نظام واحد) |
| القراءة اليدوية | File::read_to_string |
تتطلب تحكماً دقيقاً | محتويات الملف بالكامل | سريع |
| التخزين المؤقت سطراً بسطر | BufReader::read_line |
الملفات/السجلات الكبيرة | سطر واحد من البيانات | معتدل (يقلل من استدعاءات النظام) |
| التكرار المؤقت | BufReader::lines() |
المعالجة سطراً سطراً | سطر واحد من البيانات | ملائم (موصى به) |
(3) مقارنة بين أساليب الكتابة
| الطريقة | الوظيفة/النوع | حالة الاستخدام | الميزات |
|---|---|---|---|
| الكتابة لمرة واحدة | fs::write |
الملفات الصغيرة/المحتوى البسيط | الأكثر إيجازًا |
| إنشاء ملف | File::create |
الكتابة فوق الملف | إفراغ الملف إذا كان موجودًا |
| إضافة | OpenOptions::append |
إضافة إلى السجل | الاحتفاظ بالمحتوى الأصلي |
| الكتابة المؤقتة | BufWriter |
عمليات الكتابة ذات الحجم الكبير | تقلل من عدد استدعاءات النظام |
(4) دليل مرجعي سريع لفتح الملفات
| الطريقة | النهج/النوع | عندما يكون الملف موجودًا | عندما لا يكون الملف موجودًا | السيناريوهات التي تنطبق عليها |
|---|---|---|---|---|
| فتح كملف للقراءة فقط | File::open(path) |
فتح بشكل عادي | إرجاع خطأ | قراءة ملف موجود |
| إنشاء/الكتابة فوق | File::create(path) |
مسح المحتويات | إنشاء ملف جديد | الكتابة إلى ملف جديد |
| إضافة | OpenOptions::new().append(true).open() |
إضافة إلى النهاية | إرجاع خطأ | إضافة إلى السجل |
| إنشاء ملف جديد | OpenOptions::new().write(true).create_new(true).open() |
ظهور خطأ | إنشاء ملف جديد | تجنب الكتابة فوق الملف |
| مفتوح للقراءة والكتابة | OpenOptions::new().read(true).write(true).open() |
فتح عادي | خطأ في الإرجاع | تعديل الملف الموجود |
| إنشاء وقراءة/كتابة | OpenOptions::new().read(true).write(true).create(true).open() |
فتح كالمعتاد | إنشاء ملف جديد | قراءة/كتابة ملف التكوين |
4. أمثلة على عمليات الإدخال/الإخراج للملفات
(1) ▶ المثال:القراءة والكتابة الأساسية مع معالجة الأخطاء (مستوى الصعوبة ⭐)
// ============================================
// Basic File Reading and Writing: fs::read_to_string + fs::write
// Demo:Create a temporary file、Enter content、Read the content、Cleanup
// Scene: Write a simple "Memo" program
// ============================================
use std::fs;
use std::path::Path;
fn main() -> std::io::Result<()> {
println!("=== Simple Note-Taking Program ===\n");
// Define the temporary file path (In the current directory temp_note.txt)
let file_path = "temp_note.txt";
// ---------- Write to a file ----------
let content = "Today's To-Do List:
1. Study Rust Document I/O
2. Complete the Log Analyzer
3. Review Ownership and Borrowing
Rust Study Notes:
- Document I/O Usage std::fs Module
- BufReader Suitable for reading large files
- ? Simplifying Error Handling for Operators";
fs::write(file_path, content)?;
println!("[Write] The note has been saved to: {}", file_path);
// Check if the file exists
if Path::new(file_path).exists() {
println!("[Inspection] The file exists,Size: {} Byte", content.len());
}
// ---------- Read a File ----------
let read_content = fs::read_to_string(file_path)?;
println!("\n[Read] Note Content:\n{}", read_content);
// ---------- Statistical Information ----------
let line_count = read_content.lines().count();
let word_count: usize = read_content
.split_whitespace()
.count();
let char_count = read_content.chars().count();
println!("--- Memo Statistics ---");
println!(" Number of lines: {}", line_count);
println!(" Number of words: {}", word_count);
println!(" Number of characters: {}", char_count);
// ---------- Clear Temporary Files ----------
fs::remove_file(file_path)?;
println!("\n[Cleanup] The temporary file has been deleted.: {}", file_path);
// Confirm that the file has been deleted
assert!(!Path::new(file_path).exists(), "The file should be deleted.");
println!("[Confirm] The files have been successfully deleted.");
Ok(())
}
الناتج:
=== Simple Note-Taking Program ===
[Write] The note has been saved to: temp_note.txt
[Inspection] The file exists,Size: 87 Byte
[Read] Note Content:
Today's To-Do List:
1. Study Rust Document I/O
2. Complete the Log Analyzer
3. Review Ownership and Borrowing
Rust Study Notes:
- Document I/O Usage std::fs Module
- BufReader Suitable for reading large files
- ? Simplifying Error Handling for Operators
--- Memo Statistics ---
Number of lines: 10
Number of words: 24
Number of characters: 87
[Cleanup] The temporary file has been deleted.: temp_note.txt
[Confirm] The files have been successfully deleted.
يُعد كل من
fs::writeوfs::read_to_stringأكثر الطرق إيجازًا لقراءة الملفات وكتابتها — حيث تُنجز جميع العمليات في استدعاء واحد. يقوم المشغل?تلقائيًّا بنقل الأخطاء منResultإلى المُستدعي، كما يعود مبكرًا في حالة حدوث خطأ. أماPath::new(file_path).exists()فيتحقق من وجود الملف.fs::remove_fileيحذف ملفًا. في كل مرة يتم فيها تشغيل هذا المثال، يقوم بإنشاء وحذف نفس الملف المؤقت، مما يضمن نتائج قابلة للتكرار.
(2) ▶ المثال:يقوم BufReader بقراءة السجلات سطراً سطراً (مستوى الصعوبة ⭐⭐)
// ============================================
// BufReader Read line by line + Log Analysis
// Demo: Read the server logs, count the number of each status code
// Scene: Operations Engineers Automatically Analyze Log Files
// ============================================
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
fn main() -> std::io::Result<()> {
println!("=== Server Log Analyzer ===\n");
// Step 1: Create a sample log file
let log_path = "temp_server.log";
create_sample_log(log_path)?;
println!("[Generate] Sample Log File: {}", log_path);
// Step 2: Analyze Logs
println!("[Analysis] Processing logs...");
let status_counts = analyze_log(log_path)?;
// Step 3: Generate Report
let report_path = "temp_report.txt";
generate_report(&status_counts, report_path)?;
// Step 4: Read and Print the Report
let report = std::fs::read_to_string(report_path)?;
println!("\n{}", report);
// Step 5: Cleanup
std::fs::remove_file(log_path)?;
std::fs::remove_file(report_path)?;
println!("[Cleanup] The temporary file has been deleted.");
Ok(())
}
/// Create a sample log file
fn create_sample_log(path: &str) -> std::io::Result<()> {
let mut file = File::create(path)?;
// Simulated Server Logs for One Day
let log_entries = vec![
"2026-07-03 08:01:23 GET /api/users 200",
"2026-07-03 08:02:15 POST /api/login 200",
"2026-07-03 08:03:44 GET /api/products 404",
"2026-07-03 08:05:12 GET /api/users 200",
"2026-07-03 08:07:33 POST /api/order 500",
"2026-07-03 08:10:01 GET /api/products 200",
"2026-07-03 08:12:45 GET /api/users 304",
"2026-07-03 08:15:22 POST /api/login 401",
"2026-07-03 08:18:09 GET /api/products 200",
"2026-07-03 08:20:55 POST /api/order 500",
"2026-07-03 08:22:30 GET /api/users 200",
"2026-07-03 08:25:18 GET /api/products 404",
"2026-07-03 08:28:44 POST /api/login 200",
"2026-07-03 08:30:01 GET /api/search 200",
"2026-07-03 08:32:55 POST /api/order 200",
"2026-07-03 08:35:12 GET /api/users 200",
"2026-07-03 08:38:29 GET /api/products 304",
"2026-07-03 08:40:44 POST /api/login 401",
"2026-07-03 08:42:10 GET /api/search 500",
"2026-07-03 08:45:33 POST /api/order 200",
"2026-07-03 08:48:01 GET /api/users 200",
"2026-07-03 08:50:22 GET /api/products 200",
"2026-07-03 08:52:55 POST /api/login 200",
"2026-07-03 08:55:18 GET /api/search 404",
"2026-07-03 08:58:40 POST /api/order 500",
];
for entry in log_entries {
writeln!(file, "{}", entry)?;
}
Ok(())
}
/// Analyze Log Files,Count each type of HTTP Number of occurrences of status codes
fn analyze_log(path: &str) -> std::io::Result<HashMap<String, u32>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut counts: HashMap<String, u32> = HashMap::new();
let mut total_lines = 0u32;
for line_result in reader.lines() {
let line = line_result?;
total_lines += 1;
// Retrieve Status Code (The last field in the row)
// Log Format: "2026-07-03 08:01:23 GET /api/users 200"
if let Some(status) = line.split_whitespace().last() {
*counts.entry(status.to_string()).or_insert(0) += 1;
}
}
println!("[Statistics] Total Number of Lines: {}", total_lines);
// Calculate the percentage of each status code
let total: f64 = counts.values().sum::<u32>() as f64;
println!("[Statistics] Found {} Different status codes", counts.len());
let mut sorted: Vec<(&String, &u32)> = counts.iter().collect();
sorted.sort_by_key(|(_, &count)| std::cmp::Reverse(count));
for (code, count) in &sorted {
let percentage = (*count as f64 / total * 100.0);
println!(" {}: {} times ({:.1}%)", code, count, percentage);
}
Ok(counts)
}
/// Generate a statistical report file
fn generate_report(
counts: &HashMap<String, u32>,
report_path: &str,
) -> std::io::Result<()> {
let mut file = File::create(report_path)?;
writeln!(file, "========================================")?;
writeln!(file, " Server Log Status Code Statistics Report")?;
writeln!(file, " Date: 2026-07-03")?;
writeln!(file, "========================================")?;
writeln!(file)?;
// Sort by quantity in descending order
let mut sorted: Vec<(&String, &u32)> = counts.iter().collect();
sorted.sort_by_key(|(_, &count)| std::cmp::Reverse(count));
let total: u32 = counts.values().sum();
for (code, count) in &sorted {
let bar_length = (*count as f64 / total as f64 * 30.0) as usize;
let bar = "=".repeat(bar_length);
writeln!(file, " {} | {} {:.1}%", code, bar, *count as f64 / total as f64 * 100.0)?;
}
writeln!(file)?;
writeln!(file, " Total Number of Requests: {}", total)?;
writeln!(file, " Success Rate (2xx): {:.1}%",
counts.get("200").unwrap_or(&0) as &u32 + counts.get("304").unwrap_or(&0) as &u32 * 100 / total)?;
writeln!(file, "========================================")?;
Ok(())
}
الناتج:
=== Server Log Analyzer ===
[Generate] Sample Log File: temp_server.log
[Analysis] Processing logs...
[Statistics] Total Number of Lines: 25
[Statistics] Found 5 Different status codes
200: 12 times (48.0%)
404: 3 times (12.0%)
500: 4 times (16.0%)
304: 2 times (8.0%)
401: 2 times (8.0%)
========================================
Server Log Status Code Statistics Report
Date: 2026-07-03
========================================
200 | ============================== 48.0%
500 | ========== 16.0%
404 | ======== 12.0%
304 | ===== 8.0%
401 | ===== 8.0%
Total Number of Requests: 25
Success Rate (2xx): 56.0%
========================================
[Cleanup] The temporary file has been deleted.
BufReaderيُعد قارئ المخزن المؤقت مناسبًا لمعالجة الملفات الكبيرة — فهو يحتفظ بمخزن مؤقت داخلي، ويقوم بقراءة جزء كبير من البيانات إلى الذاكرة دفعة واحدة، ثم يعرضها سطرًا سطرًا، مما يقلل بشكل كبير من عدد استدعاءات النظام.reader.lines()يُرجع مُكررًا يقرأ الملف سطرًا سطرًا.split_whitespace().last()يستخرج الحقل الأخير (رمز الحالة) من كل سطر.HashMapيحسب عدد مرات ظهور كل رمز حالة، ويُنشئ تقريرًا، ويكتبه في ملف.
(3) ▶ المثال:الكتابة المؤقتة باستخدام BufWriter وعمليات المسار باستخدام Path/PathBuf (مستوى الصعوبة ⭐⭐)
// ============================================
// BufWriter Buffered Write + Path / PathBuf Path Operations
// Demo: Merging Multiple Files + Path Concatenation and Normalization
// Scene: Merge multiple log files into a single consolidated file
// ============================================
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
fn main() -> std::io::Result<()> {
println!("=== Log Merging Tool ===\n");
// Usage PathBuf Build Path
let base_dir = PathBuf::from(".");
let log_files = vec![
base_dir.join("temp_web.log"),
base_dir.join("temp_api.log"),
base_dir.join("temp_db.log"),
];
// Generate three sample log files
generate_log_files(&log_files)?;
// Merge all logs into a single file
let merged_path = base_dir.join("temp_merged.log");
merge_log_files(&log_files, &merged_path)?;
// Read and display the merge results
let merged_content = std::fs::read_to_string(&merged_path)?;
println!("--- Merged Logs ---");
println!("{}", merged_content);
// Usage Path Methods for Performing Path Operations
println!("--- Path Information ---");
let path = Path::new("temp_merged.log");
println!(" file_name: {:?}", path.file_name().unwrap_or_default());
println!(" parent: {:?}", path.parent().unwrap_or_default());
println!(" extension: {:?}", path.extension().unwrap_or_default());
println!(" exists: {}", path.exists());
println!(" is_file: {}", path.is_file());
// Get the file size
let metadata = path.metadata()?;
println!(" size: {} bytes", metadata.len());
// Delete all temporary files
cleanup_files(&log_files, &merged_path)?;
Ok(())
}
/// Generate multiple sample log files
fn generate_log_files(file_paths: &[PathBuf]) -> std::io::Result<()> {
let logs = vec![
// Web Server Logs
vec![
"[WEB] 2026-07-03 10:00:00 GET /index.html 200",
"[WEB] 2026-07-03 10:00:05 GET /style.css 200",
"[WEB] 2026-07-03 10:01:00 POST /login 500",
],
// API Server Logs
vec![
"[API] 2026-07-03 10:00:02 GET /api/users 200",
"[API] 2026-07-03 10:00:10 POST /api/orders 201",
"[API] 2026-07-03 10:01:05 GET /api/products 404",
],
// Database Logs
vec![
"[DB] 2026-07-03 10:00:01 QUERY SELECT * FROM users 0.3ms",
"[DB] 2026-07-03 10:00:11 QUERY INSERT INTO orders 1.2ms",
"[DB] 2026-07-03 10:01:01 QUERY SELECT * FROM products 0.5ms",
],
];
for (i, path) in file_paths.iter().enumerate() {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
for entry in &logs[i] {
writeln!(writer, "{}", entry)?;
}
// BufWriter will be drop Automatically flush,But you can also do it manually flush
writer.flush()?;
println!("[Generate] {:?}", path.file_name().unwrap_or_default());
}
Ok(())
}
/// Merge Multiple Log Files
fn merge_log_files(
sources: &[PathBuf],
destination: &PathBuf,
) -> std::io::Result<()> {
// Usage BufWriter Write to the target file
let dest_file = File::create(destination)?;
let mut writer = BufWriter::new(dest_file);
// Write to the header
writeln!(writer, "=== Consolidated Log Report ===")?;
writeln!(writer, "Generation Time: 2026-07-03 10:05:00")?;
writeln!(writer, "Number of source files: {}", sources.len())?;
writeln!(writer, "{}", "=".repeat(50))?;
writeln!(writer)?;
// Read and write on a per-file basis
for source_path in sources {
let file_name = source_path.file_name()
.unwrap_or_default()
.to_string_lossy();
writeln!(writer, "--- Source: {} ---", file_name)?;
let src_file = File::open(source_path)?;
let reader = BufReader::new(src_file);
for line_result in reader.lines() {
let line = line_result?;
writeln!(writer, "{}", line)?;
}
writeln!(writer)?; // Blank line separator
}
// Write to the end of the statistics
writeln!(writer, "{}", "=".repeat(50))?;
writeln!(writer, "Merge Complete")?;
writer.flush()?;
println!("[Merge] Done: {:?}", destination.file_name().unwrap_or_default());
Ok(())
}
/// Delete all temporary files
fn cleanup_files(log_files: &[PathBuf], merged_path: &PathBuf) -> std::io::Result<()> {
println!("\n--- Clear Temporary Files ---");
for path in log_files {
if path.exists() {
std::fs::remove_file(path)?;
println!("[Delete] {:?}", path.file_name().unwrap_or_default());
}
}
if merged_path.exists() {
std::fs::remove_file(merged_path)?;
println!("[Delete] {:?}", merged_path.file_name().unwrap_or_default());
}
println!("[Done] All temporary files have been deleted.");
Ok(())
}
الناتج:
=== Log Merging Tool ===
[Generate] "temp_web.log"
[Generate] "temp_api.log"
[Generate] "temp_db.log"
[Merge] Done: "temp_merged.log"
--- Merged Logs ---
=== Consolidated Log Report ===
Generation Time: 2026-07-03 10:05:00
Number of source files: 3
==================================================
--- Source: temp_web.log ---
[WEB] 2026-07-03 10:00:00 GET /index.html 200
[WEB] 2026-07-03 10:00:05 GET /style.css 200
[WEB] 2026-07-03 10:01:00 POST /login 500
--- Source: temp_api.log ---
[API] 2026-07-03 10:00:02 GET /api/users 200
[API] 2026-07-03 10:00:10 POST /api/orders 201
[API] 2026-07-03 10:01:05 GET /api/products 404
--- Source: temp_db.log ---
[DB] 2026-07-03 10:00:01 QUERY SELECT * FROM users 0.3ms
[DB] 2026-07-03 10:00:11 QUERY INSERT INTO orders 1.2ms
[DB] 2026-07-03 10:01:01 QUERY SELECT * FROM products 0.5ms
==================================================
Merge Complete
--- Path Information ---
file_name: "temp_merged.log"
parent: ""
extension: "log"
exists: true
is_file: true
size: 638 bytes
--- Clear Temporary Files ---
[Delete] "temp_web.log"
[Delete] "temp_api.log"
[Delete] "temp_db.log"
[Delete] "temp_merged.log"
[Done] All temporary files have been deleted.
BufWriterيقوم كاتب المخزن المؤقت أولاً بكتابة البيانات إلى مخزن مؤقت في الذاكرة، ولا يقوم بكتابتها على القرص إلا في عملية واحدة عندما يمتلئ المخزن المؤقت أو عند استدعاءflush()يدويًّا — مما يقلل بشكل كبير من عدد استدعاءات النظام.PathBufهي سلسلة مسار متغيرة (مشابهة لـString)، وPathهي شريحة مسار (مشابهة لـ&str). تقومPathBuf::join()بربط المسارات، وتستردPath::file_name()اسم الملف، وتستردPath::extension()امتداد الملف، وتتحققPath::exists()من وجود الملف — وتتعامل هذه الطرق تلقائيًا مع الاختلافات في فواصل المسارات عبر الأنظمة الأساسية المختلفة.
(4) ▶ المثال:محلل سجلات متكامل — محاكاة سيناريو واقعي (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Comprehensive Log Analyzer——Simulate real-world operations and maintenance scenarios
// Demo:Comprehensive Implementation Document I/O、Path Operations、Error Handling
// Features: Read the log -> Analysis -> Generate HTML Report -> Cleanup
// ============================================
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
/// Log Entry Structure
#[derive(Debug)]
struct LogEntry {
timestamp: String,
method: String,
path: String,
status_code: u16,
}
/// Analysis Results
struct AnalysisResult {
total_requests: u32,
status_counts: HashMap<u16, u32>,
method_counts: HashMap<String, u32>,
top_paths: Vec<(String, u32)>,
error_rate: f64,
}
fn main() -> std::io::Result<()> {
println!("=== Complete Log Analyzer ===\n");
let log_path = "temp_access.log";
let report_path = "temp_report.html";
// Generate a sample log
generate_access_log(log_path)?;
println!("[1/4] The simulation log has been generated: {}", log_path);
// Analysis Log
let entries = parse_log(log_path)?;
println!("[2/4] Log parsing complete: {} records", entries.len());
// Analyze Data
let result = analyze_entries(&entries);
print_analysis(&result);
// Generate HTML Report
generate_html_report(&result, report_path)?;
println!("[3/4] HTML The report has been generated: {}", report_path);
// Display Report Content
let report_content = std::fs::read_to_string(report_path)?;
println!("\n--- Report Preview(first 20 lines) ---");
for line in report_content.lines().take(20) {
println!("{}", line);
}
println!("... (total {} lines)", report_content.lines().count());
// Cleanup
cleanup(log_path, report_path)?;
println!("\n[4/4] Temporary files have been cleared.");
Ok(())
}
/// Generate a simulated access log
fn generate_access_log(path: &str) -> std::io::Result<()> {
let paths = [
"/index.html", "/api/users", "/api/products",
"/api/orders", "/login", "/search",
"/about", "/contact", "/api/settings",
];
let methods = ["GET", "POST", "PUT", "DELETE"];
let statuses = [200, 200, 200, 200, 200, 201, 204, 301, 304, 400, 401, 403, 404, 500, 502];
let file = File::create(path)?;
let mut writer = std::io::BufWriter::new(file);
for i in 0..100 {
let hour = 8 + i / 12;
let minute = (i * 3) % 60;
let second = (i * 17) % 60;
let method = methods[i % methods.len()];
let url = paths[i % paths.len()];
let status = statuses[i % statuses.len()];
writeln!(
writer,
"2026-07-03 {:02}:{:02}:{:02} {} {} {}",
hour, minute, second, method, url, status
)?;
}
writer.flush()?;
Ok(())
}
/// Analyzing Log Files
fn parse_log(path: &str) -> std::io::Result<Vec<LogEntry>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut entries = Vec::new();
for line_result in reader.lines() {
let line = line_result?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 4 {
let entry = LogEntry {
timestamp: parts[0].to_string() + " " + parts[1],
method: parts[2].to_string(),
path: parts[3].to_string(),
status_code: parts[4].parse().unwrap_or(0),
};
entries.push(entry);
}
}
Ok(entries)
}
/// Analyze Log Entries
fn analyze_entries(entries: &[LogEntry]) -> AnalysisResult {
let total = entries.len() as u32;
// Statistical Status Codes
let mut status_counts: HashMap<u16, u32> = HashMap::new();
let mut method_counts: HashMap<String, u32> = HashMap::new();
let mut path_counts: HashMap<String, u32> = HashMap::new();
for entry in entries {
*status_counts.entry(entry.status_code).or_insert(0) += 1;
*method_counts.entry(entry.method.clone()).or_insert(0) += 1;
*path_counts.entry(entry.path.clone()).or_insert(0) += 1;
}
// Calculate the error rate (4xx + 5xx)
let error_count: u32 = status_counts.iter()
.filter(|(&code, _)| code >= 400)
.map(|(_, &count)| count)
.sum();
let error_rate = if total > 0 {
error_count as f64 / total as f64 * 100.0
} else {
0.0
};
// Most Popular Routes
let mut top_paths: Vec<(String, u32)> = path_counts.into_iter().collect();
top_paths.sort_by(|a, b| b.1.cmp(&a.1));
top_paths.truncate(5);
AnalysisResult {
total_requests: total,
status_counts,
method_counts,
top_paths,
error_rate,
}
}
/// Print the analysis results
fn print_analysis(result: &AnalysisResult) {
println!("\n--- Analysis Results ---");
println!("Total Number of Requests: {}", result.total_requests);
println!("Error Rate: {:.1}%", result.error_rate);
println!("\nStatus Code Distribution:");
let mut sorted_status: Vec<_> = result.status_counts.iter().collect();
sorted_status.sort_by_key(|(_, &c)| std::cmp::Reverse(c));
for (&code, &count) in &sorted_status {
let bar = "=".repeat((count as f64 / result.total_requests as f64 * 20.0) as usize);
println!(" {} {} ({})", code, bar, count);
}
println!("\nHTTP Method Distribution:");
for (method, count) in &result.method_counts {
println!(" {}: {}", method, count);
}
println!("\nPopular Routes (Top 5):");
for (i, (path, count)) in result.top_paths.iter().enumerate() {
println!(" {}. {} ({} times)", i + 1, path, count);
}
}
/// Generate HTML Analysis Report on Formats
fn generate_html_report(
result: &AnalysisResult,
path: &str,
) -> std::io::Result<()> {
let file = File::create(path)?;
let mut writer = std::io::BufWriter::new(file);
writeln!(writer, "<!DOCTYPE html>")?;
writeln!(writer, "<html lang=\"zh\">")?;
writeln!(writer, "<head><meta charset=\"UTF-8\"><title>Log Analysis Report</title>")?;
writeln!(writer, "<style>
body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
h1 {{ color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }}
table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
th {{ background: #4CAF50; color: white; }}
.error {{ color: red; font-weight: bold; }}
.summary {{ background: white; padding: 20px; border-radius: 5px; }}
</style></head><body>")?;
writeln!(writer, "<h1>Server Log Analysis Report</h1>")?;
writeln!(writer, "<div class=\"summary\">")?;
writeln!(writer, "<p>Total Number of Requests: <strong>{}</strong></p>", result.total_requests)?;
writeln!(writer, "<p>Error Rate: <strong class=\"error\">{:.1}%</strong></p>", result.error_rate)?;
writeln!(writer, "</div>")?;
// Status Code Table
writeln!(writer, "<h2>Status Code Distribution</h2><table><tr><th>Status Code</th><th>Count</th><th>Percentage</th></tr>")?;
let mut sorted_status: Vec<_> = result.status_counts.iter().collect();
sorted_status.sort_by_key(|(_, &c)| std::cmp::Reverse(c));
for (&code, &count) in &sorted_status {
let pct = count as f64 / result.total_requests as f64 * 100.0;
writeln!(writer, "<tr><td>{}</td><td>{}</td><td>{:.1}%</td></tr>", code, count, pct)?;
}
writeln!(writer, "</table>")?;
writeln!(writer, "</body></html>")?;
writer.flush()?;
Ok(())
}
/// Clear Temporary Files
fn cleanup(log_path: &str, report_path: &str) -> std::io::Result<()> {
let log = Path::new(log_path);
let report = Path::new(report_path);
if log.exists() {
std::fs::remove_file(log)?;
}
if report.exists() {
std::fs::remove_file(report)?;
}
Ok(())
}
الناتج:
=== Complete Log Analyzer ===
[1/4] The simulation log has been generated: temp_access.log
[2/4] Log parsing complete: 100 records
--- Analysis Results ---
Total Number of Requests: 100
Error Rate: 26.0%
Status Code Distribution:
200 ============== (33)
404 ====== (13)
500 ====== (13)
401 ====== (7)
400 ====== (6)
201 ====== (6)
204 ====== (6)
304 ====== (6)
301 ====== (5)
403 ====== (5)
502 ====== (0)
HTTP Method Distribution:
GET: 29
POST: 28
PUT: 25
DELETE: 18
Popular Routes (Top 5):
1. /api/users (13 times)
2. /api/products (12 times)
3. /index.html (11 times)
4. /api/orders (11 times)
5. /login (11 times)
[3/4] HTML The report has been generated: temp_report.html
--- Report Preview(first 20 lines) ---
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>Log Analysis Report</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
h1 {{ color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }}
table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
th {{ background: #4CAF50; color: white; }}
.error {{ color: red; font-weight: bold; }}
.summary {{ background: white; padding: 20px; border-radius: 5px; }}
</style></head><body>
<h1>Server Log Analysis Report</h1>
<div class="summary">
<p>Total Number of Requests: <strong>100</strong></p>
<p>Error Rate: <strong class="error">26.0%</strong></p>
</div>
... (total 49 lines)
[4/4] Temporary files have been cleared.
يوضح هذا المثال الشامل مسار عمل «محلل السجلات» بالكامل: إنشاء بيانات محاكاة -> تحليل السجلات -> التحليل متعدد الأبعاد -> إنشاء تقارير HTML -> التنظيف. يوفر كل من
BufReaderوBufWriterمزايا أداء كبيرة عند التعامل مع الملفات الكبيرة. ويضمن المشغل?أن تكون كل عملية إدخال/إخراج موجزة وآمنة. وتوفر الطريقةPathعمليات مسار عبر الأنظمة الأساسية المختلفة. كما أن تحليل السجلات في البنيةLogEntryيجعل كود التحليل اللاحق أكثر وضوحًا وأسهل في الصيانة.
(5) ▶ المثال:تمرين شامل — تحليل ملفات CSV والإحصاءات (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Comprehensive Example: File I/O + Error Handling + Statistics
// ============================================
use std::fs;
use std::io::{Write, BufWriter};
use std::path::Path;
struct Record {
name: String,
score: u32,
grade: String,
}
fn parse_csv_line(line: &str) -> Option<Record> {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() != 3 { return None; }
let score = parts[1].trim().parse::<u32>().ok()?;
Some(Record {
name: parts[0].trim().to_string(),
score,
grade: parts[2].trim().to_string(),
})
}
fn analyze(records: &[Record]) -> (f64, u32, u32) {
if records.is_empty() { return (0.0, 0, 0); }
let sum: u32 = records.iter().map(|r| r.score).sum();
let avg = sum as f64 / records.len() as f64;
let max = records.iter().map(|r| r.score).max().unwrap();
let min = records.iter().map(|r| r.score).min().unwrap();
(avg, max, min)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let csv_data = "name,score,grade\nAlice,95,A\nBob,72,C\nCharlie,88,B\nDavid,58,F\nEve,91,A";
let input_path = "temp_scores.csv";
let report_path = "temp_report.txt";
fs::write(input_path, csv_data)?;
println!("CSV Written: {}", input_path);
let content = fs::read_to_string(input_path)?;
let records: Vec<Record> = content.lines()
.skip(1)
.filter_map(parse_csv_line)
.collect();
println!("\n=== Analysis Results ===");
for r in &records {
println!("{}: {} points, Level {}", r.name, r.score, r.grade);
}
let (avg, max, min) = analyze(&records);
println!("\nAverage: {:.1}, Highest: {}, Lowest: {}", avg, max, min);
let a_count = records.iter().filter(|r| r.grade == "A").count();
let f_count = records.iter().filter(|r| r.grade == "F").count();
let file = fs::File::create(report_path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "=== Grades Statistics Report ===")?;
writeln!(writer, "Total number of people: {}", records.len())?;
writeln!(writer, "Average Score: {:.1}", avg)?;
writeln!(writer, "Highest Score: {}, Lowest score: {}", max, min)?;
writeln!(writer, "A Level: {} people, F Level: {} people", a_count, f_count)?;
writer.flush()?;
println!("\nThe report has been submitted.: {}", report_path);
let report = fs::read_to_string(report_path)?;
println!("\n{}", report);
fs::remove_file(input_path)?;
fs::remove_file(report_path)?;
println!("Temporary files have been cleared.");
Ok(())
}
الناتج:
CSV Written: temp_scores.csv
=== Analysis Results ===
Alice: 95 points, Level A
Bob: 72 points, Level C
Charlie: 88 points, Level B
David: 58 points, Level F
Eve: 91 points, Level A
Average: 80.8, Highest: 95, Lowest: 58
The report has been submitted.: temp_report.txt
=== Grades Statistics Report ===
Total number of people: 5
Average Score: 80.8
Highest Score: 95, Lowest score: 58
A Level: 2 people, F Level: 1 people
Temporary files have been cleared.
تحليل ملف CSV → إحصاءات → إنشاء التقرير → التنظيف — وهذا يوضح بشكل كامل سير عمل عمليات الإدخال/الإخراج للملفات.
?ينقل جميع أخطاء الإدخال/الإخراج؛BufWriterيخزن عمليات الكتابة مؤقتًا لتحسين الأداء؛filter_mapيقوم بتصفية الصفوف غير الصالحة بطريقة سلسة؛fs::remove_fileينظف الملفات المؤقتة.
❓ أسئلة شائعة
fs::read_to_string وBufReader؟ ومتى ينبغي استخدام كل منهما؟fs::read_to_string بقراءة الملف بأكمله في الذاكرة دفعة واحدة (طريقة بسيطة وقوية)، بينما يقوم BufReader بقراءته سطراً سطراً (لتوفير الذاكرة). بالنسبة للملفات الصغيرة (أقل من بضعة ميغابايت)، يكون استخدام read_to_string أبسط؛ أما بالنسبة للملفات الكبيرة (أكثر من بضع مئات من الميغابايت)، فيجب عليك استخدام BufReader، وإلا ستنفد الذاكرة. تتراوح أحجام ملفات السجل عادةً بين بضع مئات من الكيلوبايت وبضعة ميغابايت، لذا فإن كلا الخيارين مناسبان، لكن BufReader أكثر أناقة.File::create وOpenOptions::append؟File::create دائمًا بإنشاء ملف جديد (أو مسحه والكتابة فوقه إن كان موجودًا)، بينما يقوم OpenOptions::append بإضافة المحتوى إلى نهاية الملف. File::create يعادل OpenOptions::new().write(true).create(true).truncate(true)، بينما يعادل وضع الإضافة OpenOptions::new().append(true).open(path). يُستخدم وضع الإضافة عادةً للتسجيل، بينما يُستخدم وضع الكتابة فوق عادةً لإنشاء التقارير.Path وPathBuf؟ ولماذا نحتاج إلى الاثنين؟Path عبارة عن شريحة مسار غير قابلة للتعديل (على غرار &str)، بينما PathBuf عبارة عن سلسلة مسار قابلة للتعديل (على غرار String). يمكن ربط PathBuf (.join()) وتعديله (.push()) ونقل ملكيته، بينما Path مخصص للقراءة فقط. العلاقة بين الاثنين مشابهة لتلك بين String و&str: PathBuf يمتلك البيانات، في حين أن Path هو مرجع مستعار. عادةً ما تستخدم معلمات الدوال &Path (أو AsRef<Path>)، بينما يتم تخزين المتغيرات باستخدام PathBuf.? في عمليات الإدخال/الإخراج للملفات؟? بفحص قيمة Result: إذا كانت Ok(val)، فإنه يستخرج val؛ وإذا كانت Err(e)، فإنه يعيد Err(e.into()) مبكرًا. في عمليات الإدخال/الإخراج للملفات، تُرجع جميع العمليات تقريبًا Result<T, io::Error>. يُحرر ? الكود من التداخل match — استخدم ? إذا لم تكن مهتمًا بتفاصيل أخطاء محددة؛ واستخدم match إذا كنت بحاجة إلى معالجة الأخطاء المختلفة بطرق مختلفة.File بإغلاق الملف تلقائيًّا عند drop (خروج المتغير من نطاقه). يضمن نظام الملكية في Rust أنه عند خروج المتغير File من نطاقه، يتم استدعاء الطريقة drop الخاصة بالسمة Drop تلقائيًا لإغلاق مقبض الملف. وبالمثل، يقوم BufWriter تلقائيًا بـ flush المخزن المؤقت عند drop. ما عليك سوى التأكد من مسح المخزن المؤقت يدويًّا باستخدام flush() (إذا كنت بحاجة إلى التأكد من كتابة البيانات على القرص قبل إغلاق الملف).📖 ملخص
std::fs::read_to_stringوstd::fs::writeهما أكثر طرق «القراءة/الكتابة لمرة واحدة» إيجازًا، وهما مناسبان للملفات الصغيرةFile::openقراءة ملف موجود؛File::createإنشاء ملف جديد أو الكتابة فوق ملف موجودBufReaderيقوم قارئ المخزن المؤقت بمعالجة الملفات الكبيرة سطراً سطراً باستخدامlines()، مما يقلل من عدد استدعاءات النظامBufWriterيعمل كاتب المخزن المؤقت على دمج عمليات كتابة صغيرة متعددة في عملية كتابة واحدة كبيرة، مما يحسّن أداء الكتابةPath(شريحة غير قابلة للتغيير) وPathBuf(سلسلة قابلة للتغيير) توفران عمليات المسار عبر الأنظمة المختلفة وتدعمان طرقًا مثل.join()و.file_name()و.extension().?ينقل الأخطاء تلقائيًا منResult، مما يجعل كود عمليات الإدخال/الإخراج للملفات موجزًا وآمنًا
📝 تمارين
-
الصعوبة ⭐: اكتب برنامجًا يقوم بإنشاء ملف باسم
note.txt، وكتابة ثلاث فقرات فيه (باستخدام الماكروwriteln!)، ثم قراءة محتويات الملف وعرضها. وأخيرًا، احذف الملف. استخدم المشغل?لمعالجة الأخطاء، والدالةmainلإرجاعstd::io::Result<()>. -
الصعوبة ⭐⭐: اكتب برنامجًا لـ«قراءة ملفات CSV». أنشئ ملف CSV يحتوي على المحتوى التالي (محاكاة):
TEXT 📖 للعرض فقطName,Age,City Alice,28,New York Bob,32,London Charlie,25,Tokyoاستخدم
BufReaderلقراءة البيانات سطراً سطراً، وتخطي السطر الأول (العنوان)، وتحليل كل سطر، وحساب متوسط العمر. اكتب النتائج فيresult.txt. وأخيراً، احذف جميع الملفات المؤقتة. -
الصعوبة ⭐⭐⭐: اكتب أداة «البحث عن الملفات والإحصائيات». قم بمسح دليل محدد بشكل متكرر (باستخدام
std::fs::read_dir) واحسب عدد الملفات لكل امتداد ملف. قم بإنشاء تقرير مرتب ترتيبًا تنازليًّا حسب العدد (اكتبه فيsummary.txt). المتطلبات: (1) استخدمPathوPathBufللتعامل مع المسارات؛ (2) استخدمBufWriterلكتابة التقرير؛ (3) استخدم?لإبلاغ الأخطاء؛ (4) يجب أن يكون تنسيق الإخراج عبارة عن جدول يحتوي على امتداد الملف، والعدد، والنسبة المئوية، ومخطط شريطي ASCII.