一、复习
 
     1.什么是接口?说说你对接口的理解。
     
         (提示:概念、语法、应用场景,与抽象类的区别。说出最特别的)
         接口是一种规范、标准,一种抽象的概念,所以本身无法实现,定义时用interface,
         只能有方法(属性、方法、索引器等)且不能有方法体。不能有字段等数据,且定义
         时不能用访问修饰符(隐式为public)。接口必须在继承中实现(除非是抽象方法),
         可以多继承。
             接口与抽象类都用于多态。都必须在继承中实现。但抽象类主要是同类中且只
         只能单继承。但接口可以不同类中实现多态(表示一种行为特征,如行为、能力等),
         且可以多继承。多个继承时,类名写在最前,后面接多个接口。如果类中有与接口
         同名的方法,可以使用显式定义接口(无修饰符)。
             结构也可以实现接口,但结构不能继承。
             
             
     2、说说try-catch的注意点?
         
         (提示:语法结构,finally,返回值)
         try-catch-finally,其中catch与finally必须至少有一个与try配对。try块中有异常
         则其后续代码不再执行,跳到catch中执行。catch可以有多个,只要有一个满足条件
         其它就不执行了。有finally时无论异常否必须执行,且在try与catch中可以有return,
         但finally中不能有return,返回前也必须执行finally.
             另外因为有返回值的方法都会创造一个临时变量存储返回值,因此当把return
         写入try或catch中时,小心返回值的变化。
         
         
     3、静态方法能被重载或重写吗?
         
         答:静态方法当类加载时就被加载到内存中,一直保持不变,直到程序退出。所以它
             是不能被重写的。
             而非静态方法是在对象实例化时,才单独申请内存空间,为每一个实例分配独立
         的运行内存,因而可以重写。
             
             静态方法与非静态方法都可以被重载,因为重载是在编译器编译时发生,与运行
         时无关。
             简言之:重载是编译时多态,重写是运行时多态。
             
             引申前面的:object.ReferenceEquals()不能被重写或重载。
         因为这是静态方法,所以不能被重写。另外不能人为进入object中去修改源代码进行
         重载(这是微软的内部领域)
         
         
二、常用类库String
 
     学习.net就是学习它的无数个类库怎么用.
     
     1、字符串的一些特性:
     
         string类型不能被继承(sealed关键字):密封类
         
         (1)sealed有两种用法:
         
                 1)类名前加sealed,表示此类不能向下继承。
                 2)方法重写时添加sealed,表示此方法不再向下进行重写。到此为止。
            internal class Program
            {
                private static void Main(string[] args)
                {
                    Person p = new C2();
                    p.SayHi();
                    Console.ReadKey();
                }
            }
            internal class Person
            {
                public virtual void SayHi()
                {
                    Console.WriteLine("基类");
                }
            }
            internal class C1 : Person
            {
                public override sealed void SayHi()
                {
                    Console.WriteLine("子类1");
                }
            }
            internal class C2 : C1
            {
                public override void SayHi()//出错,因为前面已经sealed,不能向下再重写.
                {
                    Console.WriteLine("孙类2");
                }
            }
             
             思考:
                 为什么string类型不允许继承(sealed)?(后面答案)
         
         
         
         (2)不可变性  (ToUpper演示,查看string类代码,只读索引器),
         
                 字符串的任一方法返回的都是一个新的副本,其本身未变,因为它是不可变的。
            private static void Main(string[] args)
            {
                string s1 = "Hello World";
                string s2 = s1.ToUpper();
                Console.WriteLine(s1 + "---" + s2);//本身未变
                Console.ReadKey();
            }
                    结论:
                 字符串一旦被创建,永远不可更改。
                 
            private static void Main(string[] args)
            {
                string s1 = "abc";
                string s2 = "x";
                s1 = s1 + s2;
                Console.WriteLine(s1 + "---" + s2);
                Console.ReadKey();
            }
             注意:
                 s1发生变化。原因:堆中原"abc"未变。堆中新创建了"abcx",其地址赋值
             给了栈中的s1,所以s1指向了新的堆中地址。(原abc无人指向,但不会被回收)
             
            private static void Main(string[] args)
            {
                string a = "a";
                string b = "b";
                string c = "c";
                a = a + b;
                a = a + c;
                Console.WriteLine(a);
                Console.ReadKey();
            }
             上面在堆中开辟了5块空间,也就是创建了5个对象。
             下面输出的结果相同,且开辟的空间和对象也是5个。
            private static void Main(string[] args)
            {
                string a = "a";
                a = a + "b";
                a=a+"c";
                Console.WriteLine(a);
                Console.ReadKey();
            }
             
             可以看到字符串的拼接,造成了大量的内存浪费,每次创建字符串都会浪费资源。
             这都是字符串的不可变性造成的资源浪费。
         
         
         (3)字符串暂存池(拘留池)
         
                 "abc",与控制台输入的"abc",与"a"+"b"+"c",与三个变量abe相加是否为同一
             个对象?(以此说明只针对常量。)
            string s1 = new string(new char[] { 'a', 'b', 'c' });
            string s2 = new string(new char[] { 'a', 'b', 'c' });
            s1 = s2;
             s1与s2是同一个对象.
             
             查看下面两种情况反编译情况:变量连接情况:
            private static void Main(string[] args)
            {
                string s1= "abc";
                string a = "a", b = "b", c = "c";
                string s2 = a + b + c;
                Console.WriteLine(s2);
                Console.ReadKey();
            }
             反编译如
        
      
             
             因此可以看出最后是三个字符串Concat,因此是新创建变量,s1与s2是不同的。
            private static void Main(string[] args)
            {
                string s1 = "abc";
                string a = "a", b = "b", c = "c";
                string s2 = "a" + "b" + "c";
                Console.WriteLine(s2);
                Console.ReadKey();
            }
             反编译后如:

 
             因此,看出实际,就是直接指向前面的s1,未创建新的变量,s1与s2是相同的。
             
             原因:
                 针对字符串常量,建立一个缓存池,把常量字符串,建立一张在堆中的索引
             表。内部维护一个哈希表:key为字符串,value是地址。每次为一个新变量赋值
             都会找key中是否有,如果有则直接把value中的地址赋值给新变量。
                 这样只要有,就直接返回地址,便于快速创建提高效率。
                 依赖于字符串的"不可变性",在整个程序的生命期中,该拘留池并不会消失
             也不会发生变量,只会在程序退出后释放,所以它会一直霸占堆中内存。
             
             1]、为什么只常量,不是变量?
                 因为它的霸占,只适用于少量多次使用的情况。如果再加入众多的变量情况
             会造成堆中大量内存被占用,拖累程序或使程序崩溃。
             
             2]、为什么要有字符串暂存池?
                 暂存池(拘留池)如同缓存一样,建立表后,可以有效提高命中率,节省创
             建字符串对象的时间。
             
             3]、大量的字符串常量,如何避免进入拘留池?
                 由于其不可变性,若用大量的字符串常量,会造成堆中内存无效浪费。这
             时可以尽量用变量来使用字符串,使其在变量生命期外自动释放。
                 例如:2万的字符串常量,可以存储在文件中,用变量进行读取,当这个
             文件关闭后,就可以释放该变量,而不用拘留池学期驻留在堆内存中。
             
                 
         (4)如何手动添加到拘留池?
             
             对于动态字符串本身在哈希表中没有,通过这种Intern可以添加到该哈希表中,
         目的为了提高性能。
         
         String.Intern(x): Intern方法使用暂存池来搜索与 str 值相等的字符串。如果存
                         在这样的字符串,则返回暂存池中它的引用。如果不存在,则向
                         暂存池添加对 str 的引用,然后返回该引用。
         String.lsintened(x): 此方法在暂存池中查找 str。如果已经将 str放入暂存池中
                         则返回对此实例的引用;否则返回nullNothingnullptrnull引用
         
         注意:
             可以手动添加到拘留池,但不能手动在拘留池中删除。
     
         
         (5)为什么字符串要添加sealed,即不允许继承?
             
             答:两个原因:
             1、子类若能继承,可能会对字符串类进行修改(改变字符串的特性如不可变性);
             2、CLR对字符串提供了各种特殊的操作,如果有很多类继承了字符串类,则CLR
                 需要对更多的类型提供特殊操作。这样有可能降低性能。
                 比如,不可变性,下面的众多子类也会如此处理。
     
     
三、字符串格式化
 
     1、格式化
     演示: string.Format("===0,30:c31====",3.1415926535384);
     说明:{索引[,对齐][:格式字符串]}
         索引: 表示引用的对象列表中的第n个对象参数
         对齐(可选): 设置宽度+对齐方式,该参数为带符号的整数。
                 正数为右对齐(不能加正号),负数为左对齐。
                 例如: {0,50}表示宽度为50,右对齐。{0,-50}表示宽度为50,左对齐。
         格式字符串:常见的有"数字格式"、"日期与时间格式"、自定义格式等。
                 用"冒号"开始。例如,
                 数字格式: C表示货币、D表示十进制数、E表示科学记数法、G表示常规...
                 其中可以写成Cxx.xx表示精度,范围为0-99,例如c3表示保留3位小数。
                 
                 日期时间格式: d表示短日期,D表示长日期,f表示完整日期+短时间,F表
                 示完整日期+长时间,t表示短时间模式...
        private static void Main(string[] args)
        {
            string s = "abc";
            Console.WriteLine("01234567890123456789------");
            Console.WriteLine(s);
            Console.WriteLine("=={0,10}==", s);
            Console.WriteLine("=={0,-10}==", s);
            double n = 3.14567;
            Console.WriteLine();
            Console.WriteLine("01234567890123456789------");
            Console.WriteLine(n);
            Console.WriteLine("=={0,10:f2}==", n);
            Console.WriteLine("=={0,10:e2}==", n);
            DateTime d = DateTime.Now;
            Console.WriteLine();
            Console.WriteLine("01234567890123456789------");
            Console.WriteLine(d.ToString("yyyy-MM-dd HH:mm:ss"));
            Console.WriteLine(d.ToString("d"));
            Console.WriteLine(d.ToString("D"));
            Console.WriteLine(d.ToString("f"));
            Console.WriteLine(d.ToString("F"));
            Console.ReadKey();
        }
         
         注意:
             Console.WriteLine()没有返回值直接输出到控制台
             string.Format()有返回值,可以赋值给一个字符串。
     
     
     2、String字符串常用方法
     
         字符串可以看成字符数组,不可变特性
         (通过for循环,修改string中的元素,将失败! 提示为只读属性)。
         当输入时就会提示错误:
       

         
         用net Reflector查询一下string类中的索引器char[]的反编译情况:
     
   
         可以看到属性只有get,没有set,即只能读取,不能写入。
         
         
         属性
             Length //获得字符串中字符的个数。"aA我你他"->5
             
             
         方法
             IsNullOrEmpty() 静态方法判断为null或者为""(静态方法)
             ToCharArray()   将string转换为char[]
             ToLower()     小写,必须接收返回值。(因为: 字符串的不可变)
             ToUpper()     大写。
             Equals()     比较两个字符串是否相同。忽略大小写的比较,StringComparation.
             IndexOf()    如果没有找到对应的数据,返回-1.
             LastlndexOf()  如果没有找到对应的数据,返回-1
             Substring()    2个重载,截取字符串。
             Split()      分害字符串。
             Join()   静态方法
             Format   静态方法
             Replace()        
         
         
         怎么判断一个字符串是空字符串?
             null,空对象。堆中没有分配内存,变量在栈中存储的内容为0x000000,
                     即不指向堆中的任何地址。
             "",空字串。堆中已经分配了内存,只是内容为空。变量在栈中存储的内容就
                     是在堆中分配内存的地址。
             空串的判断:
            private static void Main(string[] args)
            {
                string s = "";
                if (s == "")//方法一
                {
                }
                if (s == string.Empty)//方法二
                {
                }
                if (s.Length == 0)//方法三   推荐
                {
                }
                Console.ReadKey();
            }
             
             或者可能为null时:
            private static void Main(string[] args)
            {
                string s = "";
                if (s == null || s.Length==0)//方法一
                {
                }
                if (string.IsNullOrEmpty(s))//方法二
                {
                }
                Console.ReadKey();
            }
             
             
         对两个字符串比较是否相同,忽略大小写。
        private static void Main(string[] args)
        {
            string s1 = "abc";
            string s2 = "ABC";
            Console.WriteLine(s2.ToLower() == s1.ToLower());
            Console.WriteLine(s1.ToLower().Equals(s2.ToLower()));
            Console.WriteLine(s1.Equals(s2, StringComparison.OrdinalIgnoreCase));
            Console.ReadKey();
        }
         
         
         面试题: 统计一个字符串中,"天安门"出现的次数。
        private static void Main(string[] args)
        {
            string s = "天安门中数天安门,天安门中有几个天安门?";
            int count = 0, i = 0;
            while (true)
            {
                i = s.IndexOf("天安门", i);
                if (i == -1 || i > s.Length - 3)//未找到或已超过长度
                {
                    break;
                }
                i += 3;//词的长度
                count++;//累加
            }
            Console.WriteLine(count);
            Console.ReadKey();
        }
     
     
         如何把char[]数组转为字符串string?
             不能直接用ToString,没有这个重载,只会输出类型。
        private static void Main(string[] args)
        {
            char[] chars = new char[] { 'a', 'b', 'c' };
            string s1 = chars.ToString();
            Console.WriteLine(s1);//System.Char[]
            string s2 = new string(chars);
            Console.WriteLine(s2);//"abc"
            Console.ReadKey();
        }
         
         
         截取字符串。(从零开始计数)
        string s = "welcome to our country!!!";
        Console.WriteLine(s.Substring(11, 1));//our
         
四、字符串的处理练习
 
 
1、接收用户输入的字符串,将其中的字符以与输入相反的顺序输出。"abc"->"cba".
        private static void Main(string[] args)
        {
            Console.WriteLine("请输入字符串:");
            string s = Console.ReadLine();
            if (s.Length > 1)
            {
                char[] c = s.ToCharArray();
                for (int i = 0; i < s.Length / 2; i++)
                {
                    char temp = c[i];
                    c[i] = c[s.Length - 1 - i];
                    c[s.Length - 1 - i] = temp;
                }
                s = new string(c);
            }
            Console.WriteLine(s);
            Console.ReadKey();
        }
     
     
     2、接收用户输入的一句英文,将其中的单词以反序输出。
         "I love you""l evol uoy"
        private static void Main(string[] args)
        {
            Console.WriteLine("请输入一句英文:");
            string s = Console.ReadLine();
            s = s.Replace("\"", " \" ").Replace("?", " ? ").Replace(".", " . ").Replace(",", " , ");
            string[] s1 = s.Split(' ');
            for (int i = 0; i < s1.Length; i++)
            {
                s1[i] = Reverse(s1[i]);
            }
            s = string.Join(" ", s1).Replace(" \" ", "\"").Replace(" ? ", "?").Replace(" . ", ".").Replace(" , ", ",");
            Console.WriteLine(s);
            Console.ReadKey();
        }
        private static string Reverse(string s)
        {
            if (s.Length > 1)
            {
                char[] c = s.ToCharArray();
                for (int i = 0; i < s.Length / 2; i++)
                {
                    char temp = c[i];
                    c[i] = c[s.Length - 1 - i];
                    c[s.Length - 1 - i] = temp;
                }
                s = new string(c);
            }
            return s;
        }
         结果:
         
     
     3、"2012年12月21日"从日期字符串中把年月日分别取出来,打印到控制台.
        private static void Main(string[] args)
        {
            string s = "22012年1月1日";
            int i = 0, j = s.IndexOf("年");
            string year = s.Substring(0, j);
            i = s.IndexOf("月");
            string month = s.Substring(j + 1, i - j - 1);
            j = s.IndexOf("日");
            string day = s.Substring(i + 1, j - i - 1);
            Console.WriteLine(year + "-" + month + "-" + day);
            Console.ReadKey();
        }
     
     
     4、把csv文件中的联系人姓名和电话显示出来。简单模拟csv文件,csv文件就是使用
         分割数据的文本,输出:姓名: 张三 电话: 15001111113
         string[] lines = File.ReadAllLines("1.csv",Encoding.Default);
         //读取文件中的所有行,到数组中。        
        private static void Main(string[] args)
        {
            string[] s = File.ReadAllLines(@"E:\csv.csv");
            for (int i = 0; i < s.Length; i++)
            {
                string[] p = s[i].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                Console.WriteLine($"姓名:{p[0]},电话:{p[1]}");
            }
            Console.ReadKey();
        }
         
         
     5、123-456--7--89---123--2把类似的字符串中重复符号"-"去掉,即得到
         123-456-7-89-123-2.
         split(),StringSplitOptions.RemoveEmptyEntries,Join()
        private static void Main(string[] args)
        {
            string s = "123-456--7--89---123--2";
            string[] p = s.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
            Console.WriteLine(string.Join("-", p));
            Console.ReadKey();
        }
     
     
     
     6、从文件路径中提取出文件名(包含后缀)。比如从c:\a\b.txt中提取出b.txt这个
         文件名出来。以后还会学更简单的方式:"正则表达式",项目中我们用微软提供
         的:Path.GetFileName();(更简单)
        private static void Main(string[] args)
        {
            string s = @"c:\a\b.txt";
            string s1 = Path.GetFileName(s);
            string s2 = Path.GetDirectoryName(s);
            Console.WriteLine(s1);
            Console.WriteLine(s2);
            Console.ReadKey();
        }
     
     
     7、"192.168.10.5[port=21,type=ftp]",这个字符串表示IP地址为192.168.10.5的服务
         器的21端口提供的是ftp服务,其中如果",type=ftp"部分被省略,则默认为http服
         务。请用程序解析此字符串,然后打印出"IP地址为***的服务器的***端口提供的服
         务为***"
         line.Contains("type=")。192.168.10.5[port=21]
        private static void Main(string[] args)
        {
            string s = "192.168.10.5[port=21,type=ftp]";
            string[] p = s.Split(new string[] { "[port=", ",type=", "]" }, StringSplitOptions.RemoveEmptyEntries);
            string type;
            if (p.Length == 2)
            {
                type = "http";
            }
            else
            {
                type = p[2];
            }
            Console.WriteLine($"IP地址为{p[0]}的服务器的{p[1]}端口提供的服务为{type}");
            Console.ReadKey();
        }
     
     
     8、求员工工资文件中,员工的最高工资、最低工资、平均工资
         工资文件1.txt:
                 张三=5000
                 李四=6000
                 王五=7000
                 赵六=8000
                 田七=5500
                 孙八=7100
        private static void Main(string[] args)
        {
            string[] s = File.ReadAllLines(@"E:\1.txt");
            double[] ds = new double[s.Length];
            for (int i = 0; i < s.Length; i++)
            {
                ds[i] = Convert.ToDouble(s[i].Substring(s[i].IndexOf("=") + 1));
            }
            Console.WriteLine($"最大值{ds.Max()},最小值{ds.Min()},平均值{Math.Round(ds.Average(), 2)}");
            Console.ReadKey();
        }
         
     
     9、"北京传智播客软件培训,传智播客.net培训,传智播客Java培训。传智播客官网:
         http://www.itcast.cn。北京传智播客欢迎您。"。在以上字符串中请统计出
         "传智播客"出现的次数。找IndexOf()的重载        
        private static void Main(string[] args)
        {
            string s = "北京传智播客软件培训,传智播客.net培训,传智播客Java培"+
                "训。传智播客官网:http://www.itcast.cn。北京传智播客欢迎您。";
            int count = 0, i = 0;
            List<int> list = new List<int>();
            while (true)
            {
                i = s.IndexOf("传智播客", i);
                if (i == -1 || i > s.Length - 4)
                {
                    break;
                }
                list.Add(i);//索引位置
                i += 4;
                count++;
            }
            Console.WriteLine(count);
            Console.ReadKey();
        }
         
        private static void Main(string[] args)
        {
            string s = "北京传智播客软件培训,传智播客.net培训,传智播客Java培" +
                "训。传智播客官网:http://www.itcast.cn。北京传智播客欢迎您。";
            int count = 0, index = 0;
            while ((index = s.IndexOf("传智播客", index)) != -1)
            {
                count++;
                index += 4;
            }
            Console.WriteLine(count);
            Console.ReadKey();
        }
         
五、常用类库StringBuilder
 
     StringBuilder高效的字符串操作
         当大量进行字符串操作的时候,比如,很多次的字符串的拼接操作。
         String 对象是不可变的。每次使用 System.String 类中的一个方法时,都要
             在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。
             在需要对字符串执行重复修改的情况下,与创建新的 String 对象相关的
             系统开销可能会非常大。
             
         如果要修改字符串而不创建新的对象,则可以使用System.Text.StringBuilder 类。
             例如,当在一个循环中将许多字符串连接在一起时,
                     使用StringBuilder 类可以提升性能。
             
             
     StringBuilder != String//将StringBuilder转换为 String.用ToString().
     StringBuilder仅仅是拼接字符串的工具,大多数情况下还需要把StringBuilder转换
         为 String.
        StringBuilder sb = new StringBuilder().
        sb.Append();//追加字符串
        sb.ToString();//把StringBuilder转换为字符串。
        sb.Insert();
        sb.Replace():
         
         
六、垃圾回收
 
     1、CLR的一个核心功能一垃圾回收。
     
     
     2、垃圾回收的目的:
         提高内存利用率。
         (内存是有限的,程序中断地使用,若分配的变量都不回收,内存会越来越小,当
         不够用时,程序就会崩溃。)
         
         
     3、垃圾回收器,它回收什么?
         只回收托管堆中的内存资源,
         不回收其他资源(数据库连接、文件句柄、网络端口等)。
                     
         栈中的值类型资源,不需要垃圾回收。它们使用完成后会立即释放。
             
             
     4、什么样的对象才会被垃圾回收?
         没有变量引用的对象。没有变量引用的对象,表示可以被回收了(null)断了线的
             风筝,再也回不来了。
         大学食堂(自己收盘子)、大排档(不需要程序员自己收盘子)
         
         可以被回收,并不意味着它马上就被回收。
     
     
         问题1:下面运行到x处时p分配的空间是否可以被回收?
        internal class Program
        {
            private static void Main(string[] args)
            {
                Person p = new Person();
                p.Name = "杨贵妃";
                Person p1 = p;
                p = null;
                //p1=null;
                Console.ReadKey();//x处
            }
        }
        internal class Person
        {
            public string Name { get; set; }
        }
         
         答:不可以。p虽然为空,但已经转交给p1,由p1指向该空间,该空间仍然在被p1
             使用。如果下一句有p1=null,这块内存再也不会有任何对象引用。那么这块
             内存如断线的风筝,再也没人引用指向。这时候是可以被回收。
             
             注意:可以被回收并不意味着马上被回收。
                 什么时候回收,时间并不确定,这由GC决定。
         
         
         问题2:下面运行到x处时,p1,p2,p3是否可以被回收?
        internal class Program
        {
            private static void Main(string[] args)
            {
                Person p1 = new Person();
                Person p2 = new Person();
                Person p3 = new Person();
                Person[] ps = new Person[] { p1, p2, p3 };
                p1 = null;
                p2 = null;
                p3 = null;
                Console.ReadKey();//x
            }
        }
        internal class Person
        {
            public string Name { get; set; }
        }
         答:不可以。p1,p2,p3解除了引用,但ps仍然在引用这块内存。
         
         
     5、垃圾回收GC在什么时间回收?
         不确定,当程序需要新内存的时候开始执行回收。
         
         Gc.Collect();
         //手动调用垃圾回收器。不建议使用,垃圾回收时会暂停一下(非常短暂)
         //让程序自动去GC。
         
         垃圾回收是.Net的CLR自动来执行,一般不需手动干预。
         正如食堂吃完后,无需收拾由阿姨来自动收拾。但你吃完非要喊一句"阿姨来收拾"
         这反而打乱了程序的自动回收流程,造成效率低下。
         
         什么时候必须进行手动回收垃圾呢?
             如果在即将进行了一大段非常重要的代码运行,并且预估可能使用大量的内
         存,不需要或者尽量不让垃圾回收,这时可以提前使用手动调用垃圾回收。可以
         释放出一些较多空余的内存,同时手动回收后,在这段代码运行期间再次被自动
         垃圾回收的机率就会变小,从而不会影响这段代码的效率。
             另外就是非托管的程序代码,它已经不在GC控制范围,所以必须手动显式地
         进行回收,否则会占用系统资源,甚至可能出现意想不到的错误。
         
         
     6、垃圾回收对程序有影响吗?
         垃圾回收时,程序会暂停,保存当前的运行状态,然后进行清理,清理结束后,
         恢复原来的运行状态,程序继续进行。
             所以垃圾回收是对程序有一些影响的,但这个影响已经优化到极限,一般
         不影响程序,人也无法感受到。除非对一些性能有苛刻要求的程序才会有影响。
         
             
     7、垃圾回收的过程是怎样的?
         如果垃圾回收每次都把整个堆全面搜索一次进行清理,那么它的效率明显就很
         低,还影响程序的运行。
             于是垃圾回收器使用"代"的概念,采用"代"的机制进行回收,大大提高了垃
         圾回收的效率。
             一共分3代: 第0代、第1代、第2代。
        private static void Main(string[] args)
        {
            int n = GC.MaxGeneration;//垃圾回收支持的最大代数
            Console.WriteLine(n);//2    即0,1,2共三代
            GC.Collect(0);//只回收第0代
            GC.Collect(1);
            GC.Collect(2);
            GC.Collect();//回收所有代数
            Console.ReadKey();
        }
             
             各代的回收频率:第0代最高,其次第1代,再次第2代。
             每个代的都指定了一定的初始容量空间,来装载各代变量。
             申请变量首先进入第0代,直到占满第0代的初始规定容量时:
             先回收第0代,第0代回收后存活下来的,就进行第1代。如此往复,直到第0代
         存活下来的,试图再进入第1代时,发现第1代初始容量空间已经满了,则回收第1
         代。同时第1代回收后存活的,则进入第2代。
             如此重复,也就是说越老的对象生存几率越大,而新建的对象则最易被回收。
             第0代的就如士兵,炮灰,最易被回收;第0代的士兵存活下来的,去第1代当
         团长。一旦第1代的团长也满后,才去从回收第1代,从团长群中找存活的,进入
         第2代去当军长。
             如果第0-2代都满了,则会试着扩大0-2代各代的初始容量,以适应程序运行。
         如果一直扩大,都无法满足程序,则到达一限度后,程序就会抛出异常。
         
         
     8、.net中垃圾回收算法: 
             mark-and-compact(标记和压缩),一开始假设所有对象都是垃圾。
             
             首先确定所有可达对象,然后移动这些对象,使它们紧挨着存放,有点类似于
         磁盘碎片整理。
             一次垃圾回收周期开始时,垃圾回收器会识别出对象的所有根引用,然后遍历
         根引用所标识的树形结构,并递归确定所有根引用指向的对象,这样,垃圾回收器
         就识别出了所有可达对象。
             执行垃圾回收时,垃圾回收器将所有可达对象紧挨着放在一起,从而覆盖不可
         达对象所占用的内存。
             

             图中上部分是回收前(绿色在用,黄色为垃圾),回收后是下半部分。
             此算法的优点是不会导致内存碎片化,回收后内存分配更高效。
             缺点是:移动内存需要额外的开销,同时仍然要扫描整个内存空间。
             所以为了优化使用代的概念,避免扫描整个内存。
                 9、除了内存资源外的其他资源怎么办?
         比如a方法中调用了系统中的方法。
         由于这些资源是非托管的,所以需要手动去释放。比如析构函数,或者Dispose()等。    
     
     
     10、为什么.net没有析构函数?
         析构函数的目的是释放资源,但已经有了GC,它会自动调用GC进行资源的释放。
         
         但,也可以人为写一下象C++中的"析构函数":
        internal class Program
        {
            private static void Main(string[] args)
            {
            }
        }
        internal class Person
        {
            ~Person()
            {
            }
        }
         上面生成后,用.Net Reflector反编译看一下:
       

         可以看到这个"析构函数",实际上被编译成终结器(终结函数,也即"析构函数")
             protected override void Finalize();
         当对象可以被回收,当GC回收时,它先执行这个终结器,再调用GC进行回收。所
         以一般非托管资源的释放,就放在Finalize()这个终结器中,以便克服GC不能回
         收非托管资源的缺陷。
             但这个有一个缺点:
             尽管可以被回收,但并不能马上被回收,这个要等GC来回收时,才先执行这
         个终结器,造成非托管资资源一直被占用。
              简言之:程序员不能控制析构器何时将被执行因为这是由垃圾收集器决定的。
             
             为了克服这个缺点,使用一个叫IDispose的接口,它是所有处理所有非托管
         资源的释放方法,需要人为在对象中重新实现,可以直接手动调用马上释放。而
         不必用析构函数或终结器(它需要等待GC的到来)。
        internal class Program
        {
            private static void Main(string[] args)
            {
                Person p = new Person();
                p.Name = "晋文公";
                p.Dispose();//手动立即释放,不必等GC
                Console.ReadKey();
            }
        }
        internal class Person : IDisposable
        {
            public string Name { get; set; }
            //~Person()//不推荐,不使用
            //{
            //}
            public void Dispose()
            {
                //这里人为手动实现,在不用对象时,直接调用该方法可立即释放
                Console.WriteLine("Dispose()");
            }
        }
     
         上面代码另一个有效释放就是用using语句,主程序改为:
        private static void Main(string[] args)
        {
            using (Person p = new Person())
            {
                p.Name = "KO";
            }
            Console.ReadKey();
        }
         using()是使用非托管资源的最佳方式,可以确保资源在代码块结束之后被正确释放,
         并且代码更简洁。
         
         这里说的非托管资源指的是实现IDisposable或IAsyncDisposable接口的类。
         using实际上是一个try-finally,在finally会自动调用dipose,即无论执行正常与
         否,最终都会有非托管资源的释放。
             当代码离开using语句块,就会自动调用声明的非托管变量中dispose.
 七、弱引用    
   
 
     当一个对象在堆中没有任何人引用时,它就是一个断线的风筝,它就可以被垃圾回收。
     
     但有时我们程序中动态中可能会再次使用它,但又不清楚它是否已经被垃圾回收了。
     
     此时,在这个对象(风筝)断线(null)之前,就设置一个"标志"(弱引用)。
     
     在下次可能再使用前,进行该对象的引用或判断。
     
     1、为什么要使用弱引用?
             因为创建一个对象,特别是一个大的对象,特别费资源。弱引用能很大程序
         上免除重新创建一个新对象,而是直接使用原来的对象,节省资源。
         
     
     2、弱引用的注意事项
         
         弱引用类为WeakReference,主要有:
         IsAlive:判断是否存活。
                 注意可能判断时还存活,但下一瞬间就被回收了。
         Target: 目标对象(即原对象)。
                 利用此属性,可将当前对象进行转换。
        internal class Program
        {
            private static void Main(string[] args)
            {
                Person p = new Person();
                p.Name = "朱元彰";
                WeakReference wr = new WeakReference(p);//声明弱引用
                p = null;
                方法一:不推荐
                //if (wr.IsAlive)
                //{
                //    object o = wr.Target;
                //    if (o != null)
                //    {
                //        Person p1 = o as Person;
                //        //p活了(即p1),又可以用了
                //    }
                //}
                方法一:啰嗦
                //if (wr.Target != null)
                //{
                //    object o = wr.Target;
                //    Person p1 = o as Person;
                //    //p1又活了,又可以用了
                //}
                //方法三:推荐
                Person p1 = wr.Target as Person;
                if (p1 == null)
                {
                    //重新创建p1
                }
                else
                {
                    //p活了为p1,又可以使用了。
                }
                Console.ReadKey();
            }
        }
        internal class Person
        {
            public string Name { get; set; }
        }
         方法一:在IsAlive判断后的瞬间,可能被GC回收,所以代码本身就有bug。
         方法二:过程比较啰嗦。
         方法三:推荐写法。直接提取转换,为null就新建,否则直接使用。
         
         
     3、弱引用的工作原理
         引用https://www.cnblogs.com/johnyang/p/17205466.html
         
             弱引用保持的是一个GC"不可见"的引用,是指弱引用不会增加对象的引用计数,
         也不会阻止垃圾回收器对该对象进行回收。因此,弱引用的目标对象可以被垃圾回
         收器回收,而弱引用本身不会对垃圾回收造成任何影响。
            弱引用的原理是,在堆上分配的每个对象都有一个头部信息,用于存储对象的
         类型信息、对象的大小等信息。在头部信息中,还会有一个标志位用于表示对象是
         否被引用。
         
             当一个对象被创建时,该标志位为"未引用"。当该对象被弱引用引用时,该标
         志位不会变为"已引用",即该对象仍然会被当做未引用的对象进行处理。
         
             被强引用后,会被标记为"已引用",当所有的强引用都消失时,该标志位会变
         为"未引用",即该对象已经没有任何强引用指向它,标记的工作由GC来完成。
            在垃圾回收时,GC会根据标记-清除算法对堆中的对象进行扫描和标记,标记
         所有仍然被引用的对象,然后回收所有未被标记的对象。
         
             对于被弱引用引用的对象,由于弱引用不会增加对象的引用计数,也不会阻止
         垃圾回收器回收该对象,因此在回收时,该对象会被当做未被引用的对象进行处
         理,然后被回收。            总之,弱引用保持的是一个GC"不可见"的引用,即弱引用不会影响垃圾回收器
         对目标对象的回收,因此可以用于实现一些场景,例如缓存、对象池等场景,避免
         长时间占用内存或造成内存泄漏。
        private static void Main(string[] args)
        {
            var sb = new StringBuilder("weak");
            var weak = new WeakReference(sb);
            Console.WriteLine("before GC");
            Console.WriteLine(weak.Target);
            GC.Collect();
            Console.WriteLine("after GC");
            if (weak.Target == null)
            {
                Console.WriteLine("now it has been cleared...");
            }
            else
            {
                Console.WriteLine(weak.Target);
            }
            Console.ReadKey();
        }
         
          在vs2022上工具栏下选择Debug/Release两种模式分别生成可执行文件。在对应的
          项目的子目录中的Debug或Release中分别生成exe文件,分别执行那么结果不同:
         1)Debug下:
                 before GC
                 weak
                 after GC
                 weak
         2)Release下:
                 before GC
                 weak
                 after GC
                 now it has been cleared...
                 
             在 debug 模式下,GC.Collect 方法仍然会工作,但是它的行为可能会受到
         一些影响。
            在 debug 模式下,编译器会添加额外的调试信息到代码中,这些信息可能会
         影响垃圾回收器的行为。例如,编译器可能会保留一些对象的引用,以便调试器
         可以访问它们,这可能会导致这些对象不会被垃圾回收器回收,直到调试器不再
         需要它们为止。
             因此,当调用 GC.Collect 方法时,由于存在调试信息的影响,可能会出现
         一些对象无法被立即回收的情况。
            此外,在 debug 模式下,垃圾回收器的性能也可能会受到一定的影响,因
         为编译器会添加额外的代码和调试信息,导致程序变得更加复杂和庞大,从而使
         垃圾回收器需要更长的时间来扫描和回收对象。
            因此,如果需要在 debug 模式下进行垃圾回收操作,应该仔细考虑其影响,
         并进行充分的测试,以确保程序的正确性和性能。
             同时,还可以考虑使用其他的调试工具和技术来诊断和解决问题,避免对
         程序的垃圾回收行为产生不必要的影响。
         
         
  



















