(12)C#传智:File类,泛型,字典,FileStream,StreamReader,多态
Posted dzweather
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(12)C#传智:File类,泛型,字典,FileStream,StreamReader,多态相关的知识,希望对你有一定的参考价值。
内容有点多,重点:泛型、字典,流与多态。
继续深入学习内容:List、Dictionary、using语句、FileStream
一、File类的继续学心
File.ReadAllLines(string path,Encoding,encoding)指定编码读取返回行字串数组
File.WriteAllText(string path,Encoding encoding)
File.AppendAllText(string path,string contents) 追加文本
File.ReadAllTexT(string path,Encoding encoding) 指定编码返回文本内所有内容
File.WriteAllLines(string path,string[] contents) 将字串数组按行写入
File.AppendAllLines(string path,string[] contents) 追加多行
string p = @"E:\\1.txt";
string[] s = File.ReadAllLines(p, Encoding.UTF8);
foreach (string item in s)
Console.WriteLine(item);
string s1 = File.ReadAllText(p, Encoding.UTF8);
Console.WriteLine(s1);
注意:1)若操作文本文件用ReadAllText或ReadAllLines;
若操作音频等二进制文件,用ReadAllbytes。
2)若文本文件需精准定到行字串用ReadAllLines,反之用ReadAllText.
由于File类基本上是一次性读出或写入文件,易造成内存溢出(内部byte[]大小
固定了),所以它适用于读写小的文件。大的文件应使用FileStream。
二、绝对路径与相对路径
绝对路径:通过给定的路径,直接便可在电脑中找到该文件。
相对路径:文件相对于应用程序或目录的路径。
程序中不带路径时,直接用文件名时,用的是相对路径,即本程序当前目录。
string str=File.ReadAllText(@"1.txt");//虽未指定,但与程序本身同目录
相对路径更具优势,应尽量用相对路径。
三、List泛型集合
由于ArrayList加入元素类型任意,在处理时非常不方便。
泛型就是利用ArrayList的增加元素方便,容量自动扩展优点,但同时规定了
加入元素的类型,以方便输出时的处理。
泛型:Generic通用的,广泛的,一般的.即适用一切,专业上需指定一个特殊
的类型。所以定义后面用<T>来指明这个广泛类型中的一个特殊的类型(Type):
List<T> T-Type类型 使用时需导入 using System.Collections.Generic;
创建(构造): List<int> list=new List<int>();//将List<int>整体当作类型
方法:List.Add(T item) 单个元素,声明时限定了T
List.Add(IEnumerable<T> collection) 添加集合
基本上学会了ArrayList,就可以在List同样使用。
List<int> lst = new List<int>() 1, 2 ;
lst.Add(4); lst.AddRange(new int[] 5, 6 );
lst.AddRange(lst);//1,2,4,5,6,1,2,4,5,6
Console.WriteLine(string.Join(",", lst.ToArray()));
数组<-->List 可以相互转换。注意,类型须一致.
ToArray()可以将List转换成一个数组,返回的类型与泛型规定的类型一致.
ToList() 将数组转为List.
int[] n = 1, 2, 3, 4, 5, 6, 7, 8 ;
List<int> lst = n.ToList();
Console.WriteLine(lst[2]);//3
int[] m = lst.ToArray();
Console.WriteLine(m[7]);//8
为什么ArrayList与HashTable用得比较少,基本不用呢?
四、装箱与拆箱
装箱:把值类型转换成引用类型。
拆箱:将引用类型转换成值类型
装箱与拆箱不停进行类型转换,所以影响速度,应尽量避免装箱或拆箱操作。
注意:是否装箱或拆箱,两者没有继承关系,可能没有发生装箱或拆箱;
若两者没有继承判断,那一定没有装箱或拆箱。
以I开头的变量,是接口类型。接口类型也是引用类型
int n = 0;
object o = (object)n;//装箱
int n1 = (int)o;//拆箱
IComparable ic = n;//装箱
string s = "123";
int n2 = Convert.ToInt32(s);//没有拆箱,无继承
//--------------------------------
ArrayList alst = new ArrayList();
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
alst.Add(i);
sw.Stop();
Console.WriteLine(sw.Elapsed);//00:00:00.0942953
List<int> lst = new List<int>();
sw.Restart();
for (int i = 0; i < 1000000; i++)
lst.Add(i);
sw.Stop();
Console.WriteLine(sw.Elapsed);//00:00:00.0234275
int[] n3 = new int[1000000];
sw.Restart();
for (int i = 0; i < 1000000; i++)
n3[i] = i;
sw.Stop();
Console.WriteLine(sw.Elapsed);//00:00:00.0152354
五、字典Dictionary(对应前面Hashtable)
类似List,固定了Hashtable中key与value的特殊类型.
声明:Dictionary<Tkey,Tvalue>
在循环取值时,此时可以用键值对: KeyValuePair<Tkey,Tvalue>
分别用点号取得对应的key与value,如:kv.key,kv.value
Dictionary<int, string> dic = new Dictionary<int, string>();
dic.Add(1, "张三");
dic.Add(2, "李四");
dic[3] = "王五";
//dic.Add(1,"郑六";//错误key重复。用上句或ContainsKey判断
foreach (string item in dic.Values)
Console.WriteLine(item);
foreach (KeyValuePair<int, string> kv in dic)
Console.WriteLine("0:1", kv.Key, kv.Value);
实例1:将一个数组中的奇数放到一个集合中,再将偶数放到另一个集合中;
最终将两集合合并为一个集合,并且奇数显示在左边偶数显示在右边。
int[] n = 1, 2, 3, 4, 5, 22, 23, 45, 23 ;
List<int> lst1 = new List<int>();//奇
List<int> lst2 = new List<int>();//偶
foreach (int item in n)
if (item % 2 == 0) lst2.Add(item);
else lst1.Add(item);
lst1.AddRange(lst2);
foreach (int item in lst1)
Console.Write(item + ","); //1,3,5,23,45,23,2,4,22,
实例2:提示用户输入一个字符串,通过foreach循环将用户输入的字符串赋值
给一个字符数组
Console.WriteLine("请输入一字符串:");
string s = Console.ReadLine();
while (s == null)
Console.WriteLine("字串不能为空,请重新输入");
s = Console.ReadLine();
char[] c = new char[s.Length];
int i = 0;
foreach (char item in s)
c[i] = item;
i++;
Console.WriteLine(c);//取首字符,一样显示全字串
实例3:统计Welcome to china中每个字符出现的次数,不考虑大小写。
string s = "Welcome to china";
Dictionary<char, int> dic = new Dictionary<char, int>();
char c1;
foreach (char c in s)
if (c == ' ') continue;
c1 = char.ToUpper(c);
if (dic.ContainsKey(c1)) dic[char.ToUpper(c)] += 1;
else dic[c1] = 1;
foreach (KeyValuePair<char, int> kv in dic)
Console.WriteLine("字母0:1", kv.Key, kv.Value);
ArraryList与List<T>的区别:
最主要的:泛型效率较高,操作不是装箱拆箱,也不涉及Object类型转换。
ArrayList是Object类型,面对不同的类型,涉及到装箱与拆箱,效率低。
System.Collections和System.Collections.Generic提供了很多列表、集合和数组。
例如:List<T>、数组(int[],string[]...)、Dictionary<T,T>等,但这些列表、集合
和数组都不是安全的,不能接受并发请求。
1)数组。
优点:连续存储,索引速度快,赋值修改简单方便;
缺点:a.定义指定长度易浪费内存,或索引超限。
b.插入、删除元素效率低且麻烦。
不清楚数组长度时很尴尬。故C#提供ArrayList来解决此类问题。
2)ArrayList
优点:长度动态增加;实现Ilist接口,方便添加、插入、删除等。
缺点:元素是object,处理操作可能出现类型不匹配,是非类型安全对象;
处理对象时,要进行类型转换,涉及装箱拆箱,损耗性能。
为了提高ArrayList的效率,C#又引出了泛型。
3)List<T>
具有ArrayList的大部分方法和优点。
同时:List是强类型,编译器会验证类型安全,避免了类型的不安全,
以及数组强制转换导致装箱拆箱损耗性能。
六、FileStream文件流
将水缸A的满水,倒入到水缸B的空缸中,有两种方法:
1)扛起整个水缸A,然后象霸王一样倒入到水缸B:痛快干脆,但得力气大。
2)牵一水管由水缸A导流到水缸B中: 温柔便巧,不需要费大力。
File类读写文件,犹如方法1,全部搬运倾泄,耗费内存。只适合小文件操作。
FileStream类读写文件,犹如方法2,小量多次便捷高效,适宜大小文件操作。
下面都适合操作大文件(几百k的文件直接用File类)
1)FileStream :操作字节,适合任意类型的文件(因为都是字节).
2)StreamReader和StreamWriter:操作字符,所以适合文本文件。
所谓大牛,会的类肯定多,熟悉掌握多种类,也就是大牛了。
FileStream类 : using System.IO;
1、用FileStream读取文件.
//初始化对象.指明路径、打开方式,访问权限
FileStream fsread = new FileStream(@"E:\\1.txt", FileMode.OpenOrCreate, FileAccess.Read);
//创建缓冲字节数组,用来指读取。指定最大缓冲5M
byte[] buffer = new byte[1024 * 1024 * 5];
//read指定开始位置(0),及长度(b.length),内容返回到缓冲数组.
//尽管指定是5M,但可能实际读取的只有2M,则下句的n就是2M.
int n = fsread.Read(buffer, 0, buffer.Length);//n是实际返回的字节数
//解码:按编码格式进行解码。需指明缓冲数组的起始及长度,否则解码所有含空的数组
string s = Encoding.UTF8.GetString(buffer, 0, n);
Console.WriteLine(s);
//关闭文件流
fsread.Close();
//释放流所占用的资源
fsread.Dispose();
2、用FileStream写入文件
1)using(声明变量)
执行语句;
.NET中,托管的资源都由.NET的垃圾回收机制GC来释放;而一些非托管的资源,则需要
手动进行释放。.NET提供了主动和被动两种释放非托管资源的方式:
第一种:IDisposable接口的Dispose方法;
第二种:类型自己的Finalize方法。
任何带有非托管资源的类型,都有必要实现IDisposable的Dispose方法,并且在使用完
这些类型后,需要手动地调用对象的Dispose方法来释放对象中的非托管资源。
若类型正确地实现了Finalize方法,那么即使不调用Dispose方法,非托管资源也最终会
被释放,但到那时资源已经被很长时间无畏地占据了。
using语句的作用就是提供了一个高效的调用对象Dispose方法的方式。
对于任何IDisposable接口的类型,都可以使用using语句;
而对于那些没有实现IDisposable接口的类型,使用using语句会导致一个编译错误。
因此,应在using外面套用try-catch。
简单地说,using能有效地释放资源。
2)使用流写入文件
using (FileStream fsw = new FileStream(@"E:\\1.txt", FileMode.OpenOrCreate, FileAccess.Write))
string s = "遥远的救世主,浮生六记";
byte[] buffer = Encoding.Default.GetBytes(s);
fsw.Write(buffer, 0, buffer.Length);//仅覆盖数组数组长度的位置,其它可能乱码.
//无须手动释放,using语句自动释放
注:出现乱码,是读取与写入的编码不一致,应该注意统一。
3、使用文件流实现多媒体文件的复制。
将一个大文件复制到另一个地方
string scr = @"E:\\八段锦.mp4";
string des = @"E:\\New.mp4";
using (FileStream fsr = new FileStream(scr, FileMode.OpenOrCreate, FileAccess.Read))
using (FileStream fsw = new FileStream(des, FileMode.OpenOrCreate, FileAccess.Write))
byte[] buffer = new byte[1024 * 1024 * 5];//缓冲5M
while (true)
int n = fsr.Read(buffer, 0, buffer.Length);//预读5M
if (n == 0) break;//读取返回0字节,说明结束
fsw.Write(buffer, 0, n);//应写入实际n长度
注意:.Net使用流读写文件时,可以将流视为一组连续的一维数据,包含开头和结尾。
其中的游标指示了流的当前位置,所以读写都自动从游标处进行继续操作。
七、StreamReader与StreamWriter
StreamReader与StreamWriter是操作字符的,与FileStream操作字节是有区别的。
1、StreamReader
StreamReader sr = new StreamReader(@"E:\\1.txt", Encoding.Default);
while (!sr.EndOfStream) Console.WriteLine(sr.ReadLine());
sr.Close();//也可用using来省略这两句
sr.Dispose();
2、SteamWriter
//编码方式只能在前面,这样方便后面不再进入编码方式设置
StreamWriter sw = new StreamWriter(@"E:\\1.txt", true, Encoding.Default);//
sw.WriteLine("我是一行.");//后加行结束写入字串,即行写入
sw.Write("只是字串写入");
sw.Close(); sw.Dispose();
八、多态
定义:让一个对象能够表现出多种的状态(类型)
相同的消息给予不同的对象会引发不同的动作。
实现多态的三种手段:1、虚方法;2抽象类;3、接口。
1、问题抛出:
有一父类Person,下面三子类China,Japan,Korea.
public class Person
private string _name;
public string Name
get return _name; set _name = value;
public Person(string name)
this._name = name;
public void SayHello()
Console.WriteLine("我是人类");
public class China : Person
public China(string name) : base(name)
public void SayHello()
Console.WriteLine("中国人:0", this.Name);
public class Japan : Person
public Japan(string name) : base(name)
public void SayHello()
Console.WriteLine("日本人:0", this.Name);
public class Korea : Person
public Korea(string name) : base(name)
public void SayHello()
Console.WriteLine("韩国人:0", this.Name);
使用父类数组调用子类同名方法,只能通过判断。
China c1 = new China("张三"), c2 = new China("李四");
Japan j1 = new Japan("井边"), j2 = new Japan("村下");
Korea k1 = new Korea("思密达"), k2 = new Korea("朴真");
Person[] p = new Person[6] c1, c2, j1, j2, k1, k2 ;
for (int i = 0; i < p.Length; i++)
p[i].SayHello(); //显示六个“我是人类",无法显示对应国家.
for (int i = 0; i < p.Length; i++)
if (p[i] is China) ((China)p[i]).SayHello();
else if (p[i] is Japan) ((Japan)p[i]).SayHello();
else ((Korea)p[i]).SayHello();
//必须分别判断类型,才能显示国家
这样调用同名方法显得臃肿。下面用虚方法
2、虚方法
在父类同名方法前面加Virtual,在子类同名方法前面加Override(重写)
执行顺序,用父类方法调用同名方法时,将根据内装类型去具体调用对应方法。
调用的方法取决于父类里真实装的是什么对象。内部装的是父类,则调用的是父类。
内部装的子类,则调用的是子类。
虚方法,避免的类别的判断,让一个类型表现出多种类型的状态(多态),由此写出
通用代码(精减代码,减少冗余),最大取消它们之间的差异性(屏蔽子类间的差异性)。
后面若需加入新子类, 只须重写一次同名方法即可。
实现步骤:
1)在父类同名方法前加入Virtual
public class Person
private string _name;
public string Name
get return _name; set _name = value;
public Person(string name)
this._name = name;
public virtual void SayHello()
Console.WriteLine("我是人类");
2)在子类同名方法前加入Override,同时再加一个英国人类
public class China : Person
public China(string name) : base(name)
public override void SayHello()
Console.WriteLine("中国人:0", this.Name);
public class Japan : Person
public Japan(string name) : base(name)
public override void SayHello()
Console.WriteLine("日本人:0", this.Name);
public class Korea : Person
public Korea(string name) : base(name)
public override void SayHello()
Console.WriteLine("韩国人:0", this.Name);
public class English : Person
public English(string name) : base(name)
public override void SayHello()
Console.WriteLine("英国人:0", this.Name);
3)调用方法,用父类方法调用,将根据实际装的对象,调用对应方法
China c1 = new China("张三"), c2 = new China("李四");
Japan j1 = new Japan("井边"), j2 = new Japan("村下");
Korea k1 = new Korea("思密达"), k2 = new Korea("朴真");
English e1 = new English("汤姆"), e2 = new English("迈克");
Person[] p = new Person[8] c1, c2, j1, j2, k1, k2, e1, e2 ;
for (int i = 0; i < p.Length; i++)
p[i].SayHello(); //不再显示“我是人类"。而显示对应国家.
//由此,通过父类调用一种方法,显示不同状态
练习题1:真的鸭子嘎嘎叫 木头鸭子吱叫 橡皮鸭子唧唧叫
先定义虚方法
public class ZSDuck
public virtual void Quark()
Console.WriteLine("真实鸭子嘎嘎叫");
public class MTDuck : ZSDuck
public override void Quark()
Console.WriteLine("木头鸭子吱吱叫");
public class XPDuck : ZSDuck
public override void Quark()
Console.WriteLine("橡皮鸭子唧唧叫");
在主函数中调用:
ZSDuck zS = new ZSDuck();
MTDuck mT = new MTDuck();
XPDuck xP = new XPDuck();
ZSDuck[] z = zS, mT, xP ;
for (int i = 0; i < z.Length; i++)
z[i].Quark();
练习题2:经理十一点打卡 员工9点打卡 程序不打卡
添加一父类两子类:
public class Employee
public virtual void ClockIn()
Console.WriteLine("员工9点打卡");
public class Manager : Employee
public override void ClockIn()
Console.WriteLine("经理11点打卡");
public class Programmer : Employee
public override void ClockIn()
Console.WriteLine("程序员不打卡");
在主程序中调用:
Employee em = new Employee();
Manager mg = new Manager();
Programmer pg = new Programmer();
Employee[] ems = em, mg, pg ;
foreach (Employee item in ems)
item.ClockIn();
3、抽象类
上面很容易找到父类(添加virtual),定位子类同名重写(override)
若题为"狗狗会叫,猫咪会叫",dog与cat都不宜作父类,
应从两种中抽象出一种父类,但叫的动作却不知道具体实现,
它只有在针对到具体的子类时才能具体的表现出来。
定义:当父类中的方法不知道如何实现时,可以将父类写成抽象类,其方法写成抽象方法.
因此上面可以写一个“动物”的抽象类,里面包含一个“叫”的抽象方法。
语法:1)抽象类的前面用abstract限定,其内的抽象方法用abstract限定
2)抽象方法不允许有方法体方法,声明仅以分号结尾,且签名后没有大括号。
注意:没有花括号(无方法体)与有花括号但无语句(空实现),两者是有区别的。
3)子类同名方法前加override。
4)抽象方法是隐式的虚拟方法。在抽象方法中使用static或virtual是错误的。
重要:抽象类与接口是不允许创建对象的。
抽象类的特点:
1)抽象成员必须标记为abstract,并且不能有任何买现。
2)抽象成员必须在抽象类中;
3)抽象类不能被例化(虽然有构造函数);
4)子类继承抽象类后,必须把父类的所有抽象成员都复写。(除非子类也为抽象类)
5)抽象成员的访问修饰符不能是private,因为要通过这个桥梁访问各重写的子类。
6)在抽象类中可以包含实例成员。并且抽象类的实例成员可以不被子类实现。
7)抽象类是有构造函数的,虽然不能被实例化。
8)如果父类的抽象方法中有参数,那么,继承这个抽象父类的子类在重写父类方法时候,
必须传入对应的参数。
如果抽象父类的抽象方法中的返回值,那么子类在重写这个抽象方法的时候,也必须
要传入返回值。
一句话:抽象父类的抽象方法的签名必须与子类重写的方法签名一致。
如果父类中的方法有默认的实现,并且父类需要被实例化,这时可以考虑将父类定义成
普通类,可以用虚方法来实现。
如果父类中的方法没有默认实现,父类也不需要被实例化,则可以将该类定义为抽象类。
抽象类可以包括抽象方法,这是普通类所不能的。
抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。
普通类是具体实现抽象方法的类,是有构造函数的,能实例化,不能有抽象方法。
抽象类父类Animal中有抽象方法Bark(无方法体)
public abstract class Animal
public abstract void Bark();//必须末尾有分号
两子类具体实现,重写抽象方法
public class Cat : Animal
public override void Bark()
Console.WriteLine("小猫咪咪叫");
public class Dog : Animal
public override void Bark()
Console.WriteLine("小狗旺旺叫");
主方法中实现
//Animal a = new aninal();//错误,抽象类无构造函数不能自己初始化
Animal a = new Cat();//但可以用子类来赋值
a.Bark();//虽然调用的是父类bark,但仍取决内装的对象(cat)
Animal a1 = new Dog();
a1.Bark();
虚方法与抽象类的区别:
虚方法自身有实现,自身方法可用;所以可以被子类覆盖,也可以不覆盖。
抽象类自身无实现,自身方法不可用;所以必须一定被子类覆盖,与接口类似。
抽象类中可以拥有虚方法。因为抽象类可以包含实例成员,虚方法也是实例成员一种。
一般使用较多的是抽象类。
证明抽象类的存在构造函数
internal class Program
private static void Main(string[] args)
Student s = new Student();//此处下断点,可看到s构造前进入抽象类中构造
Console.WriteLine(s.ID);
Console.ReadKey();
public abstract class Person
private Guid _id;
public Person()//构造函数
this._id = Guid.NewGuid();
public Guid ID
get return this._id;
public class Student : Person
public Student()
4、多态的练习题
技巧:a.要实现抽象父类方法的子类时,写完“:子类名”时,在vs2022中按Alt+Shift+F10,
选择实现抽象类,可以快速实现抽象方法的重写。
b.对于类中的属性,当输入完毕属性名,如:public double Width时,按下
Ctrl+R,再按Ctr+E,可以快速形成属性代码。
1)使用多态求矩形的面积和周长,以及圆形的面积和周长。
方法相同,形状不同,构造抽象类形状。至于参数,各找各妈自己构造。
public abstract class Shape
public abstract double GetArea();
public abstract double GetPerimeter();
public class Circle : Shape
private double _r;
public double R get => _r; set => _r = value;
public Circle(double r)
this.R = r;
public override double GetArea()
return Math.PI * this.R * this.R;
public override double GetPerimeter()
return 2 * Math.PI * this.R;
public class Rectangle : Shape
private double _width;
private double _height;
public double Width get => _width; set => _width = value;
public double Height get => _height; set => _height = value;
public Rectangle(double width, double height)
this.Width = width;
this.Height = height;
public override double GetArea()
return this.Width * this.Height;
public override double GetPerimeter()
return 2 * (this.Width + this.Height);
主程序中:
Shape sc = new Circle(3);
Shape sr = new Rectangle(3, 5);
Console.WriteLine("圆0,1", sc.GetPerimeter(), sc.GetArea());
Console.WriteLine("矩形0,1", sr.GetPerimeter(), sc.GetArea());
2)用多态实现:将移动硬盘,或者U盘,或者Mp3,插到电脑上进行读写数据。
移动介质抽象类,子类三个。电脑读取类一个
public abstract class Shape
public abstract double GetArea();
public abstract double GetPerimeter();
public abstract class MobileStorage
public abstract void Read();
public abstract void Write();
public class MobileDisk : MobileStorage
public override void Read()
Console.WriteLine("移动硬盘在读取...");
public override void Write()
Console.WriteLine("移动硬盘在写入...");
public class Mp3 : MobileStorage
public override void Read()
Console.WriteLine("Mp3在读取...");
public override void Write()
Console.WriteLine("Mp3在写入...");
public void Play()
Console.WriteLine("MP3在播放音乐");
public class Udisk : MobileStorage
public override void Read()
Console.WriteLine("U盘在读取...");
public override void Write()
Console.WriteLine("U盘在写入...");
public class Computer
public void CpuRead(MobileStorage ms)
ms.Read();
public void CpuWrite(MobileStorage ms)
ms.Write();
主程序中实现:
MobileDisk md = new MobileDisk();
Udisk ud = new Udisk();
Mp3 mp = new Mp3();
Computer cp = new Computer();
cp.CpuRead(md); cp.CpuRead(ud); cp.CpuRead(mp);
cp.CpuWrite(md); cp.CpuWrite(ud); cp.CpuWrite(mp);
MobileStorage ms1 = new Mp3();
Computer cpu = new Computer();
cpu.CpuRead(ms1);
cpu.CpuWrite(ms1);
以上是关于(12)C#传智:File类,泛型,字典,FileStream,StreamReader,多态的主要内容,如果未能解决你的问题,请参考以下文章