代码效率优化总结(一)

   工作四年了,一直想写点东西总结一下自己的所知所解,但是由于懒惰的性格,始终没有实施这个想法。在同事春哥的督促和鼓励下,我终于开始踏出了勇敢的一步!望一点陋见能够朋友们带来启发。废话不多说,进入正题。

      今天我想和大家谈谈代码优化。

      四年中我一直从事B/S信息系统方面的开发,可以说,从事B/S开发,离不开和数据库打交道。在过往的项目开发中,我一直认为大学中的算法在我们的B/S信息系统开发中几乎没有用,而数据库开发的效率确是我们需要解决的重点问题。关于数据库方面,经过几个项目的历练,也逐渐有点心得,以后有机会再谈。

      诚然,算法确实在B/S信息系统开发中使用并不多,但是算法所给你形成的“效率第一”的观念是从事软件开发者必不可少的基本素质。很多同行,尤其是行业内的新人,由于经验缺乏,往往缺少这种效率的观念,仅仅满足于实现功能,实在落于下乘,长此以往,技术水平不可能有提高。

      下面举一个例子,说说我在最近开发中优化代码的一点。

      我们做的是在线的考试系统,要实现这么一个功能,统计分析出一份试卷中,学生客观题作答的干扰项。比如说,一份试卷有500个学生参加考试,某道题,A是正确答案,可是A只有100个选择了,而300个人选择了B,50个人选择了C,50个人选择了D。因此我们认为该题的干扰项伟B

      干扰项的定义为:除去正确答案外,其余选择人数最多的选项。

好,有了需求和定义,那么我们来实现这么一个功能。

我们的学生答案的存储格式如下,采用Xml格式存储:

<PaperInfoDetail>

  <Ti1 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>continents</SA>

    <SS>2.7</SS>

    <El>

      <EP1 EN="" EW="100">优秀|90</EP1>

    </El>

  </Ti1>

  <Ti2 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>geologists</SA>

    <SS>2.7</SS>

    <El>

      <EP1 EN="" EW="100">优秀|90</EP1>

    </El>

  </Ti2>

  <Ti3 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>composed</SA>

    <SS>2.7</SS>

    <El>

      <EP1 EN="" EW="100">优秀|90</EP1>

    </El>

  </Ti3>

  <Ti4 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>Africa</SA>

    <SS>2.7</SS>

    <El>

      <EP1 EN="" EW="100">优秀|90</EP1>

    </El>

  </Ti4>

  <Ti5 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>believed</SA>

    <SS>2.7</SS>

    <El>

      <EP1 EN="" EW="100">优秀|90</EP1>

    </El>

  </Ti5>

  <Ti6 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>entirely</SA>

    <SS>2.7</SS>

    <El>

      <EP1 EN="" EW="100">优秀|90</EP1>

    </El>

  </Ti6>

  <Ti7 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>drifted</SA>

    <SS>2.7</SS>

    <El>

      <EP1 EN="" EW="100">优秀|90</EP1>

    </El>

  </Ti7>

  <Ti8 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>facinated</SA>

    <SS>1.3</SS>

    <El>

      <EP1 EN="" EW="100">|43.33333</EP1>

    </El>

  </Ti8>

  <Ti9 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>It is almost as easy as to do with the coarse funny coast line of America and Europe. Fast lies plains and bones and footprints of dinosaur have been found on every continent in the world.</SA>

    <SS>2.3</SS>

    <El>

      <EP1 EN="" EW="100">合格|76.66666</EP1>

    </El>

  </Ti9>

  <Ti10 IDY="00601040037258L" Kd="K104" KN="复合式听写" OF="1" EF="1" DF="1">

    <SA>They intend to try a matched continent by an examination of the ocean bottom at barriers places of the continental coast.If there is an answer, they believe they will find there</SA>

    <SS>2.1</SS>

    <El>

      <EP1 EN="" EW="100">合格|70</EP1>

    </El>

  </Ti10>

  <Ti11 IDY="01801120103836L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>A</SA>

    <SS>5</SS>

  </Ti11>

  <Ti12 IDY="01801120103836L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>D</SA>

    <SS>5</SS>

  </Ti12>

  <Ti13 IDY="01801120103836L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>C</SA>

    <SS>5</SS>

  </Ti13>

  <Ti14 IDY="01801120103836L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>B</SA>

    <SS>5</SS>

  </Ti14>

  <Ti15 IDY="01801120103837L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>B</SA>

    <SS>5</SS>

  </Ti15>

  <Ti16 IDY="01801120103837L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>A</SA>

    <SS>5</SS>

  </Ti16>

  <Ti17 IDY="01801120103837L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>C</SA>

    <SS>5</SS>

  </Ti17>

  <Ti18 IDY="01801120103837L" Kd="K112" KN="听长对话" OF="0" EF="1" DF="1">

    <SA>A</SA>

    <SS>5</SS>

  </Ti18>

  <Ti19 IDY="01901130104383L" Kd="K113" KN="听短文填空" OF="0" EF="1" DF="1">

    <SA>Death by drowning.In the United States alone, about 7000 children under 4 drowned each year.</SA>

    <SS>0</SS>

  </Ti19>

  <Ti20 IDY="01901130104383L" Kd="K113" KN="听短文填空" OF="0" EF="1" DF="1">

    <SA>Teaching children to swim while they are babies.</SA>

    <SS>0</SS>

  </Ti20>

  <Ti21 IDY="01901130104383L" Kd="K113" KN="听短文填空" OF="0" EF="1" DF="1">

    <SA>Most large towns in Florida and California.</SA>

    <SS>0</SS>

  </Ti21>

  <Ti22 IDY="01901130104383L" Kd="K113" KN="听短文填空" OF="0" EF="1" DF="1">

    <SA>The first is to eradicate the child's fear of water, the second is tautht to float, and then to move on to bring in arm and leg movement.</SA>

    <SS>0</SS>

  </Ti22>

  <Ti23 IDY="01901130104383L" Kd="K113" KN="听短文填空" OF="0" EF="1" DF="1">

    <SA>Men and monkeys.</SA>

    <SS>0</SS>

  </Ti23>

</PaperInfoDetail>

 

上面不具体介绍Xml节点、属性等各自的含义,大体说明如下:学生的答案中包含了主观和客观题,该Xml存储的是他作答的整份试卷中的答案。

   SA代表答案节点

   SS代表学生作答后的分数节点。

   当学生作答提交完毕后,我们需要开始统计了。基本思路如下:

   从数据库中抽取500个学生的作答答案Xml数据,循环遍历每个客观题的小题(即Ti节点),得到并统计学生的作答答案。数字统计完毕后,排除正确选项,那么剩余选项中那个选择人数最多的选项即为该题的干扰项。

   思路就是这么简单,相信所有有程序思维逻辑的人都能够想的到。好,接下来,我们看如何用代码实现它。

   哦,再进行下面一段介绍之前,请各位可以模拟下,写个伪代码,你会如何实现,我可以帮你估算一下这个效率。因为一旦将整个实现过程介绍完毕后,你会觉得就那么回事,你会觉得这个太简单了。但是真正你去实现的时候,你不见得一次就能够写出效率很高的代码,你会发现,原来解决这个问题还是需要点思考的。

        /// <summary>

        /// 获取某道小题对应的干扰项(即是学生选择得最多,但却不是正确答案)

        /// </summary>

        /// <param name="originalTaskID">试卷号</param>

        /// <param name="totalCostTime">总耗时</param>

        /// <param name="dataCostTime">DB数据读取部分耗时</param>

        /// <returns>返回各个客观题的干扰项的字符串</returns>      

 public string Interferential_Answer(string originalTaskID, ref string totalCostTime, ref string dataCostTime)

        {

            //

            int tiNum = 0;

            //标准答案

            string _tiAnswer = "";

            string tiItem = "";

            string _returnValue = "";

            RunTimeTest_BLL myBll = new RunTimeTest_BLL();       //计时类,用来测试时统计每个阶段的耗时时间

            myBll.SetBeginTime(1);                               //启动计时器1

            myBll.SetBeginTime(2);                               //启动计时器2 

                      

            ArrayList _strArray = GetTiNum_BzAnswer(originalTaskID);          //根据试卷号,获取试卷中所有客观题的标准答案

            //获取所有已提交作业的学生作答内容

                      

 

            //获取所有已提交作业的学生作答内容

            DataTable table = new DataTable();

            table = QueItemAnalysis_DAL.GetAnswerDetail(originalTaskID);      //获取所有作答这份试卷的学生的作答结果

            myBll.SetEndTime(2);                                              //结束计时器2

            dataCostTime = myBll.GetCostTime(2);                             //统计计时器2的耗费时间,即数据读取耗费时间。 

            if (table != null)

            {

              

//以下部分为精髓,请仔细理解

ArrayList myXmlArray = new ArrayList();          

                int tableLength = table.Rows.Count;

                XmlDocument document = new XmlDocument();             //循环外创建Xml文档对象,代码优化步骤1

                for (int i = 0; i < table.Rows.Count; i++)            //将学生的答案Xml文件循环添加到数组列表结构中,代码优化步骤2,最重点优化

                {

                    string answerDetail = table.Rows[i]["Answ_Detail"].ToString();

                    if (answerDetail.Trim().Length != 0)

                    {

                        document.LoadXml(answerDetail);

                    }

                    myXmlArray.Add(document);

                }

                document = null;                                   //清除Xml文档对象,节省内存

               //重点优化部分结束

                //标记循环1

                for (int k = 0; k < _strArray.Count; k++)          //循环所有客观题

                {

                    tiItem = _strArray[k].ToString();              //获取小题项,如:1#|#A

                    string[] tiItemArr = Regex.Split(tiItem, "#|#", RegexOptions.IgnoreCase);

                    tiNum = int.Parse(tiItemArr[0]);                        //题号

                    _tiAnswer = tiItemArr[1];                               //题的标准答案

                    Hashtable openWith = new Hashtable();                   //在循环外构建hash表 ,代码优化步骤3

                    string _returnData = "";

                    //标记循环2

                    for (int a = 0; a < table.Rows.Count; a++)

                    {

                        XmlDocument docuemntTemp = (XmlDocument)myXmlArray[a];            //从数组列表中取出第a个学生的作答Xml文档对象

 

                        XmlNode Ti = docuemntTemp.SelectSingleNode("//PaperInfoDetail/Ti" + tiNum); //查询指定题号学生的答案(已在之前过程中确定为客观题了)

                        string StuAnsw = Ti.SelectSingleNode("SA").InnerText;              //得到学生答案

                        string StuScore = Ti.SelectSingleNode("SS").InnerText;

                        //

                        if (StuAnsw.Length != 0)

                        {

                            string key = StuAnsw;

                            //当前答案还没收录进Hashtable

                            if (!openWith.ContainsKey(key))                           //加入hash表,有该健则键值+1,无该键,则加入并赋值为1

                            {

                                openWith.Add(key, 1);

                            }

                            //如果当前答案已经存在,则对应的作答人数加1

                            else

                            {

                                openWith[key] = int.Parse(openWith[key].ToString()) + 1;

                            }

                        }

                    }

                    document = null;                    

                    int _tag = 0;

                    int _value = 0;

 

                    foreach (DictionaryEntry de in openWith)         //以下遍历当前客观题的hash表,得出干扰项

                    {

                        if (de.Key.ToString() != _tiAnswer)

                        {

                            _value = Convert.ToInt16(de.Value);

                            if (_value > _tag)

                            {

                                _tag = _value;

                                _returnData = de.Key.ToString();

                            }

                            //可能出现几个干扰项的百分比一样多(例如:A/B/C 三个都是干扰项)

                            else if (_value == _tag)

                            {

                                _returnData = _returnData + "/" + de.Key.ToString();

                            }

                        }

                    }

                    openWith = null;                             //清除hash

                    _returnValue = (_returnValue.Length == 0) ? (tiNum + ":" + _returnData) : (_returnValue + "@" + tiNum + ":" + _returnData);

                   //多使用这种结构的IFElse判断结构,虽然可读性不强,但是代码简洁让我非常喜欢

                }//小题循环

            }

            myBll.SetEndTime(1);                   //结束计时器1

            totalCostTime = myBll.GetCostTime(1);  //统计整个过程的耗时

            return _returnValue;

        }

 

    好了,以上就是最终的确定代码。看了之后,你是不是觉得没有什么优化啊,很简单啊,我也会啊,之类的感想。呵呵,我并不否认有部分人会如此写。

    请听我继续分析。

    最初的时候,是这么写的。由于我没有保留优化之前的代码,因此仅以文字表述。

    【完成该功能时】:重点优化部分当时是没有的,最初的思想是,针对每道小题,我遍历500个学生该题的作答答案,得出干扰项。因此每个小题我需要循环构建500次学生的Xml文档对象。即在标记循环2内部含有构建学生作答Xml文档对象的代码

【测试用例】: (83个学生,78道题目,其中60道客观题)

【测试结果】:

//

总用时:4687.59   数据库读取所花时间:187.5036

总用时:4500.0864 数据库读取所花时间:140.6277

总用时:4500.0864  数据库读取所花时间:140.6277

总用时:4484.4611  数据库读取所花时间:156.253

总用时:4484.4611  数据库读取所花时间:156.253

总用时:5046.9396  数据库读取所花时间:140.6268

总用时:4515.6828  数据库读取所花时间:171.8772

 

由上可以看到,在83个学生,其中有60到客观题的时候,耗时平均在4.5s。推断,在500个学生的情况,估计页面无响应。

 

【第一次优化】:初看第一次完成功能后的代码,以我的经验,我即刻将如上的对象创建代码移到循环外了,即:XmlDocument document = new XmlDocument();优化步骤1 Hashtable openWith = new Hashtable();优化步骤3

【测试用例】: (83个学生,78道题目,其中60道客观题)

【测试结果】:总耗时平均在2.8s。不可思议吧,就这么简单的改动,带来了1.7s的速度提高。你平时是否注意到了这种细节呢??

 

【第二次优化】:经过第一次优化后,该函数还是没有取得明显的效果,达不到产品鉴定的要求。我思考着,思路并没有错误,到底是哪一部分影响了效率。在经历这种情况时,可能有的人立马就会使用计时函数去挨个测试过程,从而得出结论。这不失为一种方法。但是,当时,我还在分析,到底是为什么!于是,根据代码,在纸上计算,无意中发现,83个学生,如果对每到小题均创建一次XML文档对象(当时这个文档对象还没有进行释放),那么就是83*60=4980次,这样需要耗掉多少时间和空间。看样子,要进一步提高效率,必须将这个创建过程只执行一次。最终,采用了ArrayList这么一个结构,并把创建过程移动循环外只执行一次。当然啦,为避免拆箱装箱问题,你也可以使用IList结构。但是经过测试,没有任何区别(可能是数据量不够明显,还看不出差别的原因吧)。

【测试用例】: (83个学生,78道题目,其中60道客观题)

【测试结果】:最终总耗时平均在0.45s。哈哈,终于达到了理想的效果。较之最初,运行效率提高了1 0倍,基本可以达到应用要求。我并不知道500个学生的情况,会是什么一种情况,还需要进一步的测试。

 

【总结】:

从编程感悟上来说:

1.  以上并不是说技术有多高,都是些很简单的东西。每个人养成了一种良好的编码习惯,逻辑思维更加严密点,就能够避免这种问题。(最初的代码不是我写的,呵呵)

2.  当我们写代码的时候,时刻需考虑效率的问题,保持效率的观念,要有代码Review的过程。自己创造一段代码之后,你需要重新审视自己的代码,能不能够更好的优化它。

以上是我最重要的体会,这两点你有了,恭喜你,你离代码高手不远!

 

从纯技术上来说:

1.  函数内很多变量的创建,尤其是大数据对象变量,尽量移到循环外部。循环内部只需要引用即可。

2.  对象使用完毕后,及早释放。

尽量使自己的代码结构简单简洁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值