架构深渊

慢慢走进程序的深渊……关注领域驱动设计、测试驱动开发、设计模式、企业应用架构模式……积累技术细节,以设计架构为宗。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Stream的追加问题[C#]

Posted on 2008-10-09 21:08  chen eric  阅读(946)  评论(0编辑  收藏  举报
Stream的追加问题[C#]()
         前日因为遇到了在一个文件的中间位置追加文本的问题,也看了一些文章,做了一些试验,对愚翁提出的3中文件追加方式有一些感悟:
         第一种对于文件尾扩展的操作,内容长度不限;
         -----------------------------------
         这种问题比较容易解决,只要设置FileMode的时候增加Append标示即可.
         Code如下:
         FileStream fs = new FileStream("F:\\test.txt",          FileMode.Append, FileAccess.Write);
         string str = "\n中华人string nul民1共3和 国mailto:#!@!/n";
         fs.Write(Encoding.GetEncoding("GB2312").GetBytes(str), 0, Encoding.GetEncoding("GB2312").GetByteCount(str));
         fs.Close();
         fs.Dispose();
         如果追加的内容中有汉字,那么就要使用正确的编码格式,否则就会出现乱码.


         从图中可以看到,str的内容被追加到了文件的末尾.

         第二种等字节的替换操作,位置不限;
         ----------------------------
         要实现这种替换,就需要使用文件流的seek方法了.说明如下:
         public override long Seek (
         long offset,
         SeekOrigin origin
         )

         offset
         相对于 origin 的点,从此处开始查找。
         origin
         使用 SeekOrigin 类型的值,将开始位置、结束位置或当前位置指定为 origin 的参考点。
         其中第二个参数可以有3个取值:

         成员名称 说明
         Begin 指定流的开头。
         Current 指定流内的当前位置。
         End 指定流的结尾。
 
         比如:
         fs.Seek(41, SeekOrigin.Begin);
         将流内的位置定在从流的开头向后41个字节的地方.

         举例说明:
         using (FileStream fs = new FileStream("F:\\test.txt", FileMode.Open , FileAccess.Write, FileShare.None))
         {
         fs.Seek(41, SeekOrigin.Begin);
         string str1 = "888888888888888888888888888888888888888\r\n";
         fs.Write(Encoding.GetEncoding("GB2312").GetBytes(str1), 0, Encoding.GetEncoding("GB2312").GetByteCount(str1));
         }

         在seek语句后,流内的位置就定在了从流开头偏移41个字节的位置上,然后再写入str的内容,结果如图所示:
         但是我们可以在图中看到第一行只有39个”1”,为什么偏移41个字节后的位置是第二行的第一个字符呢?

         这是因为换行符的因,我们可以用UE的HEX格式看一下就明白了:
         从图中可以看到,每次换行的时候,都有2个字符”0x0D”和”0x0A”就是ASCII中的10和13了,也就是我们编码时候的”\r\n”了,所以要想达到替换第二行39个”2”的目的,必须将流内的位置定在从开始偏移41个字节的位置上(39个”1”再加上换行回车符,共41个字符).
 
         如果我们想替换最后一行39个”8”的话,我们来计算一下,应该把位置偏移定为多少?

         应该是7*(39+2)的位置上,当然这是从流开头偏移的字节数,如果我们用SeekOrigin.End指定从流结尾的地方进行偏移就方便了,最后一行有39个”8”和换行回车符,共41个字节,这样我们用fs.Seek(-41, SeekOrigin.End);就可以将位置定在第8行的开始位置了.
         有的时候,我们不能立刻得到需要替换的地方的位置,这样,我们可以边查找边找,比如还是上面的那个文件,如果现在我需要将39个”5”替换成39个”0”,在程序中,我们可以每次读取一行数据(每次读取41个字节),进行比对,如果是39个”5”的话,就可以得到这一行的最后位置了,想要替换这一行的数据,我们还需要将这个位置向前偏移41个字节的长度,然后就可以write数据了

         Code如下:
         using (FileStream fs = new FileStream("F:\\test.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.None))
         {
         int nRealRead = 0;
         int sum = 0;
         byte[] bBuffer = new byte[41];
         do
         {
         // Read data
         nRealRead = fs.Read(bBuffer, 0, 41);
         sum += nRealRead;
         // Output data
         String str = Encoding.Default.GetString(bBuffer, 0, nRealRead);
         //偷懒了,反正39个字符都一样:)
         if (str.Substring(0, 4) == "5555")
         {
         fs.Seek(-41, SeekOrigin.Current );
         string str1 = "000000000000000000000000000000000000000\r\n";
         fs.Write(Encoding.GetEncoding("GB2312").GetBytes(str1), 0, Encoding.GetEncoding("GB2312").GetByteCount(str1));
         break;
         }
         } while (nRealRead == 41);
         }

         上面seek的时候,还可以这样写: fs.Seek(sum-41, SeekOrigin.Begin);

         因为每次读取字节的大小都累加在了sum里面了,其实sum的大小和SeekOrigin.Current指向的是一样的,再向前偏移一行的长度就成了sum-41了.

         最后一种就是位置不固定,字节数不确定。
         --------------------------------
         至于这种,我觉得需要使用的可能性不大,因为你需要替换文件某一部分,你就要知道需要替换的文本特征,这样就可以通过一行一段的查找找到了,就可以确定要替换的位置.至于”字节数不确定”,我觉得愚翁指的是这种情况:按行替换,我需要替换这行的数据,但是替换后的数据长度和替换前的数据长度不一样.比如:

         替换前的文本:
         11111
         22222
         33333

         现在需要将第二行的文本替换成6个4,如下:
         11111
         444444
         33333
 
         这种情况下,如果要单单使用覆盖的话,估计是实现不了的,因为多于的文本会覆盖其它的文本,我们做个简单的试验,还是用那个文本文件,code如下:
 
         FileStream fs = new FileStream("F:\\test.txt",          FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
         int nRealRead = 0;
         int sum = 0;
         byte[] bBuffer = new byte[41];
         do
         {
         // Read data
         nRealRead = fs.Read(bBuffer, 0, 41);
         sum += nRealRead;
         // Output data
         String str = Encoding.Default.GetString(bBuffer, 0, nRealRead);
         //偷懒了,反正39个字符都一样:)
         if (str.Substring(0, 4) == "5555")
         {
         fs.Seek(-41, SeekOrigin.Current);
         string str1 = "00000000000000000000000000000000000000099\r\n";
         fs1.Write(Encoding.GetEncoding("GB2312").GetBytes(str1), 0, Encoding.GetEncoding("GB2312").GetByteCount(str1));
         break;
         }
         } while (nRealRead == 41);
         fs.Close();
         fs.Dispose();
 
         这次替换的内容是39个0和2个9和换行回车符,长度为43,比来41要多2个字符.

         这样的话,替换后第5行变成了39个0和2个9和换行回车符,这个时候多出来的2个字符就覆盖了第6行的开头2个字符.

         这也就是说,替换来替换取,多于的字符总是要覆盖其它来的字符,…….
 
         如果要解决这个问题,我只能想出一个比较笨的方法,就是边写边读,先将不想替换的文本以前的部分读出来再写入另一个文本,再写入替换的文本,最后再将后面的文本写入......