[转载]javascript使用Array、join做字符存储和直接使用+号拼接字符串的执行效率比较

mikel阅读(1014)

[转载]javascript使用Array、join做字符存储和直接使用+号拼接字符串的执行效率比较 – bluescreen – 博客园.

因为最近在学习ASP.NET,其中也穿插着看点JavaScript.在学习过程也遇到一些问题和疑惑,就把当时的一些想法和测试结果记录下。如有不对之处还请各位大侠指正。谢谢!

在学习JavaScript时看了一本书(还没有看完)。而《JavaScript高级编程》这本书中说“使用Array做为StringBuffer,代替字符串拼接的操作。然后使用Array中的join方式处理要比直接进行字符串拼接效率要高。”然后下面就给出了相关例子。

其代码如下(函数名忘了什么,但是这样写的):

<script type="text/javascript">
function StringBuilder() {
    this.strings = new Array();
}
StringBuilder.prototype.Append = function (str) {
    this.strings.push(str);
};
StringBuilder.prototype.ToString = function () {
    return this.strings.join("");
};
</script>

经过测试代码的确可用并且感觉用着还不错,颇有C#中StringBuilder的感觉。但执行速度并未进行测试!

var str = new StringBuilder();
            str.Append("你好");
            str.Append("Hello")
           str.ToString();

由于用着比较好用,就想试着增加点其他功能。让它像C#中StringBuilder的一样多好,之后就增加了点方法。由于是新手,很多内容也不知 道如何处理,最终以失败而告终。所添加的方法全部按数组进行操作(插入,移除等),并没有字符的一些特性(由于没有达到自己想要的效果,代码就不贴了)。

第二天,不甘失败又重新修改下代码达到了自己想要的结果。但代码看上去不伦不类,有点多此几举的感觉。执行应该效率应该是不行的。不管执行效率是否如何, 由想到实现也是一点进步吧!对《javascript高级编程》提到的函数进行扩展(上面代码也给出),于是乎出现代码如下:

/*
* javascript数组模拟C#中的StringBuilder
* QQ:909507090
* http://www.qhjsw.net
*
* Copyright (c) 2012 www.qhjsw.net
*
* Date: 2012-02-10 14:25:16
*
*/
function StringBuilder() {
    this.strings = new Array();
}
StringBuilder.prototype.Append = function (str) {
    this.strings.push(str);
};
StringBuilder.prototype.ToString = function () {
    return this.strings.join("");
};
StringBuilder.prototype.Remove = function (value) {
    if (this.strings.length >= 1) {
        this.strings = this.strings.join("").split("").concat();
        for (var i = 0; i < this.strings.length; i++) {
            if (this.strings[i] == value) {
                for (var j = i; j < (this.strings.length - 1); j++) {
                    this.strings[j] = this.strings[j + 1];
                }
                this.strings.length--;
                this.strings[this.strings.length] = null;
                this.strings.length--;
                break;
            }
        }
    }
    else {
        this.strings.length = 0;
    }
};
StringBuilder.prototype.Insert = function (value, index) {
    if (index < 0) { index = 0; }
    this.strings = this.strings.join("").split("").concat();
    if ((this.strings.length >= 1) && (index <= this.strings.length)) {
        for (var i = this.strings.length; i > index; i--) {
            this.strings[i] = this.strings[i - 1];
        }
        this.strings[index] = value;
        this.strings.length++;
    }
    else {
        this.strings[0] = value;
    }
};
StringBuilder.prototype.Exist = function (value) {
    if (this.strings.length > 1) {
        if (this.strings.join("").indexOf(value) != -1) {
            return true;
        }
    }
    return false;
};
StringBuilder.prototype.Clear = function () {
    this.strings = this.strings.join("").split("").concat();
    this.strings.length = null;
};
StringBuilder.prototype.Length = function () {
    this.strings = this.strings.join("").split("").concat();
    return this.strings.length;
};

自己想做的东西出来了,现在却想测试一下执行效率如何。测试下执行效率是否入书中所说。测试是使用firefox+firebug,测试所用时间使 用firebug控制台自带计时。在要测试代码前后加入console.time()和console.timeEnd().使用三种方式做测试比较,测 试代码如下:

console.time("直接原始Array");
        var str = new Array();
        for (var i = 0; i < 10000; i++) {
            str.push(i);
        }
        var msg = str.join("");
        console.timeEnd("直接原始Array");
        console.time("使用书中提到的方法");
        var str1 = new StringBuilder();
        for (var i = 0; i < 10000; i++) {
            str1.Append(i);
        }
        str1.ToString();
        console.timeEnd("使用书中提到的方法");
        console.time("使用字符拼接+");
        var str2 = "";
        for (var i = 0; i < 10000; i++) {
            str2=str2+i;
        }

        console.timeEnd("使用字符拼接+");

进行多次测试。其测试代码内容无太大变化,主要更改循环的长度。测试结果如下表所示:

 

测试名称 10000次 100000次 1000000次 10000000次
直接原始Array
4ms 38ms 410ms 4419ms
使用书中提到的方法
9ms 86ms 940ms 9625ms
使用字符拼接+
5ms 52ms 629ms 17724ms
测试截图

有以上测试结果可以很明显的看出用“直接原始Array和join”的执行效率是最快的,用书中提到到这个函数形式在 <=1000000的时候是最慢的。使用字符串拼接(+)的执行效率并不慢,在某些情况是可以忽略不计的。要说书中说的有误,也是不对的。书中的确 说:“使用Array做为StringBuffer,代替字符串拼接的操作。然后使用Array中的join方式处理要比直接进行字符串拼接效率要高。“.而书中给点函数的执行效率却不佳。很多程序时应该很少可以达到第三个测试点的循环次数!本来还想再加个测试,奈何浏览器不给力。测试到此结束!

现在自己感觉自己做了点无用功,做了扩展但以后却不想用了。1.效率问题的确是一点。2。对自己的扩展不满意(本想继续更改,但看到上面执行效率来看就不再做无用功了)

[转载]工具型网站首页的设计思考 首页设计类型分析

mikel阅读(1208)

.

 

什么是工具型网站

我们先从wikipedia上了解三组概念:

工具:是指能够方便人们完成工作的器具。

application:用来帮助用户完成某个单独的或是一组相关的工作的计算机软件。

web application:指通过使用Web和Web浏览器技术,跨越网络完成一个或多个任务的应用程序,通常需要使用Web浏览器。

由此可见,应用就是计算机领域的工具,工具型网站可定义为承载了一个或多个网页应用的网站。它专注于让用户完成一系列的任务,如注册、支付等。

 首页的用户需求

首页的用户大体说来可分为三类:不了解的新用户、有兴趣的新用户、老用户。

对于新用户而言,他们势必会问到这些问题:

我在这能做些什么?

这个网站能提供哪些对我很重要的东西?

对于已经有兴趣的用户也会有些其他的疑问:

我应该怎么开始?

我是否必须注册?如果是,应该怎样注册?

总得来说,首页应该解答新用户的疑惑并且帮助他们找到功能的入口;而对于老用户,则需要更明显、快捷的登录入口。

首页的目标

《designing for the social web》一书将用户的使用分为几个步骤:不了解——感兴趣——第一次使用——常规使用——有情感。而首页对于前面三个步骤至关重要!

 

这样看来,首页有两个目标:

1. 让不了解的用户了解网站并产生兴趣,最终促成用户使用。

2. 让有兴趣的用户尽快开始使用。

 首页的设计

在实际工作中我们会碰到几种不同类型的工具型网站,针对其网站内容的不同,其首页的表现形式也大不一样。

 集中应用型

这类网站本质上是一组或者多组web app,功能多且相对简单,不会产生大量用户数据。

1. 核心结构

将使用最多的功能展示在首页对于用户来说就是最快的入口。用户通过logo、 banner、网站声明等了解网站性质,然后就是找到自己要的功能。

2. 注册/登录模块

因为用户不想分散精力去记起用户名和密码,或者是他们太专注在自己要做的事情上,原则上只在必要时才让用户登录。但如果大部分功能都需要先登录,那么在首页直接展示登录表单是有必要的。

3. 案例

58同城

有两级目录:城市目录、功能目录。用户通过这两级目录很容易找到想要的功能。但使用这些功能大都不用注册,所以首页并没有明显的登录模块。

 

腾讯充值中心

首页的核心模块依旧在目录结构,用户由此了解网站主要业务以及如何开始自己的任务。

 

去除一些运营性需求,可以得到一个更加纯粹的充值网站:

 

数据管理型

这类网站本质上一个大的web app。功能流程复杂,会产生大量数据,往往承载了强大的数据管理功能。

核心框架

为了方便用户掌控及管理自己创建的内容,首页的策略一般会先引导用户创建账户或下载客户端(下载客户端的下一步也是注册),然后再开始任务。为了说服用户开始任务,它一般会包括以下几个模块:

价值声明:说明网站提供的功能及意义,快速打消访问者疑虑。

 

投入产出:告诉用户能得到什么,使访问者对产品的使用有预期,并建立信心。

 

视频介绍:让用户快速全面地了解网站。

 

权威推荐/典型用户:让用户产生同理心,增强对产品的信赖。

 

行动号召:当访问者产生兴趣时,适时地让他们开始使用。

 

这些模块其实分三个等级层层深入地说服用户使用产品:

 

尽管列出了这些模块,但实际情况中并不都必要,最终得看用户对产品的信心。Evernote在等级一就能说服用户,dropbox的页面则只有一个视频介绍。

1. 注册/登录模块

这类网站注册和登录模块都非常重要,一方面已注册用户数一般远远大于新用户数,另一方面首页很大一部分程度是为了注册的,老用户有固定的入口即可。但在首页我们需要强调哪一个?

这个问题更多地取决于产品的商业目标。受产品策略的影响,同样是拥有大量已注册用户且用户登录频繁的产品,gmail的首页强调登录,而facebook的首页则强调注册。我们也看到115网盘和华为网盘的首页明显就不同:

 

但无论如何,登录模块最好不要跳转页面,否则会增加老用户的操作成本。

2. 案例

mailchimp

首页包含价值声明、投入产出、视频介绍、典型用户、行动号召等模块,极力说服用户开始使用产品。

 

独立操作型

这类网站往往是一个几步就能完成任务的小型web app。

1. 核心框架

往往只有几个简单的控件和操作说明就能完成任务。由于使用成本不高,用户也会很乐于尝试使用。

2. 注册/登录

这类网站一般不需要注册或是简单的注册,也可以将注册需要的内容贯穿在任务操作中从而摒弃注册模块。

3. 案例

Minus

用户进入页面后能很快理解这是个分享类网站,只要将图片拖入到网页中并填写资料即可开始使用。

 

Senduit

简单的步骤指引告诉用户网站的功能和操作方式,使用时无需注册。

 

结语

网站的首页需要回答用户一些非常重要的问题,才能让新用户愿意使用我们的产品。

本文主要探讨了工具类网站首页的设计目标以及三种设计框架。而在实际项目中还需对更具体的场景进行分析、考虑运营性的需求、对单个模块的交互进行推敲,最终才能得到一个能吸引用户、留住用户的有效首页。

(本文出自Tencent CDC Blog,转载时请注明出处)

[转载]ASP.NET Session的七点认识

mikel阅读(1135)

[转载]ASP.NET Session的七点认识 – 世界末末日. – 博客园.

ASP.NET Session的使用当中我们会遇到很多的问题,那么这里我们来谈下经常出现的一些常用ASP.NET Session的理解:

  • ASP.NET Session的七点认识之一

  对于值类型的变量,Session中保存的是值类型的拷贝

Session[__test0=1;
int i = (int)Session[__test0]+1;
int j = (int)Session[__test0];

  结果i=2,j=1

  • ASP.NET Session的七点认识之二

  对于引用类新的变量,Session中保存的是引用

CDACommon cda =new CDACommon();
Session[
__test= cda.GetDataSet(select top 1 * from tb_customer);
DataSet ds 
= (DataSet)Session[__test];
DataSet ds2 
= (DataSet)Session[__test];
ds.Tables[
0].Rows[0][0]=9999;

结果ds.Tables[0].Rows[0][0]==”9999″ ds2.Tables[0].Rows[0][0]==”9999″;

  • ASP.NET Session的七点认识之三

  Session周期

新 的浏览器窗口启动后,开始一个新的Session,触发Global的Session_Start的调用,从第一个浏览器窗口打开的浏览器窗口不启动新的 Session。Session过期后,执行页面的提交也会触发Session_Start,等于是新的一个Session。

  • ASP.NET Session的七点认识之四

  调用Session

对于Web Service,每个方法的调用都会启动一个Session,可以用下面的方法来使多个调用在同一个Session里

CWSSyscfg cwsCfg = new CWSSyscfg();
cwsCfg.CookieContainer 
new System.Net.CookieContainer();

CWSSyscfg 是一个Web Service类,Web Service的给代理类设置CookieContainer属性,只要多个代理的CookieContainer属性是相同的值,则对这些Web Service的调用在同一个Session。可以用单例模式来实现。

  • ASP.NET Session的七点认识之五

  Session数据有效期

只要页面有提交活动,则Session的所有项都会保持,页面在20分钟(默认配置)内没有任何提交活动时Session会失效。Session内存储的多个数据项是整体失效的。

  • ASP.NET Session的七点认识之六

  Session的保存

在Session中如果保存的是非序列化的类比如DataView,在用SQLServer保存Session的模式下,无法使用。查看一个类是否是序列化的方法是,需看是否用[Serializable]来标记了该类。

  • ASP.NET Session的七点认识之七

  关于Session的清除

如 果我在Session中保存一个比较大的DataSet,这样aspnet_wp.exe占有的内存会很大,假如我退出了使用这个DataSet 的页面,我想释放该Session,我用Session.Clear() 或者DataSet.Clear()都不能使内存的占用降下来,即使Session过了期限,内存也没有降下来,比较困惑,谁能给我详细解释一下。

   要说到session这个东西,很多人可能都不屑一顾。这个东东嘛,n年前就开始做了,有啥好讲的啊。可是,在很多地方我们还是会发现一些问题,比如有 的人说,我的session_start激发了,怎么session_end没有啊,我在session_end做了些善后工作,这下没法完成了,怎么办 啊?

  最近看了些文章,结合自己的一些经验,想和大家一起讨论一下其中的说法。

   其实,很多这类的问题都是由一个东西引起的,它就是Session ID。首先,是不是我一个IE client起来,访问一个页面,只要我不关浏览器,Session ID就是一样的呢?很多人会想,应该是一样的吧,我浏览器都没关,Web Server总归会认为我是同一个client, 不会把Session ID变来变去的。要验证这个,让我们现在做一个简单的试验。用VS.NET创建一个简单的ASP.NET Web App. 在Web Form1上加个Button, 然后在页面的Page Prefix上enable trace. 接下来浏览这个页面,不停的click button来提交Request。感谢ASP.NET的这个trace功能,我们可以看到Session ID其实是在不停的变化的。也就是说,这时候在服务器端,根本就不关心这个client的存在,每次都觉得它是来自一个新的client.

   那这到底是怎么回事呢?OK,让我们在Page_Load里面加上一句,Session[“variable1”]=”testvalue”;然后 再做一下测试。Bingo, 现在Session ID就保持一致了。我想,很多人也许以前就没有注意到这点。这里我们可以得出一个结论:要建立一个持续的Session,咱们需要至少使用一下 Session变量,用行话来说,就是要至少往Session Dictionary中写入一次。

  不过,值得注意的是,这只是个必要条件,还不是充分条件。

   在提到下一个必要条件前,我们先来弄清一件事,如果我们在程序中间有Global.asax,里面有Session_OnStart, Session_OnEnd,上面的实验是不会成功的。原因是一旦定义了Session_OnStart处理函数后,Session的state就总是会 被保存了,即使里面是空的,这样的话,Session ID就不会改变了。因为Session这东西还是消耗资源的,所以在ASP.NET Web App中如果没有必要,你就不要把Session_OnStart, Session_End写在Global.asax中。

  上面的实验中,我们也可以看到,如果Session ID在变化,我们就跟踪不到Session_OnEnd, 一旦稳定下来,Session_OnEnd就出现了。

   现在,我们再来谈谈另一个条件,还是先从实验做起,我们在刚才例子的基础上(包括Session_OnStart, Session_OnEnd),在Page_Load的Session那行的下面加上一句,Session.Abandon(). 再来运行一把,咦,这时你会发现一点奇怪的地方, Session_OnEnd不执行了,尽管Session_OnStart执行过了一遍。(这里我们需要写一些log语句来观察到)而且,如果我们把 Session.Abandon()写在Button.OnClick事件里面,Session_OnEnd就立马执行了。奇怪吧, 这里有什么区别呢?

  这样,第二个必要条件就引发了,要让Session_OnEnd成功执行,至少要有一个Request已经被完整地执行过。上面的第一种情况,在Page_Load中就中止的话,第一个Request都没有执行完毕,Session_OnEnd就没法激发了。

  综合这两个必要条件,我们终于可以得出要让Session_OnEnd执行的充分条件了:

  ◆ 至少有一个Request成功完整地执行

  ◆ 至少存储一些data在Session State中。可以通过Session变量或者加上Session_OnStart来实现。

  最后声明一点,Session_OnEnd只在InProc模式中支持,也就是说,只在Session Data在ASP.NET worker process中时支持。

  ASP.NET Session的七点认识就谈到这里,对于ASP.NET Session的理解是不是有所帮助呢?

[转载]Android拍照后文件的单个扫描上传

mikel阅读(1250)

[转载]歇一歇(1)文件的单个扫描 – One Kid Sky – 博客园.

终于能有闲暇来写些东西,就当是对前段时间工作的总结。

 

公司年前制定一个基于GPS定位的进销存数据上报的软件,大体上就是这么个东西,本人主要负责终端开发(Android手机)。开发周期3个月,包括过年。

 

首先看一个问题:

 

项目需求1:现场拍照上传,或者选择已有文件上传。就是类似于微博中发表带图片的微博是一个道理。

 

首先,从现有文件选择上传这个比较容易解决:

 

只需要发起一个Intent:

 

action:Intent.ACTION_GET_CONTENT

 

type:image/*

 

startActivityForResult(intent,code);//一定要使用此方法哦,接收返回值。

 

比较头痛的是调用系统相机并且拍照上传,原因是这个样子的,首先,调用系统相机,拍照确认并返回到主界面,图片正常显示到你规划的 ImageView中,这个时候,你突发奇想,通过选择已有文件的方式,也就是通过调用系统相册,或者什么文件浏览器来再次加载刚才新鲜出炉的图片到你的 ImageView时,这是你会发现,不管是相册还是文件管理器中都没有这张图片,图片失踪了?

 

   按照以往的逻辑,当我们打开相机,按下开门,在没有任何“外因”的阻止下,照片理当应该存储在SDCard中或内部存储中,可是为什么通过调用系统相机 后拍的相片却怎么也通过相册看不到呢?但是当连接上电脑浏览手机中的文件时,你会发现刚才拍的相片就在那里,然后你重启手机,浏览相册,发现刚才拍照的相 片神奇的出现了,如果有同学遇到了相同的问题,那么恭喜你,看这篇文章就看对头了。

 

   是这个样子的,Android原生的系统相机,在拍照的结束后还做了一件事情,就是“扫描”,他会发出一个广播,来通知硬件区扫描自己,说白了就是刷 新,用过电脑的同学都明白刷新是个什么概念,当你浏览电脑文件夹时,刷新后会显示刚才操作的结果,比如图标的变化等,从“面”上讲,这是一个相同的概念。 当然,“扫描”这件事,在android系统初始化的时候也会执行,比如开机、重启,这就是为什么,当我重启手机后,相册就能显示这张照片的原因了,难道 说,每次为了能在相册中显示新拍的相片,都要重启手机吗???那老板还不K.O 我?

 

  手动扫描:

 

  但是调用系统相机就不一样了,刷新设备是要开发人员自己手动完成的(到目前为,我没有找到自动刷新的方式。。。)。

 

  看以下代码:

 

  

 

 1 private void doCamrea() {
 2         Intent capturePic = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 3         String picPartName = makePicName();
 4         String fullFileName = CurrentParameter.PATH + picPartName;
 5         // 因为有的手机在接收相机Intent返回的值data时,收不到,所以在这里发送一个uri,让其相机在内部进行一个本地存储的操作
 6         if (CurrentUtils.isHasSdcard()) {
 7             capturePic.putExtra(MediaStore.EXTRA_OUTPUT,
 8                     Uri.fromFile(new File(CurrentParameter.PATH, picPartName)));
 9             // 将这一张相片的名字保存在共享文件中,让scan在扫描的时候,再从这个共享文件中获取此相片名
10             SharedPreferences sd = this.getSharedPreferences(
11                     CurrentParameter.SHARED_FILE_NAME, 3);
12             Editor ed = sd.edit();
13             ed.putString("fullName", fullFileName);
14             ed.commit();
15         }
16 
17         startActivityForResult(capturePic, CurrentParameter.CAPTURE_PIC);
18     }

 


 

上面只是一个调用启动系统相机意图的方法,并没有看到过关于“扫描”的,因为并不是在这里处理,这是一个 startActivityForResult,有经验的同学都知道是什么意思了吧,结果当然是要放到Activity的回调方法 onActivityResult(……)中去做啦,但以上代码有个关键之处,请看第10行,没错,我开了一个SharedPreferences(共享 文件),并且将刚才拍照的相片的:绝对路径+文件名 保存在了这个共享文件中。暂且记住这一点,我们往下看:

 

onActivityResult方法:

 

 1 /**
 2  * 接受返回结果,并做业务处理
 3 */
 4     @Override
 5     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 6         // TODO Auto-generated method stub
 7         super.onActivityResult(requestCode, resultCode, data);
 8 
 9         switch (requestCode) {
10         // 相册
11         case CurrentParameter.LOCAL_PIC:
12             if (resultCode != RESULT_OK || data == null)
13                 return;
14             Uri thisUri = data.getData();
15             Log.i("----SysLog", thisUri.toString());
16             picPath = getPicPath(thisUri);
17             if (picPath != null) {
18                 // 设置缩略图,大小为30px
19                 previewBitmap = PictureUtils.getPreview(this, picPath,
20                         CurrentParameter.PREVIEW_PX);
21                 picPathMap.put(marker, picPath);
22             }
23             break;
24         // 相机
25         case CurrentParameter.CAPTURE_PIC:
26             if (resultCode == RESULT_OK || (Build.MODEL.toString().equals("D539") && resultCode == 0)/*酷派D539*/) {
27                 // 刷新后,相册就能显示这张图片了。
28                 SharedPreferences sd = PhotoMessageEditActivity.this
29                         .getSharedPreferences(
30                                 CurrentParameter.SHARED_FILE_NAME, 3);
31                 picPath = sd.getString("fullName", "is-fuck-null");
32                 CurrentUtils.scanOneFile(this,picPath);
33 
34                 previewBitmap = PictureUtils.getPreview(this, picPath,
35                         CurrentParameter.PREVIEW_PX);
36                 picPathMap.put(marker, picPath);
37             }
38             break;
39 .
40 .
41 .
42 .

 

没错,这段代码我只是为大家展示一部分,因为其他的都无关紧要。从24行开始,调用系统相机后并返回结果

 

26行,进行resultCode的判断,如果这个值是-1,说明你拍完照点击的是完成按钮,也就是说你“确定”要把这张图片保存并显示,|| 后面是对酷派D539的适配,比较变态人家用的是0~~~~,废话不多说接着看。

 

28行,开始从上面第一段代码创建的共享文件中取出当先相片的绝对名称,32行,调用scanOneFile方法,进行文件扫描。到此为止整个处理过程结束,那么最后一段代码,scanOneFile方法的是如何实现扫描的。

 

 1 /**
 2  * 扫描
 3  * 
 4  * @param filePath
 5 */
 6     public static void scanOneFile(Context context, String filePath) {
 7         // TODO Auto-generated method stub
 8         if (filePath != null && "is-fuck-null".equals(filePath))
 9             return;
10         Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
11         Uri uri = Uri.parse("file://" + filePath);
12         intent.setData(uri);
13         context.sendBroadcast(intent);
14 
15     }

 

接收的参数,第一个是context对象,对二个是这张图片的全路径名称,没错是发送一个广播,data为一个uri对象,通过这个uri对象,系统会精确扫描这一文件,让它无处可藏,原形毕露。

 

有的同学可能会问,为什么要用共享文件来存储文件名呢?第一段代码和第二段代码不是在一个类中吗?用一个全局变量不就可以了吗?这个问题留给你们自己去实验吧。

 

 

转载请标明出处,如果有更好的解决方式,希望大家共同探讨。

[转载]一个页面重构工程师眼中的“用户体验”

mikel阅读(1484)

一个页面重构工程师眼中的“用户体验”

 

在工业化设计融入人们生活的现今,用户体验一词就常常出现在人们的视线里,随着互联网web2.0时代的到来,大大小小的网站设计中也都开始关注用户体验的方面,对什么是用户体验(百度这四个字,比我写什么解释都好)就不做详细赘述了,相信大家比我了解的更加丰富。

用户体验从产品设计阶段便开始介入进来,如原型设计中交互模式设计、功能实现方式设计都融入了设计人员对用户的关怀,听过这样的一句话:“具有良好用 户体验的产品,不仅仅取决于一个有着丰富交互设计经验的产品设计师,还与产品生产过程中的每一个环节是否都具备良好的用户体验意识有一定的关系”。

今天我想从一个页面重构工程师的角度出发,从两个方面去谈谈在我的工作中,我所理解的用户体验,以及我做了哪些和用户体验有关的事情。

  一、 从可用到易用的细节处理

  1. 按钮、链接、导航菜单的鼠标触发状态、鼠标手型等

随着视觉设计的发展,对按钮、链接、或者导航菜单的表现方式变得异常丰富,比如:

 

这些图片丰富了对鼠标点击形式的视觉表现力。在基于功能可用的前提下,逐渐通过视觉渲染达到美化的效果,有了精美的设计图后,就需要页面重构工程师们加以切割,在代码化的过程中,通常要做如下考量:

可点击区域大小,例如(下图)尽管风格上似乎按钮只有10*10,但是在实现时,要考虑用户操作起来是不是很容易获取锚点,而不是点来点去找不到

 

鼠标操作类型的提示,鼠标输入提示、手型提示、文本区域提示、不可点击提示,尽管整个页面的视觉引导更重要,但用户在操作时,人眼一直跟随着鼠标或键盘的操作而转换,如果能伴随着正确的鼠标操作提示,更可增强引导作用。从而提升交互体验。

按钮风格的一致性,按钮当前状态和点击状态的统一,按钮按照功能所作的统一,

设计师天马行空的想象力,赋予了他们创造性,而我们既要保留他们的创造力还要抽象出一些共用特征,在我看来按钮类型有3种,如果能有效区分,对网站的整体风格的建立和强化交互感受方面都会有一定一致性,同时在页面构建过程中会抽离成公用信息,非常便于管理和维护。

1)如确认、取消等,可称之为贯穿型

2)如登录、加关注等,可称之为点睛型,这一型在实际工作中通常从视觉上都略强于贯穿型,所以会建议设计师做一定统一,有时候是风格上,有时候是结构上,再甚者就是大小比例上

3)如发博文、发微博等按钮,可称之为增强型,通常这个类型不会限制设计师按照标准类型去做,即便出来的是个异型,我们还是应该理解的。

4)最后就是无论哪种类型,都要注意是否有鼠标点击的hover状态,即便设计师没有设计,也要做出hover的交互效果,至少是预留,就我而言通常 都是在原按钮颜色基础上调整一下颜色深度作为hover状态,大部分按钮我都会考虑做出交互效果,当然也有例外,比如活动专题中的点击区域,通常不会增加 点击后的过分明显效果,如果非要做出一点效果,最多是调整一下文字本身的明暗度。(我承认我有些吹毛求疵)

 2. 图片的alt解释信息

 

通常拿到设计需求,我会要求设计师帮助梳理icon,一般会分两类,功能型、注释型。同时要求两种类型图片的像素区间要各自保持一致性,这样一来设计 师在整理的过程中就会意识到,有的文字没有必要加注释图片,有的是功能和注释类型的大小、风格可能不一致,梳理过程中设计师会去调整一下,同时也在创意的 过程中渗透一些规则。

 

第一、二行是注释型,第三行是功能型

 3. 因设计或排版而没有完全显示出来的文字信息的title提示

 

 

产品或用户经常抱怨“后面的文字看不到,这到底是什么内容?”之所以出现这样的情况,主要是没有考虑到文字信息出现的位置和当时这些文字的重要性,如 果是图片瀑布流,那文字信息的作用只是索引而已,如果是文章列表页或视频列表页、甚至正文页这样的终极页面,标题名称是务必要全部展示的。所以为了避免折 磨用户,一定要给显示不完全的信息增加title,在存在缺陷的情况下也能有让用户有些许安慰,否则你就太伤用户的心了。

4. 网站logo的权重设置H1、网站主要标题、标识的权重设置H2-H6、stong、em、b等(对搜索引擎的友好可读性)

考虑各个层面的用户体验,是为了让用户尽量全方位的感受到网页的无边界浏览,在视觉和交互充分发挥作用的背后,代码标签的选择,也从一定层面决定了用 户是否能够更好的使用,目前的一些搜索引擎,如Google、Baidu等,在过滤网页信息的过程中,有一套机制去寻找你网页中的主要内容,那些对搜索引 擎比较友好的标签会更有利于页面信息的抓取,在用户搜索的过程中,抛开企业间战略合作不谈,也会相对显示在比较靠前的位置(当然如果搜索引擎的广告效益非 常好的时候,或许第一屏还是与你无缘,这……你懂的)

5. 网站字色的一致性,链接色、提示色、内容字色等 (降低学习成本,培养用户习惯)

设计一套互动类产品(博客、视频、邮箱)或者用户功能型产品(消费类产品、资讯类产品等)同样面临着一个问题,用户习惯,人其实对规律会更容易产生安 全感也更容易增加舒适性。所以在网页的设计过程中,统一视觉感受不仅让用户安心的舒适的去浏览图片文字音乐多媒体等信息,也能培养用户认知。

因此在设计师天马行空的创造力面前,我总是横亘在他们面前的那个规范和逻辑的卫斗士。当设计师天马行空的时候我是不会也不敢去干扰他们,但如果谁告诉我风格确定时,我就会站出来披荆斩棘,要求设计师给出一整套设计规范,例如:

• 链接色分主链接色和辅助链接色,建议不要超过2种,增加类功能区域除外

• 文字色也是主辅都要有,同样不要超过两种(其实有多少种都可以,但你要考虑用户花多长时间适应你的五花八门)

• 提示信息又分普通提示、正确提示、错误提示、空状态提示等

• 什么图标类型分为功能型图标和注释型图标

6. 各种内容读取花较长时间的模块,在内容尚未加载成功时,先显示图片图片列表页、或视频截图列表页面,在图片尚未加载时显示初始图片,并固定位置,防止满屏跑图

 

 

7. 提示性文字位置在不干扰用户操作的前提下,交互一致性很重要,如固定在某一提示位置或不影响操作的颜色提示等

在处理表单过程中,会考虑提示信息所在位置,包括默认提示,出错提示,正确提示等,如果提示风格不统一,会中断用户行为,页面表单填写过程中的流畅度 非常重要,为什么会提起这一点,因为在实际工作中,如果没有交互设计经验,不论产品还是设计人员都经常会遗漏表单相关的各种提示信息,但这会影响页面构建 过程中HTML的结构,因此如果前期发现产品文档,或者设计稿都没有表现出相关内容,不妨提醒他们务必考虑并添加好,减少后期调整页面结构的冗余工作量。

 

很多人会说这些不是页面范畴,那页面是什么范畴呢,这些知识有产品范畴的、有设计范畴的、有用户研究范畴的、有交互范畴的,在我看来会这些最大的好处 是减少你的工作量,我认为这些都是很基础的知识,不是必须掌握的,但最好了解,了解的好处非常显而易见,就是面对不一定靠谱的需求时,可以有的放矢的给出 一些意见,从而减少一些返工,或者细碎的体验的增加。

 二、从慢慢等待到愉悦点击的变化

  1.页面模块的按需加载

 

由于互联网产品发展愈来愈趋向于规模化和正规化,在早年间采用全站只加载一个公用样式的时代已经过去了,取而代之的是对性能更优的模块化按需加载模 式,如上图所示,模块化显而易见的好处就是代码冗余相对较低,代码量也较小。除此之外模块化的好处还包括:结构清晰,易上手;频繁变更产品需求时的快速维 护和开发;快速下线局部模块;动态调整页面模块加载优先级时,无需剥离任何代码;便于多人协同开发;降低多人协同开发时,互相覆盖代码;适合开发大型产品 等很多优点。不论对用户,还是对维护开发都是非常有利的。

 2.页面公用元素复用

所谓公用元素,主要指:

reset类型

各类文字色

各类链接色

浮层框架

页面主框架

适用于本站的高复用补丁类型

3.文件调用的层级酌情减小、文件名酌情缩短

如,image/index/module1/limoumou/icon/fabiaopinglun.jpg 酌情优化目录层级

如,fabiaopinglun_default.jpg 、.CommentListContent_linedot{} 酌情优化文件名长度

 4. 请求数量和背景大小均衡处理

图片请求数方面,有时候你要考虑CSS Sprite 拼合图片减少背景数,同时还要注意拼合图片K数不要太大,以及拼合图片注意纵横比,建议拼竖图(做过实验,同样内容,横图体积大于竖图)

当背景图片需要平铺时,请酌情考虑背景图片大小,比如1px高的虚线,请不要切一个1*2的小图,比如1*10,1*50,主要考量在于1*2的小图的平铺次数。

影响页面性能的因素还有很多,例如hack的使用、position的使用, table的使用等等……关键是保持技术的新鲜,丰富自己的知识。

以上文章只是沉淀了一下过往我在页面重构工作中所领悟的用户体验。略显肤浅,可能大家会质疑,为什么写页面的还要自己加图标状态或者修改局部效果,要 知道在早期的互联网在分工上没有现在这样精专的分工,顶多分个前台、后台、产品。当然在当初这些事情不一定有如今的专业深度,但涉猎面的确较如今是更宽阔 的。所以早几年开始从事网页制作的朋友,都不会对设计制作界定的那么明显,在图形图片处理方面也是有一定认知和操作能力的。不是为了显摆什么,只是觉得知 识的深度和广度同样重要。

[转载]Asp.net MVC突然变慢,缓存消失的一种原因

mikel阅读(1118)

[转载]Asp.net MVC突然变慢,缓存消失的一种原因 – slmk – 博客园.

今天使用SQLite数据库时,在插入数据后,网站变慢。仔细检查代码,没有发现异常,只有一点:缓存莫名消失。缓存策略没有设置依赖性,百思不得其解:数据库改变,导致缓存消失?起初以为是SQLite的问题,Google后没有答案!

 

于是想写一个测试程序,写到一半,发现了问题:自己为了图省事将Sqlite数据库放在了bin目录下。现在看来bin是ASP.NET进程重点监 视目录,只要此目录中的文件改变了,就会导致应用程序重新编译,不但缓存丢失,还导致ASP.NET程序重新预热,访问自然变慢!由此看来ASP.NET MVC还是动态编译的。测试环境是ASP.NET MVC2.0+3.5框架+iis6.0+win2003。

 

解决方法也很简单:将sqlite数据库文件放到App_Data文件夹下,其实这里才是放我们数据文件的地方,例如一些txt文件,都应放于此。

 

其实动态编译对于动态发布才有意义吧,我们的Asp.net mvc都是一次性发布的,动态编译好像没有必要,可是又找不到预编译的发布选项,望牛人指点!

 

另附Asp.net中使用Sqlite数据库的注意事项:

 

1、连接字符串需要全路径:

 

 ConnectionString = "Data Source=" + HttpRuntime.AppDomainAppPath +
                "\\App_Data\\test.db3;Pooling=true;FailIfMissing=false";

 

2、“Unable to open database file”错误:

 

此错误是由于数据库路径不对,更确切地说是:上边的连接字符串的全路径中有一个目录不存在,才会提示该错误,因为FailIfMissing=false如果数据库文件不存在,会自动新建一个同名的数据库文件。这种连接字符串会导致另一个错误的出现:“XX表不存在”。看来还是改为true好用。

 

3、“数据库文件只读”错误:

 

该错误是由于没有对数据库文件所在目录的写权限,添加此用户的读写权限

 

 

 

var user= System.Security.Principal.WindowsIdentity.GetCurrent().Name //此windows用户需要写权限


[转载]SQLite(轻量级最佳数据库) 原理分析和开发应用

mikel阅读(1055)

[转载]SQLite(轻量级最佳数据库) 原理分析和开发应用 – spring yang – 博客园.

概述

SQLite介绍

自几十年前出现 的商业应用程序以来,数据库就成为软件应用程序的主要组成部分。正与数据库管理系统非常关键一样,它们也变得非常庞大,并占用了相当多的系统资源,增加了 管理的复杂性。随着软件应用程序逐渐模块模块化,一种新型数据库会比大型复杂的传统数据库管理系统更适应。嵌入式数据库直接在应用程序进程中运行,提供了 零配置(zero-configuration)运行模式,并且资源占用非常少。
SQLite是一个开源的嵌入式关系数据库,它在2000年由D. Richard Hipp发布,它的减少应用程序管理数据的开销,SQLite可移植性好,很容易使用,很小,高效而且可靠。
SQLite嵌入到使用它的应用程序中,它们共用相同的进程空间,而不是单独的一个进程。从外部看,它并不像一个RDBMS,但在进程内部,它却是完整的,自包含的数据库引擎。

嵌 入式数据库的一大好处就是在你的程序内部不需要网络配置,也不需要管理。因为客户端和服务器在同一进程空间运行。SQLite 的数据库权限只依赖于文件系统,没有用户帐户的概念。SQLite 有数据库级锁定,没有网络服务器。它需要的内存,其它开销很小,适合用于嵌入式设备。你需要做的仅仅是把它正确的编译到你的程序。

架构(architecture)

SQLite采用了模块的设计,它由三个子系统,包括8个独立的模块构成。

 

sqlite1

 

接口(Interface)
接口由SQLite C API组成,也就是说不管是程序、脚本语言还是库文件,最终都是通过它与SQLite交互的(我们通常用得较多的ODBC/JDBC最后也会转化为相应C API的调用)。
编译器(Compiler)
在 编译器中,分词器(Tokenizer)和分析器(Parser)对SQL进行语法检查,然后把它转化为底层能更方便处理的分层的数据结构—语法树, 然后把语法树传给代码生成器(code generator)进行处理。而代码生成器根据它生成一种针对SQLite的汇编代码,最后由虚拟机(Virtual Machine)执行。
虚拟机(Virtual Machine)
架构中最核心的部分是虚拟机,或者叫做虚拟 数据库引擎(Virtual Database Engine,VDBE)。它和Java虚拟机相似,解释执行字节代码。VDBE的字节代码由128个操作码(opcodes)构成,它们主要集中在数据 库操作。它的每一条指令都用来完成特定的数据库操作(比如打开一个表的游标)或者为这些操作栈空间的准备(比如压入参数)。总之,所有的这些指令都是为了 满足SQL命令的要求(关于VM,后面会做详细介绍)。
后端(Back-End)
后 端由B-树(B-tree),页缓存(page cache,pager)和操作系统接口(即系统调用)构成。B-tree和page cache共同对数据进行管理。B-tree的主要功能就是索引,它维护着各个页面之间的复杂的关系,便于快速找到所需数据。而pager的主要作用就是 通过OS接口在B-tree和Disk之间传递页面。

SQLite的特点(SQLite’s Features and Philosophy)
零配置(Zero Configuration)
可移植(Portability):
它 是运行在Windows,Linux,BSD,Mac OS X和一些商用Unix系统,比如Sun的Solaris,IBM的AIX,同样,它也可以工作在许多嵌入式操作系统下,比如 QNX,VxWorks,Palm OS, Symbin和Windows CE。
Compactness:
SQLite是被设计成轻量级,自包含的。one header file, one library, and you’re relational, no external database server required
简单(Simplicity)
灵活(Flexibility)
可靠(Reliability):
SQLite的核心大约有3万行标准C代码,这些代码都是模块化的,很容易阅读。

 

事务(Transaction)

事务的周期(Transaction Lifecycles)
程序与事务之间有两件事值得注意:
A、哪些对象在事务下运行——这直接与API有关。
B、事务的生命周期,即什么时候开始,什么时候结束以及它在什么时候开始影响别的连接(这点对于并发性很重要)——这涉及到SQLite的具体实现。
一 个连接(connection)可以包含多个(statement),而且每个连接有一个与数据库关联的B-tree和一个pager。Pager在连接 中起着很重要的作用,因为它管理事务、锁、内存缓存以及负责崩溃恢复(crash recovery)。当你进行数据库写操作时,记住最重要的一件事:在任何时候,只在一个事务下执行一个连接。这些回答了第一个问题。
一般来说,一个事务的生命和statement差不多,你也可以手动结束它。默认情况下,事务自动提交,当然你也可以通过BEGIN..COMMIT手动提交。接下来就是锁的问题。

sqlite02

关于这个图有以下几点值得注意:
A、一个事务可以在UNLOCKED,RESERVED或EXCLUSIVE三种状态下开始。默认情况下在UNLOCKED时开始。
B、白色框中的UNLOCKED, PENDING, SHARED和 RESERVED可以在一个数据库的同一时存在。
C、从灰色的PENDING开始,事情就变得严格起来,意味着事务想得到排斥锁(EXCLUSIVE)(注意与白色框中的区别)。
虽然锁有这么多状态,但是从体质上来说,只有两种情况:读事务和写事务。

 

读者可以从http://www.sqlite.org/下载SQLite 最新的版本
Cmd 进入命令行

 

     创建数据库文件:
   >SQLite3 d:\test.db 回车
就生成了一个test.db在d盘。
这样同时也SQLite3挂上了这个test.db

   用.help可以看看有什么命令
   >.help 回车即可

   看看有创建了多少表
   >.tables
  看表结构
   >.schema 表名
看看目前挂的数据库
   >.database
  如果要把查询输出到文件
   >.output 文件名
> 查询语句;
   把查询结果用屏幕输出
   >.output stdout

   把表结构输出,同时索引也会输出
    > .dump 表名
   退出
   >.exit 或者.quit

 

http://sqlite.phxsoftware.com/下载Ado.net驱动。
   下载了安装,在安装目录中存在System.Data.SQLite.dll
我们只需要拷贝这个文件到引用目录,并添加引用即可对SQLite数据库操作了
所有的Ado.net对象都是以SQLite开头的,比如SQLiteConnection
连接串只需要如下方式
Data Source=d:\test.db 或者DataSource=test.db–应用在和应用程序或者.net能够自动找到的目录
剩下的就很简单了~~

SQL语法
   由于以前用SQLServer或者ISeries,所以DDL的语法很汗颜
创建一个单个Primary Key的table
   CREATE TABLE  [Admin] (
[UserName] [nvarchar] (20)   PRIMARY KEY NOT NULL ,
[Password] [nvarchar] (50)   NOT NULL ,
[Rank] [smallint] NOT NULL ,
[MailServer] [nvarchar] (50)   NOT NULL ,
[MailUser] [nvarchar] (50)   NOT NULL ,
[MailPassword] [nvarchar] (50)   NOT NULL ,
[Mail] [nvarchar] (50)   NOT NULL
) ;
  创建一个多个Primary Key的table
   CREATE TABLE  [CodeDetail] (
[CdType] [nvarchar] (10)  NOT NULL ,
[CdCode] [nvarchar] (20)  NOT NULL ,
[CdString1] [ntext]   NOT NULL ,
[CdString2] [ntext]   NOT NULL ,
[CdString3] [ntext]   NOT NULL,
PRIMARY KEY (CdType,CdCode)
) ;
  创建索引
   CREATE  INDEX [IX_Account] ON  [Account]([IsCheck], [UserName]);
还可以视图等等。
 

 

SQLite 分页查询

写法1:

SELECT * FROM TABLE1 LIMIT  20 OFFSET 20 ;

写法2:

SELECT * FROM TABLE1 LIMIT 20 , 20;

SQLite 文件的压缩
在多次删除数据、插入数据、更新数据后,数据库体积增大,但实际有效数据量很小,则需要对数据库进行压缩、整理,把已经删除的数据从物理文件中移除。调用一下SQL命令即可:

VACUUM

VACUUM的实现

sqlite3

 

 

数据插入与更新

使用REPLACE替代INSERT、UPDATE命令。在无满足条件记录,则执行Insert,有满足条件记录,则执行UPDATE。

1
REPLACE INTO TABLE1(col1, col2, col3) VALUES(val1, val2,val3);

Insert or Replace Into 和Replace Into 的效果是一样的上面这句话也可以这样写

1
Insert or Replace INTO TABLE1(col1, col2, col3) VALUES(val1, val2,val3);

 

字符编码转换

sqlite3的源码中,提供了utf8ToUnicode()、unicodeToUtf8()、mbcsToUnicode()、 unicodeToMbcs()、sqlite3_win32_mbcs_to_utf8 ()、utf8ToMbcs ()等8个函数进行字符在不同编码间的转换,但未在sqlite3.def、sqlite3.h文件中列出,即未对外公开。这些函数中,都使用了 MultiByteToWideChar()、WideCharToMultiByte()两个函数实现字符间转换。

 

 

 

开发示例

using System;
using System.Data;
using System.Data.SQLite;
using System.Collections.Generic;
using System.IO;
 
namespace DataHelper
{
    public class SqLiteHelper
    {
        /// <summary>
        /// ConnectionString样例:Datasource=Test.db3;Pooling=true;FailIfMissing=false
        /// </summary>
        public static string ConnectionString
        {
            get
            {
                return  @"Data source= "+DataBasePath+";";
            }
            set { throw new NotImplementedException(); }
        }
 
        public static string DataBasePath
        {
            get { return "SpringYang.db";};
        }
 
        private static object lockObject = new object();
 
        private static void PrepareCommand(SQLiteCommand cmd, SQLiteConnection conn, string cmdText, List<SQLiteParameter> parameters)
        {
            if (conn.State != ConnectionState.Open)
                conn.Open();
            cmd.Parameters.Clear();
            cmd.Connection = conn;
            cmd.CommandText = cmdText;
            cmd.CommandType = CommandType.Text;
            cmd.CommandTimeout = 30;
            foreach (var parameter in parameters)
            {
                cmd.Parameters.Add(parameter);
            }
        }
 
        public static DataSet ExecuteQuery(string cmdText, List<SQLiteParameter> parameters)
        {
            lock (lockObject)
            {
                using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
                {
                    using (SQLiteCommand command = new SQLiteCommand())
                    {
                        DataSet ds = new DataSet();
                        PrepareCommand(command, conn, cmdText, parameters);
                        SQLiteDataAdapter da = new SQLiteDataAdapter(command);
                        da.Fill(ds);
                        return ds;
                    }
                }
            }
        }
 
        public static int ExecuteNonQuery(string cmdText, List<SQLiteParameter> parameters)
        {
            lock (lockObject)
            {
                using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
                {
                    using (SQLiteCommand command = new SQLiteCommand())
                    {
 
                        PrepareCommand(command, conn, cmdText, parameters);
                        return command.ExecuteNonQuery();
                    }
                }
            }
        }
 
        public static SQLiteDataReader ExecuteReader(string cmdText, List<SQLiteParameter> parameters)
        {
            lock (lockObject)
            {
                SQLiteConnection conn = new SQLiteConnection(ConnectionString);
 
                SQLiteCommand command = new SQLiteCommand();
 
                PrepareCommand(command, conn, cmdText, parameters);
                SQLiteDataReader sqLiteDataReader = command.ExecuteReader();
                return sqLiteDataReader;
            }
        }
 
        public static object ExecuteScalar(string cmdText, List<SQLiteParameter> parameters)
        {
            lock (lockObject)
            {
                using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
                {
                    using (SQLiteCommand command = new SQLiteCommand())
                    {
                        PrepareCommand(command, conn, cmdText, parameters);
                        return command.ExecuteScalar();
                    }
                }
            }
        }
        public static void CreateDataBase()
        {
            if (!File.Exists(DataBasePath))
                SQLiteConnection.CreateFile(DataBasePath);
            CreateTable();
        }
 
 
        public static void CreateTable()
        {
            ExecuteNonQuery(CodeDetailTabale, null);
        }
 
 
        private static string CodeDetailTabale
        {
            get
            {
                return @"CREATE TABLE  [CodeDetail] (
 [CdType] [nvarchar] (10)  NOT NULL ,
 [CdCode] [nvarchar] (20)  NOT NULL ,
 [CdString1] [ntext]   NOT NULL ,
 [CdString2] [ntext]   NOT NULL ,
 [CdString3] [ntext]   NOT NULL,
  PRIMARY KEY (CdType,CdCode)        
   ) ;";
            }
        }
    }
}

示例讲解

A、使用到自己定义的锁private static object lockObject = new object();

B、使用完连接后都进行关闭操作。使用了using

C、创建数据库命令:SQLiteConnection.CreateFile(DataBasePath);

最后再讲解个Insert or Replace into的经典用法
?
1
2

Insert or Replace INTO User(ID, Name,Age) Select old.ID,new.Name,new.Age From
(select ‘Spring Yang’ as Name, ’25’ as Age) as new left join (Select ID,Name from User where Name = ‘Spring Yang’ ) as old on old.Name = new.Name

[转载]C#数据本地存储方案之SQLite

mikel阅读(1035)

[转载]C#数据本地存储方案之SQLite – 陆敏技 – 博客园.

即使是做网络应用,在断线情况下,也需要考虑数据的本地存储。在SQLite出现之前,数据量大的情况下,我们一直使用ACCESS,数据量小,则文件存储。ACCESS不支持事务原子性,在断电情况下(这种情况总是会发生)会导致数据很难恢复。

 

一:安装

SQLITE,是一款轻型的数据库,是遵守ACID的关联式数据库管理系统。我直接使用的是http://sqlite.phxsoftware.com/(An open source ADO.NET provider for the SQLite database engine)。下载完毕是一个EXE,安装后根目录如下:

clip_image002

Bin下有一个测试工具,可以查看本地运行SQLITE的各项性能指标。

 

二:新建数据库

安装完毕后,打开visual studio,新建数据连接,可以看到数据源多了一项SQLite。

clip_image004

新建连接,如下图。SQLITE的数据库,保存后是一个文件。

clip_image006

 

三:数据库维护

可以在VS中方面的维护SQLITE数据,如下图:

clip_image008

可以在VS中使用类似SQL查询分析器的功能,如下图:

clip_image010

 

四:混合模式

安装完毕,可以直接在项目集的引用中,多了

System.Data.SQLite

System.Data.SQLite.Linq

两个程序集,由于http://sqlite.phxsoftware.com/的System.Data.SQLite是混合模式程序集,是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集。故需要在App.config中配置如下参数。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

 

 

五:SQLiteHelper

最后,提供一个自己写的SQLiteHelper:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Data;
using System.Data.Common;

namespace Com.Luminji.DataService.SQLHelpers
{
    public class SQLiteHelper
    {
        /// <summary>
        /// ConnectionString样例:Data Source=Test.db3;Pooling=true;FailIfMissing=false
        /// </summary>
        public static string ConnectionString { get; set; }

        private static void PrepareCommand(SQLiteCommand cmd, SQLiteConnection conn, string cmdText, params object[] p)
        {
            if (conn.State != ConnectionState.Open)
                conn.Open();
            cmd.Parameters.Clear();
            cmd.Connection = conn;
            cmd.CommandText = cmdText;
            cmd.CommandType = CommandType.Text;
            cmd.CommandTimeout = 30;
            if (p != null)
            {
                foreach (object parm in p)
                    cmd.Parameters.AddWithValue(string.Empty, parm);
            }
        }

        public static DataSet ExecuteQuery(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    DataSet ds = new DataSet();
                    PrepareCommand(command, conn, cmdText, p);
                    SQLiteDataAdapter da = new SQLiteDataAdapter(command);
                    da.Fill(ds);
                    return ds;
                }
            }
        }

        public static int ExecuteNonQuery(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    PrepareCommand(command, conn, cmdText, p);
                    return command.ExecuteNonQuery();
                }
            }
        }

        public static SQLiteDataReader ExecuteReader(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    PrepareCommand(command, conn, cmdText, p);
                    return command.ExecuteReader(CommandBehavior.CloseConnection);
                }
            }
        }

        public static object ExecuteScalar(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    PrepareCommand(command, conn, cmdText, p);
                    return command.ExecuteScalar();
                }
            }
        }

    }
}
 
六:附注
SQLite官方网站: http://www.sqlite. org/ 时第一眼看到关于SQLite的特性。
  1. ACID事务
  2. 零配置 – 无需安装和管理配置
  3. 储存在单一磁盘文件中的一个完整的数据库
  4. 数据库文件可以在不同字节顺序的机器间自由的共享
  5. 支持数据库大小至2TB
  6. 足够小, 大致3万行C代码, 250K
  7. 比一些流行的数据库在大部分普通数据库操作要快
  8. 简单, 轻松的API
  9. 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定
  10. 良好注释的源代码, 并且有着90%以上的测试覆盖率
  11. 独立: 没有额外依赖
  12. Source完全的Open, 你可以用于任何用途, 包括出售它
  13. 支持多种开发语言,C, PHP, Perl, Java, ASP .NET,Python

[原创]EasyUI的组件加载两次的问题

mikel阅读(1351)

最近项目调试的时候在firebug中发现EasyUI的组件异步加载时总是加载两次url属性的问题,代码如下:

$(function(){
$('#comb').combobox({
url:'/area/list'
});
});

<input id="comb" class="e a syui-combobox" type="text" name="comb" />

从以上的代码可以看到页面初始化的时候加载一次combobox组件的url,
html代码中也利用class声明了一次combobox,这样就导致,easyUI解析html代码的时候先解析class声明中的combobox组件请求了一次url然后又调用了js中初始化的代码初始化一次数据,导致重复加载,避免的方法就是只用一种初始化方法来声明easyUI组件,避免重复的提交请求
修改后的代码如下:

$(function(){
$('#comb').combobox({
url:'/area/list'
});
});

<input id="comb" type="text" name="comb" />