[转载]关于商城系统中商品类别的设计

mikel阅读(876)

[转载]关于商城系统中商品类别的设计 – 七七可可 – 博客园.

以XiZiShop为例,如下效果图:

上图红色方框所示为一级类别。

上图所示为二级类别、三级类别和筛选条件类别,以及筛选条件的值。

数据库设计步骤如下:

【步骤一】对于一级类别、二级类别,三级类别,我统一把它们放在一张表里面,名曰:栏目表(T_Columns),各级的父子关系通过表中的FatherCol这一列去进行关联。如下图:

【步骤二】建立好存储一级类别、二级类别和三级类别的表之后,再开始考虑“筛选条件表”(表名称:T_FilterCategory)和“筛选条件值表”(表名称:T_FilterAttributes) 的设计。

然后再分析“筛选条件表”和“筛选条件值表”之间的对应关系。

同一个筛选条件可以有多个筛选条件值。例如:对于手机“品牌”来说,它下面可以有多个筛选条件值:三星、摩托罗拉、苹果…..

同一个筛选条件值又可以被多个筛选条件所拥有。

观察上图中的红色方框内容,我发现手机“品牌”里有“三星”,笔记本“品牌”里也有“三星”,换句话说,“三星”既属于手机“品牌”又属于电脑“品牌”,这样一来,“三星”就可以对应多个“品牌”了,因此得出结论同一个筛选条件值有可能被多个筛选条件所拥有。

综合上述1和2,我得出“筛选条件表”和“筛选条件值表”之间是多对多的关系。于是,按照数据库设计规范,需要为这两张表添加一个中间表,名曰:(RF_FilterCategory_TFilterAttributes)

然后建立它们的外键关联:

然后再分析“筛选条件表”和“栏目表”之间的对应关系:

“栏目表”属于“筛选条件表”的父表,即只有“栏目表”中的三级类别才和“筛选条件表”直接关联,如下图:

分析它们的关系:

1:一个三级类别可以有多个筛选条件。

2:同一个筛选条件可以被多个三级类别拥有。如下图:

如上图:“品牌”和“价格”可以同时被三级类别中的“手机”和“对讲机”拥有。所以,“栏目表”和“筛选条件表”之间也是多对多的关系。于是又需要建一个 中间表,名曰:(RF_Columns_FilterCategory),如下图:

然后建立外键关系如下图:

设计完这一步,整个关系图如下:

到了这一步,似乎数据库已经设计的很完美了,但是它有一处致命的硬伤!

这个硬伤在哪儿呢,答案见续篇。

[转载]开源中最好的Web开发的资源

mikel阅读(1416)

[转载]开源中最好的Web开发的资源 | 酷壳 – CoolShell.cn.

学习HTML 5编程和设计

服务器端的软件

  • ★ Node.js 是服务器端的 JavaScript 环境,其使用了异步事件驱动模式。其让Node.js在很多互联网应用体系结构下获得非常不错的性能。 源码 和 实时演示
  • PhantomJS 也是一个服务器端的 JavaScript API的WebKit。其支持各种Web标准: DOM 处理, CSS 选择器, JSON, Canvas, 和 SVG
  • Lighttpd 一个轻量级的开源Web服务器。新闻,文档,benchmarks, bugs, 和 download. Lighttpd 支撑了几个非常著名的 Web 2.0 网站,如:YouTube, wikipedia 和 meebo.
  • NGinx, 性能巨高无比的轻量级的Web服务器。比Apache高多了。花了6年的时间,终于走到了1.0版。
  • Apache HTTP Server 是一个很流行的并支持多个流行的操作系统的Web服务器。
  • ★ PHP 可能是最流行的服务器端的Web脚本动态处理语言。
  • 当然,还有 RubyPythonErlangPerlJava.NETAndroidC++Go, Fantom,CoffeeScriptD, …

PHP 框架和工具

  • ★ WordPress 是一个基于博客系统的开源软件。参看《WordPress是怎么赢的?
  • Drupal 是一个内容管理系统 (CMS).
  • Centurion 是一个新出现的开源 CMS ,一个灵然的 PHP5 Content Management Framework. 使用 Zend Framework, 其组件坚持通用,简单,清楚和可重用的设计原则。
  • phpBB 一个开源的论坛(国内的Discuz!更多)
  • ★ SimplePie : 超快的,易用的,  RSS  和 Atom feed PHP解析。
  • ★ PHPthumb, PHP 图片处理库
  • ★ PHPMailer 强大的全功能的PHP邮件库
  • PubSubHubbub协议,一个简单,开放, server-to-server 的 pubsub (publish/subscribe) 协议——Atom and RSS的扩展。
  • 更多的请参看 – 20个你应该知道PHP库 和 9个强大免费的PHP库

数据库

  • ★ Apache CouchDB 是一个面向文档的数据库管理系统。它提供以JSON 作为数据格式的REST 接口来对其进行操作,并可以通过视图来操纵文档的组织和呈现。.源码.
  • MonoQL 是一个采用PHP+ExtJS开发的MySQL数据库管理工具。界面极像一个桌面应用程序,支持大部分常用的功能包括:表格设计,数据浏览/编辑,数据导入/导出和高级查询等。
  • MariaDBMySQL的一个分支,由MySQL 创始人Monty Widenius 所开发。GPL,用来对抗Oracle所有的MySQL的license的不测。自Oracle收购SUN以来,整个社区对于MySQL前途的担忧就没有停止过。
  • ★ SQLite 不像常见的客户端/服务器结构范例,SQLite引擎不是个程序与之通信的独立进程,而是连接到程序中成为它的一个主要部分。所以主要的通信协议是在编程 语言内的直接API调用。这在消耗总量、延迟时间和整体简单性上有积极的作用。整个数据库(定义、表、索引和数据本身)都在宿主主机上存储在一个单一的文 件中。它的简单的设计是通过在开始一个事务的时候锁定整个数据文件而完成的。库实现了多数的SQL-92标准,包括事务,就是代表原子性、一致性、隔离性 和持久性的(ACID),触发器和多数的复杂查询。不进行类型检查。你可以把字符串插入到整数列中。某些用户发现这是使数据库更加有用的创新,特别是与无 类型的脚本语言一起使用的时候。其他用户认为这是主要的缺点。
  • SQL 在线设计编辑器,这一节的那个图片就是这个在线编辑器的样子了。一个画数据库图表的在线工具。很强大。

API 和 在线数据

在线代码和媒体编辑器

  • ★ CodeRun Studio一个基于JavaScript语言开发的跨平台的集成开发环境,它立足于云计算的设计思路,方便开发者在浏览器端便可以轻松开发、调试和部署网络应用程序。(参看《Coderun.com 在线开发IDE》)
  • Cloud9 IDE – 一个基于Node.JS构建的JavaScript程序开发Web IDE。它拥有一个非常快的文本编辑器支持为JS, HTML, CSS和这几种的混合代码进行着色显示。
  • ★ jsFiddle – Javascript的在线运行展示框架,这个工具可以有效的帮助web前端开发人员来有效分享和演示前端效果,其简单而强大 (JavaScript, MooTools, jQuery, Prototype, YUI, Glow and Dojo, HTML, CSS)
  • Akshell一种云服务,它使用服务端的JavaScript和在线的IDE帮助开发者进行快速应用程序开发。 它还提供云托管,所以部署是即时的。
  • JSONeditor, 一个好用的JSON 编辑器
  • ★ TinyMCE 一个轻量级的基于浏览器的所见即所得编辑器,支持目前流行的各种浏览器,由JavaScript写成。
  • Ext Designer 是一个桌面应用工具,帮助你快速开发基于ExtJS 的用户界面。
  • ★  LucidChart,一款基于最新的html5技术的在线图表绘制软件,功能强大,速度快捷,运行此软件需要支持html5的浏览器。
  • Balsamiq Mockups, 产品设计师绘制线框图或产品原型界面的利器。
  • Color Scheme Designer 3 – 一个免费的线上调色工具
  • ★ Pixlr, 是一个来自瑞典基于Flash的免费在线图片处理网站。除了操作介面和功能接近Photoshop,还是多语言版本,支持简体中文。(以前酷壳介绍过
  • Aviary, 是一个基于HTML5 的在线图片处理工具,可以很容易的对图片进行后期处理。 Aviary API
  • Favicon Generator, 线上favicon(16×16)制作工具。

代码资源和版本控制

  • ★ GitHub 是一个用于使用Git版本控制系统的项目的基于互联网的存取服务。
  • Git 是一个由Linus为了更好地管理linux内核开发而创立的分布式版本控制/软件配置管理软件。其巨快无比,高效,采用了分布式版本库的方式,不必服务器端软件支持,使源代码的发布和交流极其方便。
  • Google Code 谷歌公司官方的开发者网站,包含各种开发技术的API、开发工具、以及开发技术参考资料。
  • Google Libraries API Google 将优秀的 JavaScript 框架部署在其 CDN 上,在我们的网站上使用 Google Libraries API 可以加速 JavaScript 框架的加载速度。
  • Snipplr 一个开放的源代码技巧分享社区,号称Code 2.0。和一般的源码分享网站不同,它针对的并不是大型网站源码,而是一些编程的代码技巧。

JavaScript 桌面应用框架

  • ★ jQuery 是一个快速、简单的JavaScript library, 它简化了HTML 文件的traversing,事件处理、动画、Ajax 互动,从而方便了网页制作的快速发展。  源码APIAPI浏览很不错的文档.
  • ★ 官方的 jQuery User Interface (UI) library (演示和文档). 源码,Themes RollerDownload.
  • YUI 2 — Yahoo! User Interface Library
  • Mootools, 一个超级轻量级的 web2.0 JavaScript framework
  • Prototype 提供面向对象的Javascript和AJAX
  • Dojo The Dojo Toolkit,一个强大的无法被打败的面向对象JavaScript框架。主要由三大模块组成:Core、Dijit、DojoX。Core提供 Ajax,events,packaging,CSS-based querying,animations,JSON等相关操作API。Dijit是一个可更换皮肤,基于模板的WEB UI控件库。DojoX包括一些创新/新颖的代码和控件:DateGrid,charts,离线应用,跨浏览器矢量绘图等。
  • ★ Ext JS 4, 业内最强大的 JavaScript framework。
  • PHP.js, 一个开源的JavaScript 库,它尝试在JavaScript 中实现PHP 函数。在你的项目中导入PHP.JS 库,可以在静态页面使用你喜欢的PHP 函数。

JavaScript 移动和触摸框架

  • ★ jQuery Mobile : 是 jQuery 在手机上和平板设备上的版本。jQuery Mobile 不仅会给主流移动平台带来jQuery核心库,而且会发布一个完整统一的jQuery移动UI框架。支持全球主流的移动平台。jQuery Mobile开发团队说:能开发这个项目,我们非常兴奋。移动Web太需要一个跨浏览器的框架,让开发人员开发出真正的移动Web网站。我们将尽全力去满 足这样的需求。 Sources.
  • Zepto.js Zepto.js 是支持移动WebKit浏览器的JavaScript框架,具有与jQuery兼容的语法。2-5k的库,通过不错的API处理绝大多数的基本工作。 Sources.
  • MicroJS : Microjs网站应用列出了很多轻量的Javascript类库和框架,它们都很小,大部分小于5kb。这样你不需要因为只需要一个功能就要加载一个JS的框架。
  • ★ PhoneGap :是一款开源的手机应用开发平台,它仅仅只用HTML和JavaScript语言就可以制作出能在多个移动设备上运行的应用。 Sources.
  • ★ Sencha Touch Sencha Touch 是一个支持多种智能手机平台(iPhone, Android, 和BlackBerry)的 HTML5 框架。Sencha Touch可以让你的Web App看起来像Native App。美丽的用户界面组件和丰富的数据管理,全部基于最新的HTML5和CSS3的 WEB标准,全面兼容Android和Apple iOS设备。
  • JQtouch, 是一个jQuery 的插件,主要用于手机上的Webkit 浏览器上实现一些包括动画、列表导航、默认应用样式等各种常见UI效果的JavaScript 库。 Sources.
  • DHTMLX Touch 针对移动和触摸设备的JavaScript 框架。DHTMLX Touch基于HTML5,创建移动web应用。它不只是一组UI 小工具,而是一个完整的框架,可以针对移动和触摸设备创建跨平台的web应用。它兼容主流的web浏览器,用DHTMLX Touch创建的应用,可以在iPad、iPhone、Android智能手机等上面运行流畅。

jQuery 插件

  • Waypoints 是一个jQuery 用来实现捕获各种滚动事件的插件,例如实现无翻页的内容浏览,或者固定某个元素不让滚动等等。支持主流浏览器版本。
  • Lazy loader 插件可以实现图片的延迟加载,当网页比较长的时候,会先只加载用户视窗内的图片,视窗外的图片会等到你拖动滚动条至后面才加载,这样有效的避免了因图片过多而加载慢的弊端。
  • TweenJS : 一个简单和强大的 tweening / animation 的Javascript库。
  • Easings 类Css3的jQuery 动画插件
  • Spritely 这个插件可以创建出如flash一样的动画效果,比如:在页面上有一只飞动的小鸟,一个动态滚动的背景等。
  • File Upload, jQuery 文件上传插件4.4.1
  • Slideshow/Carousel 插件. Sources.
  • Supersized – 全屏式的背景/幻灯片插件
  • Masonry i一款非常酷的自动排版插件,这款jQuery工具可以根据网格来自动排列水平和垂直元素,超越原来的css. Sources.
  • jQuery 简单 Layout 演示,管理各种边栏式,可改变大小式的布局。
  • Flexigrid – jQuery 数据表插件
  • Isotope绝对是一个令人难以置信的jQuery插件,你可以用它来创建动态和智能布局。你可以隐藏和显示与过滤项目,重新排序和整理甚至更多。
  • Super Gestures jQuery 插件可以实现鼠标手势的功能。
  • MouseWheel 是由Brandon Aaron开发的jQuery插件,用于添加跨浏览器的鼠标滚轮支持。
  • AutoSuggest jQuery 插件可以让你添加一些自动完成的功能。
  • qTip 一个漂亮的jQuery 的工具提示插件,这个插件功能相当强大。
  • jQuery Charts and graphic 用来制作图表。
  • jQuery Tools– The missing UI library

其它 jQuery 资源

HTML5 视频播放器

  • ★ Popcorn.js 是一个HTML5 Video框架,它提供了易于使用的API来同步交互式内容,让操作HTML5 Video元素的属性,方法和事件变得简单易用。 (来自Mozilla)
  • LeanBack Player HTML5视频播放器,没有依赖任何JavaScript框架。支持全屏播放,音量控制,在同一个页面中播放多个视频。 (来自Google)
  • Vid.ly 为你上传的视频提供转换功能,并且为转换后的视频创建一个短网址。通过Vid.ly,让你的视频可以在14种不同的浏览器和设备上播放,不需要再去考虑将 要浏览视频的人使用什么设备了,以避免各各软件巨头之间的利益之争带来了不兼容,给用户带来了巨大的困扰,短网址让你可以通过Twitter、 Facebook等方式方便分享视频。Vid.ly还可以通过html代码嵌入到其他网页中。Vid.ly免费帐户空间为1GB,免费帐户也没有播放或浏 览限制。

JavaScript 音频处理与可视化效果

  • ★ 使用HTML5 和 Flash, SoundManager V2 只用单一API的提供了可靠,简单和强大的跨平台的音频处理。
  • DSP, JavaScript的声音Digital Signal Processing
  • The Radiolab Hyper Audio Player v1, 带给你 WNYC Radiolab, SoundCloud 和 Mozilla Drumbeat
  • jPlayer, 一个 jQuery HTML5 音频/ 视频库,功能齐全的API

JavaScript 图形 和 3D

  • ★ Processing.js是一个开放的编程语言,在不使用Flash或Java小程序的前提下, 可以实现程序图像、动画和互动的应用。其使用Web标准,无需任何插件。
  • ★ Javascript 3D 引擎: ThreeJS 由 Mr Doob 开发,一个轻量级的 3D 引擎,不需要了解细节,傻瓜都能使用。这个引擎可以使用<canvas>, <svg> 和 WebGL.
  • Shader Toy, 一款使用WebGL的在线着色器编辑器(2D/3D). 基于在线的应用架构使您无需下载任何软件即可开始体验. Shader Toy包含大量实用着色器, 诸如光线追踪, 场景距离渲染, 球体, 隧道, 变形, 后期处理特效等.
  • PhiloGL, Sencha的PhiloGL是首个WebGL开发工具之一,提供了高水准的功能,来构建WebGL应用。Sencha创建了几个演示,来描述框架交互式3D虚拟化的能力,比如3D view of global temperature changes
  • WebGL Inspector 你就Firebug等Web调试工具一样,这个是 WebGL的调试工具。
  • WebGL frameworks 由 Khronos Group 收集的一个WebGL框架列表。
  • EaselJS, 一个使用html5的canvas的 JavaScript 库. Sources.
  • JavaScript Game Frameworks 免费的JS游戏框架列表。另,可参看 JS游戏框架列表
  • Raphaël是一个小型的JavaScript 库,用来简化在页面上显示向量图的工作。你可以用它在页面上绘制各种图表、并进行图片的剪切、旋转等操作。参看Javascript向量图Lib–Raphaël
  • jQuery SVG 插件让你可以了 SVG canvas 进行交互。
  • Google chart tools –  参看本站的使用Google API做统计图
  • Arbor.js, 是一个利用webworkers和jQuery创建的数据图形可视化JavaScript框架。它为图形组织和屏幕刷新处理提供了一个高效、力导向布局算法。

JavaScript 浏览器接口 (HTML5)

  • ★ Modernizr – 是一个专为HTML5 和CSS3 开发的功能检测类库,可以根据浏览器对HTML5 和CSS3 的支持程度提供更加便捷的前端优化方案.Sources. 一个有用的列表 cross-browser Polyfills
  • HTML5Shiv : 该项目的目的是为了让IE 能识别HTML5 的元素。
  • Polyfills : 这个项目收集了一些代码片段其用Javascript支持不同的浏览器的特别功能,有些代码需要Flash。
  • YepNopeJS : 一个异步的条件式的加载器。Sources.
  • jQuery CSS3 Finalise : 是否厌倦了为每一个浏览器的CSS3属性加前缀?
  • ★ Amplify.js :一套用于web应用数据管理和应用程序通讯的 jQuery 组件库。 提供简单易用的API接口。Amplify的目标是通过为各种数据源提供一个统一的程序接口简化各种格式数据的数据处理。Amplify的存储组件使用 localStorage 和 sessionStorage标准处理客户端的存储信息,对一些老的浏览器支持可能有问题。Amplify’为jQuery的ajax方法request 增加了一些额外的特性。 Sources.
  • History.js 优美地支持了HTML5 History/State APIs
  • Socket.IO Web的socket编程。

JavaScript 工具

  • ★  {{mustaches}} 小型的 JavaScript 模板引擎。
  • json:select(), CSS式的JSON选择器
  • HeadJS, 异步JavaScript装载。其最大特点就是不仅可以按顺序执行还可以并发装载载js。
  • JsDoc Toolkit是一款辅助工具,你只需要根据约定在JavaScript 代码中添加相应的注释,它就可以根据这些注释来自动生成API文档。
  • Responsive image, 一个试验性的项目,用来处理responsive layouts 式的图片。
  • UglifyJS是基于NodeJS的Javascript语法解析/压缩/格式化工具,它支持任何CommonJS模块系统的Javascript平台。
  • Dhteumeuleu, 交互式的 DOM 脚本和DHTML 的开源演示。
  • Backbone是 一个前端 JS 代码 MVC 框架,被著名的 37signals 用来构建他们的移动客户端。它不可取代 Jquery,不可取代现有的Template 库。而是和这些结合起来构建复杂的 web 前端交互应用。如果项目涉及大量的 javascript 代码,实现很多复杂的前端交互功能,首先你会想到把数据和展示分离。使用 Jquery 的 selector 和 callback 可以轻松做到这点。但是对于富客户端的WEB应用大量代码的结构化组织非常必要。Backbone 就提供了 javascript 代码的组织的功能。Backbone 主要包括 models, collections, views 和 events, controller 。

客户端和模拟器

CSS3 和 字库

Website (FULL) 模板

  • ★ HTML5 Boilerplate 是一个HTML5 / CSS / js模板,是实现跨浏览器正常化、性能优化,稳定的可选功能如跨域Ajax和Flash的最佳实践。 项目的开发商称之为技巧集合,目的是满足您开发一个跨浏览器,并且面向未来的网站的需求。 Sources.
  • HTML5 starter pack 是一个干净的和有组织的目录结构,其可适合很多项目,还有一些很常用的文件,以及简单的Photoshop设计模板。
  • ★ Initializr 是一个HTML5 模板生成器,其可以帮你在15秒内创建一个HTML5的项目。
  • Animated Portfolio Gallery教程
  • Slick MobileApp Website 如果通过 jQuery 和 CSS 制作一个手机应用的网站。
  • RSS Reader 如果通过 jQuery Mobile 创建一个RSS Reader
  • Single Page Applications 使用jQuery的朋友们 (Backbone, Underscore, …)创建单一页面。
  • Google TV Optimized Templates, 传统电视已经开始和网路融合,但现阶段产业仍然正在摸索之中,为此将来的网页亦会有结构上的改变。Google TV Optimized Templates是 一个用HTML/JavaScript制成的开源软体,一如其名是一个对Google TV作出了最佳化的的网页范本,其特色是以遥控器作为操作的前提,令使用者无需输入任何文字就可以进行控制。未来除了会有专用遥控器外,还会采用智能手机 透过W-iFi控制Google TV的方法。Optimized Templates的界面中左方会展示分类,右方会显示该分类下的影片截图,影片播放、切换、全画面表示都可透过键盘上的方向键、Backspace或 Enter等键完成,方便今后的网站开发人员借镜。HTML5 版的模板使用了 Google TV UI library, jQuery  和 Closure 。

(全文完)

[转载]条码扫描二维码扫描——ZXing android 源码简化

mikel阅读(1063)

[转载]条码扫描二维码扫描——ZXing android 源码简化 – indexRoad – 博客园.

前言

最近公司的Android项目需要用到摄像头做条码或二维码的扫描,Google一下,发现一个以Apache License 2.0 开源的 ZXing项目。Zxing项目里的Android实现太过复杂多余东西太多,得对其进行简化。

前提条件

下载源代码:点击这里

编译核心库:Zxing的主页上有介绍具体步骤,大家也可以参照这篇博文:android 条码识别软件开发全解析(续2详解绝杀!)

导入项目

打开Eclipse 导入 源码中的 Android 项目,然后右击项目 选择“Build path”——》”Add External Archives” 把核心库 core.jar文件加入到项目中。

此时编译一下项目,会发现报错,“ Multiple substitutions specified in non-positional format; did you mean to add the formatted=”false” attribute?”之类的。打开raw 下的Values 发现错误是在一个<String>上。这里把 “preferences_custom_product_search_summary” 里的  %s  %f  全部都改成  %1$s  %1$f(因为我们用不到多国语言,建议只保留默认的Value ,其他全部删除)。

原因:由于新的SDK采用了新版本的aapt(Android项目编译器),这个版本的aapt编译起来会比老版本更加的严格,然后在Android最新的开发文档的描述String的部分,已经说明如何去设置 %s 等符号

“If you need to format your strings using String.format(String, Object…) , then you can do so by putting your format arguments in the string resource. For example, with the following resource:

<string name=”welcome_messages”>Hello, %1$s! You have %2$d new messages.</string>

In this example, the format string has two arguments: %1$s is a string and %2$d is a decimal number. You can format the string with arguements from your application…“

经过以上步骤后项目应该就可以运行了。

但是ZXing的android项目东西太多了,有很多是我们不需要的,得新建另一个项目简化它。

简化

在开始前大致介绍一下简化ZXing需要用到各个包 、类的职责。

  • CaptureActivity。这个是启动Activity 也就是扫描器(如果是第一安装,它还会跳转到帮助界面)。
  • CaptureActivityHandler 解码处理类,负责调用另外的线程进行解码。
  • DecodeThread 解码的线程。
  • com.google.zxing.client.android.camera 包,摄像头控制包。
  • ViewfinderView 自定义的View,就是我们看见的拍摄时中间的框框了。

新建另一个项目

新建另一个项目将启动的Activity命名为CaptureActivity,并导入核心库。项目新建完成后我们打开 CaptureActivity 的布局文件,我这里为main。把里面的XML修改为:


可以看到在XML里面用到了 ViewfinderView 自定义view 。所以新建一个View 的包,然后把:ViewfinderView 和 ViewfinderResultPointCallback 靠到里面(记得对应修改XML里面的包)。

打开 CaptureActivity 覆盖 onCreate 方法:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//初始化 CameraManager
CameraManager.init(getApplication());

viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
txtResult = (TextView) findViewById(R.id.txtResult);
hasSurface = false;
inactivityTimer = new InactivityTimer(this);
}

这里调用到的 CameraManager 类是控制摄像头的包里的类。新建一个camera包把:com.google.zxing.client.android.camera 里面的类全部拷入,另外我把PlanarYUVLuminanceSource也拷入到这个包里面。根据错误的提示来修正代码,主要是修改正包结构。(整 个简化的流程都是如此:“根据错误提示,修改代码”)。

在修改的过程中,有很多是关于R 资源的问题,在此我们需要将Values  里面的两个xml资源文件拷入项目中:colos.xml 和ids.xml 。 ctrl+b 一下看看error 是不是少了很多。在CameraManager中有些地方需要用到项目的配置,这里需要把配置直接写入代码中:

// SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
//是否使用前灯
// if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) {
// FlashlightManager.enableFlashlight();
// }
FlashlightManager.enableFlashlight();

使用摄像头需要加入相应的权限:






当View 和 camera 包里的错误修正完成后,我们继续来看CaptureActivity。

覆盖onResume方法初始化摄像头:

@Override
protected void onResume() {
super.onResume();
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (hasSurface) {
initCamera(surfaceHolder);
} else {
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
decodeFormats = null;
characterSet = null;

playBeep = true;
AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
playBeep = false;
}
initBeepSound();
vibrate = true;
}

private void initCamera(SurfaceHolder surfaceHolder) {
try {
CameraManager.get().openDriver(surfaceHolder);
} catch (IOException ioe) {
return;
} catch (RuntimeException e) {
return;
}
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats,
characterSet);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
hasSurface = false;

}

initCamera () 方法用于初始化摄像头,如果排除了所有的error ,运行项目时就可以看到大致扫描界面了。 surfaceHolder.addCallback(this);表示让CaptureActivity实现其callback接口。

handler = new CaptureActivityHandler(this, decodeFormats, characterSet) 用于进行扫描解码处理。
解码

上面的步骤主要都是用于对摄像头的控制,而解码的真正工作入口是在CaptureActivityHandler 里面的。新建一个Decoding包把以下文件拷入包中:

CaptureActivityHandler
DecodeFormatManager
DecodeHandler
DecodeThread
FinishListener
InactivityTimer
Intents

由于我们的包结构和Zxing 项目的有所不同所以需要注意一下类的可访问性

同样开始ctrl+B 编译一下,然后开始修正错误。

在CaptureActivityHandler 里 把 handleMessage 里的部分方法先注释掉如:“decode_succeeded ”分支,这是解码成功时调用 CaptureActivity 展示解码的结果。

在DecodeThread 类里,修改部分涉及Preference配置的代码:

DecodeThread(CaptureActivity activity,
Vector decodeFormats,
String characterSet,
ResultPointCallback resultPointCallback) {

this.activity = activity;
handlerInitLatch = new CountDownLatch(1);

hints = new Hashtable(3);

// // The prefs can't change while the thread is running, so pick them up once here.
// if (decodeFormats == null || decodeFormats.isEmpty()) {
// SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
// decodeFormats = new Vector();
// if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D, true)) {
// decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
// }
// if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_QR, true)) {
// decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
// }
// if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_DATA_MATRIX, true)) {
// decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
// }
// }
if (decodeFormats == null || decodeFormats.isEmpty()) {
decodeFormats = new Vector();
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);

}

hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);

if (characterSet != null) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}

hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
}

这里是设置 解码的类型,我们现在默认将所有类型都加入。

错误类型基本上都是:包结构、PreferencesActivity 的配置 、类可访问性的问题。根据错误提示耐心把错误解决。
返回解码结果

还记得在 CaptureActivityHandler 的 messagehandler 里注销掉的Case分支吗?现在CaptureActivity 里实现它。

public void handleDecode(Result obj, Bitmap barcode) {
inactivityTimer.onActivity();
viewfinderView.drawResultBitmap(barcode);
playBeepSoundAndVibrate();
txtResult.setText(obj.getBarcodeFormat().toString() + ":"
+ obj.getText());
}

最后

ZXing的简化已基本完成,有几位是可以运行成功的?呵呵。

下面是CaptureActivity的源码:

public class CaptureActivity extends Activity implements Callback {

private CaptureActivityHandler handler;
private ViewfinderView viewfinderView;
private boolean hasSurface;
private Vector decodeFormats;
private String characterSet;
private TextView txtResult;
private InactivityTimer inactivityTimer;
private MediaPlayer mediaPlayer;
private boolean playBeep;
private static final float BEEP_VOLUME = 0.10f;
private boolean vibrate;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//初始化 CameraManager
CameraManager.init(getApplication());

viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
txtResult = (TextView) findViewById(R.id.txtResult);
hasSurface = false;
inactivityTimer = new InactivityTimer(this);
}

@Override
protected void onResume() {
super.onResume();
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (hasSurface) {
initCamera(surfaceHolder);
} else {
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
decodeFormats = null;
characterSet = null;

playBeep = true;
AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
playBeep = false;
}
initBeepSound();
vibrate = true;
}

@Override
protected void onPause() {
super.onPause();
if (handler != null) {
handler.quitSynchronously();
handler = null;
}
CameraManager.get().closeDriver();
}

@Override
protected void onDestroy() {
inactivityTimer.shutdown();
super.onDestroy();
}

private void initCamera(SurfaceHolder surfaceHolder) {
try {
CameraManager.get().openDriver(surfaceHolder);
} catch (IOException ioe) {
return;
} catch (RuntimeException e) {
return;
}
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats,
characterSet);
}
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
hasSurface = false;

}

public ViewfinderView getViewfinderView() {
return viewfinderView;
}

public Handler getHandler() {
return handler;
}

public void drawViewfinder() {
viewfinderView.drawViewfinder();

}

public void handleDecode(Result obj, Bitmap barcode) {
inactivityTimer.onActivity();
viewfinderView.drawResultBitmap(barcode);
playBeepSoundAndVibrate();
txtResult.setText(obj.getBarcodeFormat().toString() + ":"
+ obj.getText());
}

private void initBeepSound() {
if (playBeep &amp;&amp; mediaPlayer == null) {
// The volume on STREAM_SYSTEM is not adjustable, and users found it
// too loud,
// so we now play on the music stream.
setVolumeControlStream(AudioManager.STREAM_MUSIC);
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setOnCompletionListener(beepListener);

AssetFileDescriptor file = getResources().openRawResourceFd(
R.raw.beep);
try {
mediaPlayer.setDataSource(file.getFileDescriptor(),
file.getStartOffset(), file.getLength());
file.close();
mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
mediaPlayer.prepare();
} catch (IOException e) {
mediaPlayer = null;
}
}
}

private static final long VIBRATE_DURATION = 200L;

private void playBeepSoundAndVibrate() {
if (playBeep &amp;&amp; mediaPlayer != null) {
mediaPlayer.start();
}
if (vibrate) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(VIBRATE_DURATION);
}
}

/**
* When the beep has finished playing, rewind to queue up another one.
*/
private final OnCompletionListener beepListener = new OnCompletionListener() {
public void onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.seekTo(0);
}
};

简化过的包结构图:

简化后的ZXing 更加方便我们了解ZXing项目 是如何解码的。只要仔细查看源码,进行单点跟踪调试,相信大家很容易能理解。

[转载]Android从SDCard中取得图片并设置为桌面背景

mikel阅读(1111)

[转载]Android从SDCard中取得图片并设置为桌面背景 – 老牛啊 – 博客园.

1、 把背景图片push到SDCard中
adb push MM-320×480.png /sdcard/
确认是否已经存在了,可以到SDCard看一下:
adb shell
cd sdcard
ls
可以看到刚才上传的图片:
2、编写Activity程序
  public static final String  TAG       = "WallpaperActivity";

    /**
     * 背景图片名称
     */
    private static final String FILE_NAME = "MM-320x480.png";

    /** 
     * @see android.app.Activity#onCreate(android.os.Bundle)
     */
    public void onCreate(Bundle cycle) {
        super.onCreate(cycle);
        super.setContentView(R.layout.wallpaper);
        
        // 取得背景图片
        Bitmap wallpaper = this.getWallpager();

        // 设置桌面背景
        this.putWallpaper(wallpaper);
    }

    /**
     * 取得SDCard中的背景图片
     */
    private Bitmap getWallpager() {
        // SDCard的路径,也就是“/sdcard/”
        File root = Environment.getExternalStorageDirectory();
        // MM图片
        File wall = new File(root, FILE_NAME);
        // MM图片路径
        String path = wall.getAbsolutePath();

        Log.d(TAG, "MM文件路径为:" + path);

        return BitmapFactory.decodeFile(path);
    }

    /**
     * 设置桌面背景
     */
    private void putWallpaper(Bitmap bitmap) {
        try {
            WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
            wallpaperManager.setBitmap(bitmap);
        } catch (IOException e) {
            String msg = "设置桌面背景发生异常:" + e.getLocalizedMessage();
            Log.e(TAG, "设置桌面背景发生异常!", e);
            Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
        }
    }

3、增加权限
AndroidManifest.xml文件中增加设置桌面背景权限:

<!-- 设置桌面背景的权限 -->
<uses-permission android:name="android.permission.SET_WALLPAPER" />

4、运行程序
运行程序,再回到桌面,背景已经改变了。

[转载]Android动画之Frame动画实战

mikel阅读(925)

[转载]Android动画之Frame动画实战 – 老牛啊 – 博客园.

Android动画分为Tween动画和Frame动画,Tween动画主要包括图片的放大缩小、旋转、透明度变化、移动等等操作;Frame动画则简单得多了,就是把一张张的图片连续播放产生动画效果。

本节主要介绍一下Frame动画,Tween动画会在后面的文章中介绍,敬请关注。

Frame动画主要是通过AnimationDrawable类来实现的,它有start()和stop()两个重要的方法来启动和停止动画。Frame 动画一般通过XML文件配置,在工程的res/anim目录下创建一个XML配置文件,该配置文件有一个<animation-list>根 元素和若干个<item>子元素。

实现一个人跳舞的Frame动画,6张图片如下所示:

1、把这6张图片放到res/drawable目录下,分别取名为:p01.png,p02.png,p03.png,p04.png,p05.png,p06.png。

2、在res/anim目录下创建一个XML配置文件,文件名为:dance.xml,文件内容:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:apk="http://schemas.android.com/apk/res/android" apk:oneshot="false">
<item apk:drawable="@drawable/p01" apk:duration="500" />
<item apk:drawable="@drawable/p02" apk:duration="500" />
<item apk:drawable="@drawable/p03" apk:duration="500" />
<item apk:drawable="@drawable/p04" apk:duration="500" />
<item apk:drawable="@drawable/p05" apk:duration="500" />
<item apk:drawable="@drawable/p06" apk:duration="500" />
</animation-list>



apk:oneshot指示是否只运行一次,设置为false则意味着循环播放。

3、在res/layout目录下创建layout配置文件dance.xml,文件内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:apk="http://schemas.android.com/apk/res/android" apk:orientation="vertical" apk:layout_width="fill_parent" apk:layout_height="fill_parent">
<!-- Frame动画图片 -->
<ImageView apk:id="@+id/ImgDance" apk:layout_width="wrap_content" apk:layout_height="wrap_content" apk:background="@anim/dance" />

<!-- 动画控制按钮 -->
<LinearLayout apk:layout_width="fill_parent" apk:layout_height="wrap_content" apk:orientation="horizontal">
<Button apk:text="开始" apk:layout_width="wrap_content" apk:layout_height="wrap_content" apk:onClick="onStartDance" />
<Button apk:text="结束" apk:layout_width="wrap_content" apk:layout_height="wrap_content" apk:onClick="onStopDance" />
</LinearLayout>
</LinearLayout>

apk:background使用上面的动画作为背景,意味着要取得动画,只要取得该View的背景即可,当然可以在代码中通过设置背景的方式指定;

apk:onClick指示按钮的动作,当然可以在代码中通过实现OnClickListener的方式实现。

4、Activity代码:

/**
 * Copyright (c) 2004-2011 All Rights Reserved.
 */
package com.aboy.android.study.animation;

import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

import com.aboy.android.study.R;

/**
 * Frame动画
 *
 * @author obullxl@gmail.com
 * @version $Id: FrameActivity.java, v 0.1 2011-6-10 下午12:49:46 oldbulla Exp $
 */
public class FrameActivity extends Activity {
    public static final String TAG = "FrameActivity";
    
    // 显示动画的组件
    private ImageView imgDance;
    // Frame动画
    private AnimationDrawable animDance;
    
    /** 
     * @see android.app.Activity#onCreate(android.os.Bundle)
     */
    public void onCreate(Bundle cycle) {
        super.onCreate(cycle);
        super.setContentView(R.layout.dance);
        
        // 实例化组件
        this.imgDance = (ImageView) super.findViewById(R.id.ImgDance);
        
        // 获得背景(6个图片形成的动画)
        this.animDance = (AnimationDrawable) this.imgDance.getBackground();
    }
    
    /**
     * 按钮:开始‘跳舞’动画
     */
    public void onStartDance(View view) {
        this.animDance.start();
    }
    
    /**
     * 按钮:停止‘跳舞’动画
     */
    public void onStopDance(View view) {
        this.animDance.stop();
    }
    
}

代码就那么的几行,运行之后,点击“开始”按钮后,动画一直不停的播放,直到点击“停止”为止。

[转载]看懂SqlServer查询计划

mikel阅读(1036)

[转载]看懂SqlServer查询计划 – fish-li – 博客园.

对于SQLServer的优化来说,可能优化查询是很常见的事情。关于数据库的优化,本身也是一个涉及面比较的广的话题, 本文只谈优化查询时如何看懂SQLServer查询计划。由于本人对SQLServer的认识有限,如有错误,也恳请您在发现后及时批评指正。

首先,打开【SQL Server Management Studio】,输入一个查询语句看看SQLServer是如何显示查询计划的吧。
说明:本文所演示的数据库,是本人写的一个演示程序专用的数据库, 可以在此网页中下载。

select v.OrderID, v.CustomerID, v.CustomerName, v.OrderDate, v.SumMoney, v.Finished
from   OrdersView as v
where v.OrderDate >= '2010-12-1' and v.OrderDate < '2011-12-1';

其中,OrdersView是一个视图,其定义如下:

SELECT     dbo.Orders.OrderID, dbo.Orders.CustomerID, dbo.Orders.OrderDate, 
            dbo.Orders.SumMoney, dbo.Orders.Finished, 
            ISNULL(dbo.Customers.CustomerName, N'') AS CustomerName
FROM         dbo.Orders LEFT OUTER JOIN
                dbo.Customers ON dbo.Orders.CustomerID = dbo.Customers.CustomerID

对于前一句查询,SqlServer给出的查询计划如下(点击工具栏上的【显示估计的执行计划】按钮):

从这个图,我们至少可以得到3个有用的信息:
1. 哪些执行步骤花费的成本比较高。显然,最右边的二个步骤的成本是比较高的。
2. 哪些执行步骤产生的数据量比较多。对于每个步骤所产生的数据量, SqlServer的执行计划是用【线条粗细】来表示的,因此也很容易地从分辨出来。
3. 每一步执行了什么样的动作。

对于一个比较慢的查询来说,我们通常首先要知道哪些步骤的成本比较高,进而,可以尝试一些改进的方法。 一般来说,如果您不能通过:提高硬件性能或者调整OS,SqlServer的设置之类的方式来解决问题,那么剩下的可选方法通常也只有以下这些了:
1. 为【scan】这类操作增加相应字段的索引。
2. 有时重建索引或许也是有效的,具体情形请参考后文。
3. 调整语句结构,引导SqlServer采用其它的查询方案去执行。
4. 调整表结构(分表或者分区)。

下面再来说说一些很重要的理论知识,这些内容对于执行计划的理解是很有帮助的。

Sql Server 查找记录的方法

说到这里,不得不说SqlServer的索引了。SqlServer有二种索引:聚集索引和非聚集索引。二者的差别在于:【聚集索引】直接决定了记录的存放位置, 或者说:根据聚集索引可以直接获取到记录。【非聚集索引】保存了二个信息:1.相应索引字段的值,2.记录对应聚集索引的位置(如果表没有聚集索引则保存记录指针)。 因此,如果能通过【聚集索引】来查找记录,显然也是最快的。

Sql Server 会有以下方法来查找您需要的数据记录:
1. 【Table Scan】:遍历整个表,查找所匹配的记录行。这个操作将会一行一行的检查,当然,效率也是最差的。
2. 【Index Scan】:根据索引,从表中过滤出来一部分记录,再查找所匹配的记录行,显示比第一种方式的查找范围要小,因此比【Table Scan】要快。
3. 【Index Seek】:根据索引,定位(获取)记录的存放位置,然后取得记录,因此,比起前二种方式会更快。
4. 【Clustered Index Scan】:和【Table Scan】一样。注意:不要以为这里有个Index,就认为不一样了。 其实它的意思是说:按聚集索引来逐行扫描每一行记录,因为记录就是按聚集索引来顺序存放的。 而【Table Scan】只是说:要扫描的表没有聚集索引而已,因此这二个操作本质上也是一样的。
5. 【Clustered Index Seek】:直接根据聚集索引获取记录,最快!

所以,当发现某个查询比较慢时,可以首先检查哪些操作的成本比较高,再看看那些操作是查找记录时, 是不是【Table Scan】或者【Clustered Index Scan】,如果确实和这二种操作类型有关,则要考虑增加索引来解决了。 不过,增加索引后,也会影响数据表的修改动作,因为修改数据表时,要更新相应字段的索引。所以索引过多,也会影响性能。 还有一种情况是不适合增加索引的:某个字段用0或1表示的状态。例如可能有绝大多数是1,那么此时加索引根本就没有意义。 这时只能考虑为0或者1这二种情况分开来保存了,分表或者分区都是不错的选择。

如果不能通过增加索引和调整表来解决,那么可以试试调整语句结构,引导SqlServer采用其它的查询方案去执行。 这种方法要求: 1.对语句所要完成的功能很清楚, 2.对要查询的数据表结构很清楚, 3.对相关的业务背景知识很清楚。 如果能通过这种方法去解决,当然也是很好的解决方法了。不过,有时SqlServer比较智能,即使你调整语句结构,也不会影响它的执行计划。

如何比较二个同样功能的语句的性能好坏呢,我建议采用二种方法: 1. 直接把二个查询语句放在【SQL Server Management Studio】,然后去看它们的【执行计划】,SqlServer会以百分比的方式告诉你二个查询的【查询开销】。 这种方法简单,通常也是可以参考的,不过,有时也会不准,具体原因请接着往下看(可能索引统计信息过旧)。
2. 根据真实的程序调用,写相应的测试代码去调用:这种方法就麻烦一些,但是它更能代表现实调用情况, 得到的结果也是更具有参考价值的,因此也是值得的。

Sql Server Join 方式

在Sql Server中,我们每个join命令,都会在内部执行时,采用三种更具体的方式来运行:

1. 【Nested Loops join】,如果一个联接输入很小,而另一个联接输入很大而且已在其联接列上创建了索引, 则索引 Nested Loops 连接是最快的联接操作,因为它们需要的 I/O 和比较都最少。

嵌套循环联接也称为“嵌套迭代”,它将一个联接输入用作外部输入表(显示为图形执行计划中的顶端输入),将另一个联接输入用作内部(底端)输入表。外部循环逐行处理外部输入表。内部循环会针对每个外部行执行,在内部输入表中搜索匹配行。可以用下面的伪码来理解:

foreach(row r1 in outer table)
    foreach(row r2 in inner table)
        if( r1, r2 符合匹配条件 )
            output(r1, r2);

最简单的情况是,搜索时扫描整个表或索引;这称为“单纯嵌套循环联接”。如果搜索时使用索引,则称为“索引嵌套循环联接”。如果将索引生成为查询计划的一部分(并在查询完成后立即将索引破坏),则称为“临时索引嵌套循环联接”。查询优化器考虑了所有这些不同情况。

如果外部输入较小而内部输入较大且预先创建了索引,则嵌套循环联接尤其有效。在许多小事务中(如那些只影响较小的一组行的事务),索引嵌套循环联接优于合并联接和哈希联接。但在大型查询中,嵌套循环联接通常不是最佳选择。

2. 【Merge Join】,如果两个联接输入并不小但已在二者联接列上排序(例如,如果它们是通过扫描已排序的索引获得的),则合并联 接是最快的联接操作。如果两个联接输入都很大,而且这两个输入的大小差不多,则预先排序的合并联接提供的性能与哈希联接相近。但是,如果这两个输入的大小 相差很大,则哈希联接操作通常快得多。

合并联接要求两个输入都在合并列上排序,而合并列由联接谓词的等效 (ON) 子句定义。通常,查询优化器扫描索引(如果在适当的一组列上存在索引),或在合并联接的下面放一个排序运算符。在极少数情况下,虽然可能有多个等效子句, 但只用其中一些可用的等效子句获得合并列。

由于每个输入都已排序,因此 Merge Join 运算符将从每个输入获取一行并将其进行比较。例如,对于内联接操作,如果行相等则返回。如果行不相等,则废弃值较小的行并从该输入获得另一行。这一过程将重复进行,直到处理完所有的行为止。

合并联接操作可以是常规操作,也可以是多对多操作。多对多合并联接使用临时表存储行(会影响效率)。如果每个输入中有重复值,则在处理其中一个输入中的每个重复项时,另一个输入必须重绕到重复项的开始位置。 可以创建唯一索引告诉SqlServer不会有重复值。

如果存在驻留谓词,则所有满足合并谓词的行都将对该驻留谓词取值,而只返回那些满足该驻留谓词的行。

合并联接本身的速度很快,但如果需要排序操作,选择合并联接就会非常费时。然而,如果数据量很大且能够从现有 B 树索引中获得预排序的所需数据,则合并联接通常是最快的可用联接算法。

3. 【Hash Join】,哈希联接可以有效处理未排序的大型非索引输入。它们对复杂查询的中间结果很有用,因为: 1. 中间结果未经索引(除非已经显式保存到磁盘上然后创建索引),而且通常不为查询计划中的下一个操作进行适当的排序。 2. 查询优化器只估计中间结果的大小。由于对于复杂查询,估计可能有很大的误差,因此如果中间结果比预期的大得多,则处理中间结果的算法不仅必须有效而且必须适度弱化。

哈希联接可以减少使用非规范化。非规范化一般通过减少联接操作获得更好的性能,尽管这样做有冗余之险(如不一致的更新)。哈希联接则减少使用非规范化的需要。哈希联接使垂直分区(用单独的文件或索引代表单个表中的几组列)得以成为物理数据库设计的可行选项。

哈希联接有两种输入:生成输入和探测输入。查询优化器指派这些角色,使两个输入中较小的那个作为生成输入。

哈希联接用于多种设置匹配操作:内部联接;左外部联接、右外部联接和完全外部联接;左半联接和右半联接;交集;联合和差异。此外,哈希联接的某种变形可以 进行重复删除和分组,例如 SUM(salary) GROUP BY department。这些修改对生成和探测角色只使用一个输入。

哈希联接又分为3个类型:内存中的哈希联接、Grace 哈希联接和递归哈希联接。

内存中的哈希联接:哈希联接先扫描或计算整个生成输入,然后在内存中生成哈希表。根据计算得出的哈希键的哈希值,将每行插入哈希存储桶。如果整个生成输入 小于可用内存,则可以将所有行都插入哈希表中。生成阶段之后是探测阶段。一次一行地对整个探测输入进行扫描或计算,并为每个探测行计算哈希键的值,扫描相 应的哈希存储桶并生成匹配项。

Grace 哈希联接:如果生成输入大于内存,哈希联接将分为几步进行。这称为“Grace 哈希联接”。每一步都分为生成阶段和探测阶段。首先,消耗整个生成和探测输入并将其分区(使用哈希键上的哈希函数)为多个文件。对哈希键使用哈希函数可以 保证任意两个联接记录一定位于相同的文件对中。因此,联接两个大输入的任务简化为相同任务的多个较小的实例。然后将哈希联接应用于每对分区文件。

递归哈希联接:如果生成输入非常大,以至于标准外部合并的输入需要多个合并级别,则需要多个分区步骤和多个分区级别。如果只有某些分区较大,则只需对那些 分区使用附加的分区步骤。为了使所有分区步骤尽可能快,将使用大的异步 I/O 操作以便单个线程就能使多个磁盘驱动器繁忙工作。

在优化过程中不能始终确定使用哪种哈希联接。因此,SQL Server 开始时使用内存中的哈希联接,然后根据生成输入的大小逐渐转换到 Grace 哈希联接和递归哈希联接。
如果优化器错误地预计两个输入中哪个较小并由此确定哪个作为生成输入,生成角色和探测角色将动态反转。哈希联接确保使用较小的溢出文件作为生成输入。这一技术称为“角色反转”。至少一个文件溢出到磁盘后,哈希联接中才会发生角色反转。

说明:您也可以显式的指定联接方式,SqlServer会尽量尊重您的选择。比如你可以这样写:inner loop join, left outer merge join, inner hash join
但是,我还是建议您不要这样做,因为SqlServer的选择基本上都是正确的,不信您可以试一下。

好了,说了一大堆理论东西,再来个实际的例子来解释一下吧。

更具体执行过程

前面,我给出一张图片,它反映了SqlServer在执行某个查询的执行计划,但它反映的信息可能不太细致,当然,您可以把鼠标指标移动某个节点上,会有以下信息出现:

刚好,我装的是中文版的,上面都是汉字,我也不多说了。我要说的是另一种方式的执行过程,比这个包含更多的执行信息, 而且是实际的执行情况。(当然,您也可以继续使用图形方式,在运行查询前点击工具栏上的【包括实际的执行计划】按钮)

让我们再次回到【SQL Server Management Studio】,输入以下语句,然后执行。

set statistics profile on 

select v.OrderID, v.CustomerID, v.CustomerName, v.OrderDate, v.SumMoney, v.Finished
from   OrdersView as v
where v.OrderDate >= '2010-12-1' and v.OrderDate < '2011-12-1';

注意:现在加了一句,【set statistics profile on 】,得到的结果如下:

可以从图片上看到,执行查询后,得到二个表格,上面的表格显示了查询的结果,下面的表格显示了查询的执行过程。相比本文的第一张图片, 这张图片可能在直观上不太友好,但是,它能反映更多的信息,而且尤其在比较复杂的查询时,可能看起来更容易,因为对于复杂的查询,【执行计划】的步骤太多,图形方式会造成图形过大,不容易观察。 而且这张执行过程表格能反映2个很有价值的数据(前二列)。

还是来看看这个【执行过程表格】吧。我来挑几个重要的说一下。
【Rows】:表示在一个执行步骤中,所产生的记录条数。(真实数据,非预期)
【Executes】:表示某个执行步骤被执行的次数。(真实数据,非预期)
【Stmt Text】:表示要执行的步骤的描述。
【EstimateRows】:表示要预期返回多少行数据。

在这个【执行过程表格】中,对于优化查询来说,我认为前三列是比较重要的。对于前二列,我上面也解释了,意思也很清楚。 前二列的数字也大致反映了那些步骤所花的成本,对于比较慢的查询中,应该留意它们。 【Stmt Text】会告诉你每个步骤做了什么事情。对于这种表格,它所要表达的其实是一种树型信息(一行就表示在图形方式下的一个节点), 所以,我建议从最内层开始去读它们。做为示例,我来解释一下这张表格它所表达的执行过程。

第5行:【Clustered Index Seek(OBJECT:([MyNorthwind].[dbo].[Customers].[PK_Customers]), SEEK:([MyNorthwind].[dbo].[Customers].[CustomerID]=[MyNorthwind].[dbo].[Orders].[CustomerID]) ORDERED FORWARD)】, 意思是说,SqlServer在对表Customers做Seek操作,而且是按照【Clustered Index Seek】的方式,对应的索引是【PK_Customers】,seek的值来源于[Orders].[CustomerID]

第4行:【Clustered Index Scan(OBJECT:([MyNorthwind].[dbo].[Orders].[PK_Orders]), WHERE:([MyNorthwind].[dbo].[Orders].[OrderDate]>=’2010-12-01 00:00:00.000′ AND [MyNorthwind].[dbo].[Orders].[OrderDate]<‘2011-12-01 00:00:00.000’))】, 意思是说,SqlServer在对表Customers做Scan操作,即:最差的【表扫描】的方式,原因是,OrderDate列上没有索引,所以只能 这样了。

第3行:【Nested Loops(Left Outer Join, OUTER REFERENCES:([MyNorthwind].[dbo].[Orders].[CustomerID]))】, 意思是说,SqlServer把第5行和第4行产生的数据用【Nested Loops】的方式联接起来,其中Outer表是Orders,要联接的匹配操作也在第5行中指出了。

第2行:【Compute Scalar(DEFINE:([Expr1006]=isnull([MyNorthwind].[dbo].[Customers].[CustomerName],N”)))】, 意思是说,要执行一个isnull()函数的调用。具体原因请参考本文前部分中给出视图定义代码。

第1行:【SELECT [v].[OrderID],[v].[CustomerID],[v].[CustomerName],[v].[OrderDate],[v].[SumMoney],[v].[Finished] FROM [OrdersView] [v] WHERE [v].[OrderDate]>=@1 AND [v].[OrderDate]<@2】, 通常第1行就是整个查询,表示它的返回值。

索引统计信息:查询计划的选择依据

前面一直说到【执行计划】,既然是计划,就表示要在具体执行前就能确定下来的操作方案。那么SqlServer是如何选择一种执行计划的呢? SqlServer怎么知道什么时候该用索引或者用哪个索引? 对于SqlServer来说,每当要执行一个查询时,都要首先检查有没有这个查询的执行计划是否存在缓存中,如果没有,则要生成一个执行计划, 具体在产生执行计划时,并不是看有哪些索引可用(随机选择),而是会参考一种被称为【索引统计信息】的数据。 如果您仔细地看一下前面的执行计划或者执行过程表格,会发现SqlServer能预估每个步骤所产生的数据量, 正是因为SqlServer能预估这些数据量,SqlServer才能选择一个它认为最合适的方法去执行查询过程, 此时【索引统计信息】就能告诉SqlServer这些数据。 说到这里,您是不是有点好奇呢,为了让您对【索引统计信息】有个感性的认识,我们来看看【索引统计信息】是个什么样子的。 请在【SQL Server Management Studio】,输入以下语句,然后执行。

dbcc show_statistics (Products, IX_CategoryID)

得到的结果如下图:

首先,还是解释一下命令:【dbcc show_statistics】这个命令可以显示我们想知道的【索引统计信息】,它需要二个参数,1. 表名,2. 索引名

再来看看命令的结果,它有三个表格组成:
1. 第一个表格,它列出了这个索引统计信息的主要信息。

列名 说明
Name 统计信息的名称。
Updated 上一次更新统计信息的日期和时间。
Rows 表中的行数。
Rows Sampled 统计信息的抽样行数。
Steps 数据可分成多少个组,与第三个表对应。
Density 第一个索引列前缀的选择性(不包括 EQ_ROWS)。
Average key length 所有索引列的平均长度。
String Index 如果为“是”,则统计信息中包含字符串摘要索引,以支持为 LIKE 条件估算结果集大小。仅适用于 charvarcharncharnvarcharvarchar(max)nvarchar(max)text 以及 ntext 数据类型的前导列。

2. 第二个表格,它列出各种字段组合的选择性,数据越小表示重复越性越小,当然选择性也就越高。

列名 说明
All density 索引列前缀集的选择性(包括 EQ_ROWS)。注意:这个值越小就表示选择性越高。
如果这个值小于0.1,这个索引的选择性就比较高,反之,则表示选择性就不高了。
Average length 索引列前缀集的平均长度。
Columns 为其显示 All densityAverage length 的索引列前缀的名称。

3. 第三个表格,数据分布的直方图,SqlServer就是靠它预估一些执行步骤的数据量。

列名 说明
RANGE_HI_KEY 每个组中的最大值。
RANGE_ROWS 每组数据组的估算行数,不包含最大值。
EQ_ROWS 每组数据组中与最大值相等的行的估算数目。
DISTINCT_RANGE_ROWS 每组数据组中的非重复值的估算数目,不包含最大值。
AVG_RANGE_ROWS 每组数据组中的重复值的平均数目,不包含最大值,计算公式:RANGE_ROWS / DISTINCT_RANGE_ROWS for DISTINCT_RANGE_ROWS > 0

为了能让您更好的理解这些数据,尤其是第三组,请看下图:

当时我在填充测试数据时,故意把CategoryId为1到8的组,每组取了78条数据。所以【索引统计信息】的第三个表格的数据也都是正确的, 也正是根据这些统计信息,SqlServer才能对每个执行步骤预估相应的数据量,从而影响Join之类的选择。当然了,在选择Join方式时, 也要参考第二个表格中的字段选择性。最终在为新的查询生成执行计划时, 查询优化器使用这些统计信息并通过估计使用索引评估查询的开销来确定最佳查询计划。

再来个例子来说明一下统计信息对于查询计划选择的重要性。首先多加点数据,请看以下代码:

declare @newCategoryId int;
insert into dbo.Categories (CategoryName) values(N'Test statistics');
set @newCategoryId = scope_identity();

declare @count int;
set @count = 0;

while( @count < 100000 )
begin
    insert into Products (ProductName, CategoryID, Unit, UnitPrice, Quantity, Remark) 
    values( cast(newid() as nvarchar(50)), @newCategoryId, N'个', 100, @count +1, N'');

    set @count = @count + 1;
end
go

update statistics Products;
go

再来看看索引统计信息:

再来看看同一个查询,但因为查询参数值不同时,SqlServer选择的执行计划:

select p.ProductId, t.Quantity 
from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId 
where p.CategoryId = 26;    -- 26 就是最新产生的CategoryId,因此这个查询会返回10W条记录

select p.ProductId, t.Quantity 
from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId 
where p.CategoryId = 6;    -- 这个查询会返回95条记录

从上图可以看出,由于CategoryId的参数值不同,SqlServer会选择完全不同的执行计划。统计信息重要性在这里体现地很清楚吧。

创建统计信息后,数据库引擎对列值(根据这些值创建统计信息)进行排序, 并根据这些值(最多 200 个,按间隔分隔开)创建一个“直方图”。直方图指定有多少行精确匹配每个间隔值, 有多少行在间隔范围内,以及间隔中值的密度大小或重复值的发生率。

SQL Server 2005 引入了对 char、varchar、varchar(max)、nchar、nvarchar、nvarchar(max)、text 和 ntext 列创建的统计信息收集的其他信息。这些信息称为“字符串摘要”,可以帮助查询优化器估计字符串模式中查询谓词的选择性。 查询中有 LIKE 条件时,使用字符串摘要可以更准确地估计结果集大小,并不断优化查询计划。 这些条件包括诸如 WHERE ProductName LIKE ‘%Bike’ 和 WHERE Name LIKE ‘[CS]heryl’ 之类的条件。

既然【索引统计信息】这么重要,那么它会在什么时候生成或者更新呢?事实上,【索引统计信息】是不用我们手工去维护的, SqlServer会自动去维护它们。而且在SqlServer中也有个参数来控制这个更新方式:

统计信息自动功能工作方式

创建索引时,查询优化器自动存储有关索引列的统计信息。另外,当 AUTO_CREATE_STATISTICS 数据库选项设置为 ON(默认值)时, 数据库引擎自动为没有用于谓词的索引的列创建统计信息。

随着列中数据发生变化,索引和列的统计信息可能会过时,从而导致查询优化器选择的查询处理方法不是最佳的。 例如,如果创建一个包含一个索引列和 1,000 行数据的表,每一行在索引列中的值都是唯一的, 则查询优化器将把该索引列视为收集查询数据的好方法。如果更新列中的数据后存在许多重复值, 则该列不再是用于查询的理想候选列。但是,查询优化器仍然根据索引的过时分布统计信息(基于更新前的数据),将其视为好的候选列。

当 AUTO_UPDATE_STATISTICS 数据库选项设置为 ON(默认值)时,查询优化器会在表中的数据发生变化时自动定期更新这些统计信息。 每当查询执行计划中使用的统计信息没有通过针对当前统计信息的测试时就会启动统计信息更新。 采样是在各个数据页上随机进行的,取自表或统计信息所需列的最小非聚集索引。 从磁盘读取一个数据页后,该数据页上的所有行都被用来更新统计信息。 常规情况是:在大约有 20% 的数据行发生变化时更新统计信息。但是,查询优化器始终确保采样的行数尽量少。 对于小于 8 MB 的表,则始终进行完整扫描来收集统计信息。

采样数据(而不是分析所有数据)可以将统计信息自动更新的开销降至最低。 在某些情况下,统计采样无法获得表中数据的精确特征。可以使用 UPDATE STATISTICS 语句的 SAMPLE 子句和 FULLSCAN 子句, 控制按逐个表的方式手动更新统计信息时采样的数据量。FULLSCAN 子句指定扫描表中的所有数据来收集统计信息, 而 SAMPLE 子句用来指定采样的行数百分比或采样的行数

在 SQL Server 2005 中,数据库选项 AUTO_UPDATE_STATISTICS_ASYNC 提供了统计信息异步更新功能。 当此选项设置为 ON 时,查询不等待统计信息更新,即可进行编译。而过期的统计信息置于队列中, 由后台进程中的工作线程来更新。查询和任何其他并发查询都通过使用现有的过期统计信息立即编译。 由于不存在等待更新后的统计信息的延迟,因此查询响应时间可预测;但是过期的统计信息可能导致查询优化器选择低效的查询计划。 在更新后的统计信息就绪后启动的查询将使用那些统计信息。这可能会导致重新编译缓存的计划(取决于较旧的统计信息版本)。 如果在同一个显式用户事务中出现某些数据定义语言 (DDL) 语句(例如,CREATE、ALTER 和 DROP 语句),则无法更新异步统计信息。

AUTO_UPDATE_STATISTICS_ASYNC 选项设置于数据库级别,并确定用于数据库中所有统计信息的更新方法。 它只适用于统计信息更新,而无法用于以异步方式创建统计信息。只有将 AUTO_UPDATE_STATISTICS 设置为 ON 时, 将此选项设置为 ON 才有效。默认情况下,AUTO_UPDATE_STATISTICS_ASYNC 选项设置为 OFF。

从以上说明中,我们可以看出,对于大表,还是有可能存在统计信息更新不及时的时候,这时,就可能会影响查询优化器的判断了。
有些人可能有个经验:对于一些慢的查询,他们会想到重建索引来尝试解决。其实这样做是有道理的。 因为,在某些时候一个查询突然变慢了,可能和统计信息更新不及时有关,进而会影响查询优化器的判断。 如果此时重建索引,就可以让查询优化器知道最新的数据分布,自然就可以避开这个问题。 还记得我前面用【set statistics profile on】显示的执行过程表格吗?注意哦,那个表格就显示每个步骤的实际数据量和预估的数据量。要不要重建索引,其实我们可以用【set statistics profile on】来看一下,如果实际数据量和预估的数据量的差值比较大, 那么我们可以考虑手工去更新统计信息,然后再去试试。

优化视图查询

再来说说优化视图查询,虽然视图也是由一个查询语句定义的,本质上也是一个查询,但它和一般的查询语句在优化时,还是有点要区分的地方。 这里主要的区别在于,视图虽然是由一个查询语句定义的,但如果只去分析这个查询定义,可能得到的意义不大,因为视图多数时候就不是直接使用, 而是在使用前,会加上where语句,或者放在其它语句中被from所使用。下面还是举个例子吧,在我的演示数据库中有个视图OrdersView,定义代码前面有。 我们来看看,如果直接使用这个视图,会有什么样的执行计划出来:

从这个视图可以看出,SqlServer会对表Orders做全表扫描,应该是很低效的。再来看看下面这个查询:

从这个执行计划可以看出,与上面那个就不一样了。前一个查询中对Orders表的查找是使用【Clustered Index Scan】的方式, 而现在在使用【Clustered Index Seek】的方式了,最右边二个步骤的成本的百分比也发生了改变。这样就足以说明,优化视图时, 最好能根据实际要求,应用不同的过滤条件,再来决定如何去优化。

再来一个由三个查询组成的情况来看看这个视图的执行计划。

select * from dbo.OrdersView where OrderId = 1;
select * from dbo.OrdersView where CustomerId = 1;
select * from dbo.OrdersView where OrderDate >= '2010-12-1' and OrderDate < '2011-12-1';

很明显,对于同一个视图,在不同的过滤条件下,执行计划的差别很明显。

推荐阅读-MSDN文章

索引统计信息
http://msdn.microsoft.com/zh-cn/library/ms190397(SQL.90).aspx

查询优化建议
http://msdn.microsoft.com/zh-cn/library/ms188722(SQL.90).aspx

用于对运行慢的查询进行分析的清单
http://msdn.microsoft.com/zh-cn/library/ms177500(SQL.90).aspx

逻辑运算符和物理运算符引用
http://msdn.microsoft.com/zh-cn/library/ms191158(SQL.90).aspx

[转载]短信猫的实现(C#)类库开源啦

mikel阅读(1102)

[转载]短信猫的实现(C#)类库开源啦 – 给我一杯酒 – 博客园.

前段时间又把类库修改了一番,把它放到了google的开源项目里;
开源地址:http://code.google.com/p/gsmmodem/

花了一段时间,学了下单元测试,添加了一个测试项目,假如单元测试同时发现不少bug,对其做了修改作为开源项目gsmmodem放到了google上。

项目文件结构:
image

Exception 文件夹:本打算对程序的异常处理做一次比较系统的改动(原类库几乎没有异常机制),只添加了一个类,异常处理等还未加入,有待于改 进;ISerialPort接口:测试项目测试串口用,因短信猫发送短信收费,以及用时较长,测试采用moq对串口建立mock对象以进行测试

测试项目:单元测试框架选用的是开源单元测试框架:XUnit.net,mock框架选用的比较易用的开源框架:moq ;由于moq是基于.net3.5的,所以测试项目用的是.net3.5 ,而类库依然是.net2.0的。

在这里,多谢各位网友的支持了。

[转载].NET实现之(WebBrowser数据采集—终结篇)

mikel阅读(1050)

[转载].NET实现之(WebBrowser数据采集—终结篇) – 南京.王清培 – 博客园.

我们继续上一篇“.NET实现之(WebBrowser数据采集-基础篇)”, 由于时间关系这篇文未能及时编写;上一篇文章发布后,得来了部分博友的反对意见,觉得这样的文章没有意义,WebBrowser采集数据效率低下用 WebRequest效率就能提高了,本人不理解,为什么同样是HTTP协议进行数据采集,效率能提高多少,在采集过程中同样要经历种种的高层协议向底层 协议转换等过程,我个人感觉WebRequest是实现更多的扩展性,本人的WebBrowser数据采集,并不是谈抓取数据的效率,重点是讲解 WebBrowser控件的原理,能用WebBrowser与HTML网页进行很方便的集成,本人的下一篇文章”.NET实现之(WebBrowser数 据采集-续)”,就将用WebBrowser进行与HTML网页进行混合使用,在HTML的对象中我要在我的WebBrowser控件中通过读取数据库, 将Winform的控件在HTML中进行呈现,然后将我们的Winform中的数据动态的填入HTML网页中;这样的人性化、方便性、模拟性我想是 WebRequest所不能取代的,我们大部分的软件是要提供给用户使用的,有一个友好的用户界面是必须的;[王清培版权所有,转载请给出署名]

今天我要讲的主要内容是通过WebBrowser实现数据抓取,上一篇文章并没有讲到怎么抓取数据,而是简单的讲解了WebBrowser控件的由 来和一些互操作方面的东西,这篇文章将完全的讲解在用WebBrowser进行数据抓取时遇到的种种问题,本人有很长一段时间都在做数据抓取,第一次做数 据抓取的时候是和我的一个好搭档一起做的,当时遇到很多困难还是他默默的解决了;在项目完成之后,我就决定将这样的经验与大家分享;我感觉在用 WebBrowser进行抓取的时候最大的问题就是网页加载问题,在WebBrowser控件中有一个事件是我们必须要用到的 DocumentCompleted事件,在网页加载完毕过后我们要进行相应的判断,比如当前页面是否是登录页面,我们做的网页抓取工具不可能是傻瓜式的 手动抓取点一下抓一下,我们是完全自动化的,我们只需要一个登录就行了,因为现在基本上的网页登陆都是需要验证码的,所以登录需要我们人工的去识别填写, 一旦登录成功后,一切均有系统自动完成,比如:动态跳转到采集页面、数据抓取、翻页等等过程都已经自动化,由于网页在频繁的请求过程中难免不太稳定,会造 成无缘无故的停止,比如断网、异步加载迟钝、这样我们只能是通过观看界面才能清楚发生了什么,在通过断点继续抓取;我们不讨论那种抓取好坏是讨论哪种更适 合用户的项目需求;我们需要看一张图才能明白抓取的大概原理:[王清培版权所有,转载请给出署名]

1:这是一幅整图,由于图片太大,本人就截成三个小图了;

通过上图的详细的步骤说明,我们清楚的指导我们要怎么做了;我们切换到代码片段:

2:

这是本人的项目结构,在Common文件夹里面有一个显眼的文件GatherWebBrowser.cs这是本人对WebBrowser控件进行了 一层简单的封装,就是在里面加上了一些事件参数,这个文件下面有一个GatherWebBrowserArgs.cs文件,这个是 WebBrowserDocumentCompletedEventArgs对象的配送对象;

3:

这个对象的目的就是将DocumentCompleted事件分离出来,让我们处理N层跳转的时候能很方便; 由于代码较多,我们就看几个关键的地方,能帮大家理清一个头绪就行了;

4:

这个是我们Winform窗口,WebBrowser控件就是我封装的那个控件,大家请注意,在我们层层跳转到最后的采集页面时,我们要将事件链中 的第一个事件断开,为什么要断开,是因为我们的WebBrowser控件的DocumentCompleted事件总是会在网页加载完毕后触发,一旦当我 们进入到采集生命周期中的时候,这部分的逻辑我们是不需要在处理的,这里就涉及到一个技术细节“委托链”,如果对这方面的东西不太了解的话,请查阅本人的“.NET简谈委托链”一文;

由于数据采集逻辑比较复杂,特别是自动数据采集,需要处理很多逻辑问题,这些东西都是因需求不同而不同,所以本人也只能讲解到这个层度了,大概的实 现方式都是这样的,只要我们去慢慢的分析前后关系,HTML的组织,我相信肯定能写出好的数据采集;在下面的一篇文章中我将要通过WebBrowser实 现与网页交互的系统,这方面的需求也是我们经常碰到的,比如我们有一个C/S的系统,但是我们的C/S系统要依附于B/S的系统,需要将我们的C/S系统 的数据作为B/S系统的操作条件,在输入查询条件的时候,我们需要调用我们的Winform窗口,进行数据查询,然后将我们的Winform结果填到 HTML的文本框中;请读者继续关注,谢谢;

[转载]WebForms和ASP.NET MVC之间的抉择

mikel阅读(951)

[转载]WebForms和MVC之间的抉择 – iTech – 博客园.

翻译自: http://blog.gadodia.net/choosing-between-webforms-and-mvc/

WebForms和MVC之间的抉择

ASP.NET现在已经被广泛的使用,但是有一个问题始终困扰着我们,就是我们应该使用WebFroms还是MVC。下面通过对WebForms和 MVC的SWOT(strengths,weaknesses,threats,opportunities)分析来回答这个问题。

1)WebForms

2)MVC

3)基于上面的WebForms和MVC的分析,下面是一个让我们决定WebForms还是MVC的流程图。

4) 综述,我认为当在选择WebForms还是MVC的问题上,我们需要主要考虑一下两点:
*测试驱动开发:使用MVC将使得测试驱动开发更加容易;
*数据驱动开发:如果数据相当多的话,使用WebForms将使得开发更加容易;

当然了,选择本身并没有对和错,任何web都可以使用WebForms和MVC人一种framework来开发。理论上你还可以在一个web开发中混合使用WebForms和MVC(哈哈)。

感谢,Thanks!

[转载]基于WEB 的实时事件通知方案

mikel阅读(1037)

[转载]基于WEB 的实时事件通知方案 – wayshan – 博客园.

基于 WEB 的实时事件通知方式大致有五种方案:HTTP拉取方式(pull),HTTP流,Long Polling,FlashXMLSocket方式,Java Applet。

首先说下Comet这个词,Comet 这个词是最早由Alex Russell(DojoToolkit 的项目 Lead)提出的,称基于 HTTP 长连接、无须在浏览器端安装插件的“服务器推(Push)”技术为“Comet”。

1.HTTP拉取方式(pull)
在 这种传统的方法中,客户端以用户可定义的时间间隔去检查服务器上的最新数据。这种拉取方式的频率要足够高才能保证很高的数据精确度,但高频率可能会导致多 余的检查,从而导致较高的网络流量。而另一方面,低频率则会导致错过更新的数据。理想地,拉取的时间间隔应该等于服务器状态改变的速度。常见的实现如利用 “<meta http-equiv=”refresh” c />” tag,当然利用xmlHttpRequest定时取也是一种方法。

2.HTTP流(Push机制)
HTTP流有两种形式:
* Page Stream: 页面上不间断的HTTP连接响应(HTTP 1.1Keep Alive).
通过在 HTML 页面里嵌入一个隐蔵帧(iframe),然后将这个隐蔵帧的 SRC属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。
* Service Stream:XMLHttpRequest连接中的服务器数据流。
客户端是在 XMLHttpRequest 的 readystate 为4(即数据传输结束)时调用回调函数,进行信息处理。当 readystate 为 4 时,数据传输结束,连接已经关闭。Mozilla Firefox 提供了对Streaming AJAX 的支持,即 readystate 为 3时(数据仍在传输中),客户端可以读取数据,从而无须关闭连接,就能读取处理服务器端返回的信息。IE 在 readystate 为 3时,不能读取服务器返回的数据,目前 IE 不支持基于 Streaming AJAX。

注:使用 Page Stream(iframe) 请求一个长连接有一个很明显的不足之处:IE、Morzilla Firefox下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。Google 的天才们使用一个称为“htmlfile”的 ActiveX解决了在 IE 中的加载显示问题,并将这种方法用到了 gmail+gtalk 产品中。Alex Russell 在 “What else is burrieddown in the depth’s of Google’s amazing JavaScript?”文章中介绍了这种方法。Zeitoun 网站提供的comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持IE、Mozilla Firefox 浏览器,可以作为参考。(http://alex.dojotoolkit.org/?p=538)

3.长时间轮询(Long Polling)
也 就是所谓的异步轮询(AsynchronousPolling),这种方式是纯服务器端推送方式和客户端拉取方式的混合。它是基于BAYEUX协议 (http://svn.xantus.org/shortbus/trunk/bayeux/bayeux.html) 的。这个协议遵循基于主题的发布——订阅机制。在订阅了某个频道后,客户端和服务器间的连接会保持打开状态,并保持一段事先定义好的时间(默认为45 秒)。如果服务器端没有事件发生,而发生了超时,服务器端就会请求客户端进行异步重新连接。如果有事件发生,服务器端会发送数据到客户端,然后客户端重新 连接。
1.  服务器端会阻塞请求直到有数据传递或超时才返回。
2. 客户端 JavaScript响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
3.当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。

4.Flash XMLSocket(push机制)

这种方案实现的基础是:
1. 安装了 Flash 播放器,Flash 提供了 XMLSocket 类(Flash 7.0.14以上版本)。
2.JavaScript 和 Flash 的紧密结合:在 JavaScript 可以直接调用 Flash 程序提供的接口。

具 体实现方法:在 HTML 页面中内嵌入一个使用了 XMLSocket 类的 Flash 程序。JavaScript 通过调用此 Flash程序提供的套接口接口与服务器端的套接口进行通信。JavaScript 在收到服务器端以 XML 格式传送的信息后可以很容易地控制 HTML页面的内容显示。

关于如何去构建 JavaScript 与 Flash XMLSocket 的 Flash 程序,以及如何在 JavaScript 里调用Flash 提供的接口,我们可以参考 AFLAX(Asynchronous Flash and XML)项目提供的 Socket Demo 以及SocketJS(请参见 [http://www.aflax.org/ Asynchronous Flash and XML,提供了强大的Flash、Javascript 库和很多范例。])。

Javascript 与 Flash 的紧密结合,极大增强了客户端的处理能力。从 Flash 播放器 V7.0.19 开始,已经取消了XMLSocket 的端口必须大于 1023 的限制。Linux 平台也支持 Flash XMLSocket 方案。但此方案的缺点在于:
1. 客户端必须安装 Flash 播放器;
2. 因为 XMLSocket 没有 HTTP 隧道功能,XMLSocket类不能自动穿过防火墙;
3. 因为是使用Socket接口,需要设置一个通信端口,防火墙、代理服务器也可能对非 HTTP通道端口进行限制;
4. 必须使用XML格式作为消息格式,数据冗余增大。

此方案在一些网络聊天室,网络互动游戏中得到广泛使用。

5. Java Applet(Push机制)
类似于Flash XMLSocket方式。目前已经很少使用,原因极可能是因在手机等移动终端缺少支持。

总结和建议:

如 果我们想要高数据一致性和高网络性能,我们就应该选择推送方式。但是,推送会带来一些扩展性问题;服务器应用程序CPU使用率是拉取方式的7倍。根据 TUD(http://swerl.tudelft.nl/twiki/pub/Main/TechnicalReports/TUD-SERG- 2007-016.pdf)的测试结果,服务器性能会在350-500个用户时趋于饱和。对于更大数量的用户,服务器端需要维护大量并发的长连接。在这种 应用背景下,服务器端需要考虑负载均衡和集群技术;或是在服务器端为长连接作一些改进。

使用拉取方式,要想达到完整的数据一致性以及很高 的网络性能是很困难的。如果拉取的时间间隔大于数据更新的时间间隔,就会发生一些数据的遗失。而如果小于数据更新的时间间隔,网络性能就会受到影响。拉取 方式只有在拉取时间间隔等同于数据更新时间间隔时,才会恰到好处。但是,为了达到那样的目标,我们就需要提前知道准确的数据更新时间间隔。然而,数据更新 的时间间隔很少是静态不变并可以预知的。这使得拉取方式只有在数据是根据某种特定模式发布的情况才有用。

控制信息与数据信息使用不同的 HTTP 连接

使 用长连接时,存在一个很常见的场景:客户端网页需要关闭,而服务器端还处在读取数据的堵塞状态,客户端需要及时通知服务器端关闭数据连接。服务器在收到关 闭请求后首先要从读取数据的阻塞状态唤醒,然后释放为这个客户端分配的资源,再关闭连接。所以在设计上,我们需要使客户端的控制请求和数据请求使用不同的 HTTP 连接,才能使控制请求不会被阻塞。

在实现上,如果是基于 iframe 流方式的长连接,客户端页面需要使用两个iframe,一个是控制帧,用于往服务器端发送控制请求,控制请求能很快收到响应,不会被堵塞;一个是显示帧, 用于往服务器端发送长连接请求。如果是基于 AJAX的长轮询方式,客户端可以异步地发出一个 XMLHttpRequest 请求,通知服务器端关闭数据连接。

在客户和服务器之间保持“心跳”信息
在浏览器与服务器之间维持一个长连接会为通信带来一些不确定性:因为数据传输是随机的,客户端不知道何时服务器才有数据传送。服务器端需要确保当客户端不再工作时,释放为这个客户端分配的资源,防止内存泄漏。因此需要一种机制使双方知道大家都在正常运行。在实现上:
1.服务器端在阻塞读时会设置一个时限,超时后阻塞读调用会返回,同时发给客户端没有新数据到达的心跳信息。此时如果客户端已经关闭,服务器往通道写数据会出现异常,服务器端就会及时释放为这个客户端分配的资源。
2. 如果客户端使用的是基于 AJAX的长轮询方式;服务器端返回数据、关闭连接后,经过某个时限没有收到客户端的再次请求,会认为客户端不能正常工作,会释放为这个客户端分配、维护的资源。
3. 当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。