C# Advanced Tutorial 1-10-IOSerialize


主要内容概要

1 文件夹/文件 检查、新增、复制、移动、删除,递归编程技巧
2 文件读写,记录文本日志,读取配置文件
3 三种序列化器,xml和json
4 验证码、图片缩放

IO

文件夹检测和管理

配置文件AppSettings:会有一些在开发环境 测试环境 生产环境 不同,例如:数据库连接 路径 开关。最好有个配置类全部集中在一起进行配置。
配置路径
绝对路径:
相对路径: /然后拼装,相当于应用程序所在路径

// 集中管理系统的配置字段
public class Constant
{
  // 配置绝对路径
  public static string LogPath = ConfigurationManager.AppSettings["LogPath"];
  public static string LogMovePath = ConfigurationManager.AppSettings["LogMovePath"];

  // 序列化数据地址
  public static string SerializeDataPath = ConfigurationManager.AppSettings["SerializeDataPath"];
}

检测文件和文件夹是否存在

if (!Directory.Exists(LogPath))//检测文件夹是否存在
{

}

值得注意的是,另外一种判断文件夹的方式,不存在不会报错,仍然会创建一个对象。Directory是帮助类,但是DirectoryInfo是对象类。

DirectoryInfo directory = new DirectoryInfo(LogPath);//不存在不报错  注意exists属性
Console.WriteLine(string.Format("{0} {1} {2}", directory.FullName, directory.CreationTime, directory.LastWriteTime));

文件夹和文件的路径拼接:

if (!File.Exists(Path.Combine(LogPath, "info.txt")))//拼接
{

}

和Directory一样,File同样是一个帮助类,而FileInfo是一个对象类:

FileInfo fileInfo = new FileInfo(Path.Combine(LogPath, "info.txt"));//文件不存在也会自动创建一个对象
Console.WriteLine(string.Format("{0} {1} {2}", fileInfo.FullName, fileInfo.CreationTime, fileInfo.LastWriteTime));

Directory

文件夹的剪切,复制,删除等操作如下:

if (!Directory.Exists(LogPath))
{
  DirectoryInfo directoryInfo = Directory.CreateDirectory(LogPath);//一次性创建全部的子路径
  Directory.Move(LogPath, LogMovePath);//移动(剪切)  原文件夹就不在了
  Directory.Delete(LogMovePath);//删除
}

File

string fileName = Path.Combine(LogPath, "log.txt");
string fileNameCopy = Path.Combine(LogPath, "logCopy.txt");
string fileNameMove = Path.Combine(LogPath, "logMove.txt");
bool isExists = File.Exists(fileName);

创建了文件夹之后,才能创建里面的文件,打开文件流 (创建文件并写入)创建覆盖 OPen是打开。

Directory.CreateDirectory(LogPath);
using (FileStream fileStream = File.Create(fileName))
{
  string name = "12345567778890";
  byte[] bytes = Encoding.Default.GetBytes(name);
  fileStream.Write(bytes, 0, bytes.Length);
  fileStream.Flush();
}

如果文件不存在就创建,如果存在就新建一个覆盖掉:

using (FileStream fileStream = File.Create(fileName))//打开文件流 (创建文件并写入)
{
  StreamWriter sw = new StreamWriter(fileStream);
  sw.WriteLine("1234567890");
  sw.Flush();
}

流写入器(创建/打开文件并写入)

using (StreamWriter sw = File.AppendText(fileName))//流写入器(创建/打开文件并写入)
{
  string msg = "今天是2020年02月3号,今天天气不错!";
  sw.WriteLine(msg);
  sw.Flush();
}

using (StreamWriter sw = File.AppendText(fileName))//流写入器(创建/打开文件并写入)
{
  string name = "0987654321";
  byte[] bytes = Encoding.Default.GetBytes(name);
  sw.BaseStream.Write(bytes, 0, bytes.Length);
  sw.Flush();
}

读取文件:

foreach (string result in File.ReadAllLines(fileName))
{
  Console.WriteLine(result);
}
string sResult = File.ReadAllText(fileName);
Byte[] byteContent = File.ReadAllBytes(fileName);
string sResultByte = System.Text.Encoding.UTF8.GetString(byteContent);

读取大文件,文件太大,用工具去打开的时候,计算机直接卡死了:

using (FileStream stream = File.OpenRead(fileName))//分批读取
{
  int length = 5;
  int result = 0;

  do
  {
    byte[] bytes = new byte[length];
    result = stream.Read(bytes, 0, 5);
    Console.WriteLine(Encoding.UTF8.GetString(bytes, 0, result));
    for (int i = 0; i < result; i++)
    {
      Console.WriteLine(bytes[i].ToString());
    }
  }
  while (length == result);
}

文件拷贝,剪切,删除操作:

File.Copy(fileName, fileNameCopy);
File.Move(fileName, fileNameMove);
File.Delete(fileNameCopy);
File.Delete(fileNameMove);//尽量不要delete

硬盘信息DriveInfo

DriveInfo[] drives = DriveInfo.GetDrives();
foreach (DriveInfo drive in drives)
{
  if (drive.IsReady)
    Console.WriteLine("类型:{0} 卷标:{1} 名称:{2} 总空间:{3} 剩余空间:{4}", drive.DriveType, drive.VolumeLabel, drive.Name, drive.TotalSize, drive.TotalFreeSpace);
  else
    Console.WriteLine("类型:{0}  is not ready", drive.DriveType);
}

路径注意事项

Console.WriteLine(Path.GetDirectoryName(LogPath));  //返回目录名,需要注意路径末尾是否有反斜杠对结果是有影响的
Console.WriteLine(Path.GetDirectoryName(@"d:\\abc")); //将返回 d:\
Console.WriteLine(Path.GetDirectoryName(@"d:\\abc\"));// 将返回 d:\abc
Console.WriteLine(Path.GetRandomFileName());//将返回随机的文件名             Console.WriteLine(Path.GetFileNameWithoutExtension("d:\\abc.txt"));// 将返回abc
Console.WriteLine(Path.GetInvalidPathChars());// 将返回禁止在路径中使用的字符
Console.WriteLine(Path.GetInvalidFileNameChars());//将返回禁止在文件名中使用的字符
Console.WriteLine(Path.Combine(LogPath, "log.txt"));//合并两个路径

实例:日志

try catch旨在上端使用,保证对用户的展示
下端不要吞掉异常,隐藏错误是没有意义的,抓住再throw也没意义
除非这个异常对流程没有影响或者你要单独处理这个异常

public static void Log(string msg)
{
  StreamWriter sw = null;
  try
  {
    string fileName = "log.txt";
    string totalPath = Path.Combine(LogPath, fileName);

    if (!Directory.Exists(LogPath))
    {
    Directory.CreateDirectory(LogPath);
    }
    sw = File.AppendText(totalPath); // 如果文件不存在,就新建一个文件,然后写入内容
    sw.WriteLine(string.Format("{0}:{1}", DateTime.Now, msg));
    sw.WriteLine("***************************************************");
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.Message);//log //建议在写日志的时候,把异常吞掉;
    //throw ex;
    //throw new exception("这里异常");
  }
  finally//无论是否发生异常  全都都会执行
  {
    if (sw != null)
    {
      sw.Flush();
      sw.Close();
      sw.Dispose();
    }
  }
}

实例: 找出全部的子文件夹

递归:可以理解为类型,或者说是一种编程方式
获取“D:\Git_Work”路径下的所有文件夹:
1、在递归的时候,计算机是在高强度的计算;
2、一定要有跳出循环的判断,避免死循环;
3、在使用递归的时候,尽量避免多线程;

public static List<DirectoryInfo> GetAllDirectory(string rootPath)
{
  if (!Directory.Exists(rootPath))
  return new List<DirectoryInfo>();

  //一个存储路径信息的容器
  List<DirectoryInfo> directoryList = new List<DirectoryInfo>();//容器

  DirectoryInfo directory = new DirectoryInfo(rootPath);//root文件夹
  directoryList.Add(directory);

  var directioryList = GetChilds(directoryList, directory);
  return directioryList;
}

private static List<DirectoryInfo> GetChilds(List<DirectoryInfo> directoryList, DirectoryInfo directory)
{
  var chaildArray = directory.GetDirectories();
  if (chaildArray != null && chaildArray.Length > 0)
  {
    foreach (var child in chaildArray)
    {
      directoryList.Add(child);
      GetChilds(directoryList, child);
    }
  }
  return directoryList;
}

递归对内存会有压力:

// 计算机的计算能力是超强,会死机
private void Wait()
{
  if (DateTime.Now.Millisecond < 999)
  {
    //启动个多线程??  会疯狂的启动多个子线程
    Wait();
    //Thread.Sleep(5);//最多可能浪费4ms
  }
  else
    return;
}

序列化与反序列化

Serialization
简单一点说,从一个立体的数据变成字符串就叫做序列化,再从字符串变成立体的对象叫做反序列化。
下面介绍几种序列化与反序列化的方式,首先有个DataFactory,构造一些需要序列化的数据源:

[Serializable]  //必须添加序列化特性
public class Person
{
  [NonSerialized]
  public int Id = 1;
  public string Name { get; set; }
  public string Sex { get; set; }
}

[Serializable]  //必须添加序列化特性
public class Programmer : Person
{
  private string Language { get; set; }//编程语言
  public string Description { get; set; }
}

public class DataFactory
{
  public static List<Programmer> BuildProgrammerList()
  {
    List<Programmer> list = new List<Programmer>();
     list.Add(new Programmer()
     {
        Id = 1,
        Description="一班学生",
        Name = "Chaoqiang",
        Sex = "男"
     });
     //Add Programmers
     return list;
  }
}

方式一:二进制序列化器
二进制序列化器 不一定跨平台 体积小:

public static void BinarySerialize()
{
  //使用二进制序列化对象
  string fileName = Path.Combine(Constant.SerializeDataPath, @"BinarySerialize.txt");//文件名称与路径
  using (Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
  {
    //需要一个stream,这里是直接写入文件了
    List<Programmer> pList = DataFactory.BuildProgrammerList();
    BinaryFormatter binFormat = new BinaryFormatter();//创建二进制序列化器
    binFormat.Serialize(fStream, pList);
  }
  using (Stream fStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite))
  {
    //需要一个stream,这里是来源于文件
    BinaryFormatter binFormat = new BinaryFormatter();//创建二进制序列化器
    //使用二进制反序列化对象
    fStream.Position = 0;//重置流位置
    List<Programmer> pList = (List<Programmer>)binFormat.Deserialize(fStream);//反序列化对象
  }
}

方式二:soap序列化器
soap序列化器 标准协议 跨平台的 体积大一些

public static void SoapSerialize()
{
  //使用Soap序列化对象
  string fileName = Path.Combine(Constant.SerializeDataPath, @"SoapSerialize.txt");//文件名称与路径
  using (Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
  {
    List<Programmer> pList = DataFactory.BuildProgrammerList();
    SoapFormatter soapFormat = new SoapFormatter();//创建二进制序列化器
    //soapFormat.Serialize(fStream, list);//SOAP不能序列化泛型对象
    soapFormat.Serialize(fStream, pList.ToArray());
  }
  using (Stream fStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite))
  {
    SoapFormatter soapFormat = new SoapFormatter();//创建二进制序列化器
    //使用二进制反序列化对象
    fStream.Position = 0;//重置流位置

    List<Programmer> pList = ((Programmer[])soapFormat.Deserialize(fStream)).ToList();//反序列化对象
  }
}

两种序列化方式的特点:

  • BinaryFormatter序列化自定义类的对象时,序列化之后的流中带有空字符,以致于无法反序列化,反序列化时总是报错“在分析完成之前就遇到流结尾”(已经调用了stream.Seek(0, SeekOrigin.Begin);)。
  • 改用XmlFormatter序列化之后,可见流中没有空字符,从而解决上述问题,但是要求类必须有无参数构造函数,而且各属性必须既能读又能写,即必须同时定义getter和setter,若只定义getter,则反序列化后的得到的各个属性的值都为null。

方式三:XML序列化器

public static void XmlSerialize()
{
  //使用XML序列化对象
  string fileName = Path.Combine(Constant.SerializeDataPath, @"Student.xml");//文件名称与路径
  using (Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
  {
    List<Programmer> pList = DataFactory.BuildProgrammerList();
    XmlSerializer xmlFormat = new XmlSerializer(typeof(List<Programmer>));//创建XML序列化器,需要指定对象的类型
    xmlFormat.Serialize(fStream, pList);
  }
  using (Stream fStream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite))
  {
    XmlSerializer xmlFormat = new XmlSerializer(typeof(List<Programmer>));//创建XML序列化器,需要指定对象的类型
    //使用XML反序列化对象
    fStream.Position = 0;//重置流位置
    List<Programmer> pList = pList = (List<Programmer>)xmlFormat.Deserialize(fStream);
  }
}

方式四: Json序列化器

public static void Json()
{
  List<Programmer> pList = DataFactory.BuildProgrammerList();
  string result = JsonHelper.ObjectToString<List<Programmer>>(pList);
  List<Programmer> pList1 = JsonHelper.StringToObject<List<Programmer>>(result);
}

具体看一下JsonHelper的实现,既可以借助Newtonsoft.Json这个第三方库,也可以直接使用JavaScriptSerializer这个微软自带的类。

public class JsonHelper
{
  public static string ObjectToString<T>(T obj)
  {
    JavaScriptSerializer jss = new JavaScriptSerializer();
    return jss.Serialize(obj);
  }

  public static T StringToObject<T>(string content)
  {
    JavaScriptSerializer jss = new JavaScriptSerializer();
    return jss.Deserialize<T>(content);
  }

  public static string ToJson<T>(T obj)
  {
    return JsonConvert.SerializeObject(obj);
  }

  public static T ToObject<T>(string content)
  {
    return JsonConvert.DeserializeObject<T>(content);
  }
}

画验证码

绘图的原理很简单:Bitmap就像一张画布,Graphics如同画图的手,把Pen或Brush等绘图对象画在Bitmap这张画布上。
首先读取图片存储的配置路径:

private static string ImagePath = ConfigurationManager.AppSettings["ImagePath"];
private static string VerifyPath = ConfigurationManager.AppSettings["ImagePath"];

画验证码和图片

public static void Drawing()
{
  Bitmap bitmapobj = new Bitmap(100, 100);
  //在Bitmap上创建一个新的Graphics对象
  Graphics g = Graphics.FromImage(bitmapobj);
  //创建绘画对象,如Pen,Brush等
  Pen redPen = new Pen(Color.Red, 8);
  g.Clear(Color.White);
  //绘制图形
  g.DrawLine(redPen, 50, 20, 500, 20);
  g.DrawEllipse(Pens.Black, new Rectangle(0, 0, 200, 100));//画椭圆
  g.DrawArc(Pens.Black, new Rectangle(0, 0, 100, 100), 60, 180);//画弧线
  g.DrawLine(Pens.Black, 10, 10, 100, 100);//画直线
  g.DrawRectangle(Pens.Black, new Rectangle(0, 0, 100, 200));//画矩形
  g.DrawString("我爱北京天安门", new Font("微软雅黑", 12), new SolidBrush(Color.Red), new PointF(10, 10));//画字符串
  //g.DrawImage(
  if (!Directory.Exists(ImagePath))
    Directory.CreateDirectory(ImagePath);
  bitmapobj.Save(ImagePath + "pic1.jpg", ImageFormat.Jpeg);
  //释放所有对象
  bitmapobj.Dispose();
  g.Dispose();
}
public static void VerificationCode()
{
  Bitmap bitmapobj = new Bitmap(300, 300);
  //在Bitmap上创建一个新的Graphics对象
  Graphics g = Graphics.FromImage(bitmapobj);
  g.DrawRectangle(Pens.Black, new Rectangle(0, 0, 150, 50));//画矩形
  g.FillRectangle(Brushes.White, new Rectangle(1, 1, 149, 49));
  g.DrawArc(Pens.Blue, new Rectangle(10, 10, 140, 10), 150, 90);//干扰线
  string[] arrStr = new string[] { "我", "们", "孝", "行", "白", "到", "国", "中", "来", "真" };
  Random r = new Random();
  int i;
  for (int j = 0; j < 4; j++)
  {
    i = r.Next(10);
    g.DrawString(arrStr[i], new Font("微软雅黑", 15), Brushes.Red, new PointF(j * 30, 10));
  }
  bitmapobj.Save(Path.Combine(VerifyPath, "Verif.jpg"), ImageFormat.Jpeg);
  bitmapobj.Dispose();
  g.Dispose();
}

按比例缩放图片

按比例缩放,图片不会变形,会优先满足原图和最大长宽比例最高的一项。

public static void CompressPercent(string oldPath, string newPath, int maxWidth, int maxHeight)
{
  Image _sourceImg = Image.FromFile(oldPath);
  double _newW = (double)maxWidth;
  double _newH = (double)maxHeight;
  double percentWidth = (double)_sourceImg.Width > maxWidth ? (double)maxWidth : (double)_sourceImg.Width;

  if ((double)_sourceImg.Height * (double)percentWidth / (double)_sourceImg.Width > (double)maxHeight)
  {
    _newH = (double)maxHeight;
    _newW = (double)maxHeight / (double)_sourceImg.Height * (double)_sourceImg.Width;
  }
  else
  {
    _newW = percentWidth;
    _newH = (percentWidth / (double)_sourceImg.Width) * (double)_sourceImg.Height;
  }
  Image bitmap = new Bitmap((int)_newW, (int)_newH);
  Graphics g = Graphics.FromImage(bitmap);
  g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
  g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
  g.Clear(Color.Transparent);
  g.DrawImage(_sourceImg, new Rectangle(0, 0, (int)_newW, (int)_newH), new Rectangle(0, 0, _sourceImg.Width, _sourceImg.Height), GraphicsUnit.Pixel);
  _sourceImg.Dispose();
  g.Dispose();
  bitmap.Save(newPath, System.Drawing.Imaging.ImageFormat.Jpeg);
  bitmap.Dispose();
}

按照指定大小对图片进行缩放,可能会图片变形。

public static void ImageChangeBySize(string oldPath, string newPath, int newWidth, int newHeight)
{
  Image sourceImg = Image.FromFile(oldPath);
  System.Drawing.Image bitmap = new System.Drawing.Bitmap(newWidth, newHeight);
  Graphics g = Graphics.FromImage(bitmap);
  g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
  g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
  g.Clear(Color.Transparent);
  g.DrawImage(sourceImg, new Rectangle(0, 0, newWidth, newHeight), new Rectangle(0, 0, sourceImg.Width, sourceImg.Height), GraphicsUnit.Pixel);
  sourceImg.Dispose();
  g.Dispose();
  bitmap.Save(newPath, System.Drawing.Imaging.ImageFormat.Jpeg);
  bitmap.Dispose();
}

文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
C# Advanced Tutorial 1-11-Async C# Advanced Tutorial 1-11-Async
主要内容概要1 进程-线程-多线程,同步和异步2 委托启动异步调用3 多线程特点:不卡主线程、速度快、无序性4 异步的回调和状态参数5 异步等待三种方式6 异步返回值 一些概念 进程:计算机概念,程序运行在服务器占据的全部计算机的资源。 线
下一篇 
C# Advanced Tutorial 1-9-Expression C# Advanced Tutorial 1-9-Expression
主要内容概要1 什么是表达式目录树Expression2 动态拼装Expression3 基于Expression扩展应用4 ExpressionVisitor解析表达式目录树5 解析Expression生成Sql6 Expression扩
  目录