故事起源于一个简单的上传功能,功能要求是这样的,将数据按照指定格式拼接,然后写入txt文件,再通过请求对方api进行文件上传。
故事背景有了,然后下面介绍故事走向。
按个人以往的尿性,肯定是直接将内容按指定编码写入txt中,然后上传了事,但故事(或者说事故)之所以是故事,就在于它一定是不按惯例走的,所以这次理所当然的,脑子必须抽了,然后想到这个txt文件生成是必须的吗?文件内容我都有了,我为什么还要通过IO写入文件,然后再读取上传呢?我是不是可以直接确认字符串写入txt文件后与原始字符串有什么区别,通过修正区别来得到要上传的文件byte数组呢?
想到了当然要动手尝试下,于是下面的测试代码就产生了
static void FileConvertDemo(Encoding encoding)
{
string filePath = @"E:\test.txt";
string content = "测试内容";
var color = Console.ForegroundColor;
var titleColor = ConsoleColor.Magenta;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("****************** " + encoding.BodyName + " ******************");
Console.ForegroundColor = color;
byte[] datas;
Console.ForegroundColor = titleColor;
Console.WriteLine("测试直接Encoding获取byte[]的代码");
Console.ForegroundColor = color;
datas = encoding.GetBytes(content);
Console.WriteLine("数组长度:" + datas.Length);
Console.WriteLine(BitConverter.ToString(datas));
Console.ForegroundColor = titleColor;
Console.WriteLine("测试将内容通过原生方法指定Encoding后写入文件的方式获取byte[]的代码");
Console.ForegroundColor = color;
File.WriteAllText(filePath, content, encoding);
datas = File.ReadAllBytes(filePath);
Console.WriteLine("数组长度:" + datas.Length);
Console.WriteLine(BitConverter.ToString(datas));
}
然后测试要全面,所以这里将所有默认的字符编码都测试一下
List<Encoding> list = new List<Encoding>()
{
Encoding.ASCII,
Encoding.BigEndianUnicode,
Encoding.UTF8,
Encoding.UTF7,
Encoding.UTF32,
Encoding.Unicode,
Encoding.GetEncoding("gb2312"),
Encoding.GetEncoding("gbk"),
};
list.ForEach(encoding => FileConvertDemo(encoding));
测试结果,呵呵哒,很顺利的就发现规律明显,根据编码不同,文件加上了不同的标头,如下图,除了差异部分(草绿色方框和淡蓝色方框),其它位置的byte都是一样的
所以我现在只要测试补充上差异标头后,得到的byte[]是不是就是写入文件后的byte[],所以下面这段代码产生了
using System.Collections.Concurrent;
using System.IO;
public interface IFileConverter
{
/// <summary>
/// 获取指定内容输出到指定文本后,File.ReadAllBytes应当读取到的byte数组
/// </summary>
/// <param name="contents">要写入文件的内容</param>
/// <param name="filePath">文件要保存的物理位置</param>
/// <returns></returns>
byte[] GetFileBytes(string contents, string filePath);
}
public class TxtFileConverter : IFileConverter
{
public TxtFileConverter()
{
this.Encoding = Encoding.UTF8;
}
public Encoding Encoding { get; set; }
public byte[] GetFileBytes(string contents, string filePath = null)
{
//File.WriteAllText(filePath, contents, this.Encoding);
byte[] stringBytes = this.Encoding.GetBytes(contents);
byte[] result = stringBytes;
var flags = EncodingFlagHelper.GetEncodingFlags(this.Encoding);
if (flags.Length > 0)
{//不生成物理文件,直接通过文件头来补充缺少的字节
result = new byte[flags.Length + stringBytes.LongLength];
Array.Copy(flags, result, flags.Length);
Array.Copy(stringBytes, 0, result, flags.Length, stringBytes.LongLength);
}
return result;
}
class EncodingFlagHelper
{
static readonly ConcurrentDictionary<string, byte[]> Dictionary = new ConcurrentDictionary<string, byte[]>();
/// <summary>
/// 根据当前编码类型获取对应txt的编码标识
/// </summary>
/// <param name="encoding"></param>
/// <returns></returns>
public static byte[] GetEncodingFlags(Encoding encoding)
{
return Dictionary.GetOrAdd(encoding.BodyName, name =>
{
//因为空字符串输出数组就是个零长数组,所以可以简单的通过写一个空字符串到文本中来得到编码标识
string content = string.Empty;
string tmpFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "~tmpFlags");//可能存在权限问题,所以不用Path.GetTempFileName()
File.WriteAllText(tmpFile, content, encoding);
var flags = File.ReadAllBytes(tmpFile);
File.Delete(tmpFile);
return flags;
});
}
}
}
下面就是加上这部分的测试代码,在FileConvertDemo方法内追加下列代码
Console.ForegroundColor = titleColor;
Console.WriteLine("测试不生成文件,通过补位的方式获取byte[]的代码");
Console.ForegroundColor = color;
datas = new TxtFileConverter() { Encoding = encoding }.GetFileBytes(content, filePath);
Console.WriteLine("数组长度:" + datas.Length);
Console.WriteLine(BitConverter.ToString(datas));
然后运行,输出结果如下图
最后上传测试这里就不做了,因为实际项目中比目前还要复杂,文件生成后还有加解密、解压缩,其中加解密要求的原始内容就是byte[],这里可以不需做任何处理,解压缩部分需要Stream,byte作为参数传给MemoryStream即可,当然你真想验证结果,那么可以试下File.WriteAllBytes来比较是否与File.WriteAllText一致。