l GC
l 非泛型集合和泛型集合
l 模拟foreach迭代器和自定义排序
l IO操作(目录、文件类,路径类,流类)
l 序列化和反序列化(拷贝)
1.关于GC
GC是.net FrameWork框架内置的垃圾回收器,它由CLR管理,可以对托管资源中没有变量指向的对象等进行回收,释放内存资源,具体的回收时间并不确定,手动回收资源可用GC的Collect方法。相对应的非托管资源包括流操作类等创建的对象在失去引用后需要人为的释放资源,可以调用对象的Dispose方法或者用关键字using管理对象占用资源。
弱引用:对于创建较复杂的资源,可以为其添加弱引用,方便在其没被回收前的再次引用。
例如:Person p=new Person (){Name="王哲"};
WeakReference wr=new WeakReference (p);
p = null;//取消对象引用
object obj = wr.Target; //从弱引用中获取对象
if (obj != null)
{
Person p1=obj as Person;
Console.WriteLine(p1.Name);
}
Console.ReadKey();
2.集合类
与数组想比,集合类具有长度不固定,类型不限定的特点。集合的使用,让我们对数据的处理方式变得更灵活,而根据使用约束等,又将集合分为非泛型集合和泛型集合。
1.非泛型集合:
非泛型集合使用之前需要引用命名空间System.Collections。下面是常用的两种集合。
ArrayList 集合
可以将任何类型的数据存入到集合中,默认情况下数据装入集合会被转换为object类型,因此在使用集合内数据时需要做类型转换。
常用属性和方法:
Capacity 获取集合的容量
Count 获取集合中的元素个数
Add() 向集合中添加元素 AddRange() 向集合中添加数组
Remove()删除指定元素 Removeat() 删除指定索引的元素
Clear() 清除集合元素 Contains() 确定是否包含指定对象,返回值为bool类型 ToArray() 将集合转换成object类型的数组
Sort() 对集合进行排序 Reverse() 反转集合
Hashtable 键值对集合
可以将任意类型的数据以键值对应的形式存储,之后可以通过键来查找对应的值,要求添加的键的哈希码不能重复,另外可以重写 GetHashCode()方法来指定自定义类型键的哈希码计算方式。
Hashtable hs = new Hashtable();
hs[new Person() { Name = "白洋" }] = 1;
hs[new Person() { Name = "白洋" }] = 2;
foreach (DictionaryEntry item in hs)
{
Console.WriteLine("{0}-{1}",item.Key,item.Value);
}
Console.ReadKey();
}
}
class Person
{
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
public override int GetHashCode()
{
return this.Name.GetHashCode();
}
public override bool Equals(object obj)
{
Person p = obj as Person;
return this.Name.Equals(p.Name);
}
}
如上,如果不希望加入相同姓名的对象,我们需要重写键的比较方法(ToString()和GetHashCode()方法);
Hashtable 类型对象有集合普遍的属性count等和方法Add()等,特有的方法为ContainsKey()和ContainsValue().在不使用方法的情况下,可以直接用”对象[键]= ”向集合中添加或修改元素,Remove()方法参数为键,调用后会移除对应的键值对。
可以通过指定键值集合类型对其遍历:
Hashtable hs = new Hashtable();
//调用方法添加三对
hs.Add(1, "宾宾");
hs.Add(2, "王勃");
hs.Add(3, "李杨");
//对第一对进行修改
hs[1] = "刘忻";
//直接添加第四对
hs[4] = "王哲";
//移除第二对
hs.Remove(2);
//DictionaryEntry 为键值集合
foreach (DictionaryEntry item in hs)
{
Console.WriteLine("{0}-{1}", item.Key, item.Value);
}
Console.ReadKey();
也可以通过遍历键或值的集合或者让键或值的集合实现IEnumerator 接口来单独遍历键或值:
Hashtable hs = new Hashtable();
hs.Add(1, "宾宾");
hs.Add(2, "王勃");
hs.Add(3, "李杨");
//更改为值集合可以遍历hs集合中存储的值
foreach (var item in hs.Keys)
{
Console.WriteLine(item);
}
Console.ReadKey();
Hashtable hs = new Hashtable();
hs.Add(1, "宾宾");
hs.Add(2, "王勃");
hs.Add(3, "李杨");
//更改为值集合可以遍历hs集合中存储的值
IEnumerator ie= hs.Keys.GetEnumerator();
while (ie.MoveNext())
{
Console.WriteLine(ie.Current);
}
Console.ReadKey();
2. 泛型集合
泛型集合允许用户自定义类型,指定了装入集合的元素类型,相比非泛型集合,减少了存入或者使用过程中类型转换操作,处理数据更加灵活。使用之前,需要先引入命名空间System.Collections.Generic。下面是常用的两种泛型集合,对比可以发现,他们是非泛型集合ArrayList 与Hashtable的升级。
List<T> 集合
T表示数据类型,可以是.net Framework 内置类型,也可以是用户自定义类型,有了类型限定,存入集合的元素类型虽然受到限制,却极大的提高了其使用灵活性。
list类型的对象常用的方法和属性,与非泛型集合ArryList类型对象的方法属性相同,除此之外,还拓展了RemoveRange() 移除部分元素,Average() 计算集合元素平均值insert()插入元素等方法和属性。
Dictionary<Tkey,Tvalue>集合
与Hastable类似,也是键值对应添加,只是在声明之初,已经指定了键和值的类型,保护了类型安全,去除了类型转换操作。
键值对的添加方式与Hashtable相似,只是存储键值对的项与其不同,为KeyValuePair<Tkey,Tstring>,键集合或者值集合遍历方式与hashtable类似。另外,对于自定义类型,也需要重写GetHashCode()方法来指定自定义类型键的哈希码计算方式。
Dictionary<int, string> dic = new Dictionary<int, string>();
dic[1] = "王哲";
dic[2] = "刘忻";
dic[3] = "孟非";
foreach (KeyValuePair<int,string> item in dic)
{
Console.WriteLine("{0}-{1}",item.Key,item.Value);
}
Console.ReadKey();
泛型集合与非泛型集合相比,拥有了更多的数据处理方法,消除了类型转换带来的资源消耗,限制了类型使用,保护了类型安全,因此,在需要使用集合来处理数据时,优先选择泛型集合而不是非泛型集合。
3.foreach及自定义排序的实现
Foreach 可以用于对集合数组等包含多个元素的数列进行遍历查询,要求这些数列的声明类型,实现了IEnumerable接口,
内置数据类型的数组,都继承自Array类,该类实现了IEnumerable接口。Foreach遍历查询是只读的,不能对元素进行修改。可以认为Foreach 语句封装了一个迭代器,通过执行不同的方法完成对指定数列的遍历。如下是对foreach迭代器的模拟。
int[] nums = { 2, 4, 56, 6, 67 };
IEnumerator ie = nums.GetEnumerator();
while (ie.MoveNext())
{
//不能直接调用nums.GetEnumerator()的current方法,因为不能在未调用枚举之前获取当前枚举
Console.WriteLine(ie.Current);
}
Console.ReadKey();
因为foreach遍历查询的数列要求实现IEnumeratable 接口,实现
GetEnumerator()方法。如上实例模拟了其内部执行过程,通过调用
IEnumerator 类型对象的MoveNext()和Current方法,完成遍历数列的目的,另外该对象的reset方法可以对查询指针重置。
自定义比较排序比较的实现
第一种,实现IComparable接口
List<Person> list = new List<Person>();
list.Add(new Person() { Name = "王勃", Age = 21 });
list.Add(new Person() { Name = "芦路", Age = 24 });
list.Add(new Person() { Name = "赵括", Age = 17 });
list.Add(new Person() { Name = "刘楠", Age = 17 });
list.Sort();
foreach (var item in list)
{
Console.WriteLine(item.Name+"-"+item.Age);
}
Console.ReadKey();
}
}
/// <summary>
/// 实现IComparable接口,自定义比较规则,根据对象年龄进行排列
/// </summary>
/// <param name="obj"></param>
/// <returns>0,相同;1当前在后;-1,当前在前</returns>
class Person:IComparable
{
public int Age { get; set; }
public string Name { get; set; }
public int CompareTo(object obj)
{
//比较对象为空,不改变位置
if (obj == null)
{
return 0;
}
Person p = obj as Person;
if (p != null)
{
//当前对象年龄比比较对象年龄大则排在其后
if (this.Age > p.Age)
{
return 1;
}
//当前对象年龄比比较对象年龄小则排在其前
else if(this.Age<p.Age)
{
return -1;
}
//当前对象年龄与比较对象年龄相同则排在相同位置
else
{
return 0;
}
}
else
{
return 0;
}
}
}
第二种,实现IComparer<T>接口
List<Person> list = new List<Person>();
list.Add(new Person() { Name = "王勃", Age = 21 });
list.Add(new Person() { Name = "芦路", Age = 24 });
list.Add(new Person() { Name = "赵括", Age = 17 });
list.Add(new Person() { Name = "刘楠", Age = 17 });
IComparer<Person> icp=new Person();
list.Sort(icp);
foreach (var item in list)
{
Console.WriteLine(item.Name+"-"+item.Age);
}
Console.ReadKey();
}
}
/// <summary>
/// 实现Icomparer接口,比较两个对象年龄
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>0,相等;1,前者大;-1,后者大;</returns>
class Person :IComparer<Person>
{
public int Age { get; set; }
public string Name { get; set; }
public int Compare(Person x, Person y)
{
if (x == null)
{
return -1;
}
if (y == null)
{
return 1;
}
Person p1 = x as Person;
Person p2 = y as Person;
if (p1 == null)
{
return -1;
}
if (p2 == null)
{
return 1;
}
if (p1.Age > p2.Age)
{
return 1;
}
else if (p2.Age > p1.Age)
{
return -1;
}
else
{
return 0;
}
}
}
4.IO
IO是包含多个文件操作和目录操作类的命名空间,这部分学习主要是记忆一些方法和属性,包括目录操作类Directory;文件操作类File;路径操作类Path;流操作类FileStream、StreamReader、StreamWriter在内的一些方法属性。
1)目录操作:
//在D盘下创建a目录及子目录aa,创建文件01
Directory.CreateDirectory(@"D:\a");
Directory.CreateDirectory(@"D:\a\aa");
//需要用using来释放占用资源
using (File.Create(@"D:\a\02.txt")) { }
//获取该目录下的所有的目录路径
string []strs=Directory.GetDirectories(@"D:\a");
//获取该目录下的所有文件
string []strs1= Directory.GetFiles(@"D:\a");
if (Directory.Exists(@"D:\a"))
{
DialogResult dre= MessageBox.Show("存在,删除?");
if (dre == System.Windows.Forms.DialogResult.OK)
{
Directory.Delete(@"D:\a", true);
}
}
else
{
MessageBox.Show("不存在");
}
2)路径类操作:
string txt = null;
//拼接字符串形成路径
string str=Path.Combine(@"D:\a\", "b.txt");
txt += "1 "+ str + "\r\n";
//更改文件扩展名
str= Path.ChangeExtension(str,".exe");
txt +="2 "+str + "\r\n";
//获取文件目录(不包含文件的目录)
string str1 = Path.GetDirectoryName(str);
txt += "3 "+str1 + "\r\n";
//获取文件扩展名
string str2=Path.GetExtension(str);
txt += "4 "+str2 + "\r\n";
//获取文件名(含扩展名)
string str3=Path.GetFileName(str);
txt += "5 "+str3 + "\r\n";
//获取文件名(不含扩展名)
string str4=Path.GetFileNameWithoutExtension(str);
txt += "6 "+str4 + "\r\n";
//获取文件完整路径(abc为当前目录下文件)
string str5=Path.GetFullPath("abc.txt");
txt +="7 "+str5 + "\r\n";
txtShow.Text = txt;
3)文件类操作:
string txt = null;
string str = @"D:\01.txt";
string strd =@"F:\02.txt";
//创建文件
using (File.Create(str)) { }
//向文件中添加文本
File.AppendAllText(str, "12345 ",Encoding.Default);
//输出D:\01.txt文本和创建时间到文本框
txt += "1 " + File.ReadAllText(str) + File.GetCreationTime(str)+"\r\n";
//复制文件,改名不改变文件父目录
File.Copy(str, Path.GetDirectoryName(str) + "b.txt");
//移动文件01.txt到F盘并改名为02.txt
File.Move(str, strd);
//输出F:\02.txt到文本框
txt += "2 " + File.ReadAllText(strd) + File.GetCreationTime(strd) + "\r\n";
//如果存在则删除
if (File.Exists(str))
{
File.Delete(str);
txt += "3 存在,但已删除\r\n";
}
else
{
txt += "3 不存在,文件已移走\r\n";
}
txtShow.Text = txt;
4)文件流类操作:
文件流类操作使用资源为非托管资源,使用后要对其进行手动释放,为了方便起见,我们使用using语句来完成资源释放。
string str=@"D:\b.txt";
string strd=@"D:\c.txt";
string[] strs = {str,strd };
//读取D:\b.txt文件并写入D:\c.txt中
using (FileStream fsRead = new FileStream(str, FileMode.Open, FileAccess.Read))
{
using (FileStream fsWrite = new FileStream(strd, FileMode.Create, FileAccess.Write))
{
int len;
byte[] bytes = new byte[1024 * 1024 * 2];
//获取文件实际长度
do
{
len = fsRead.Read(bytes, 0, bytes.Length);
fsWrite.Write(bytes, 0, len);
} while (len != 0);
}
}
//向D:\c.txt中加入文字
using (StreamWriter sw = new StreamWriter(strd,true,Encoding.Default))
{
sw.Write("新加入的文字");
}
//输出D:\c.txt中内容到文本框
for (int i = 0; i < strs.Length; i++)
{
using (StreamReader sr = new StreamReader(strs[i], Encoding.Default))
{
txtShow.Text +=string.Format("{0}\t{1}\r\n",strs[i],sr.ReadToEnd());
}
}
5.序列化与反序列化
通过序列化操作可以将对象的某些属性转成二进制数据,进行持久化保存,通过反序列化可以将二进制数据还原成对象。序列化需要通过创建BinaryFormatter类的实例进行操作,要求类有[Serializable]做标记。下面的例子演示的序列化和反序列化的操作,要求反序列化操作时,引用还原对象所属的类。
Person p = new Person() { Name = "刘忻", Age = 20, MyId = new ID() { Id="山西"} };
//序列化操作类
BinaryFormatter bf = new BinaryFormatter();
//将对象存入文件进行存储
using (FileStream fs = new FileStream("test01", FileMode.Create, FileAccess.Write))
{
bf.Serialize(fs, p);
}
Console.WriteLine("序列化成功,姓名:{0}-年龄:{1}-籍贯:{2}", p.Name, p.Age,p.MyId.Id);
//从文件中读取出数据还原成对象
using (FileStream fs=new FileStream ("test01",FileMode.Open,FileAccess.Read))
{
object obj=bf.Deserialize(fs);
Person p1 = obj as Person;
Console.WriteLine("反序列化成功,姓名:{0}-年龄:{1}-籍贯:{2}",p1.Name,p1.Age,p1.MyId.Id);
p1.Do();
}
Console.ReadKey();
}
}
//可序列化标记
[Serializable]
class Person
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
int age;
public int Age
{
get { return age; }
set { age = value; }
}
private ID myId;
public ID MyId
{
get { return myId; }
set { myId = value; }
}
public void Do()
{
Console.WriteLine("水水水水撒");
}
}
[Serializable]
class ID
{
string id;
public string Id
{
get { return id; }
set { id = value; }
}
}
通过序列化和反序列化可以完成包含引用类型数据对象的深拷贝。