posts - 22, comments - 32, trackbacks - 0, articles - 73
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2009年8月11日

最近在公司有点时间所以深入研究了下数据库索引btree/b+tree数据结构和原理,由此牵引出了好多问题,请看如下带着问题研究。

1:为什么 btree/b+tree 数据结构适合数据库索引,它到底是怎么样一个原理和结构?

btree/b+tree 数据结构:

在之前的文章中我们介绍过AVL树,红黑树,它们都属于二叉树,即每个节点最多只能拥有2个子节点,而B-tree(B树)的每个节点可以拥有2个以上的子节点,所以我们简单概括一下:B-tree就是一颗多路平衡查找树,它广泛应用于数据库索引和文件系统中。

首先我们介绍一下一颗 m 阶B-tree的特性,那么这个 m 阶是怎么定义的呢?这里我们以一个节点能拥有的最大子节点数来表示这颗树的阶数。举个例子,如果一个节点最多有 n 个key,那么这个节点最多就会有 n+1 个子节点,这棵树就叫做 n+1(m=n+1)阶树。一颗 m 阶B-tree包括以下5条特性:

  1. 每个节点最多有 m 个子节点
  2. 除根节点和叶子节点,其它每个节点至少有 [m/2] (向上取整的意思)个子节点
  3. 若根节点不是叶子节点,则其至少有2个子节点
  4. 所有NULL节点到根节点的高度都一样
  5. 除根节点外,其它节点都包含 n 个key,其中 [m/2] -1 <= n <= m-1

这些特性可能看着不太好理解,下面我们会介绍B-tree的插入,在插入节点的过程中我们就会慢慢理解这些特性了。B-tree的插入比较简单,就是一个节点至下而上的分裂过程。下面我们具体以一颗4阶树来展示B-tree的插入过程。

首先我们 插入 200,300,400,没有什么问题,直接插入就好。

| 200 | 300 | 400 |

现在我们接着插入500,这个时候我们发现有点问题,根据定义及特性1我们知道一颗4阶B-tree的每个节点最多只能有3个key,插入500后这个节点就有4个key了。

| 200 | 300 | 400 | 500 |

这个时候我们就需要分裂,将中间的key上移到父节点,左边的作为左节点,右边的作为右节点,如下图所示:

这个时候我们是不是就明白特性3了,如果根节点不是叶子节点,那么它肯定发生了分裂,所以至少会有2个子节点。同样我们接着插入600,700,800,900插入过程如下图所示:

现在根节点也已经满了,如果我们继续插入910,920,会怎样呢?根节点就会继续分裂,树继续向上生长。看下图:

通过整个的插入过程我们也会发现,B-tree和二叉树的一个显著的区别就是,B-tree是从下往上生长,而二叉树是从上往下生长的。现在我们想想特性2和特性5是为什么?首先我们知道子节点的个数是等于key的数目+1,然后一个节点达到m个key后就会分裂,所以分裂后的节点最少能得到 m/2 - 1个key 。为啥还要减一呢?因为还要拿一个作为父节点。所以这个节点最少回拥有 m/2 - 1 + 1 = m/2 个子节点。同样得到特性5,因为最少有m/2个子节点,所以最少就含有m/2-1个key,m 阶树,每个节点存到了m个key就会分裂,所以最多就有 m-1个key。

根据以上特性我们能推出一棵含有N个总关键字数的m阶的B-tree树的最大高度h的值,

树的高度h: 1, 2, 3 , 4 ,.......... , h

节点个数s: 1, 2, 2*(m/2), 2*(m/2)(m/2), ........ ,2*(m/2)的h-2次方

s = 1 + 2(1 - (m/2)^{h-1} )/(1- (m/2))

N = 1 + s * ((m/2) - 1) = 2 * ((m/2)^{h-1} ) - 1

h = log┌m/2┐((N+1)/2 )+1

2:为什么btree/b+tree 为常用数据库索引结构?

上文说过,红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用B-/+Tree作为索引结构,这一节将结合计算机组成原理相关知识讨论B-/+Tree作为索引的理论基础。

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。下面先介绍内存和磁盘存取原理,然后再结合这些原理分析B-/+Tree作为索引的效率。

主存存取原理

目前计算机使用的主存基本都是随机读写存储器(RAM),现代RAM的结构和存取原理比较复杂,这里本文抛却具体差别,抽象出一个十分简单的存取模型来说明RAM的工作原理。

图5

从抽象角度看,主存是一系列的存储单元组成的矩阵,每个存储单元存储固定大小的数据。每个存储单元有唯一的地址,现代主存的编址规则比较复杂,这里将其简化成一个二维地址:通过一个行地址和一个列地址可以唯一定位到一个存储单元。图5展示了一个4 x 4的主存模型。

主存的存取过程如下:

当系统需要读取主存时,则将地址信号放到地址总线上传给主存,主存读到地址信号后,解析信号并定位到指定存储单元,然后将此存储单元数据放到数据总线上,供其它部件读取。

写主存的过程类似,系统将要写入单元地址和数据分别放在地址总线和数据总线上,主存读取两个总线的内容,做相应的写操作。

这里可以看出,主存存取的时间仅与存取次数呈线性关系,因为不存在机械操作,两次存取的数据的“距离”不会对时间有任何影响,例如,先取A0再取A1和先取A0再取D3的时间消耗是一样的。

磁盘存取原理

上文说过,索引一般以文件形式存储在磁盘上,索引检索需要磁盘I/O操作。与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的。

图6是磁盘的整体结构示意图。

图6

一个磁盘由大小相同且同轴的圆形盘片组成,磁盘可以转动(各个磁盘必须同步转动)。在磁盘的一侧有磁头支架,磁头支架固定了一组磁头,每个磁头负责存取一个磁盘的内容。磁头不能转动,但是可以沿磁盘半径方向运动(实际是斜切向运动),每个磁头同一时刻也必须是同轴的,即从正上方向下看,所有磁头任何时候都是重叠的(不过目前已经有多磁头独立技术,可不受此限制)。

图7是磁盘结构的示意图。

图7

盘片被划分成一系列同心环,圆心是盘片中心,每个同心环叫做一个磁道,所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段,每个段叫做一个扇区,每个扇区是磁盘的最小存储单元。为了简单起见,我们下面假设磁盘只有一个盘片和一个磁头。

当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间。

局部性原理与磁盘预读

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:

当一个数据被用到时,其附近的数据也通常会马上被使用。

程序运行期间所需要的数据通常比较集中。

由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。

预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

B-/+Tree索引的性能分析

到这里终于可以分析B-/+Tree索引的性能了。

上文说过一般使用磁盘I/O次数评价索引结构的优劣。先从B-Tree分析,根据B-Tree的定义,可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B- Tree还需要使用如下技巧:

每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。

B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。

综上所述,用B-Tree作为索引结构效率是非常高的。

而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。

上文还说过,B+Tree更适合外存索引,原因和内节点出度d有关。从上面分析可以看到,d越大索引的性能越好,而出度的上限取决于节点内key和data的大小:

dmax = floor(pagesize / (keysize + datasize + pointsize))  (pagesize – dmax >= pointsize)

dmax = floor(pagesize / (keysize + datasize + pointsize)) - 1  (pagesize – dmax < pointsize)

floor表示向下取整。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

这一章从理论角度讨论了与索引相关的数据结构与算法问题,下一章将讨论B+Tree是如何具体实现为MySQL中索引,同时将结合MyISAM和InnDB存储引擎介绍非聚集索引和聚集索引两种不同的索引实现形式。



posted @ 2018-01-25 13:44 为自己代言 阅读(3796) | 评论 (0)编辑 收藏

同事遇到问题是一个java web 工程,依赖了一个jar包,但是jar包中也有自己一套配置文件(例spring 配置文件,资源文件等),这样如果让web工程中的war 包去加载jar 中的配置文件和资源文件呢?
我当时也有一个思想误区,以为web中加载不到jar中的文件,但是经过一番研究,终于明白了,按J2EE规范中web-inf/lib目录下是web工程的classpath 目录,容器会自动去扫描这个目录下所有的配置文件和jar加载到容器中,即:像jar中有自己一套配置文件,war 中又要依赖jar包,这样只需要把这些jar包打成war时候放到web-inf/lib下即可。
注意:1:jar包和war包中配置文件和一些资源文件不能重名。
        
         2:要在war包中的spring 配置文件中引入jar包中的配置文件。



posted @ 2015-04-24 20:42 为自己代言 阅读(6928) | 评论 (0)编辑 收藏

1、不同的tomcat的启动文件startup.sh 中要指定各自的CATALINA_HOME和CATALINA_BASE这两个环境变量。

2、不同的tomcat启动和关闭监听不同的端口

很多人喜欢把CATALINA_HOME和CATALINA_BASE配置到系统环境变量中去,我们不这么做,我们要做的只是把JDK及CLASSPATH配置到环境变量中去即可,因为这个可以通用。
CATALINA_HOME和CATALINA_BASE的区别。简单的说,CATALINA_HOME是Tomcat的安装目 录,CATALINA_BASE是Tomcat的工作目录。如果我们想要运行Tomcat的 多个实例,但是不想安装多个Tomcat软件副本。那么我们可以配置多个工作 目录,每个运行实例独占一个工作目录,但是共享同一个安装目录

下面讲具体的配置方法。

找到Tomcat的startup.sh文件,打开进行编辑。

在文件的开始位置,可以在一大堆注释的后面,加入
export CATALINA_BASE=/usr/ratest/apache-tomcat-7.0.16
export CATALINA_HOME=/usr/ratest/apache-tomcat-7.0.16

/usr/ratest/apache-tomcat-7.0.16这个就是tomcat的安装文件夹位置,不同的tomcat指定相应的文件夹即可。

注意,这两句话一定要在exec “$PRGDIR”/”$EXECUTABLE” start “$@”这句话的前面,我们放在文件的开始位置了,所以,就可以不考虑了。

然后就是要修改shutdown.sh文件,同样在头部加入上面的两行即可,也要在exec “$PRGDIR”/”$EXECUTABLE” stop “$@”的前面。

好了,解决了第一个问题,下面说第二个问题的解决方法。

找到并打开server.xml文件,里面有诸如8080,8009,8443等等端口配置,统一给这些数字加上100,或者1000或者其他什么数字,只要是不跟其他Tomcat或者当前linux上其他服务的端口重复即可。

现在进入Tomcat的bin文件夹,运行./startup.sh看看是不是可以启动多个了。

posted @ 2014-03-28 19:58 为自己代言 阅读(458) | 评论 (0)编辑 收藏

1,设置跟路径时,三种方式

在Tomcat默认安装后,tomcat的主目录是webapps/root目录,所以如果想改变tomcat的主目录的话可以如下所做,所以
第一种方法是:
打开C:/Tomcat/conf/server.xml,在<host></host>之间
加入代码:<Context docBase="d:/Tomcat 5.5/webapps/medi" path="" debug="0" reloadable="true"/>
这样重新启动tomcat,我们的主目录就被设置为dolphin这个项目了。

第二种方法是:
将tomcat安装目录下的ROOT下的所有文件全部删除,然后将工程的解压后的文件全部拷进去。

第三种方法是:
Tomcat5.0以下版本在d:/Tomcat/conf/Catalina/localhost目录下会自动生成了一个ROOT.Xml,
但是5.0以上版本不再生成此文件,所以可以新建个ROOT.xml,在里面加入如下代码:
<?Xml version='1.0' encoding='utf-8'?>
<Context crossContext="true" docBase="d:/Tomcat 5.5/webapps/medi" path="" reloadable="true">
</Context>

2,注意
此时即然配置了 默认访问目录,则不要再tomcat 部署工程了,
比如 在 conf\Catalina\localhost 增加配置文件 指定的工程路径 此文件要去掉, 否则会重复加载工程。

posted @ 2013-11-06 18:44 为自己代言 阅读(675) | 评论 (0)编辑 收藏

很多tomcat进程退出(或者进程假死),都是由于频繁的抛出OutOfMemeoryError导致的。

为了让tomcat退出前或者发生OutOfMemeoryError时自动dump堆栈信息,方便事后排查问题,我们可以做如下操作:

1、 在tomcat启动参数中加入两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/home/tomcat/domains/server2/oom.hprof

2、 重启tomcat

参数说明
(1)-XX:+HeapDumpOnOutOfMemoryError 表示当JVM发生OOM时,自动生成DUMP文件。
(2)-XX:HeapDumpPath=存储文件/目录 表示生成DUMP文件的路径

posted @ 2013-10-30 13:51 为自己代言 阅读(6198) | 评论 (2)编辑 收藏

想从JAVA转到obj-c,看了些基础的东西,感觉很奇怪,可能不太习惯,在网上看到一个不错文章有助理解obj-c的一些核心机制。

Objective-C——消息、Category和Protocol

2012-06-22 20:13 by 池建强, 2627 阅读, 11 评论, 收藏, 编辑

面向对象永远是个可以吐槽的话题,从开始提出到推崇备至,到充满质疑,一路走来让人唏嘘不已。面向对象的思想可谓历史悠久,20世纪70年代的Smalltalk可以说是面向对象语言的经典,直到今天我们依然将这门语言视为面向对象语言的基础。
面向对象是大部分编程语言的基本特性,像 C++、Java、Objective-C这样的静态语言,Ruby、Python这样的动态语言都是面向对象的语言。但是如何编写面向对象的程序却一直是困扰人们的话题,即使是Smalltalk,也有人认为这是一个有缺陷的面向对象的语言实现。
 
我在2010年翻译过的一篇InfoQ的文章,《面向对象编程──走错了路》中提到,面向对象编程的三个原则是:基于消息传递机制,对象分离和多态。文章中还举了Erlang例子,认为Erlang具备了这些原则,“所以可能是唯一的面向对象语言”。除了之前提到的三个特征,单继承和动态类型也被引用为面向对象语言的“绝对需求”。基于这些考虑,文章指出,Smalltalk在实现对象思想时的“错误”──例如,只关注状态和行为,在类和基于映像的语言里缺乏良好的并发模型和消息机制。
 
这篇文章中的核心就是,面向对象思想中除了对象的状态、行为,还应该关注其并发机制、消息机制,后者更为重要。这一点事实上是我在接触了Objective-C之后才有了更深入的体会。
 
Ojbective-C的语法设计主要基于Smalltalk,除了提供传统的面向对象编程特性之外,还增加了很多类似动态语言Ruby、Python才具有的特性,例如动态类型、动态加载、动态绑定等等,同时强化了消息传递机制和表意(Intention Revealing Interface)接口的概念。
 
—消息—
消息传递模型(Message Passing)是Objective-C语言的核心机制。在Objective-C中,没有方法调用这种说法,只有消息传递。在C++或Java中调用某个类的方法,在Objective-C中是给该类发送一个消息。在C++或Java里,类与类的行为方法之间的关系非常紧密,一个方法必定属于一个类,且于编译时就已经绑定在一起,所以你不可能调用一个类里没有的方法。而在Objective-C中就比较简单了,类和消息之间是松耦合的,方法调用只是向某个类发送一个消息,该类可以在运行时再确定怎么处理接受到的消息。也就是说,一个类不保证一定会响应接收到的消息,如果收到了一个无法处理的消息,那么程序就是简单报一个错。甚至你可以向一个值为nil的空对象发送消息,系统都不会出错或宕掉。这种设计本身也比较符合软件的隐喻。
 
在表意接口(Intention Revealing Interface)方面,Objective-C也是设计的比较出色的语言。面向对象语言的特性之一就是通过API把实现封装起来,为上层建筑提供服务。但是需要注意的一点就是,你封装的API最好能够让调用者看到接口描述就知道怎么使用。如果为了使用一个API必须要去研究它的实现,那么就失去了封装的意义。Objective-C通过显式的API描述,让开发者不自觉的写出满足表意接口的API,比如下图中的API描述。

 
上图中描述了一个传统意义的实例方法,但和Java或C++不同的是,其方法关键字由多个字符串组成,在这个例子是insertObject和atIndex,(id)anObject和 (NSUInterger)index分别表示参数类型和参数名称。整个方法看上去就像一个英语句子,我们可以很容易的知道,这个方法就是在索引为 index处插入一个对象。如果你是从其他语言转到Objective-C,那么开始的时候会感觉这种写法有些繁复,但是一旦理解并习惯了你会感受到其巨大的好处,这种写法会强制你写出优美易读的代码和API,而且有了XCode强大的提示功能,再长的方法也是一蹴而就。
 
下面我们来说说多态和继承。
 与Java一样,Objective-C一样不支持多重继承,但是通过类别(Category)和协议(Protocol)可以很好的实现代码复用和扩展。
 
—Category—
首先我们来谈谈Category。
 
Objective-C提供了一种与众不同的方式——Catagory,可以动态的为已经存在的类添加新的行为。这样可以保证类的原始设计规模较小,功能增加时再逐步扩展。使用Category 对类进行扩展时,不需要访问其源代码,也不需要创建子类。Category使用简单的方式,实现了类的相关方法的模块化,把不同的类方法分配到不同的分类文件中。
 
实现起来很简单,我们举例说明。
SomeClass.h
@interface SomeClass : NSObject{
}
-(void) print;
@end 
 
这是类SomeClass的声明文件,其中包含一个实例方法print。如果我们想在不修改原始类、不增加子类的情况下,为该类增加一个hello的方法,只需要简单的定义两个文件 SomeClass+Hello.h和SomeClass+Hello.m,在声明文件和实现文件中用“()”把Category的名称括起来即可。声明文件代码如下:
 
#import "SomeClass.h"
 
@interface SomeClass (Hello)
-(void)hello;
@end
实现文件代码如下
#import "SomeClass+Hello.h"
@implementationSomeClass (Hello)
-(void)hello{
    NSLog (@"name:%@ ", @"Jacky");
}
@end 
其中Hello是Category的名称,如果你用XCode创建Category,那么需要填写的内容包括名称和要扩展的类的名称。这里还有一个约定成俗的习惯,将声明文件和实现文件名称统一采用“原类名+Category”的方式命名。
调用也非常简单,毫无压力,如下:
首先引入Category的声明文件,然后正常调用即可。
#import "SomeClass+Hello.h"
 
SomeClass * sc =[[SomeClass alloc] init];
[sc hello] 
执行结果是:
nameJacky 
 
Category的使用场景:
1、当你在定义类的时候,在某些情况下(例如需求变更),你可能想要为其中的某个或几个类中添加方法。
2、一个类中包含了许多不同的方法需要实现,而这些方法需要不同团队的成员实现
3、当你在使用基础类库中的类时,你可能希望这些类实现一些你需要的方法。
 
遇到以上这些需求,Category可以帮助你解决问题。当然,使用Category也有些问题需要注意,
1、Category可以访问原始类的实例变量,但不能添加变量,如果想添加变量,可以考虑通过继承创建子类。
2、Category可以重载原始类的方法,但不推荐这么做,这么做的后果是你再也不能访问原来的方法。如果确实要重载,正确的选择是创建子类。
3、和普通接口有所区别的是,在分类的实现文件中可以不必实现所有声明的方法,只要你不去调用它。
 
用好Category可以充分利用Objective-C的动态特性,编写出灵活简洁的代码。
 
—Protocol— 
下面我们再来看Protocol。
Protocol,简单来说就是一系列不属于任何类的方法列表,其中声明的方法可以被任何类实现。这种模式一般称为代理(delegation)模式。你通过Protocol定义各种行为,在不同的场景采用不同的实现方式。在iOS和OS X开发中,Apple采用了大量的代理模式来实现MVC中View和Controller的解耦。
 
定义Protocol很简单,在声明文件(h文件)中通过关键字@protocol定义,然后给出Protocol的名称,方法列表,然后用@end表示Protocol结束。在@end指令结束之前定义的方法,都属于这个Protocol。例如:
@protocol ProcessDataDelegate <NSObject>
@required
- (void) processSuccessful: (BOOL)success;

@optional
- (id) submitOrder: (NSNumber *) orderid;
@end
 
以上代码可以单独放在一个h文件中,也可以写在相关类的h文件中,可以视具体情况而定。该Protocol包含两个方法,processSuccessful和submitOrder。这里还有两个关键字,@required和@optional,表示如果要实现这个协议,那么processSuccessful方法是必须要实现的,submitOrder则是可选的,这两个注解关键字是在Objective-C 2.0之后加入的语法特性。如果不注明,那么方法默认是@required的,必须实现。
 
那么如何实现这个Protocol呢,很简单,创建一个普通的Objective-C类,取名为TestAppDelegate,这时会生成一个h文件和m文件。在h文件中引入包含Protocol的h文件,之后声明采用这个Protocol即可,如下:
@interface TestAppDelegate : NSObject<ProcessDataDelegate>;

@end
用尖括号(<...>)括起来的ProcessDataDelegate就是我们创建的Protocol。如果要采用多个Protocol,可以在尖括号内引入多个Protocol 名称,并用逗号隔开即可。例如<ProcessDataDelegate,xxxDelegate>
 
m文件如下:
@implementation TestAppDelegate

- (void) processSuccessful: (BOOL)success{
    if (success) {
        NSLog(@"成功");
    }else {
        NSLog(@"失败");
    }
}

@end 
由于submitOrder方法是可选的,所以我们可以只实现processSuccessful。
 
Protocol一般使用在哪些场景呢?Objective-C里的Protocol和Java语言中的接口很类似,如果一些类之间没有继承关系,但是又具备某些相同的行为,则可以使用 Protocol来描述它们的关系。不同的类,可以遵守同一个Protocol,在不同的场景下注入不同的实例,实现不同的功能。其中最常用的就是委托代理模式,Cocoa框架中大量采用了这种模式实现数据和UI的分离。例如UIView产生的所有事件,都是通过委托的方式交给Controller完成。根据约定,框架中后缀为Delegate的都是Protocol,例如UIApplicationDelegate,UIWebViewDelegate 等,使用时大家可以留意一下,体会其用法。
 
使用Protocol时还需要注意的是:
1、Protocol本身是可以继承的,比如:
@protocol A
     -(void)methodA;
@end
@protocol B <A>
     -(void)methodB;
@end

如果你要实现B,那么methodA和methodB都需要实现。 

2、Protocol是类无关的,任何类都可以实现定义好的Protocol。如果我们想知道某个类是否实现了某个Protocol,还可以使用conformsToProtocol进行判断,如下:
[obj conformsToProtocol:@protocol(ProcessDataDelegate)] 
 
好吧,具体的语言特性这次就介绍这么多。从某种意义上来说,Objective-C是一门古老的语言,发明于1980年。1988年,乔布斯的Next公司获得了Objective-C语言的授权,并开发出了Objective-C的语言库和NEXTSTEP的开发环境。NextStep是以Mach和BSD为基础,Objective-C是其语言和运行库,后来的事大家都清楚,苹果买了Next,乔布斯回归苹果,开始神奇的苹果振兴之路,NextStep成了Max OS X的基础。以后发展越来越好,Objctive-C成了Apple的当家语言,现在基本上是Apple在维护Objctive-C的发展。
 
在苹果的AppStore推出之前,Objective-C一直相对小众,但是其优秀的语言特性似乎一直在为后面的爆发积蓄力量,当苹果平台级的应用出现之后,Objective-C开始大放异彩,静态语言的效率和动态语言的特性得到众多程序员的喜爱,目前它已经以火箭般的速度蹿升TIOBE语言排行版第四位。
 
对于喜爱苹果技术的技术人员来说,Objective-C是你必须深入了解和值得学习的一门语言,希望以后有机会多写一些相关的文章。

转载于:http://www.cnblogs.com/chijianqiang/archive/2012/06/22/objc-category-protocol.html

 

posted @ 2012-10-17 13:49 为自己代言 阅读(348) | 评论 (0)编辑 收藏

     摘要: 我们大部分情况使用默认的或者chain或者redirect,其实struts2还有很多其他类型的,今天我们就来看一下都有哪些类型。 struts2的源码中struts-default.xml文件有全部类型下边解释下: 类型 chain 描述 用来处理Action链,被跳转的action中仍能获取上个页面的值,如request信息。 使用的类...  阅读全文

posted @ 2012-06-01 12:56 为自己代言 阅读(412) | 评论 (0)编辑 收藏

 org.apache.cxf.binding.soap.SoapFault: "http://schemas.xmlsoap.org/wsdl/" is  not a valid SOAP version.

在写好服务器端测试没有问题,在客户端调用时候出现上面错误,经过分析原面如下:

1:CXF 有两种客户端调用,一种是动态工厂形式, 第二种是通过CXF 中wsdl2java 命令先把客户端类生成到本地,在调用;

第一种: 1:  用org.apache.cxf.jaxws.JaxWsProxyFactoryBean配置

<bean id="msgWebServiceSoap" class="com.jd.sms.MsgWebServiceSoap" factory-bean="msgClientFactory"
          factory-method="create"/>
    <bean id="msgClientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
        <property name="serviceClass" value="com.jd.sms.MsgWebServiceSoap"/>
        <property name="address" value="http://127.0.0.1/services/AlarmInformationServices"/>
    </bean>


第二种: 2:  

   用标签<jaxws:client直接配置:

<jaxws:client id="orderClient" serviceClass=

                "demo.order.OrderProcess" address=

                 "http://localhost:8080/orderapp/OrderProcess" />

以上两种webService 的接口地址都不需要在后面加上?wsdl这个后缀,因为这样spring 调用时候要先通过 CXF/bin 命令行wsdl2java 这个命令生成本地客户端调用,把生成的本地客户端代码复制到客户端工程中去,在spring 配置文件中通过上面的任意一种配置方式选择配置,即可调用。
以上CXF异常,就是用这种方式调用,但是接口URL 多了?wsdl后缀的原面;
附wsdl2java 使用方式cd CXF/bin目录下:

例输入:wsdl2java -p com.jd.ws.all  -d F://src - client http://localhost:9000/helloWorld?wsdl
其作用上面的build.xml作用一样。
附加:wsdl2java用法:
wsdl2java -p com -d src -all aa.wsdl
-p 指定其wsdl的命名空间,也就是要生成代码的包名:
-d 指定要产生代码所在目录
-client 生成客户端测试web service的代码
-server 生成服务器启动web service的代码
-impl 生成web service的实现代码
-ant 生成build.xml文件


posted @ 2012-05-22 14:00 为自己代言 阅读(2616) | 评论 (0)编辑 收藏

首先在web工程src目录下新建一个database.properties 文件
内容如下:

user=root
password=root
databaseType=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.2.232:3306/oa? seUnicode=true&amp;characterEncoding=UTF8&amp;zeroDateTimeBehavior=convertToNull

 这里的内容随自己的合适而变化,这里不多说了;

在新建一个读取.properties文件新类:

package com.junhai.tamsys.util;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class DatabaseConfigure {
 private Properties property;
 private FileInputStream inputFile;
 private FileOutputStream outputFile;

 public DatabaseConfigure() {
  property = new Properties();
 }

 public DatabaseConfigure(String filePath) {
  property = new Properties();
  try {
   inputFile = new FileInputStream(filePath);
   property.load(inputFile);
   inputFile.close();
  } catch (FileNotFoundException e) {
   System.out.println("读取属性文件--->失败!- 原因:文件路径错误或者文件不存在");
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 /*
  * 重载函数,得到key的值 @param key 取得其值的键 @return key的值
  */
 public String getValue(String key) {
  if (property.containsKey(key)) {
   return property.getProperty(key);

  } else
   return "";
 }

 /*
  * 重载函数,得到key的值
  *
  * @param fileName propertys文件的路径+文件名 @param key 取得其值的键 @return key的值
  */
 public String getValue(String fileName, String key) {
  try {
   String value = "";
   inputFile = new FileInputStream(fileName);
   property.load(inputFile);
   inputFile.close();
   if (property.containsKey(key)) {
    value = property.getProperty(key);
    return value;
   } else
    return value;
  } catch (FileNotFoundException e) {
   e.printStackTrace();
   return "";
  } catch (IOException e) {
   e.printStackTrace();
   return "";
  } catch (Exception ex) {
   ex.printStackTrace();
   return "";
  }
 }

 /*
  * 清除properties文件中所有的key和其值
  */
 public void clear() {
  property.clear();
 }

 /*
  * 改变或添加一个key的值,当key存在于properties文件中时该key的值被value所代替, 当key不存在时,该key的值是value
  * @param key 要存入的键 @param value 要存入的值
  */
 public void setValue(String key, String value) {
  property.setProperty(key, value);
 }

 /*
  * 将更改后的文件数据存入指定的文件中,该文件可以事先不存在。 @param fileName 文件路径+文件名称 @param
  * description 对该文件的描述
  */
 public void saveFile(String fileName, String description) {
  try {
   outputFile = new FileOutputStream(fileName);
   property.store(outputFile, description);
   outputFile.close();
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  } catch (IOException ioe) {
   ioe.printStackTrace();
  }
 }

 public static void main(String[] args) {
  DatabaseConfigure test=new DatabaseConfigure("./src/database.properties");
  System.out.println(test.getValue("user"));
  System.out.println(test.getValue("databaseType")+";"+test.getValue("url"));
  
 }
}


这样就可以通过key得到相应的value了;
想在这里多说一点是路径问题,java工程和web 工程读取.properties路径是不一样的,我在这里就花了不少时间。
JAVA工程: DatabaseConfigure test=new DatabaseConfigure("./src/database.properties");这样读取就可以了:
web工程这样读取:DatabaseConfigure  dc = new DatabaseConfigure(Thread.currentThread().getContextClassLoader()
                                                .getResource("").getPath()+"database.properties");
 
因为当服务器启动后工程里面东西会编译后加到\WEB-INF\classes这个目录,服务也是从这个目录下读取信息的。所以先取到这个路径,才能正确读取到database.properties这里边的内容。

posted @ 2010-01-28 15:46 为自己代言 阅读(2974) | 评论 (0)编辑 收藏

     摘要:   Struts2 拦截器详细配置过程 1:所有拦截器的超级接口Interceptor ,Action去实现这个接口;  Interceptor 它其中有三个方法(init(),destroy() ,interceptor()):       Init()方法:在服务器起动的时候加载一次,并且只加载一次;  ...  阅读全文

posted @ 2009-10-12 22:41 为自己代言 阅读(33084) | 评论 (6)编辑 收藏

 

Hiberante3 一级缓存总结

1.             Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;管理一级缓存,一级缓存无法取消,用两个方法管理,clear(),evict()

2.             两个session 不能共享一级缓存,因它会伴随session的生命周期的创建和消毁;

3.             Session缓存是实体级别的缓存,就是只有在查询对象级别的时候才使用,如果

使用HQLSQL是查询属性级别的,是不使用一级缓存的!切记!!!!

4 .  iterate 查询使用缓存,会发出查询IdSQLHQL语句,但不会发出查实体的,

它查询完会把相应的实体放到缓存里边,一些实体查询如果缓存里边有,就从缓存中查询,但还是会发出查询idSQLHQL语句。如果缓存中没有它会数据库中查询,然后将查询到的实体一个一个放到缓存中去,所以会有N+1问题出现。

5 . List()iterate 查询区别:

使用iterate,list查询实体对象*N+1问题,在默认情况下,使用query.iterate查询,有可以能出现N+1问题

所谓的N+1是在查询的时候发出了N+1sql语句1:首先发出一条查询对象id列表的sqlN:

根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的sql语句listiterate的区别?

list每次都会发出sql语句,list会向缓存中放入数据,而不利用缓存中的数据

iterate:在默认情况下iterate利用缓存数据,但如果缓存中不存在数据有可以能出现N+1问题

6Get()load(),iterate方法都会使用一级缓存,

 

7.hiberate3 session 存储过程如下:

       例如 object 对象

       Session.save(object);

       这时候不会把数据放到数据库,会先放到session缓存中去,数据库中没有相应记录,session.flush();才发SQLHQL语句,数据库中有了相应记录,

       但是数据库用select查不到,这是跟数据库事物级别有关系,(这里在说下数据库的事务隔离级别一共四种如下:)
    数据库隔离级别:
        隔离级别                       是否存在脏读  是否存在不可重复读     是否存在幻读;
    Read UnCommited(未提交读)               Y            Y                     Y
    Read Commited (提交读 oraclel默认)      N            Y                     Y
    Repeatable Read(不可重复读(Msql默认))  N            N                     Y 
    Serializable(使用很少)                  N            N                     N

    


       Session.beginTrransaction().commit();

       事物提交后可以查询到了。

Session.flush()语句但是为什么不写呢,因为commit()会默认调用flush();


Hiberante3 二级缓存总结

1.Hibernate3的(sessionFactory)二级缓存和session级别的缓存一样都只对实体对象做缓存,不对属性级别的查询做缓存;二级缓存的生命周期和sessionFactory的生命周期是一样的,sessionFactory可以管理二级缓存;

2.sessionFactory级别的缓存,需要手动配置;所有的session可以共享sessionFactory 级别的缓存;(一般把一些不经常变化的实体对象放到sessionFactory级别的缓存中,适合放不经常变化的实体对象。)

3.Hiberante3二级缓存的配置和使用方法如下:

1. 必须把ehcache.jar包导入,然后到Hibernate3.2etc文件下把ehcache.xml复制到工程src目录下(ehcache.xml里边的参数里边有详细英文说明);

(说明:ehcache.jar是第三方法的缓存产品,hiberante只是把它做了集成,还有好多第三方hibernate集成的缓存产品,相关说明请查阅hiberante3开发手册;ehcache支持分布应用的(这个和Hibernate3.2开发手册有出入,经过官网查证确实支持了),如果有分布式需求,请换成支持分布式的二级缓存产品,hiberate3开发手册都有相头说明。配置方法都类似);

4.Hibernate3的二级缓存默认是开起的,也可以指定开起。在hibernate.cfg.xml 文件下配置如下:

*修改hibernate.cfg.xml文件,开户二级缓存;

                     <property name=”hibernate.cache.use_second_level_cache”>true</property>

                     *指定二级缓存产品的提供商;

<property name=”hibernate.cache.provider_class”> org.hibernate.cache.EhCacheProvider

</property>

要让那些实体使用二级缓存,在hibernate.cfg.xml配置文件中加入:

<!—

让这个实体用二级缓存 也可以在实体中映射文件去配置即:

<cache usage="read-only"/>

-->

<class-cache class=”com.zzz.hibernate.ClassT” usage=”read-only”/>

Read-only一般使用这个策略,其它的hibernate3开发手册中也有详细介绍;

CacheModehibernate3开发手册中搜索这个关键字,可以找到一级缓存和二级缓存交互使用的问题;

posted @ 2009-08-11 23:01 为自己代言 阅读(7881) | 评论 (3)编辑 收藏