RSSC#

C# – Chèn dữ liệu tại vị trí bất kì trong file nhị phân

Một trong những vấn đề thường gặp phải khi bạn thao tác với các file nhị phân là chèn dữ liệu tại một vị trí nào đó. Giả sử bạn tìm được vị trí cần thiết để chèn, tuy nhiên sau khi thực hiện thì phần dữ liệu phía sau sẽ bị ghi đè. Mặt khác bạn không muốn thực hiện điều này bằng cách nạp toàn bộ file vào memory. Có một số cách giải quyết vấn đề này, tuy nhiên chúng cũng chỉ theo một nguyên tắc gồm 3 bước là: tìm vị trí, tạo khoảng trống và ghi dữ liệu.

Một trong những vấn đề thường gặp phải khi bạn thao tác với các file nhị phân là chèn dữ liệu tại một vị trí nào đó. Giả sử bạn tìm được vị trí cần thiết để chèn, tuy nhiên sau khi thực hiện thì phần dữ liệu phía sau sẽ bị ghi đè. Mặt khác bạn không muốn thực hiện điều này bằng cách nạp toàn bộ file vào memory. Có một số cách giải quyết vấn đề này, tuy nhiên chúng cũng chỉ theo một nguyên tắc gồm 3 bước là: tìm vị trí, tạo khoảng trống ghi dữ liệu.

 

Trong ví dụ sau tôi sẽ sử dụng dữ liệu là văn bản (string), với các kiểu dữ liệu khác bạn cần dùng lớp System.BitConverter để chuyển qua lại giữa dữ liệu và mảng byte.

Tìm vị trí trong file

Để tìm vị trí của dữ liệu ta thực hiện tương tự như tìm kiếm một từ trong một chuỗi. Tất cả đều thực hiện trên mảng byte. Ta sử dụng phương thức GetBytes() của lớp UTF8Encoding để chuyển văn bản sang mảng byte:

UTF8Encoding  _encoding =new UTF8Encoding();

Tìm vị trí bắt đầu một phần dữ liệu:

 

public long FindOffset(Stream stream, string text)
{

	byte[] bytes=_encoding.GetBytes(text);
	int aByte;
	int index = 0;
	long position = 0;

	while((aByte=stream.ReadByte())!=-1)
	{
		if (bytes[index++] != (byte)aByte) {
			index = 0;
			position = stream.Position;
		}

		if (index == bytes.Length)
			return position;

	}
	return -1;
}

 

Biến position sẽ lưu vị trí đầu tiên của phần dữ liệu trùng với văn bản cần tìm. Tuy nhiên vì đối tượng stream được đọc cho đến khi hết phần dữ liệu đó nên thuộc tính Position của stream sẽ là vị trí cuối cùng của phần dữ liệu được tìm thấy.

Tìm vị trí của một dòng bất kì:

 

public long FindOffsetLine(Stream stream,int line)
{
	if(line==0)
		return 0;

	int aByte;
	int counter = 0;
	byte lf=(byte)'\n'; // line feed
	while((aByte=stream.ReadByte())!=-1)
	{
		if((byte)aByte==lf)
			counter++;
		if(counter==line)
			return stream.Position;
	}
	return -1;
}

 

Chèn dữ liệu

Sau khi đã có vị trí của dữ liệu cần chèn, ta chỉ cần thực hiện công việc là lưu phần dữ liệu phía sau vị trí đó vào buffer, ghi dữ liệu chèn và sau đó là ghi phần dữ liệu từ buffer ra.

Phương thức chèn dữ liệu tại một vị trí bất kì:

 

public void InsertAt(Stream stream,int offset,string insertObj)
{
	// Lấy phần dữ liệu phía sau vị trí chèn và lưu vào buffer
	stream.Position=offset;
	BinaryReader reader=new BinaryReader(stream);
	byte[] buffer= reader.ReadBytes((int)stream.Length-offset);

	stream.Position=offset;
	BinaryWriter writer=new BinaryWriter(stream);
	// Ghi dữ liệu cần chèn và buffer vào phía sau
	writer.Write(_encoding.GetBytes(insertObj));
	writer.Write(buffer);
	writer.Flush();
	writer.Close();
 }

 

Nhận xét

Mặc dù thuật toán ta sử dụng chỉ dùng buffer lưu một phần dữ liệu của file, tuy nhiên trong những trường hợp như chèn ở phần đầu, buffer sẽ lưu gần như toàn bộ file. Hướng giải quyết cho vấn đề này là thay vì lưu toàn bộ dữ liệu từ vị trí cần chèn đến cuối, ta sẽ lưu một phần dữ liệu, sau đó lại tiến hành chèn phần dữ liệu đó vào vị trí tiếp theo cho đến hết file.

Minh họa cho ví dụ sử dụng cách chèn liên tục với độ lớn của buffer bằng với dữ liệu cần chèn:

string1 string2 string3
(buffer: )

string1 string4 string3
(buffer: string2)

string1 string4 string2
(buffer: string3)

string1 string4 string2 string3

Mã nguồn hoàn chỉnh

Y2BinaryIO.cs:

 

/*
 * Binary File Manipulation
 * User: Yin Yang
 * Date: 4/5/2011
 * Time: 8:38 PM
 *
 * http://yinyangit.wordpress.com
 *
 */
using System;
using System.IO;
using System.Text;

namespace BinaryFileManipulation
{
	/// 
	/// Description of BinaryIO.
	/// 
	public class Y2BinaryIO
	{
		string _fileName;
		UTF8Encoding  _encoding;

		public Y2BinaryIO(string fileName)
		{
			this._fileName=fileName;
			_encoding =new UTF8Encoding();
		}

		public void CreateFile(params string[] contents)
		{
			FileStream fs = new FileStream(_fileName, FileMode.Create);
			BinaryWriter writer = new BinaryWriter(fs);

			foreach(string item in contents)
			{
				writer.Write(_encoding.GetBytes(item));
			}

			writer.Flush();
			writer.Close();
			fs.Close();
			fs.Dispose();
		}
		public void InsertAtLine(int line,string insertObj)
		{
			FileStream fs = new FileStream(_fileName, FileMode.Open);
			int pos=(int)FindOffsetLine(fs,line);
			if(pos==-1)
				throw new Exception("Line not found: "+line);
			InsertAt(fs,pos,insertObj);
		}

		public void InsertBeforeData(string findObj,string insertObj)
		{
			FileStream fs = new FileStream(_fileName, FileMode.Open);

			int pos=(int)FindOffset(fs,findObj);
			if(pos==-1)
				throw new Exception("Data not found: '"+insertObj+"'");
			// if insert after
			// pos=(int)fs.Position;

			InsertAt(fs,pos,insertObj);
		}
		public void InsertAt(Stream stream,int offset,string insertObj)
		{
			// Lấy phần dữ liệu phía sau vị trí chèn và lưu vào buffer
			stream.Position=offset;
			BinaryReader reader=new BinaryReader(stream);
			byte[] buffer= reader.ReadBytes((int)stream.Length-offset);

			stream.Position=offset;
			BinaryWriter writer=new BinaryWriter(stream);
			// Ghi dữ liệu cần chèn và buffer vào phía sau
			writer.Write(_encoding.GetBytes(insertObj));
			writer.Write(buffer);
			writer.Flush();
			writer.Close();
		}
		public string ReadToEnd()
		{
			StreamReader reader=new StreamReader(_fileName);
			string ret= reader.ReadToEnd();
			reader.Close();
			return ret;
		}
		/// 
		/// Tìm offset của 1 dòng
		/// 
		/// 
		/// 
		/// 
		public long FindOffsetLine(Stream stream,int line)
		{
			int aByte;
			int counter = 0;
			byte lf=(byte)'\n'; // line feed
			while((aByte=stream.ReadByte())!=-1)
			{
				if((byte)aByte==lf)
					counter++;
				if(counter==line)
					return stream.Position;
			}
			return -1;
		}
		/// 
		/// Sau khi thực hiện thì Position của stream sẽ ở cuối text cần tìm
		/// 
		/// 
		/// 
		/// vị trí bắt đầu text cần tìm, ngược lại là -1
		public long FindOffset(Stream stream, string text)
		{

			byte[] bytes=_encoding.GetBytes(text);
			int aByte;
			int index = 0;
			long position = 0;

			while((aByte=stream.ReadByte())!=-1)
			{
				if (bytes[index++] != (byte)aByte) {
					index = 0;
					position = stream.Position;
				}

				if (index == bytes.Length)
					return position;

			}
			return -1;
		}
	}
}

 

 

Kiểm tra chương trình


 

using System;
using System.IO;

namespace BinaryFileManipulation
{
	class Program
	{
		public static void Main(string[] args)
		{
			Console.Title="YinYang - Test Binary File Manipulation";
			Y2BinaryIO bin=new Y2BinaryIO("yinyang.dat");
			// create
			bin.CreateFile("string 1\n","string 2\n","string 3\n");
			Console.WriteLine("Before inserting");

			Console.WriteLine(bin.ReadToEnd());

			// insert "string 4" before "string 2"
			bin.InsertBeforeData("string 2\n","string 4\n");
			// insert "string 5" at line 2
			bin.InsertAtLine(2,"string 5\n");
			Console.WriteLine("After inserting");

			Console.WriteLine(bin.ReadToEnd());

			Console.ReadKey(true);
		}
	}
}

Output:

http://yinyangit.wordpress.com

 

Tags:

Nếu bạn thấy bài viết hữu ích, hãy nhấn +1 và các liên kết chia sẻ để website ngày càng phát triển hơn. Xin cám ơn bạn!

Nếu là khách, bạn phải đăng ký tài khoản và kích hoạt tài khoản để bình luận được hiển thị ở đây.
Thông tin kích hoạt gửi đến mail của bạn.

Tin mới hơn

Tin cũ hơn

Lên trên đầu