|
|
用户名:http88 笔名:Yachun Miao 地区: 陕西-西安 行业:其他 |
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
字符编码问题
这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题:
问题一:
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?
我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?
问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode (UCS2)、 GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。
0、big endian和little endian
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。如果将49写在前面,就是little endian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
1、字符编码、内码,顺带介绍汉字编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。
从ASCII、 GB2312到GBK,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK都属于双字节字符集 (DBCS)。
2000 年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。从汉字字汇上说,GB18030在GB13000.1的20902个汉字的基础上增加了CJK扩展A的6582个汉字(Unicode码 0x3400-0x4db5),一共收录了27484个汉字。
CJK就是中日韩的意思。Unicode为了节省码位,将中日韩三国语言中的文字统一编码。GB13000.1就是ISO/IEC 10646-1的中文版,相当于Unicode 1.1。
GB18030 的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。4字节编码的码位就是收录了CJK扩展A的6582个汉字。例如:UCS的0x3400在GB18030中的编码应该是8139EF30,UCS的0x3401在GB18030中的编码应该是8139EF31。
微软提供了GB18030的升级包,但这个升级包只是提供了一套支持CJK扩展A的6582个汉字的新字体:新宋体-18030,并不改变内码。Windows 的内码仍然是GBK。
这里还有一些细节:
*GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
*对于任何字符编码,编码单元的顺序是由编码方案指定的,与endian无关。例如GBK的编码单元是字节,用两个字节表示一个汉字。这两个字节的顺序是固定的,不受CPU字节序的影响。UTF-16的编码单元是word(双字节),word之间的顺序是编码方案指定的,word内部的字节排列才会受到endian的影响。后面还会介绍UTF-16。
*GB2312 的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。
2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。
Unicode 也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。
根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。
在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。
目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是ISO 10646-3:2003。
UCS 只是规定如何编码,并没有规定如何传输、保存这个编码。例如“汉”字的UCS编码是6C49,我可以用4个ascii数字来传输、保存这个编码;也可以用 utf-8编码:3个连续的字节E6B1 89来表示它。关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的方案。UTF-8的一个特别的好处是它与ISO- 8859-1完全兼容。UTF是“UCS Transformation Format”的缩写。
IETF的RFC2781和 RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。
2.1、内码和code page
目前Windows的内核已经支持Unicode字符集,这样在内核上可以支持全世界所有的语言文字。但是由于现有的大量程序和文档都采用了某种特定语言的编码,例如GBK,Windows不可能不支持现有的编码,而全部改用Unicode。
Windows使用代码页(code page)来适应各个国家和地区。code page可以被理解为前面提到的内码。GBK对应的code page是CP936。
微软也为GB18030定义了code page:CP54936。但是由于GB18030有一部分4字节编码,而Windows的代码页只支持单字节和双字节编码,所以这个code page是无法真正使用的。
3、UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:
UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。
UCS -4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为 256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
4、UTF编码
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
读者可以用记事本测试一下我们的编码是否正确。需要注意,UltraEdit在打开utf-8编码的文本文件时会自动转换为UTF-16,可能产生混淆。你可以在设置中关掉这个选项。更好的工具是Hex Workshop。
UTF -16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于 0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为 UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如“奎”的Unicode编码是594E, “乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎” 还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在UCS 编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
UTF -8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。
6、进一步的参考资料
本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" 。
我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看:
1. "Understanding Unicode A general introduction to the Unicode Standard"
2. "Character set encoding basics Understanding character set encodings and l
egacy encodings"
我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用Windows API和不使用Windows API的版本。以后有时间的话,我会整理一下放到我的个人主页上(http://fmddlmyy.home4u.china.com)。
我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。
附录1 再说说区位码、GB2312、内码和代码页
有的朋友对文章中这句话还有疑问:“GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。”
我再详细解释一下:
“GB2312 的原文”是指国家1980年的一个标准《中华人民共和国国家标准 信息交换用汉字编码字符集 基本集 GB 2312-80》。这个标准用两个数来编码汉字和中文符号。第一个数称为“区”,第二个数称为“位”。所以也称为区位码。1-9区是中文符号,16-55 区是一级汉字,56-87区是二级汉字。现在Windows也还有区位输入法,例如输入1601得到“啊”。(这个区位输入法可以自动识别16进制的 GB2312和10进制的区位码,也就是说输入B0A1同样会得到“啊”。)
内码是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的。现在的Windows在系统内部支持Unicode,然后用代码页适应各种语言,“内码”的概念就比较模糊了。微软一般将缺省代码页指定的编码说成是内码。
内码这个词汇,并没有什么官方的定义,代码页也只是微软这个公司的叫法。作为程序员,我们只要知道它们是什么东西,没有必要过多地考证这些名词。
所谓代码页(code page)就是针对一种语言文字的字符编码。例如GBK的code page是CP936,BIG5的code page是CP950,GB2312的code page是CP20936。
Windows中有缺省代码页的概念,即缺省用什么编码来解释字符。例如Windows的记事本打开了一个文本文件,里面的内容是字节流:BA、BA、D7、D6。Windows应该去怎么解释它呢?
是按照Unicode编码解释、还是按照GBK解释、还是按照BIG5解释,还是按照ISO8859-1去解释?如果按GBK去解释,就会得到“汉字”两个字。按照其它编码解释,可能找不到对应的字符,也可能找到错误的字符。所谓“错误”是指与文本作者的本意不符,这时就产生了乱码。
答案是Windows按照当前的缺省代码页去解释文本文件里的字节流。缺省代码页可以通过控制面板的区域选项设置。记事本的另存为中有一项ANSI,其实就是按照缺省代码页的编码方法保存。
Windows的内码是Unicode,它在技术上可以同时支持多个代码页。只要文件能说明自己使用什么编码,用户又安装了对应的代码页,Windows就能正确显示,例如在HTML文件中就可以指定charset。
有的HTML文件作者,特别是英文作者,认为世界上所有人都使用英文,在文件中不指定charset。如果他使用了0x80-0xff之间的字符,中文 Windows又按照缺省的GBK去解释,就会出现乱码。这时只要在这个html文件中加上指定charset的语句,例如:<meta http-equiv="Content-Type" content="text/html; charset=ISO8859-1">如果原作者使用的代码页和ISO8859-1兼容,就不会出现乱码了。
再说区位码,啊的区位码是1601,写成16进制是0x10,0x01。这和计算机广泛使用的ASCII编码冲突。为了兼容00-7f的ASCII 编码,我们在区位码的高、低字节上分别加上A0。这样“啊”的编码就成为B0A1。我们将加过两个A0的编码也称为GB2312编码,虽然GB2312的原文根本没提到这一点。
- 作者: Yachun Miao 2007年01月30日, 星期二 17:50 回复(3) | 引用(0) 加入博采
比较常用的Oracle的SQL语句语法
- 作者: Yachun Miao 2007年01月29日, 星期一 16:36 回复(0) | 引用(0) 加入博采
Turbo C头文件
ALLOC.H 说明内存管理函数(分配、释放等)。
ASSERT.H 定义 assert调试宏。
BIOS.H 说明调用IBM—PC ROM BIOS子程序的各个函数。
CONIO.H 说明调用DOS控制台I/O子程序的各个函数。
CTYPE.H 包含有关字符分类及转换的名类信息(如 isalpha和toascii等)。
DIR.H 包含有关目录和路径的结构、宏定义和函数。
DOS.H 定义和说明MSDOS和8086调用的一些常量和函数。
ERRON.H 定义错误代码的助记符。
FCNTL.H 定义在与open库子程序连接时的符号常量。
FLOAT.H 包含有关浮点运算的一些参数和函数。
GRAPHICS.H 说明有关图形功能的各个函数,图形错误代码的常量定义,正对不同驱动程序的各种颜色值,及函数用到的一些特殊结构。
IO.H 包含低级I/O子程序的结构和说明。
LIMIT.H 包含各环境参数、编译时间限制、数的范围等信息。
MATH.H 说明数学运算函数,还定了 HUGE VAL 宏, 说明了matherr和matherr子程序用到的特殊结构。
MEM.H 说明一些内存操作函数(其中大多数也在STRING.H中说明)。
PROCESS.H 说明进程管理的各个函数,spawn…和EXEC …函数的结构说明。
SETJMP.H 定义longjmp和setjmp函数用到的jmp buf类型,说明这两个函数。
SHARE.H 定义文件共享函数的参数。
SIGNAL.H 定义SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,说明rajse和signal两个函数。
STDARG.H 定义读函数参数表的宏。(如vprintf,vscarf函数)。
STDDEF.H 定义一些公共数据类型和宏。
STDIO.H 定义Kernighan和Ritchie在Unix System V 中定义的标准和扩展的类型和宏。还定义标准I/O 预定义流:stdin,stdout和stderr,说明 I/O流子程序。
STDLIB.H 说明一些常用的子程序:转换子程序、搜索/ 排序子程序等。
STRING.H 说明一些串操作和内存操作函数。
SYS\STAT.H 定义在打开和创建文件时用到的一些符号常量。
SYS\TYPES.H 说明ftime函数和timeb结构。
SYS\TIME.H 定义时间的类型time[ZZ(Z] [ZZ)]t。
TIME.H 定义时间转换子程序asctime、localtime和gmtime的结构,ctime、 difftime、 gmtime、 localtime和stime用到的类型,并提供这些函数的原型。
VALUE.H 定义一些重要常量,包括依赖于机器硬件的和为与Unix System V相兼容而说明的一些常量,包括浮点和双精度值的范围。
- 作者: Yachun Miao 2007年01月28日, 星期日 21:56 回复(0) | 引用(0) 加入博采
进程与线程
- 作者: Yachun Miao 2007年01月28日, 星期日 18:05 回复(0) | 引用(0) 加入博采
堆和栈
链表与数组的区别
A 从逻辑结构来看
A-1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当 数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
A-2. 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、 删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
B 从内存存储来看
B-1. (静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小
B-2. 链表从堆中分配空间, 自由度大但是申请管理比较麻烦.
堆和栈的区别
solost 于 2004年 10月09日 发表
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器(Compiler)自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 — 常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区— 存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。
2.2 申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
2.5堆和栈中的存储内容
栈: 在函数调用时,(1) 第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,(2) 然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,(3) 然后是函数中的局部变量。 注意: 静态变量是不入栈的。
当本次函数调用结束后,(1) 局部变量先出栈,(2) 然后是参数,(3) 最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
2.6存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。
2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
深度优先搜索与广度优先搜索算法有何区别呢?
通常深度优先搜索法不全部保留结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库中存储的结点数就是深度值,因此它占用空间较少。所以,当搜索树的结点较多,用其它方法易产生内存溢出时,深度优先搜索不失为一种有效的求解方法。
广度优先搜索算法,一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题。但广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些
- 作者: Yachun Miao 2007年01月28日, 星期日 16:48 回复(0) | 引用(0) 加入博采
三十分钟掌握STL
contact:karymay@163.net
STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但这种分离确实使得STL变得非常通用。例如,由于STL的sort()函数是完全通用的,你可以用它来操作几乎任何数据集合,包括链表,容器和数组。
要点
STL算法作为模板函数提供。为了和其他组件相区别,在本书中STL算法以后接一对圆括弧的方式表示,例如sort()。
STL另一个重要特性是它不是面向对象的。为了具有足够通用性,STL主要依赖于模板而不是封装,继承和虚函数(多态性)——OOP的三个要素。你在STL中找不到任何明显的类继承关系。这好像是一种倒退,但这正好是使得STL的组件具有广泛通用性的底层特征。另外,由于STL是基于模板,内联函数的使用使得生成的代码短小高效。
提示
确保在编译使用了STL的程序中至少要使用-O优化来保证内联扩展。
STL提供了大量的模板类和函数,可以在OOP和常规编程中使用。所有的STL的大约50个算法都是完全通用的,而且不依赖于任何特定的数据类型。下面的小节说明了三个基本的STL组件:
1) 迭代器提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器也可以是那些定义了operator*()以及其他类似于指针的操作符地方法的类对象。
2) 容器是一种数据结构,如list,vector,和deques ,以模板类的方法提供。为了访问容器中的数据,可以使用由容器类输出的迭代器。
3) 算法是用来操作容器中的数据的模板函数。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象。函数本身与他们操作的数据的结构和类型无关,因此他们可以在从简单数组到高度复杂容器的任何数据结构上使用。
为了避免和其他头文件冲突, STL的头文件不再使用常规的.h扩展。为了包含标准的string类,迭代器和算法,用下面的指示符:
#include <string>
#include <iterator>
#include <algorithm>
如果你查看STL的头文件,你可以看到象iterator.h和stl_iterator.h这样的头文件。由于这些名字在各种STL实现之间都可能不同,你应该避免使用这些名字来引用这些头文件。为了确保可移植性,使用相应的没有.h后缀的文件名。表1列出了最常使用的各种容器类的头文件。该表并不完整,对于其他头文件,我将在本章和后面的两章中介绍。
表 1. STL头文件和容器类
#include | Container Class |
<deque> | deque |
<list> | list |
<map> | map, multimap |
<queue> | queue, priority_queue |
<set> | set, multiset |
<stack> | stack |
<vector> | vector, vector<bool> |
你的编译器可能不能识别名字空间。名字空间就好像一个信封,将标志符封装在另一个名字中。标志符只在名字空间中存在,因而避免了和其他标志符冲突。例如,可能有其他库和程序模块定义了sort()函数,为了避免和STL地sort()算法冲突,STL的sort()以及其他标志符都封装在名字空间std中。STL的sort()算法编译为std::sort(),从而避免了名字冲突。
尽管你的编译器可能没有实现名字空间,你仍然可以使用他们。为了使用STL,可以将下面的指示符插入到你的源代码文件中,典型地是在所有的#include指示符的后面:
using namespace std;
迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器不仅仅是指针,因此你不能认为他们一定具有地址值。例如,一个数组索引,也可以认为是一种迭代器。
迭代器有各种不同的创建方法。程序可能把迭代器作为一个变量创建。一个STL 容器类可能为了使用一个特定类型的数据而创建一个迭代器。作为指针,必须能够使用*操作符类获取数据。你还可以使用其他数学操作符如++。典型的,++操作符用来递增迭代器,以访问容器中的下一个对象。如果迭代器到达了容器中的最后一个元素的后面,则迭代器变成past-the-end值。使用一个 past-the-end值得指针来访问对象是非法的,就好像使用NULL或为初始化的指针一样。
提示
STL不保证可以从另一个迭代器来抵达一个迭代器。例如,当对一个集合中的对象排序时,如果你在不同的结构中指定了两个迭代器,第二个迭代器无法从第一个迭代器抵达,此时程序注定要失败。这是STL灵活性的一个代价。STL不保证检测毫无道理的错误。
对于STL数据结构和算法,你可以使用五种迭代器。下面简要说明了这五种类型:
· Input iterators 提供对数据的只读访问。
· Output iterators 提供对数据的只写访问
· Forward iterators 提供读写操作,并能向前推进迭代器。
· Bidirectional iterators提供读写操作,并能向前和向后操作。
· Random access iterators提供读写操作,并能在数据中随机移动。
尽管各种不同的STL实现细节方面有所不同,还是可以将上面的迭代器想象为一种类继承关系。从这个意义上说,下面的迭代器继承自上面的迭代器。由于这种继承关系,你可以将一个Forward迭代器作为一个output或input迭代器使用。同样,如果一个算法要求是一个bidirectional 迭代器,那么只能使用该种类型和随机访问迭代器。
正如下面的小程序显示的,一个指针也是一种迭代器。该程序同样显示了STL的一个主要特性——它不只是能够用于它自己的类类型,而且也能用于任何C或C++类型。Listing 1, iterdemo.cpp, 显示了如何把指针作为迭代器用于STL的find()算法来搜索普通的数组。
表 1. iterdemo.cpp
#include <iostream.h>#include <algorithm> using namespace std; #define SIZE 100int iarray[SIZE]; int main(){ iarray[20] = 50; int* ip = find(iarray, iarray + SIZE, 50); if (ip == iarray + SIZE) cout << "50 not found in array" << endl; else cout << *ip << " found in array" << endl; return 0;}在引用了I/O流库和STL算法头文件(注意没有.h后缀),该程序告诉编译器使用std名字空间。使用std名字空间的这行是可选的,因为可以删除该行对于这么一个小程序来说不会导致名字冲突。
程序中定义了尺寸为SIZE的全局数组。由于是全局变量,所以运行时数组自动初始化为零。下面的语句将在索引20位置处地元素设置为50,并使用find()算法来搜索值50:
iarray[20] = 50;int* ip = find(iarray, iarray + SIZE, 50);find()函数接受三个参数。头两个定义了搜索的范围。由于C和C++数组等同于指针,表达式iarray指向数组的第一个元素。而第二个参数iarray + SIZE等同于past-the-end 值,也就是数组中最后一个元素的后面位置。第三个参数是待定位的值,也就是50。find()函数返回和前两个参数相同类型的迭代器,这儿是一个指向整数的指针ip。
提示
必须记住STL使用模板。因此,STL函数自动根据它们使用的数据类型来构造。
为了判断find()是否成功,例子中测试ip和 past-the-end 值是否相等:
if (ip == iarray + SIZE) ...如果表达式为真,则表示在搜索的范围内没有指定的值。否则就是指向一个合法对象的指针,这时可以用下面的语句显示::
cout << *ip << " found in array" << endl;测试函数返回值和NULL是否相等是不正确的。不要象下面这样使用:
int* ip = find(iarray, iarray + SIZE, 50);if (ip != NULL) ... // ??? incorrect当使用STL函数时,只能测试ip是否和past-the-end 值是否相等。尽管在本例中ip是一个C++指针,其用法也必须符合STL迭代器的规则。
尽管C++指针也是迭代器,但用的更多的是容器迭代器。容器迭代器用法和iterdemo.cpp一样,但和将迭代器申明为指针变量不同的是,你可以使用容器类方法来获取迭代器对象。两个典型的容器类方法是begin()和end()。它们在大多数容器中表示整个容器范围。其他一些容器还使用rbegin()和rend()方法提供反向迭代器,以按反向顺序指定对象范围。
下面的程序创建了一个矢量容器(STL的和数组等价的对象),并使用迭代器在其中搜索。该程序和前一章中的程序相同。
Listing 2. vectdemo.cpp
#include <iostream.h>#include <algorithm>#include <vector> using namespace std; vector<int> intVector(100); void main(){ intVector[20] = 50; vector<int>::iterator intIter = find(intVector.begin(), intVector.end(), 50); if (intIter != intVector.end()) cout << "Vector contains value " << *intIter << endl; else cout << "Vector does not contain 50" << endl;} 注意用下面的方法显示搜索到的数据:
cout << "Vector contains value " << *intIter << endl;和指针一样,你可以给一个迭代器赋值。例如,首先申明一个迭代器:
vector<int>::iterator first;该语句创建了一个vector<int>类的迭代器。下面的语句将该迭代器设置到intVector的第一个对象,并将它指向的对象值设置为123::
first = intVector.begin();*first = 123;这种赋值对于大多数容器类都是允许的,除了只读变量。为了防止错误赋值,可以申明迭代器为:
const vector<int>::iterator result;result = find(intVector.begin(), intVector.end(), value);if (result != intVector.end()) *result = 123; // ???警告
另一种防止数据被改变得方法是将容器申明为const类型。
『呀!在VC中测试出错,正确的含义是result成为常量而不是它指向的对象不允许改变,如同int *const p;看来这作者自己也不懂』
你已经见到了迭代器的一些例子,现在我们将关注每种特定的迭代器如何使用。由于使用迭代器需要关于STL容器类和算法的知识,在阅读了后面的两章后你可能需要重新复习一下本章内容。
输入迭代器是最普通的类型。输入迭代器至少能够使用==和!=测试是否相等;使用*来访问数据;使用++操作来递推迭代器到下一个元素或到达past-the-end 值。
为了理解迭代器和STL函数是如何使用它们的,现在来看一下find()模板函数的定义:
template <class InputIterator, class T>InputIterator find( InputIterator first, InputIterator last, const T& value) { while (first != last && *first != value) ++first; return first; }
注意
在find()算法中,注意如果first和last指向不同的容器,该算法可能陷入死循环。
输出迭代器缺省只写,通常用于将数据从一个位置拷贝到另一个位置。由于输出迭代器无法读取对象,因此你不会在任何搜索和其他算法中使用它。要想读取一个拷贝的值,必须使用另一个输入迭代器(或它的继承迭代器)。
Listing 3. outiter.cpp
#include <iostream.h>#include <algorithm> // Need copy()#include <vector> // Need vector using namespace std; double darray[10] = {1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9}; vector<double> vdouble(10); int main(){ vector<double>::iterator outputIterator = vdouble.begin(); copy(darray, darray + 10, outputIterator); while (outputIterator != vdouble.end()) { cout << *outputIterator << endl; outputIterator++; } return 0;}注意
当使用copy()算法的时候,你必须确保目标容器有足够大的空间,或者容器本身是自动扩展的。
前推迭代器能够读写数据值,并能够向前推进到下一个值。但是没法递减。replace()算法显示了前推迭代器的使用方法。
template <class ForwardIterator, class T>void replace (ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value);使用replace()将[first,last]范围内的所有值为old_value的对象替换为new_value。:
replace(vdouble.begin(), vdouble.end(), 1.5, 3.14159);双向迭代器要求能够增减。如reverse()算法要求两个双向迭代器作为参数:
template <class BidirectionalIterator>void reverse (BidirectionalIterator first, BidirectionalIterator last);使用reverse()函数来对容器进行逆向排序:
reverse(vdouble.begin(), vdouble.end());随机访问迭代器能够以任意顺序访问数据,并能用于读写数据(不是const的C++指针也是随机访问迭代器)。STL的排序和搜索函数使用随机访问迭代器。随机访问迭代器可以使用关系操作符作比较。
random_shuffle() 函数随机打乱原先的顺序。申明为:
template <class RandomAccessIterator>void random_shuffle (RandomAccessIterator first, RandomAccessIterator last);使用方法:
random_shuffle(vdouble.begin(), vdouble.end());要学会使用迭代器和容器以及算法,需要学习下面的新技术。
本书的很多例子程序使用I/O流语句来读写数据。例如:
int value;cout << "Enter value: ";cin >> value;cout << "You entered " << value << endl;对于迭代器,有另一种方法使用流和标准函数。理解的要点是将输入/输出流作为容器看待。因此,任何接受迭代器参数的算法都可以和流一起工作。
Listing 4. outstrm.cpp
#include <iostream.h>#include <stdlib.h> // Need random(), srandom()#include <time.h> // Need time()#include <algorithm> // Need sort(), copy()#include <vector> // Need vector using namespace std; void Display(vector<int>& v, const char* s); int main(){ // Seed the random number generator srandom( time(NULL) ); // Construct vector and fill with random integer values vector<int> collection(10); for (int i = 0; i < 10; i++) collection[i] = random() % 10000;; // Display, sort, and redisplay Display(collection, "Before sorting"); sort(collection.begin(), collection.end()); Display(collection, "After sorting"); return 0;} // Display label s and contents of integer vector vvoid Display(vector<int>& v, const char* s){ cout << endl << s << endl; copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\t")); cout << endl;}函数Display()显示了如何使用一个输出流迭代器。下面的语句将容器中的值传输到cout输出流对象中:
copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\t"));第三个参数实例化了ostream_iterator<int>类型,并将它作为copy()函数的输出目标迭代器对象。“\t”字符串是作为分隔符。运行结果:
$ g++ outstrm.cpp$ ./a.outBefore sorting677 722 686 238 964 397 251 118 11 312After sorting11 118 238 251 312 397 677 686 722 964这是STL神奇的一面『确实神奇』。为定义输出流迭代器,STL提供了模板类ostream_iterator。这个类的构造函数有两个参数:一个ostream对象和一个string值。因此可以象下面一样简单地创建一个迭代器对象:
ostream_iterator<int>(cout, "\n")该迭代起可以和任何接受一个输出迭代器的函数一起使用。
插入迭代器用于将值插入到容器中。它们也叫做适配器,因为它们将容器适配或转化为一个迭代器,并用于copy()这样的算法中。例如,一个程序定义了一个链表和一个矢量容器:
list<double> dList;vector<double> dVector;通过使用front_inserter迭代器对象,可以只用单个copy()语句就完成将矢量中的对象插入到链表前端的操作:
copy(dVector.begin(), dVector.end(), front_inserter(dList));三种插入迭代器如下:
· 普通插入器 将对象插入到容器任何对象的前面。
· Front inserters 将对象插入到数据集的前面——例如,链表表头。
· Back inserters 将对象插入到集合的尾部——例如,矢量的尾部,导致矢量容器扩展。
使用插入迭代器可能导致容器中的其他对象移动位置,因而使得现存的迭代器非法。例如,将一个对象插入到矢量容器将导致其他值移动位置以腾出空间。一般来说,插入到象链表这样的结构中更为有效,因为它们不会导致其他对象移动。
Listing 5. insert.cpp
#include <iostream.h>#include <algorithm>#include <list> using namespace std; int iArray[5] = { 1, 2, 3, 4, 5 }; void Display(list<int>& v, const char* s); int main(){ list<int> iList; // Copy iArray backwards into iList copy(iArray, iArray + 5, front_inserter(iList)); Display(iList, "Before find and copy"); // Locate value 3 in iList list<int>::iterator p = find(iList.begin(), iList.end(), 3); // Copy first two iArray values to iList ahead of p copy(iArray, iArray + 2, inserter(iList, p)); Display(iList, "After find and copy"); return 0;} void Display(list<int>& a, const char* s){ cout << s << endl; copy(a.begin(), a.end(), ostream_iterator<int>(cout, " ")); cout << endl;}运行结果如下:
$ g++ insert.cpp$ ./a.outBefore find and copy5 4 3 2 1After find and copy5 4 1 2 3 2 1可以将front_inserter替换为back_inserter试试。
如果用find()去查找在列表中不存在的值,例如99。由于这时将p设置为past-the-end 值。最后的copy()函数将iArray的值附加到链表的后部。
在涉及到容器和算法的操作中,还有两个迭代器函数非常有用:
· advance() 按指定的数目增减迭代器。
· distance() 返回到达一个迭代器所需(递增)操作的数目。
例如:
list<int> iList;list<int>::iterator p = find(iList.begin(), iList.end(), 2);cout << "before: p == " << *p << endl;advance(p, 2); // same as p = p + 2;cout << "after : p == " << *p << endl; int k = 0;distance(p, iList.end(), k);cout << "k == " << k << endl; advance()函数接受两个参数。第二个参数是向前推进的数目。对于前推迭代器,该值必须为正,而对于双向迭代器和随机访问迭代器,该值可以为负。
使用 distance()函数来返回到达另一个迭代器所需要的步骤。注意
distance()函数是迭代的,也就是说,它递增第三个参数。因此,你必须初始化该参数。未初始化该参数几乎注定要失败。
STL中,函数被称为算法,也就是说它们和标准C库函数相比,它们更为通用。STL算法通过重载operator()函数实现为模板类或模板函数。这些类用于创建函数对象,对容器中的数据进行各种各样的操作。下面的几节解释如何使用函数和函数对象。
经常需要对容器中的数据进行用户自定义的操作。例如,你可能希望遍历一个容器中所有对象的STL算法能够回调自己的函数。例如
#include <iostream.h>#include <stdlib.h> // Need random(), srandom()#include <time.h> // Need time()#include <vector> // Need vector#include <algorithm> // Need for_each() #define VSIZE 24 // Size of vectorvector<long> v(VSIZE); // Vector object // Function prototypesvoid initialize(long &ri);void show(const long &ri);bool isMinus(const long &ri); // Predicate function int main(){ srandom( time(NULL) ); // Seed random generator for_each(v.begin(), v.end(), initialize);//调用普通函数 cout << "Vector of signed long integers" << endl; for_each(v.begin(), v.end(), show); cout << endl; // Use predicate function to count negative values // int count = 0; vector<long>::iterator p; p = find_if(v.begin(), v.end(), isMinus);//调用断言函数 while (p != v.end()) { count++; p = find_if(p + 1, v.end(), isMinus); } cout << "Number of values: " << VSIZE << endl; cout << "Negative values : " << count << endl; return 0;} // Set ri to a signed integer valuevoid initialize(long &ri){ ri = ( random() - (RAND_MAX / 2) ); // ri = random();} // Display value of rivoid show(const long &ri){ cout << ri << " ";} // Returns true if ri is less than 0bool isMinus(const long &ri){ return (ri < 0);} 所谓断言函数,就是返回bool值的函数。
除了给STL算法传递一个回调函数,你还可能需要传递一个类对象以便执行更复杂的操作。这样的一个对象就叫做函数对象。实际上函数对象就是一个类,但它和回调函数一样可以被回调。例如,在函数对象每次被for_each()或find_if()函数调用时可以保留统计信息。函数对象是通过重载operator()()实现的。如果 TanyClass定义了opeator()(),那么就可以这么使用:
TAnyClass object; // Construct objectobject(); // Calls TAnyClass::operator()() functionfor_each(v.begin(), v.end(), object);STL定义了几个函数对象。由于它们是模板,所以能够用于任何类型,包括C/C++固有的数据类型,如long。有些函数对象从名字中就可以看出它的用途,如plus()和multiplies()。类似的greater()和less-equal()用于比较两个值。
注意
有些版本的ANSI C++定义了times()函数对象,而GNU C++把它命名为multiplies()。使用时必须包含头文件<functional>。
一个有用的函数对象的应用是accumulate() 算法。该函数计算容器中所有值的总和。记住这样的值不一定是简单的类型,通过重载operator+(),也可以是类对象。
Listing 8. accum.cpp
#include <iostream.h>#include <numeric> // Need accumulate()#include <vector> // Need vector#include <functional> // Need multiplies() (or times()) #define MAX 10vector<long> v(MAX); // Vector object int main(){ // Fill vector using conventional loop // for (int i = 0; i < MAX; i++) v[i] = i + 1; // Accumulate the sum of contained values // long sum = accumulate(v.begin(), v.end(), 0); cout << "Sum of values == " << sum << endl; // Accumulate the product of contained values // long product = accumulate(v.begin(), v.end(), 1, multiplies<long>());//注意这行 cout << "Product of values == " << product << endl; return 0;}编译输出如下:
$ g++ accum.cpp$ ./a.outSum of values == 55Product of values == 3628800『注意使用了函数对象的accumulate()的用法。accumulate() 在内部将每个容器中的对象和第三个参数作为multiplies函数对象的参数,multiplies(1,v)计算乘积。VC中的这些模板的源代码如下:
// TEMPLATE FUNCTION accumulate
template<class _II, class _Ty> inline
_Ty accumulate(_II _F, _II _L, _Ty _V)
{for (; _F != _L; ++_F)
_V = _V + *_F;
return (_V); }
// TEMPLATE FUNCTION accumulate WITH BINOP
template<class _II, class _Ty, class _Bop> inline
_Ty accumulate(_II _F, _II _L, _Ty _V, _Bop _B)
{for (; _F != _L; ++_F)
_V = _B(_V, *_F);
return (_V); }
// TEMPLATE STRUCT binary_function
template<class _A1, class _A2, class _R>
struct binary_function {
typedef _A1 first_argument_type;
typedef _A2 second_argument_type;
typedef _R result_type;
};
// TEMPLATE STRUCT multiplies
template<class _Ty>
struct multiplies : binary_function<_Ty, _Ty, _Ty> {
_Ty operator()(const _Ty& _X, const _Ty& _Y) const
{return (_X * _Y); }
};
引言:如果你想深入了解STL到底是怎么实现的,最好的办法是写个简单的程序,将程序中涉及到的模板源码给copy下来,稍作整理,就能看懂了。所以没有必要去买什么《STL源码剖析》之类的书籍,那些书可能反而浪费时间。』
有一类有用的函数对象是“发生器”(generator)。这类函数有自己的内存,也就是说它能够从先前的调用中记住一个值。例如随机数发生器函数。
普通的C程序员使用静态或全局变量 “记忆”上次调用的结果。但这样做的缺点是该函数无法和它的数据相分离『还有个缺点是要用TLS才能线程安全』。显然,使用类来封装一块:“内存”更安全可靠。先看一下例子:
Listing 9. randfunc.cpp
#include <iostream.h>#include <stdlib.h> // Need random(), srandom()#include <time.h> // Need time()#include <algorithm> // Need random_shuffle()#include <vector> // Need vector#include <functional> // Need ptr_fun() using namespace std; // Data to randomizeint iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};vector<int> v(iarray, iarray + 10); // Function prototypesvoid Display(vector<int>& vr, const char *s);unsigned int RandInt(const unsigned int n); int main(){ srandom( time(NULL) ); // Seed random generator Display(v, "Before shuffle:"); pointer_to_unary_function<unsigned int, unsigned int> ptr_RandInt = ptr_fun(RandInt); // Pointer to RandInt()//注意这行 random_shuffle(v.begin(), v.end(), ptr_RandInt); Display(v, "After shuffle:"); return 0;} // Display contents of vector vrvoid Display(vector<int>& vr, const char *s){ cout << endl << s << endl; copy(vr.begin(), vr.end(), ostream_iterator<int>(cout, " ")); cout << endl;} // Return next random value in sequence modulo nunsigned int RandInt(const unsigned int n){ return random() % n;}编译运行结果如下:
$ g++ randfunc.cpp$ ./a.outBefore shuffle:1 2 3 4 5 6 7 8 9 10After shuffle:6 7 2 8 3 5 10 1 9 4首先用下面的语句申明一个对象:
pointer_to_unary_function<unsigned int, unsigned int> ptr_RandInt = ptr_fun(RandInt);这儿使用STL的单目函数模板定义了一个变量ptr_RandInt,并将地址初始化到我们的函数RandInt()。单目函数接受一个参数,并返回一个值。现在random_shuffle()可以如下调用:
random_shuffle(v.begin(), v.end(), ptr_RandInt);在本例子中,发生器只是简单的调用rand()函数。 关于常量引用的一点小麻烦(不翻译了,VC下将例子中的const去掉)
下面的例子说明发生器函数类对象的使用。
Listing 10. fiborand.cpp
#include <iostream.h>#include <algorithm> // Need random_shuffle()#include <vector> // Need vector#include <functional> // Need unary_function using namespace std; // Data to randomizeint iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};vector<int> v(iarray, iarray + 10); // Function prototypevoid Display(vector<int>& vr, const char *s); // The FiboRand template function-object classtemplate <class Arg>class FiboRand : public unary_function<Arg, Arg> { int i, j; Arg sequence[18];public: FiboRand(); Arg operator()(const Arg& arg);}; void main(){ FiboRand<int> fibogen; // Construct generator object cout << "Fibonacci random number generator" << endl; cout << "using random_shuffle and a function object" << endl; Display(v, "Before shuffle:"); random_shuffle(v.begin(), v.end(), fibogen); Display(v, "After shuffle:");} // Display contents of vector vrvoid Display(vector<int>& vr, const char *s){ cout << endl << s << endl; copy(vr.begin(), vr.end(), ostream_iterator<int>(cout, " ")); cout << endl;} // FiboRand class constructortemplate<class Arg>FiboRand<Arg>::FiboRand(){ sequence[17] = 1; sequence[16] = 2;for (int n = 15; n > 0; n—)
sequence[n] = sequence[n + 1] + sequence[n + 2]; i = 17; j = 5;} // FiboRand class function operatortemplate<class Arg>Arg FiboRand<Arg>::operator()(const Arg& arg){ Arg k = sequence[i] + sequence[j]; sequence[i] = k; i--; j--; if (i == 0) i = 17; if (j == 0) j = 17; return k % arg;}编译运行输出如下:
$ g++ fiborand.cpp$ ./a.outFibonacci random number generatorusing random_shuffle and a function objectBefore shuffle:1 2 3 4 5 6 7 8 9 10After shuffle:6 8 5 4 3 7 10 1 9该程序用完全不通的方法使用使用rand_shuffle。Fibonacci 发生器封装在一个类中,该类能从先前的“使用”中记忆运行结果。在本例中,类FiboRand 维护了一个数组和两个索引变量I和j。
FiboRand类继承自unary_function() 模板:
template <class Arg>class FiboRand : public unary_function<Arg, Arg> {...Arg是用户自定义数据类型。该类还定以了两个成员函数,一个是构造函数,另一个是operator()()函数,该操作符允许random_shuffle()算法象一个函数一样“调用”一个FiboRand对象。
一个绑定器使用另一个函数对象f()和参数值V创建一个函数对象。被绑定函数对象必须为双目函数,也就是说有两个参数,A和B。STL 中的帮定器有:
· bind1st() 创建一个函数对象,该函数对象将值V作为第一个参数A。
· bind2nd()创建一个函数对象,该函数对象将值V作为第二个参数B。
举例如下:
Listing 11. binder.cpp
#include <iostream.h>#include <algorithm>#include <functional>#include <list> using namespace std; // Dataint iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};list<int> aList(iarray, iarray + 10); int main(){ int k = 0; count_if(aList.begin(), aList.end(), bind1st(greater<int>(), 8), k); cout << "Number elements < 8 == " << k << endl; return 0;}Algorithm count_if()计算满足特定条件的元素的数目。 这是通过将一个函数对象和一个参数捆绑到为一个对象,并将该对象作为算法的第三个参数实现的。 注意这个表达式:
bind1st(greater<int>(), 8)该表达式将greater<int>()和一个参数值8捆绑为一个函数对象。由于使用了bind1st(),所以该函数相当于计算下述表达式:
8 > q表达式中的q是容器中的对象。因此,完整的表达式
count_if(aList.begin(), aList.end(), bind1st(greater<int>(), 8), k);计算所有小于或等于8的对象的数目。
所谓否定(negator)函数对象,就是它从另一个函数对象创建而来,如果原先的函数返回真,则否定函数对象返回假。有两个否定函数对象:not1()和not2()。not1()接受单目函数对象,not2()接受双目函数对象。否定函数对象通常和帮定器一起使用。例如,上节中用bind1nd来搜索q<=8的值:
count_if(aList.begin(), aList.end(), bind1st(greater<int>(), 8), k);如果要搜索q>8的对象,则用bind2st。而现在可以这样写:
start = find_if(aList.begin(), aList.end(),
not1(bind1nd(greater<int>(), 6)));你必须使用not1,因为bind1nd返回单目函数。
尽管很多程序员仍然在使用标准C函数,但是这就好像骑着毛驴寻找Mercedes一样。你当然最终也会到达目标,但是你浪费了很多时间。
尽管有时候使用标准C函数确实方便(如使用sprintf()进行格式化输出)。但是C函数不使用异常机制来报告错误,也不适合处理新的数据类型。而且标准C函数经常使用内存分配技术,没有经验的程序员很容易写出bug来。.
C++标准库则提供了更为安全,更为灵活的数据集处理方式。STL最初由HP实验室的Alexander Stepanov和Meng Lee开发。最近,C++标准委员会采纳了STL,尽管在不同的实现之间仍有细节差别。
STL的最主要的两个特点:数据结构和算法的分离,非面向对象本质。访问对象是通过象指针一样的迭代器实现的;容器是象链表,矢量之类的数据结构,并按模板方式提供;算法是函数模板,用于操作容器中的数据。由于STL以模板为基础,所以能用于任何数据类型和结构。
- 作者: Yachun Miao 2007年01月26日, 星期五 17:06 回复(0) | 引用(0) 加入博采
Visual C++ .NET编程:托管C++概述
2000年6月,Microsoft推出了"Microsoft.NET下一代互联网软件和服务战略",引起IT行业的广泛关注。2000年9月, Microsoft在旧金山发布了Enterprise 2000。同月,Microsoft原总裁兼首席执行官鲍尔默来到中国就"下一代互联网"的主题进行演讲,在中国掀起了一股".NET旋风"。2000年 11月,Microsoft在Comdex计算机大展上发布了Visual Studio.NET软件,并展示了其.NET发展战略的框架体系和开发工具的相关特性,全面加速了Microsoft以.NET技术进军市场的步伐。
Microsoft的.NET战略意味着:Microsoft以及在Microsoft平台上的开发者将会制造服务,而不是制造软件。在未来几年之内,Microsoft将陆续发布有关.NET的平台和工具,用于在因特网上开发Web服务。那时,工作在.NET上的用户、开发人员和 IT工作人员都不再购买软件、安装软件和维护软件。取而代之的是,他们将定制服务,软件会自动安装,所有的维护和升级也会通过互联网进行。 "Microsoft.NET 代表了一个集合、一个环境、一个可以作为平台支持下一代Internet的可编程结构。"这就是鲍尔默对.NET的描述。
作为.NET的最新特性组成部分,Microsoft .NET Framework是一个用于构建,部署和运行Web服务及应用程序的平台。它为将现有投资与下一代应用程序和服务的集成提供了高产的,基于标准的,多语言环境,同时它还用于解决Internet级应用程序的部署和操作问题。.NET框架包含三个主要部分:通用语言运行时,一组层次化的统一的类库,及组件化版本的动态服务器主页(称为ASP.NET)。
用于开发.NET Framework的语言有Visual C#、VB.NET和C++托管扩展(Managed Extensions for C++)。其中C#是开发.NET的元语言,而C++托管扩展是在C++基础上建立起来的,用来为Visual C++程序员开发.NET框架应用程序而设计。为叙述方便,我们将C++托管扩展就称之为"托管C++"。
为了帮助C/C++以及Visual C++程序员或爱好者快速使用托管C++开发.NET Framework程序,我们将陆续推出相关的一系列文章。
本篇"托管C++概述"主要讲述了什么是托管C++、开发.NET Framework(框架)的项目类型以及与标准C++之间的区别。
1、什么是托管C++?
在回答这个问题,首先要搞清楚什么是"托管"(Managed)。托管是.NET的一个专门概念,它是融于通用语言运行时(CLR)中的一种新的编程理念,因此我们完全可以把"托管"视为".NET"。那么什么是"通用语言运行时"?通用语言运行时是.NET 框架应用程序的执行引挚。它提供了许多服务,其中包括:代码管理(装入和执行)、类型安全性验证、元数据(高级类型信息)访问、为管理对象管理内存、管理代码,COM对象和预生成的DLLs(非管理代码和数据)的交互操作性、对开发人员服务的支持等等。
也就是说,使用托管C++意味着,我们的代码可以被CLR所管理,并能开发出具有最新特性如垃圾自动收集、程序间相互访问等的.NET框架应用程序。
由托管概念所引发的C++应用程序包括托管代码、托管数据和托管类三个组成部分。
(1) 托管代码:. Net环境提供了许多核心的运行(RUNTIME)服务,比如异常处理和安全策略。为了能使用这些服务,必须要给运行环境提供一些信息代码(元数据),这种代码就是托管代码。所有的C#、VB.NET、JScript.NET默认时都是托管的,但Visual C++默认时不是托管的,必须在编译器中使用命令行选项(/CLR)才能产生托管代码。
(2) 托管数据:与托管代码密切相关的是托管数据。托管数据是由公共语言运行的垃圾回收器进行分配和释放的数据。默认情况下,C#、Visual Basic 和 JScript.NET 数据是托管数据。不过,通过使用特殊的关键字,C# 数据可以被标记为非托管数据。Visual C++数据在默认情况下是非托管数据,即使在使用 /CLR 开关时也不是托管的。
(3) 托管类:尽管Visual C++数据在默认情况下是非托管数据,但是在使用C++的托管扩展时,可以使用"__gc"关键字将类标记为托管类。就像该名称所显示的那样,它表示类实例的内存由垃圾回收器管理。另外,一个托管类也完全可以成为 .NET 框架的成员,由此可以带来的好处是,它可以与其他语言编写的类正确地进行相互操作,如托管的C++类可以从Visual Basic类继承等。但同时也有一些限制,如托管类只能从一个基类继承等。需要说明的是,在托管C++应用程序中既可使用托管类也可以使用非托管类。这里的非托管类不是指标准C++类,而是使用托管C++语言中的__nogc关键字的类。
#using
|
| 类型描述 | 标准C++类型名 | 托管C++类型名 | 长度(位) |
| 布尔型 | bool | bool | 8 |
| 字符型 | char | signed char | 8 |
| 无符号字符型 | unsigned char | char | 8 |
| 短整型 | short [int] | short | 16 |
| 无符号短整型 | unsigned short [int] | unsigned short | 16 |
| 整型 | int | int 或 long | 32 |
| 无符号整型 | unsigned [int] | unsigned int 或 long | 32 |
| 长整型 | long [int] | long | 32 |
| 无符号长整型 | unsigned long [int] | unsigned long | 32 |
| 单精度浮点型 | float | float | 32 |
| 双精度浮点型 | double | double | 64 |
| 长双精度浮点型 | long double | -- | 64 |
| Unicode字符 | -- | wchar_t | 16 |
| 64位整型 | -- | __int64 | 64 |
| 无符号64位整型 | -- | unsigned __int64 | 64 |
| 96位十进制值 | -- | Decimal | 96 |
| 对象类型 | -- | Object* | 32 |
| 字符串类型 | -- | String* | -- |
| #using using namespace System; int main(void) { String* hello = S"Hello World"; Console::WriteLine(hello); return 0; } |
__gc class G { { return i*(i + 1)/2; }
|
| #using using namespace System; __value struct V { int i; }; __gc struct G { V v; }; // 嵌入到__gc类中 V f(V v) { // 定义一个全局函数,其值存储在运行栈中 v.i += 1; // 不影响原来形参v的值 return v; // 返回V结构类型的值 } int main(void) { V v1 = {10}; // 在运行栈中声明并初始化 V v2 = f(v1); // 调用f函数,此时v1中的i为10,而v2中的i为11 G *pG = new G; // 为G实例分配堆空间 pG->v = v1; // pG的v中的i为10 pG->v.i += v2.i; // pG的v中的i为10+11=21 Console::WriteLine(v1.i); // 输出结果为10 Console::WriteLine(v2.i); // 输出结果为11 Console::WriteLine(pG->v.i); // 输出结果为21 return 0; } |
| __gc __interface Ibase { void f(); }; |
| #using using namespace System; __gc __interface Ibase1 { int f(int); }; __gc __interface Ibase2 { int f(int); }; __gc struct C: Ibase1, Ibase2 { int f(int i) { // 接口方法的实现 return 2*i-1; }; }; int main(void){ C* c = new C; Console::WriteLine((c -> f(1)).ToString()); // 输出结果为1 Console::WriteLine((__try_cast (c)->f(2)).ToString()); // 输出结果为3 Console::WriteLine((__try_cast (c)->f(3)).ToString()); // 输出结果为5 return 0; } |
代码中,__try_cast用来将某个对象转换成一个指定类型,并当类型转换失败时自动处理由此产生的异常。ToString用来将对象描述成一个字符串。
(4) 简化属性操作
在__gc类中可以使用.NET的属性,这个属性简化了属性函数的调用操作,这与标准C++中的属性不一样。在标准C++中分别通过get_和put_成员函数来设置或获取相关属性的值。现在,托管C++中的属性操作就好比是对一个属性变量进行操作,例如下列代码:
| #using using namespace System; __gc class G { public: __property int get_Size() { Console::WriteLine(S"get_属性"); return nSize; }; __property void set_Size(int i) { Console::WriteLine(S"set_属性"); nSize = i; }; private: int nSize; }; int main() { G * pG = new G; pG->Size = 10; // 调用set_Size int i = pG->Size; // 调用get_Size Console::WriteLine(i); } |
| #using using namespace System; __delegate int GetDayOfWeek(); // 委派方法的声明 __gc class MyCalendar { public: MyCalendar() : m_nDayOfWeek(4) {} int MyGetDayOfWeek() { Console::WriteLine("非静态方法"); return m_nDayOfWeek; } static int MyStaticGetDayOfWeek() { Console::WriteLine("静态方法"); return 6; } private: int m_nDayOfWeek; }; int main(void) { GetDayOfWeek * pGetDayOfWeek; // 声明委派类型变量 int nDayOfWeek; // 将类的静态方法MyStaticGetDayOfWeek绑定成委派 pGetDayOfWeek = new GetDayOfWeek(0, &MyCalendar::MyStaticGetDayOfWeek); nDayOfWeek = pGetDayOfWeek->Invoke(); // 委派的调用 Console::WriteLine(nDayOfWeek); // 将一个类的实例绑定成委派 MyCalendar * pcal = new MyCalendar(); pGetDayOfWeek = static_cast(Delegate::Combine(pGetDayOfWeek, new GetDayOfWeek(pcal, &MyCalendar::MyGetDayOfWeek))); nDayOfWeek = pGetDayOfWeek->Invoke(); Console::WriteLine(nDayOfWeek); // 删除绑定委派的类实例 pGetDayOfWeek = static_cast(Delegate::Remove(pGetDayOfWeek, new GetDayOfWeek(pcal, &MyCalendar::MyGetDayOfWeek))); return 0; } |
- 作者: Yachun Miao 2007年01月26日, 星期五 15:57 回复(0) | 引用(0) 加入博采
Log4j属性文件的配置
步骤:
1. place log4j.properties to classes path;
2. declare logger with class var: private static Logger logger = Logger.getLogger(MyApp.class);
3. use logger to log: logger.debug("1 application.");
Example config file log4j.properties:
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=ERROR, A1, A2 # set default log level
log4j.category.org.apache.struts=WARN
log4j.category.org.apache.commons.logging=WARN
log4j.category.com.myproject=DEBUG # set individual log level apply to special package
log4j.category.org.pi=DEBUG
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#log4j.appender.A1.layout.ConversionPattern=%5p [%t] (%F:%M:%L) - %m%n
log4j.appender.A1.layout.ConversionPattern=%5p [%d{HH:mm:ss}] (%F:%M:%L) - %m%n
# A2 is set to be a RollingFileAppender
log4j.appender.A2=org.apache.log4j.RollingFileAppender
log4j.appender.A2.File=E:/system.log #here use: / don't use:\
log4j.appender.A2.MaxFileSize=2048KB
log4j.appender.A2.MaxBackupIndex=100
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%5p [%d{dd MMM HH:mm:ss}] (%F:%M:%L) - %m%n
实践:
1、先把那个log4j-1.2.8.jar down 下来。
2、新建一个java工程(我是用的Eclipse3.0),然后将log4j-1.2.8.jar 添加到工程的java build path中 lib里。
3、新建一个Logs类。
import org.apache.log4j.Logger ;
/**
* 日志输出
* @author dxg
*
*/
public class Logs {
String log4j;
static Logger logger = Logger.getLogger(Logs.class.getName ()) ;
}
4、新建一个属性文件,后缀为.properties。例如我的:Log4j.properties,文件的内容如下:
log4j.rootLogger=debug, A1,A2
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern= "%-4r [%t] %-5p %c %x - %m%n
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%r][%t][%c]-[%p](%F:%L) %m%n
log4j.appender.A2=org.apache.log4j.RollingFileAppender
log4j.appender.A2.File=c:\logout.log
log4j.appender.A2.MaxFileSize=100KB
log4j.appender.A2.MaxBackupIndex=1
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} {%r}[%t][%c]-[%p](%F:%L) %m%n
5、属性文件的目录放置到工程里的根目录
WebRoot\WEB-INF\classes
6、新建一个测试类。并在类中引用logs,
public static boolean fileReader1() {
boolean bl = false;
String line = null;
try {
BufferedReader br = new BufferedReader(
new FileReader("d:\\sss.txt"));
while ((line = br.readLine()) != null) {
//System.out.println(line);//读取文件
Logs.logger.debug(line);
bl = true;
}
} catch (Exception e) {
System.out.println("" + e.toString());
Logs.logger.debug(e.toString());
} finally {
//System.out.println("It's just a test!");
}
return bl;
}
- 作者: Yachun Miao 2006年12月28日, 星期四 23:53 回复(0) | 引用(0) 加入博采
Axis开发Web Service的实例
一、Axis安装 1、环境 J2SE SDK 1.3 or 1.4: 我使用 1.4.2 Servlet Container: 我使用的Tomcat 5.0
2、到 http://ws.apache.org/Axis/网站下载Axis安装包
3、解压缩安装包,将Axis_UNZIP_PATH\Axis-version\webapps下的Axis包拷贝到TOMCAT_HOME\webapps\下,以下约定Axis_HOME为该TOMCAT_HOME\webapps\Axis目录
4、启动tomcat,访问http://localhost:8080/Axis 检查安装是否成功
5、以上步骤执行成功,可以开发webservice例子了
Axis支持三种web service的部署和开发,分别为:
1、Dynamic Invocation Interface ( DII)
2、Stubs方式
3、Dynamic Proxy方式
二、编写DII(Dynamic Invocation Interface )方式web服务
1.编写服务端程序HelloClient
public class HelloClient
{
public String getName(String name)
{
return "hello "+name;
}
}
2、将源码拷贝到Axis_HOME下,重命名为 HelloClient.jws
3、访问连接http://localhost:8080/Axis/HelloClient.jws?wsdl,页面显示Axis自动生成的wsdl
4、编写访问服务的客户端 TestHelloClient.java
import org.apache.Axis.client.Call;
import org.apache.Axis.client.Service;
import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceException;
import java.net.MalformedURLException;
import java.rmi.RemoteException;
public class SayHelloClient2
{
public static void main(String[] args)
{
try
{
String endpoint =
"http://localhost:8080/Axis/HelloClient.jws";
Service service = new Service();
Call call = null;
call = (Call) service.createCall();
call.setOperationName(new QName(
"http://localhost:8080/Axis/HelloClient.jws",
"getName"));
call.setTargetEndpointAddress
(new java.net.URL(endpoint));
String ret = (String) call.invoke(new Object[]
{"zhangsan"});
System.out.println("return value is " + ret);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
三、编写Dynamic Proxy方式访问服务
1、编写部署服务端程序,同上边DII方式,本次仍使用上边部署的HelloClient
2、编写代理接口
public interface HelloClientInterface
extends java.rmi.Remote
{
public String getName(String name)
throws java.rmi.RemoteException;
}
3、编写并执行客户端程序TestHelloClient.java
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import java.net.URL;
import javax.xml.namespace.QName;
public class TestHelloClient
{
public static void main(String[] args)
{
try
{
String wsdlUrl =
"http://localhost:8080/Axis/HelloClient.jws?wsdl";
String nameSpaceUri =
"http://localhost:8080/Axis/HelloClient.jws";
String serviceName = "HelloClientService";
String portName = "HelloClient";
ServiceFactory serviceFactory =
ServiceFactory.newInstance();
Service afService =
serviceFactory.createService(new URL(wsdlUrl),
new QName(nameSpaceUri, serviceName));
HelloClientInterface proxy = (HelloClientInterface)
afService.getPort(new QName(
nameSpaceUri, portName),
HelloClientInterface.class);
System.out.println
("return value is "+proxy.getName("john") ) ;
}catch(Exception ex)
{
ex.printStackTrace() ;
}
}
}
四、编写wsdd发布web服务,编写stub client访问web服务
1、编写服务端程序server,SayHello.java,编译server.SayHello.java
package server;
public class SayHello
{
public String getName(String name)
{
return "hello "+name;
}
}
2.编写LogHandler.java
import org.apache.Axis.AxisFault;
import org.apache.Axis.Handler;
import org.apache.Axis.MessageContext;
import org.apache.Axis.handlers.BasicHandler;
import java.util.Date;
public class LogHandler
extends BasicHandler
{
public void invoke
(MessageContext msgContext)
throws AxisFault
{
/** Log an access each time
we get invoked.
*/
try {
Handler serviceHandler
= msgContext.getService();
Integer numAccesses =
(Integer)serviceHandler.getOption("accesses");
if (numAccesses == null)
numAccesses = new Integer(0);
numAccesses = new Integer
(numAccesses.intValue() + 1);
Date date = new Date();
String result =
date + ": service " +
msgContext.getTargetService() +
" accessed " + numAccesses + " time(s).";
serviceHandler.setOption
("accesses", numAccesses);
System.out.println(result);
} catch (Exception e)
{
throw AxisFault.makeFault(e);
}
}
}
3、编写wsdd文件
deploy.wsdd
<deployment xmlns=
"http://xml.apache.org/Axis/wsdd/"
xmlns:java=
"http://xml.apache.org/Axis/wsdd/providers/java">
<handler name="print" type="java:LogHandler"/>
<service name="sayhello"
provider="java:RPC">
<requestFlow>
<handler type="print"/>
</requestFlow>
<parameter name="className"
value="server.SayHello"/>
<parameter name="allowedMethods"
value="*"/>
</service>
</deployment>
3、将编译后的文件拷贝到Axis_HOME/WEB-INF/classes下,如:D:\tomcat\webapps\Axis\WEB-INF\classes
4、发布服务:
java org.apache.Axis.client.AdminClient deploy.wsdd
5、生成client stub文件
a:方式1
将SayHello.java拷贝到Axis_HOME/下,重命名为SayHello.jws,
执行下面的命令生存client stub
java org.apache.Axis.wsdl.WSDL2Java
-p client http://localhost:8080
/Axis/services/SayHello.jws?wsdl
b:方式2
执行如下命令生成SayHello.wsdl
java org.apache.Axis.wsdl.Java2WSDL
-oSayHello.wsdl -lhttp://localhost:8080
/Axis/services/SayHello -nsayhello server.SayHello
执行如下命令生成client stub
java org.apache.Axis.wsdl.WSDL2Java
SayHello.wsdl -p client
生成的stub client文件列表为:
1.SayHello.java
2.SayHelloService.java。
3.SayHelloServiceLocator.java
4.SayHelloSoapBindingStub.java
6、编写客户端程序,编译并执行
public class SayHelloClient
{
public static void main(String[] args)
{
try
{
SayHelloService service = new client.
SayHelloServiceLocator();
client.SayHello_PortType
client = service.getSayHello();
String retValue=client.getName("zhangsan");
System.out.println(retValue);
}
catch (Exception e)
{
System.err.println
("Execution failed. Exception: " + e);
}
}
}
- 作者: Yachun Miao 2006年12月27日, 星期三 23:43 回复(0) | 引用(0) 加入博采
JAVA 设计模式的另类理解
1、FACTORY?
追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅
”就行了。麦当劳和肯德基就是生产鸡翅的Factory 。
工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也
要做相应的修改。如:如何创建及如何向客户端提供。
2、BUILDER?
MM最爱听的就是“我爱你”这句话了,见到不同地方的MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键,见
到MM我只要按对应的键,它就能够用相应的语言说出“我爱你”这句话了,国外的MM也可以轻松搞掂,这就是我的“我爱你”builder。(这一定比美军在伊拉克
用的翻译机好卖)
建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的
变化,客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。
3、FACTORY METHOD?
请MM去麦当劳吃汉堡,不同的MM有不同的口味,要每个都记住是一件烦人的事情,我一般采用Factory Method模式,带着MM到服务员那儿,说“要一个汉堡
”,具体要什么样的汉堡呢,让MM直接跟服务员说就行了。
工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口
,而不接触哪一个产品类应当被实例化这种细节。
4、PROTOTYPE?
跟MM用QQ聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要copy出来放到QQ里面就行了,这就是我的情话prototype了。(100块钱一
份,你要不要)
原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增
加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。
5、SINGLETON?
俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公Sigleton,她们只要说道“老公”,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这
么好的事)
单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可
使用。 [b:9ceca65206]结构型模式[/b:9ceca65206]
6、ADAPTER?
在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我
和Sarah可以相互交谈了(也不知道他会不会耍我)
适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可
以根据参数返还一个合适的实例给客户端。
7、BRIDGE?
早上碰到MM,要说早上好,晚上碰到MM,要说晚上好;碰到MM穿了件新衣服,要说你的衣服好漂亮哦,碰到MM新做的发型,要说你的头发好漂亮哦。不要问我
“早上碰到MM新做了个发型怎么说”这种问题,自己用BRIDGE组合一下不就行了。
桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使
用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。
8、COMPOSITE?
Mary今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。
”“喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。”“……”,MM都会用Composite
模式了,你会了没有?
合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用
树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。
9、DECORATOR?
Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上“最好的的礼物,就是爱你
的Fita”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都
在修饰我这个人呀,怎么样,看懂了吗?
装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可
以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。
10、FACADE?
我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Facade设计模式,
把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。
门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门
面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。
11、FLYWEIGHT?
每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再
不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是提取出来的外部特征,根据上下文情况使用。
享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状
态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享
的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建
被共享的对象。享元模式大幅度的降低内存中对象的数量。
12、PROXY?
跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到
这些话都设置好了自动的回答,接收到其他的话时再通知我回答,怎么样,酷吧。
代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。
某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理
模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并
传入。 [b:9ceca65206]行为模式[/b:9ceca65206]
13、CHAIN OF RESPONSIBLEITY?
晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上“Hi,可以做我的女朋友吗?如果不愿意请向前传”,纸条
就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了,听说是个老处女呀,快跑!
责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接 起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此
请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把
责任推给下家。一个请求可以最终不被任何接收端对象所接受。
14、COMMAND?
俺有一个MM家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送
过来一个COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送COMMAND,就数你最小气,才请我吃面。”
命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的
一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行
的。系统支持命令的撤消。
15、INTERPRETER?
俺有一个《泡MM真经》,上面有各种泡MM的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟MM约会时,只要做一个Interpreter,照着上面的脚本执
行就可以了。
解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解
释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模
式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中
的对象的任何排列组合都是一个语言。
16、ITERATOR?
我爱上了Mary,不顾一切的向她求婚。 Mary:“想要我跟你结婚,得答应我的条件” 我:“什么条件我都答应,你说吧” Mary:“我看上了
那个一克拉的钻石” 我:“我买,我买,还有吗?” Mary:“我看上了湖边的那栋别墅” 我:“我买,我买,还有吗?” Mary:“我看上
那辆法拉利跑车” 我脑袋嗡的一声,坐在椅子上,一咬牙:“我买,我买,还有吗?”……
迭代子模式:迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对
象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。迭代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以
上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。
17、MEDIATOR?
四个MM打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我,一切就OK啦
,俺得到了四个MM的电话。
调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时
,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的
行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。
18、MEMENTO?
同时跟几个MM聊天时,一定要记清楚刚才跟MM说了些什么话,不然MM发现了会不高兴的哦,幸亏我有个备忘录,刚才与哪个MM说了什么话我都拷贝一份放到备
忘录里面保存,这样可以随时察看以前的记录啦。
备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部
化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
19、OBSERVER?
想知道咱们公司最新MM情报吗?加入公司的MM情报邮件组就行了,tom负责搜集情报,他发现的新情报不用一个一个通知我们,直接发布给邮件组,我们作为
订阅者(观察者)就可以及时收到情报啦
观察者模式:观察者模式定义了一种一队多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者
对象,使他们能够自动更新自己。
20、STATE?
跟MM交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的MM就会说“有事情啦”,对你不讨厌
但还没喜欢上的MM就会说“好啊,不过可以带上我同事么?”,已经喜欢上你的MM就会说“几点钟?看完电影再去泡吧怎么样?”,当然你看电影过程中表现良好
的话,也可以把MM的状态从不讨厌不喜欢变成喜欢哦。
状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的
状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每
一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。
21、STRATEGY?
跟不同类型的MM约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到MM的芳心,我的追MM锦
囊中有好多Strategy哦。
策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端
的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。由于算法和环境独立开来,算法的增减,修改
都不会影响到环境和客户端。
22、TEMPLATE METHOD?
看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤
(Template method),但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦(具体实现)。
模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同
的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。
23、VISITOR?
情人节到了,要给每个MM送一束鲜花和一张卡片,可是每个MM送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑,我一个人哪搞得清楚,还是
找花店老板和礼品店老板做一下Visitor,让花店老板根据MM的特点选一束花,让礼品店老板也根据每个人特点选一张卡,这样就轻松多了。
访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问
者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操
作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽
可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。
- 作者: Yachun Miao 2006年12月27日, 星期三 14:16 回复(0) | 引用(0) 加入博采
eclipse PHP Language IDE试用
以前玩PHP都用的DzSoft PHP Edit, 感觉用着挺好的
功能比较的多比如
1.插入代码模板 : Ctrl +Space
2.插入函数模板: Ctrl +Enter
HTML编辑也比较方便, 但是没有调试功能, 比较头疼的事情.
昨天去eclipse官方网站上看到了个PHP的IDE:eclipse PHP Language IDE
是个正在进行中的项目 规划如下:
Scehdule:
| - PHP IDE 1.0 release | |
| - PHP IDE 0.7 release | |
| - 0.7 RC2 | |
| - 0.7 RC1 | |
| - 0.7 Milestone 4 | |
| - Unit testing and debugger | |
| - Debugger infrastructure improvements and changes | |
| - Bug fixes and Include Path Explorer ui and core changes | |
| - Eclipse Summit: Presenting PHP IDE | |
| - Upgrade to Eclipse 3.2 and WTP 1.5 | |
| - Finish 3rd development stage | |
| - EclipseCon: Presenting PHP IDE | |
| - Creation review |
但是网站提供了0.7 Stable Build的版本下载, 可以先试用一下
大致用了一下, 可以支持断点调试;
也有代码模板, 函数模板, 最好的是有代码提示功能(统一用快捷键Ctrl + /).
但在添加server的时候出现了问题, 没有添加成功, 只好使用外置的APACHE服务器,建立项目的时候把项目的路径指向APACHE文档目录下, 用eclipse PHP Language IDE内置的WEB 浏览器运行.
总的来说用eclipse PHP Language IDE开发更迅速, 调试方便, 很爽.
很期待eclipse PHP Language IDE1.0的发布
- 作者: Yachun Miao 2006年12月26日, 星期二 17:11 回复(1) | 引用(0) 加入博采
Apache2.2 + php5.2最新配置方案
很久没玩Apache 与 php 了,发现新版本配置方法有所改变,于是查了资料作了总结
下面是这两个最新版本的整合配置方案:
apache2.2 side
httpd.conf configure file:
1. 在加载模块区添加:
LoadModule php5_module D:/PHP/php-5.2.0-Win32/php5apache2_2.dll //这个变了,记得以前是php5apache2.dll
2. 在添加类型区添加:
AddType application/x-httpd-php .php //使apache能处理php类型的文件
AddType application/x-httpd-php-source .phps
3. ScriptAlias /php/ "D:/PHP/php-5.2.0-Win32" //指定PHP脚本的目录,这个没变
4.改DocumentRoot的时候要注意在修改目录的时候,一定要修改两个地方
DocumentRoot "D:/......"
与 <Directory "D:/....."> 两地方的路径要一致,不然会发送访问拒绝的错误.
5. 定位DirectoryIndex:
<IfModule dir_module>
DirectoryIndex index.php index.php.var index.htm index.htm.var index.xml index.xml.var
</IfModule>
这里比以前多了<IfModule dir_module>标签.
6.AddDefaultCharset 这个属性找不到了!估计字符问题APACHE已经使用标准的编码方式.
php5.2 side
php.ini configure file:
1. 把php目录下的php.ini-dist改名为php.ini复制到c:\windows(或:\winnt视操作系统而定),把php5ts.dll和libmysql复制到c:\windows\system32下
也可以设个PHPRC的环境变量指定php.ini的位置
2. 查找extension_dir,添加:
extension_dir = "extension_dir = "D:\PHP\php-5.2.0-Win32\ext"" //PHP5.0安装目录里的ext文件夹用于定位DLL文件
在extension区选择一些常用的PHP的DLL文件:
extension=php_mysql.dll //扩展php_mysql.dll,可以使用mysql语句了
extension=php_gd2.dll //允许建立,修改图片
extension=php_ming.dll //swf支持
extension=php_dbase.dll //maplab需要php_dbase支持
这些因人而异什么时候需要什么时候加,有的时候还要另外下载dll文件
3. 修改c:\windows\system32下的php.ini文件session.save_path = "D:\Apache Software Foundation\Apache2.2\ymiao\sessiontmp"
这个是设定会话变量的路径
4. session.auto_start =1 //自动开始会话变量
5. cgi.force_redirect = 1改成 cgi.force_redirect = 0
这样做是强迫不运行在cgi模式下
6. register_globals 默认 Off,改成on
- 作者: Yachun Miao 2006年12月26日, 星期二 16:30 回复(1) | 引用(0) 加入博采
java的AbstractList源码分析
AbstractList给List提供了一个骨架实现,它的声明是这样的:
public abstract class AbstractList extends AbstractCollection implements List
继承AbstractCollection,实现List接口。
有关AbstractCollection:http://blog.csdn.net/treeroot/archive/2004/09/11/101622.aspx
有关List: http://blog.csdn.net/treeroot/archive/2004/09/14/104638.aspx
下面来看一下该类中的方法
public boolean add(Object o) {
add(size(), o);
return true;
}
直接调用方法add(int index,Object o)在末尾插入一个数据
abstract public Object get(int index);
该方法未实现
public Object set(int index, Object element) {
throw new UnsupportedOperationException();
}
该方法不受支持
public void add(int index, Object element) {
throw new UnsupportedOperationException();
}
该方法不受支持,这个方法直接影响上面的public boolean add(Object o)方法。
public Object remove(int index) {
throw new UnsupportedOperationException();
}
该方法不受支持
public int indexOf(Object o) {
ListIterator e = listIterator();
if (o==null) {
while (e.hasNext())
if (e.next()==null)
return e.previousIndex();
} else {
while (e.hasNext())
if (o.equals(e.next()))
return e.previousIndex();
}
return -1;
}
该方法获得指定元素在列表中的索引,正向搜索,找到的是一个最小值。
找不到的话就返回-1,找不到的情况就要遍历整个列表。
public int lastIndexOf(Object o) {
ListIterator e = listIterator(size());
if (o==null) {
while (e.hasPrevious())
if (e.previous()==null)
return e.nextIndex();
} else {
while (e.hasPrevious())
if (o.equals(e.previous()))
return e.nextIndex();
}
return -1;
}
这个方法几乎和上面的是一样的,不过是从后面向前面找而已,找到的是一个索引的最大值。
public void clear() {
removeRange(0, size());
}
清楚两个索引之间的所有元素,包括开始,不包括结束,参见removeRange方法。
public boolean addAll(int index, Collection c) {
boolean modified = false;
Iterator e = c.iterator();
while (e.hasNext()) {
add(index++, e.next());
modified = true;
}
return modified;
}
这个方法通过循环调用add方法来实现,因为每次调用add方法都要完成元素的后移,所以一种需要移动
c.size()次,效率比较低下。子类中一般都会覆盖这个方法。
public Iterator iterator() {
return new Itr();
}
这里有一个内部类Itr,参见下面的定义。
public ListIterator listIterator() {
return listIterator(0);
}
返回默认的列表迭代器,起始位置在最前面。
public ListIterator listIterator(final int index) {
if (index<0 || index>size())
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
先检查越界情况,同样有一个内部类ListItr,参见下面的定义。
以下是Itr的定义:
有关Iterator接口:http://blog.csdn.net/treeroot/archive/2004/09/11/101589.aspx
私有的内部类
private class Itr implements Iterator {
int cursor = 0;
记录游标位置
int lastRet = -1;
最后一次调用next()或者previous()的索引,其实Iterator都没有定义previous()。
int expectedModCount = modCount;
记录修改次数,modCount在AbstractList中定义为结构话改变List的次数。
这里是为了在Iterator和ListIterotor访问List时出现并发访问,我们后面再讨论这个问题。
public boolean hasNext() {
return cursor != size();
}
如果当前游标不等于集合的大小(那么肯定0到size()-1中的一个值)说明还有下一个值。
size()是AbstractList中的方法。
public Object next() {
try {
Object next = get(cursor);
checkForComodification();
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
这里比较讨厌的是调用了AbstractList中的方法get(int index),这里捕捉了一个系统异常(可以不
捕捉的异常)IndexOutOfBoundsException。无论如何都先抛出并发访问异常ConcurrentModificationException
(如果有的话)。正常情况下当前游标和最后一次访问索引都加1。
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch(IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
如果最后一次访问的索引是-1(刚获得Iterator时就是这样的),抛出IllegalStateException异常。
否则就删除该元素,如果该元素在当前游标之前,游标值要前移。因为是Iterator改变了List的结构,
这里要修正expertedModCount值。这里如果删除的时候的时候越界,一定是其他地方在修改这个List,
所以抛出并发访问异常。注意:这里把lastRet设置为了-1,此时不能调用remove以及ListIterator中
的add,set方法了。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
如果两个地方记录的修改次数不一样,说明还有其他地方在修改这个List。
}
以下是ListItr的定义:
有关ListIterator接口:http://blog.csdn.net/treeroot/archive/2004/09/14/104608.aspx
内部私有类
private class ListItr extends Itr implements ListIterator {
ListItr(int index) {
cursor = index;
}
构造函数,初始化当前游标位置。
public boolean hasPrevious() {
return cursor != 0;
}
当前游标不为0表示前面有元素。
public Object previous() {
try {
int i = cursor - 1;
Object previous = get(i);
checkForComodification();
lastRet = cursor = i;
return previous;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
返回当前游标的前一个元素,游标值和最后一次访问索引都有改变,有关并发控制参考Itr
public int nextIndex() {
return cursor;
}
下一个元素的索引和当前游标相等。
public int previousIndex() {
return cursor-1;
}
不用多说
public void set(Object o) {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, o);
expectedModCount = modCount;
} catch(IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
调用外围类的set方法,并发控制参考Itr中remove方法的说明。
public void add(Object o) {
checkForComodification();
try {
AbstractList.this.add(cursor++, o);
lastRet = -1;
expectedModCount = modCount;
} catch(IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
参考Itr中remove方法的说明。
}
回到AbstractList的方法
public List subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ?
new RandomAccessSubList(this, fromIndex, toIndex) :
new SubList(this, fromIndex, toIndex));
}
如果该List实现了RandomAccess接口,返回一个新的RandomAccessSubList实例,
否则返回一个SubList实例,这两个类在后面定义。
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
ListIterator e1 = listIterator();
ListIterator e2 = ((List) o).listIterator();
while(e1.hasNext() && e2.hasNext()) {
Object o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
这个方法比较简洁,通过遍历两个列表来比较,只有两个列表的元素以及顺序完全一样才相等。
public int hashCode() {
int hashCode = 1;
Iterator i = iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
这种算法完全可以保证:两个List相等时他们的hashCode也相等。
protected void removeRange(int fromIndex, int toIndex) {
ListIterator it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
这里通过迭代器来删除指定的元素,而迭代器调用的是remove方法,所以这个方法的效率不高。
protected transient int modCount = 0;
这个域表示该List被修改的次数,目的是为了控制并发访问。
AbstractList的内容已经结束,但是我们还用到了两个类:RandomAccessList和SubList。
看看SubList的定义:
class SubList extends AbstractList {
private AbstractList l;
private int offset;
private int size;
private int expectedModCount;
SubList(AbstractList list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
expectedModCount = l.modCount;
}
public Object set(int index, Object element) {
rangeCheck(index);
checkForComodification();
return l.set(index+offset, element);
}
public Object get(int index) {
rangeCheck(index);
checkForComodification();
return l.get(index+offset);
}
public int size() {
checkForComodification();
return size;
}
public void add(int index, Object element) {
if (index<0 || index>size)
throw new IndexOutOfBoundsException();
checkForComodification();
l.add(index+offset, element);
expectedModCount = l.modCount;
size++;
modCount++;
}
public Object remove(int index) {
rangeCheck(index);
checkForComodification();
Object result = l.remove(index+offset);
expectedModCount = l.modCount;
size--;
modCount++;
return result;
}
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
l.removeRange(fromIndex+offset, toIndex+offset);
expectedModCount = l.modCount;
size -= (toIndex-fromIndex);
modCount++;
}
public boolean addAll(Collection c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection c) {
if (index<0 || index>size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
int cSize = c.size();
if (cSize==0)
return false;
checkForComodification();
l.addAll(offset+index, c);
expectedModCount = l.modCount;
size += cSize;
modCount++;
return true;
}
public Iterator iterator() {
return listIterator();
}
public ListIterator listIterator(final int index) {
checkForComodification();
if (index<0 || index>size)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
return new ListIterator() {
private ListIterator i = l.listIterator(index+offset);
public boolean hasNext() {
return nextIndex() < size;
}
public Object next() {
if (hasNext())
return i.next();
else
throw new NoSuchElementException();
}
public boolean hasPrevious() {
return previousIndex() >= 0;
}
public Object previous() {
if (hasPrevious())
return i.previous();
else
throw new NoSuchElementException();
}
public int nextIndex() {
return i.nextIndex() - offset;
}
public int previousIndex() {
return i.previousIndex() - offset;
}
public void remove() {
i.remove();
expectedModCount = l.modCount;
size--;
modCount++;
}
public void set(Object o) {
i.set(o);
}
public void add(Object o) {
i.add(o);
expectedModCount = l.modCount;
size++;
modCount++;
}
};
}
public List subList(int fromIndex, int toIndex) {
return new SubList(this, fromIndex, toIndex);
}
private void rangeCheck(int index) {
if (index<0 || index>=size)
throw new IndexOutOfBoundsException("Index: "+index+
",Size: "+size);
}
private void checkForComodification() {
if (l.modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
这个类继承AbstractList,基本上很好理解,不过有几点需要主要:
1.注意l.modCount,modCount,expectedModCount的区别,modCount是SubList继承过来的域
expectedModCount是SubList为防止并发访问新加的域,l.modCount当然好理解。
2.public ListIterator listIterator(final int index)方法中用了一个匿名类。
3.注意SubList的构造函数只有一个,需要带三个参数,而且SubList只是一个视图,修改SubList
也就等于修改了参数中的list。
最后是RandomAccessSubList
class RandomAccessSubList extends SubList implements RandomAccess {
RandomAccessSubList(AbstractList list, int fromIndex, int toIndex) {
super(list, fromIndex, toIndex);
}
public List subList(int fromIndex, int toIndex) {
return new RandomAccessSubList(this, fromIndex, toIndex);
}
}
这个类没有什么实在的东西,不过是类型和SubList不一样而已(因为多了一个RandomAccess接口).
这里只是分析了这个类的实现,并没有评价这个类设计的好坏,不过我是比较讨厌嵌套类(特别是嵌套
类还能调用外围类的方法),另外SubList返回的是一个视图,而不是一个完全独立的List,这样到底好不好呢?
- 作者: Yachun Miao 2006年12月25日, 星期一 18:48 回复(0) | 引用(0) 加入博采
java的AbstractCollection源码分析
AbstractCollection抽象类提供了Collection的骨架实现,Collection分析请看:
http://blog.csdn.net/treeroot/admin/Referrers.aspx?EntryID=99591
这里直接看它的代码是如何实现的.
public abstract Iterator iterator();
该方法没有实现.
public abstract int size();
该方法没有实现.
public boolean isEmpty() {
return size() == 0;
}
非常简单,直接调用size()方法返回大小,如果是0就认为是空集合.
你不会觉得奇怪吧,size()方法没有实现,怎么可以调用呢?因为这个是抽象类,不可以实例化的,具体的时候调用的是子类中的实现.
public boolean contains(Object o) {
Iterator e = iterator();
if (o==null) {
while (e.hasNext())
if (e.next()==null)
return true;
} else {
while (e.hasNext())
if (o.equals(e.next()))
return true;
}
return false;
}
代码不复杂,也是调用自己的方法iterator,遍历集合,如果找到了就返回true,在没找到的情况下是要遍历整个集合的.对于null值的情况一定要放在if里面,而不能放在else里面.
public Object[] toArray() {
Object[] result = new Object[size()];
Iterator e = iterator();
for (int i=0; e.hasNext(); i++)
result[i] = e.next();
return result;
}
返回数组,先生成一个和集合一样大小的数组,然后通过遍历赋值.
public Object[] toArray(Object a[]) {
int size = size();
if (a.length < size)
a = (Object[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
Iterator it=iterator();
for (int i=0; i
a[i] = it.next();
if (a.length > size)
a[size] = null;
return a;
}
这个方法如果在数组a比集合小的情况和上面是一样的,这种情况下生成了一个新的数组,这里用到了反射(从字面上就可以看出意思).为什么不可以直接new呢?因为数组a中的元素是具有运行时类型的,而不仅仅是Object.可以看出当a可以容纳整个集合时是不用重新分配空间的,而且如果a比集合大,会设置a[size]为null值,而且仅仅设置了这一个.如果集合允许null值的话,通过返回数组是无法判断集合的大小的,因为可能集合的最后几个都是null值.
public boolean add(Object o) {
throw new UnsupportedOperationException();
}
这个方法提供了实现,但是是直接抛出一个异常,和未实现的方法是有区别的,非抽象子类中可以不重写这个方法,如果子类不想支持这个操作的话,而未实现的方法子类必须实现,否则编译同不过.
public boolean remove(Object o) {
Iterator e = iterator();
if (o==null) {
while (e.hasNext()) {
if (e.next()==null) {
e.remove();
return true;
}
}
} else {
while (e.hasNext()) {
if (o.equals(e.next())) {
e.remove();
return true;
}
}
}
return false;
}
这个方法和contians方法基本一样,多了一条e.remove语句而已.
public boolean containsAll(Collection c) {
Iterator e = c.iterator();
while (e.hasNext())
if(!contains(e.next()))
return false;
return true;
}
这个方法比较简洁,但是时间复杂度是m*n,通过检查没一个元素是否在集合中,如果发现一个不在直接返回false.可以看出返回true的情况花的时间比返回false要多.
public boolean addAll(Collection c) {
boolean modified = false;
Iterator e = c.iterator();
while (e.hasNext()) {
if(add(e.next()))
modified = true;
}
return modified;
}
这里是通过一个一个加入集合的,同样这里的add方法是没有实现的,要注意的一点是,只要加入了至少一个元素函数就返回true,表示原来的集合有变化.
public boolean removeAll(Collection c) {
boolean modified = false;
Iterator e = iterator();
while (e.hasNext()) {
if(c.contains(e.next())) {
e.remove();
modified = true;
}
}
return modified;
}
这个方法对集合中的每一个元素判断,如果在集合c中就删除,相同值的会被一并删除,删除至少一个元素就返回true.
public boolean retainAll(Collection c) {
boolean modified = false;
Iterator e = iterator();
while (e.hasNext()) {
if(!c.contains(e.next())) {
e.remove();
modified = true;
}
}
return modified;
}
这个方法和上面的几乎一样,多了一个非(!).
public void clear() {
Iterator e = iterator();
while (e.hasNext()) {
e.next();
e.remove();
}
}
这个方法把集合清空,不过这个方法效率显得比较低,清空应该不需要遍历集合,不过子类可以重写整个方法.
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[");
Iterator i = iterator();
boolean hasNext = i.hasNext();
while (hasNext) {
Object o = i.next();
buf.append(o == this ? "(this Collection)" : String.valueOf(o));
hasNext = i.hasNext();
if (hasNext)
buf.append(", ");
}
buf.append("]");
return buf.toString();
}
最后一个方法了,就是把所有的元素用[]括起来返回,元素间用", "分隔.这里唯一注意的是集合可以包含自己,如果没有判断就成死循环了.
- 作者: Yachun Miao 2006年12月25日, 星期一 18:42 回复(0) | 引用(0) 加入博采
Struts的action跳转大全
[1] 完整的action
<action path="/aFullAction"
type="somePackage.someActionClass">
name="someForm"
input="someJSP.jsp"
<forward name="successful" path="someJSP.jsp"/>
<forward name="failed" path="someOtherJSP.jsp"/>
</action>
首先,Struts的ActionServlet接收到一个请求,然后根据struts-config.xml的配置定位到相应的mapping(映射);接下来如果form的范围是request或者在定义的范围中找不到这个form,创建一个新的form实例;取得form实例以后,调用其reset ()方法,然后将表单中的参数放入form,如果validate属性不为false,调用validate()方法;如果validate()返回非空的ActionErrors,将会被转到input属性指定的URI,如果返回空的ActionErrors,那么执行Action的execute() 方法,根据返回的ActionForward确定目标URI。
这样做的效果是:execute()仅当validate()成功以后才执行;input属性指定的是一个URI。
[2] 仅有Form的action
<action path="/aFormOnlyAction"
type="org.apache.struts.actions.ForwardAction"
name="someForm"
input="someJSP.jsp"
parameter="someOtherJSP.jsp"
/>
首先,Struts会在定义的scope搜寻someForm,如果找到则重用,如果找不到则新建一个实例;取得form实例以后,调用其reset()方法,然后将表单中的参数放入form,如果validate属性不为false,调用validate()方法;如果validate()返回非空的 ActionErrors,将会被转到input属性指定的URI,如果返回空的ActionErrors,那么转到parameter属性指定的目标 URI。
这样做的效果是:没有action类可以存放我们的业务逻辑,所以所有需要写入的逻辑都只能写到form的reset()或者 validate()方法中。validate()的作用是验证和访问业务层。因为这里的action映射不包括forward(也没有意义),所以不能重定向,只能用默认的那个forward。这种仅有form的action可以用来处理数据获取并forward到另一个JSP来显示。
[3] 仅有Action的action
<action path="/anActionOnlyAction"
type="somePackage.someActionClass">
input="someJSP.jsp"
<forward name="successful" path="someJSP.jsp"/>
<forward name="failed" path="someOtherJSP.jsp"/>
</action>
首先,ActionServlet接收到请求后,取得action类实例,调用execute()方法;然后根据返回的ActionForward在配置中找forward,forward到指定的URI或action。
这样做的效果是:没有form实例被传入execute()方法,于是execute()必须自己从请求中获取参数。Action可以被forward或者重定向。这种action不能处理通过HTML FORM提交的请求,只能处理链接式的请求。
[4] 仅有JSP的action
<action path="/aJSPOnlyAction"
type="org.apache.struts.actions.ForwardAction"
parameter="someOtherJSP.jsp"
/>
首先,ActionServlet接到请求后调用ForwardAction的execute()方法,execute()根据配置的parameter属性值来forward到那个URI。
这样做的效果是:没有任何form被实例化,比较现实的情形可能是form在request更高级别的范围中定义;或者这个action被用作在应用程序编译好后充当系统参数,只需要更改这个配置文件而不需要重新编译系统。
[5] 两个action对应一个form
<action path="/anAction"
type="somePackage.someActionClass">
name="someForm"
input="someJSP.jsp"
<forward name="successful" path="/anotherAction.do"/>
</action>
<action path="/anotherAction"
type="somePackage.someOtherActionClass">
name="someForm"
input="someOtherJSP.jsp"
<forward name="successful" path="someResultJSP.jsp"/>
</action>
就每个单独的action来讲,处理上并没有和完整的action有什么实质的区别。这个组合模式可以被用来传递命令对象(form)。需要注意的是在后一个action中同样会调用form的reset()和validate()方法,因此我们必须确保form中的信息不被重写。
处理的方式大致分为两种:a) 在request中放入一个指示器表明前一个action有意向后一个action传递form,从而在后一个action可以保留那个form中的值,这一方式只能在使用forward时使用。b) 当使用redirect而不是forward时,可以把指示器放在session或更高的级别,在命令链的最后一环将这个指示器清除。
[6] 两个action对应两个form
<action path="/anAction"
type="somePackage.someActionClass">
name="someForm"
input="someJSP.jsp"
<forward name="successful" path="/anotherAction.do" redirect="true"/>
</action>
<action path="/anotherAction"
type="somePackage.someOtherActionClass">"
name="someOtherForm"
input="someOtherJSP.jsp"
<forward name="successful" path="someResultJSP.jsp"/>
</action>
这个组合方式跟前一种在流程上没有太大区别,只是我们现在对于两个action分别提供了form,于是代码看上去更加清晰。于是我们可以分别处理WEB应用程序的输入和输出。值得注意的是,后一个action同样会尝试往form中写入那些参数,不过我们可以这样处理:a) 在后一个form中使用另一套属性名;b) 只提供getter而不提供setter。
大致的处理是这样:
前一个action接收输入、验证、然后将数据写入业务层或持久层,重定向到后一个action,后一个action手动的从业务层/持久层取出数据,写入form(通过其他方式),交给前台JSP显示。
这样做的好处是不必保留输入form中的值,因此可以使用redirect而不是forward。这样就降低了两个action之间的耦合度,同时也避免了不必要的重复提交。
- 作者: Yachun Miao 2006年12月25日, 星期一 18:41 回复(0) | 引用(0) 加入博采
Java的堆heap,栈stack
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
3. Java中的数据类型有两种。
一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。
4. String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。
5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。
(3)将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!
为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。
我们再来更进一步,将以上代码改成:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false
这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。
事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。
再修改原来代码:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
String str3 = str1;
System.out.println(str3); //bcd
String str4 = "bcd";
System.out.println(str1 == str4); //true
str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。
我们再接着看以下的代码。
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); //false
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。
6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。 7. 结论与建议:
(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。
(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。
(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。
(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。
- 作者: Yachun Miao 2006年12月25日, 星期一 18:36 回复(0) | 引用(0) 加入博采
比较C#、C++和Java最重要的功能
比较C#、C++和Java最重要的功能 | |||
功能 | C# | C++ | Java |
继承 | 允许继承单个类,允许实现多个接口 | 允许从多个类继承 | 允许继承单个类,允许实现多个接口 |
接口实现 | 通过“interface”关键词 | 通过抽象类 | 通过“interface”关键词 |
内存管理 | 由运行时环境管理,使用垃圾收集器 | 需要手工管理 | 由运行时环境管理,使用垃圾收集器 |
指针 | 支持,但只在很少使用的非安全模式下才支持。通常以引用取代指针 | 支持,一种很常用的功能。 | 完全不支持。代之以引用。 |
源代码编译后的形式 | .NET中间语言(IL) | 可执行代码 | 字节码 |
单一的公共基类 | 是 | 否 | 是 |
异常处理 | 异常处理 | 返回错误 | 异常处理 |
- 作者: Yachun Miao 2006年12月20日, 星期三 18:14 回复(0) | 引用(0) 加入博采
HashMap源码分析
HashMap是Java新Collection Framework中用来代替HashTable的一个实现,HashMap和HashTable的区别是: HashMap是未经同步的,而且允许null值。HashTable继承Dictionary,而且使用了Enumeration,所以被建议不要使用。
HashMap的声明如下:
public class HashMap extends AbstractMap implements Map, Cloneable,Serializable
有关AbstractMap:http://blog.csdn.net/treeroot/archive/2004/09/20/110343.aspx
有关Map:http://blog.csdn.net/treeroot/archive/2004/09/20/110331.aspx
有关Cloneable:http://blog.csdn.net/treeroot/archive/2004/09/07/96936.aspx
这个类比较复杂,这里只是重点分析了几个方法,特别是后面涉及到很多内部类都没有解释
不过都比较简单。
static final int DEFAULT_INITIAL_CAPACITY = 16; 默认初始化大小
static final int MAXIMUM_CAPACITY = 1 << 30; 最大初始化大小
static final float DEFAULT_LOAD_FACTOR = 0.75f; 默认加载因子
transient Entry[] table; 一个Entry类型的数组,数组的长度为2的指数。
transient int size; 映射的个数
int threshold; 下一次扩容时的值
final float loadFactor; 加载因子
transient volatile int modCount; 修改次数
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +loadFactor);
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY);
注意:这里应该是一个失误! 应该是:threshold =(int)(DEFAULT_INITIAL_CAPACITY * loadFactor);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
public HashMap(Map m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}
void init() {}
static final Object NULL_KEY = new Object();
static Object maskNull(Object key){
return (key == null ? NULL_KEY : key);
}
static Object unmaskNull(Object key) {
return (key == NULL_KEY ? null : key);
}
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
在HashTable中没有这个方法,也就是说HashTable中是直接用对象的hashCode值,但是HashMap做了改进 用这个算法来获得哈希值。
static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
根据哈希值和数组的长度来返回该hash值在数组中的位置,只是简单的与关系。
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public Object get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (true) {
if (e == null) return e;
if (e.hash == hash && eq(k, e.key)) return e.value;
e = e.next;
}
}
这个方法是获取数据的方法,首先获得哈希值,这里把null值掩饰了,并且hash值经过函数hash()修正。然后计算该哈希值在数组中的索引值。如果该索引处的引用为null,表示HashMap中不存在这个映射。否则的话遍历整个链表,这里找到了就返回,如果没有找到就遍历到链表末尾,返回null。这里的比较是这样的:e.hash==hash && eq(k,e.key) 也就是说如果hash不同就肯定认为不相等,eq就被短路了,只有在 hash相同的情况下才调用equals方法。现在我们该明白Object中说的如果两个对象equals返回true,他们的 hashCode应该相同的道理了吧。假如两个对象调用equals返回true,但是hashCode不一样,那么在HashMap 里就认为他们不相等。
public boolean containsKey(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (e != null) {
if (e.hash == hash && eq(k, e.key)) return true;
e = e.next;
}
return false;
}
这个方法比上面的简单,先找到哈希位置,再遍历整个链表,如果找到就返回true。
Entry getEntry(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (e != null && !(e.hash == hash && eq(k, e.key)))
e = e.next;
return e;
}
这个方法根据key值返回Entry节点,也是先获得索引位置,再遍历链表,如果没有找到返回的是null。
public Object put(Object key, Object value) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
Object oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, k, value, i);
return null;
}
首先获得hash索引位置,如果该位置的引用为null,那么直接插入一个映射,返回null。如果此处的引用不是null,必须遍历链表,如果找到一个相同的key,那么就更新该value,同时返回原来的value值。如果遍历完了没有找到,说明该key值不存在,还是插入一个映射。如果hash值足够离散的话,也就是说该索引没有被使用的话,那么不不用遍历链表了。相反,如果hash值不离散,极端的说如果是常数的话,所有的映射都会在这一个链表上,效率会极其低下。这里举一个最简单的例子,写两
个不同的类作为key插入到HashMap中,效率会远远不同。
class Good{
int i;
public Good(int i){
this.i=i;
}
public boolean equals(Object o){
return (o instanceof Good) && (this.i==((Good)o).i)
}
public int hashCode(){
return i;
}
}
class Bad{
int i;
public Bad(int i){
this.i=i;
}
public boolean equals(Object o){
return (o instanceof Bad) && (this.i==((Bad)o).i)
}
public int hashCode(){
return 0;
}
}
执行代码:
Map m1=new HashMap();
Map m2=new HashMap();
for(int i=0;i<100;i++){
m1.put(new Good(i),new Integer(i)); //这里效率非常高
}
for(int i=0;i<100;i++){
m2.put(new Bad(i),new Integer(i)); //这里几乎要崩溃
}
上面的是两个非常极端的例子,执行一下就知道差别有多大。
private void putForCreate(Object key, Object value) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
e.value = value;
return;
}
}
createEntry(hash, k, value, i);
}
void putAllForCreate(Map m) {
for (Iterator i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry) i.next();
putForCreate(e.getKey(), e.getValue());
}
}
上面的两个方法是被构造函数和clone方法调用的。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (size < threshold || oldCapacity > newCapacity)
return;
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
这个方法在需要的时候重新分配空间,相当于ArrayList的ensureCapacity方法,不过这个更加复杂。
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry e = src[j];
if (e != null) {
src[j] = null;
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
遍历原来的数组,如果该Entry不是null的话,说明有映射,然后遍历这个链表,把所有的映射插入到新的数组中,注意这里要从新计算索引位置。
public void putAll(Map t) {
int n = t.size();
if (n == 0)
return;
if (n >= threshold) {
n = (int)(n / loadFactor + 1);
if (n > MAXIMUM_CAPACITY)
n = MAXIMUM_CAPACITY;
int capacity = table.length;
while (capacity < n) capacity <<= 1;
resize(capacity);
}
for (Iterator i = t.entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry) i.next();
put(e.getKey(), e.getValue());
}
}
这个方法先确定是否需要扩大空间,然后循环调用put方法。
public Object remove(Object key) {
Entry e = removeEntryForKey(key);
return (e == null ? e : e.value);
}
Entry removeEntryForKey(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null) { 如果e==null表示不存在
Entry next = e.next;
if (e.hash == hash && eq(k, e.key)) {
modCount++;
size--;
if (prev == e)
table[i] = next; 链表的第一个元素就是要删除的,这里最好加一句 e.next=null.
else
prev.next = next; 存在但不是链表的第一个元素, 这里最好加一句 e.next=null.
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e; 这里其实就是return null;
}
这个方法其实也不复杂,也是遍历链表,这里建议加一句e.next=null,可以改为
if(prev==e)
table[i]=next;
else
prev.next=next;
e.next=null; 这一句是多加的,可以提高效率。
这里简单说明我的看法:
因为e是被删除的节点,删除它其实就是使指向它的指针指向它的后面一个节点。所以e可以作为GC回收的对象。可事e还有一个next指针指向我们的数据。如果e没有被回收,而且此时e.next指向的节点也变为没用的了,但是却有一个它的引用(e.next),所以虽然e的下一个节点没用了,但是却不能作为 GC回收的对象,除非e先被回收。虽然不一定会引起很大的问题,但是至少会影响GC的回收效率。就像数据库中的外键引用一样,删除起来很麻烦呀。
Entry removeMapping(Object o) {
if (!(o instanceof Map.Entry))
return null;
Map.Entry entry = (Map.Entry)o;
Object k = maskNull(entry.getKey());
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null) {
Entry next = e.next;
if (e.hash == hash && e.equals(entry)) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
这个方法和上面的一样。
public void clear() {
modCount++;
Entry tab[] = table;
for (int i = 0; i < tab.length; i++)
tab[i] = null;
size = 0;
}
同样可以改进
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry tab[] = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value)) return true;
return false;
}
private boolean containsNullValue() {
Entry tab[] = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null) return true;
return false;
}
public Object clone() {
HashMap result = null;
try {
result = (HashMap)super.clone();
}
catch (CloneNotSupportedException e) { // assert false; }
result.table = new Entry[table.length];
result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
result.putAllForCreate(this);
return result;
}
static class Entry implements Map.Entry {
final Object key;
Object value;
final int hash;
Entry next;
Entry(int h, Object k, Object v, Entry n) {
value = v;
next = n;
key = k;
hash = h;
}
public Object getKey() {
return unmaskNull(key);
}
public Object getValue() {
return value;
}
public Object setValue(Object newValue) {
Object oldValue = value;
value = newValue;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2))) return true;
}
return false;
}
public int hashCode() {
return (key==NULL_KEY ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
void recordAccess(HashMap m) { }
void recordRemoval(HashMap m) { }
}
一个静态内部类
void addEntry(int hash, Object key, Object value, int bucketIndex) {
table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
if (size++ >= threshold)
resize(2 * table.length);
}
注意这个方法,插入连表的头。
可以写成这样更好理解:
Entry oldHead=table[bucketIndex];
Entry newHead = new Entry(hash,key,value,oldHead);
table[bucketIndex]=newHead;
void createEntry(int hash, Object key, Object value, int bucketIndex) {
table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
size++;
}
private abstract class HashIterator implements Iterator {
Entry next;
int expectedModCount;
int index;
Entry current;
HashIterator() {
expectedModCount = modCount;
Entry[] t = table;
int i = t.length;
Entry n = null;
if (size != 0) {
while (i > 0 && (n = t[--i]) == null) ;
}
next = n;
index = i;
}
public boolean hasNext() {
return next != null;
}
Entry nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry e = next;
if (e == null)
throw new NoSuchElementException();
Entry n = e.next;
Entry[] t = table;
int i = index;
while (n == null && i > 0)
n = t[--i]; index = i;
next = n;
return current = e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
private class ValueIterator extends HashIterator {
public Object next() {
return nextEntry().value;
}
}
private class KeyIterator extends HashIterator {
public Object next() {
return nextEntry().getKey();
}
}
private class EntryIterator extends HashIterator {
public Object next() {
return nextEntry();
}
}
Iterator newKeyIterator() {
return new KeyIterator();
}
Iterator newValueIterator() {
return new ValueIterator();
}
Iterator newEntryIterator() {
return new EntryIterator();
}
private transient Set entrySet = null;
public Set keySet() {
Set ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSet {
public Iterator iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
public Collection values() {
Collection vs = values; return (vs != null ? vs : (values = new Values()));
}
private class Values extends AbstractCollection {
public Iterator iterator() {
return newValueIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
HashMap.this.clear();
}
}
public Set entrySet() {
Set es = entrySet;
return (es != null ? es : (entrySet = new EntrySet()));
}
private class EntrySet extends AbstractSet {
public Iterator iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Entry candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
HashMap.this.clear();
}
}
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(table.length);
s.writeInt(size);
for (Iterator i = entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry) i.next();
s.writeObject(e.getKey());
s.writeObject(e.getValue());
}
}
private static final long serialVersionUID = 362498820763181265L;
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int numBuckets = s.readInt();
table = new Entry[numBuckets];
init();
size = s.readInt(); for (int i=0;
for (int i=0; i
Object key = s.readObject();
Object value = s.readObject();
putForCreate(key, value);
}
}
int capacity() {
return table.length;
}
float loadFactor() {
return loadFactor;
- 作者: Yachun Miao 2006年12月19日, 星期二 17:52 回复(0) | 引用(0) 加入博采
ArrayList源码分析
声明如下:
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable
有关AbstractList:http://blog.csdn.net/treeroot/archive/2004/09/14/104743.aspx
有关List: http://blog.csdn.net/treeroot/archive/2004/09/14/104638.aspx
有关RandomAccess:http://blog.csdn.net/treeroot/archive/2004/09/14/104538.aspx
有关Cloneable :http://blog.csdn.net/treeroot/archive/2004/09/07/96936.aspx
有关java.io.Serializeable: 主要用于对象序列化。
private static final long serialVersionUID = 8683452581122892189L;
版本控制
private transient Object elementData[];
内部结构,原来就是一个数组,这里不让它串行化。
private int size;
该列表的大小,也就是元素个数。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
this.elementData = new Object[initialCapacity];
}
构造函数,初始化内部数组为指定大小,注意列表是空的。
public ArrayList() {
this(10);
}
默认初始话内部数组大小为10,为什么是10是没有理由的,可能比较合适吧。
public ArrayList(Collection c) {
size = c.size();
// Allow 10% room for growth
elementData = new Object[(int)Math.min((size*110L)/100,Integer.MAX_VALUE)];
c.toArray(elementData);
}
通过一个集合来初始话该ArrayList,内部数组申请的空间比c大,主要是为了提高效率。注意到c.toArray(elementData)
方法的调用,这里肯定不会生成新的数组,如果用elementData=c.toArray()效率就差不少了。另外这里调用了Math的静态
方法min来获得较小值。
public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
Object oldData[] = elementData;
elementData = new Object[size];
System.arraycopy(oldData, 0, elementData, 0, size);
}
}
这个方法去掉多余的空间,使内部数组的大小刚好等于ArrayList的size(),这个方法需要重新分配空间,而已需要一个数组
拷贝过程(arraycopy是一个native方法,用的比较多),一般情况下这个方法很少被调用。
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
elementData = new Object[newCapacity];
System.arraycopy(oldData, 0, elementData, 0, size);
}
}
这个方法来扩大ArrayList的容量,使它至少能容纳minCapacity个元素,如果数组容量大于该值,什么也不做。否则按某个
算法(1.5倍加1)增加,如果不够minCapacity大的话,就设置为minCapacity。这个方法在add和addAll方法中都要调用,
这里为什么设置为public呢?因为每次重新分配空间都是比较消耗时间的(new操作还要arrayCopy),如果能预计可能的大小
的话,这个方法就有比较的灵活性。虽然该扩容算发已经比较好,但是还是可以通过自己的控制提高效率,这个方法为程序员
带来的方便。
eg1:
ArrayList al=new ArrayList();
for(int i=0;i<100;i++){
Object obj=new Object();
al.add(obj);
}
eg2(更高效):
ArrayList al=new ArrayList(100);
for(int i=0;i<100;i++){
Object obj=new Object();
al.add(obj);
}
或者
ArrayList al=new ArrayList();
al.ensureCapacity(100);
for(int i=0;i<100;i++){
Object obj=new Object();
al.add(obj);
}
public int size() {
return size;
}
返回大小
public boolean isEmpty() {
return size == 0;
}
是否为空
public boolean contains(Object elem) {
return indexOf(elem) >= 0;
}
是否包含指定元素,调用的是indexOf()方法。
public int indexOf(Object elem) {
if (elem == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (elem.equals(elementData[i]))
return i;
}
return -1;
}
这个方法遍历列表(数组0..size-1)
public int lastIndexOf(Object elem) {
if (elem == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (elem.equals(elementData[i]))
return i;
}
return -1;
}
这个方法和上面的基本一样,顺序不一样而已。
public Object clone() {
try {
ArrayList v = (ArrayList)super.clone();
v.elementData = new Object[size];
System.arraycopy(elementData, 0, v.elementData, 0, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
覆盖Object中的clone()方法,实现clone,注意这里是一个浅拷贝,两个ArrayList中的数组中的元素
是相同的,因为System.arraycopy就是浅拷贝。
public Object[] toArray() {
Object[] result = new Object[size];
System.arraycopy(elementData, 0, result, 0, size);
return result;
}
返回ArrayList元素的一个数组,注意这里虽然生成了一个新的数组,但是数组元素和集合中的元素是共享的,
Collection接口中说这个是安全的是不严格的,下面的例子演示了这个效果。
eg1:
ArrayList al=new ArrayList();
al.add(new StringBuffer("hello"));
Object[] a=al.toArray();
StringBuffer sb=(StringBuffer)a[0];
sb.append("changed"); //改变数组元素同样也改变了原来的ArrayList中的元素
System.out.println(al.get(0));
这里不要用String来代替StringBuffer,因为String是常量。
public Object[] toArray(Object a[]) {
if (a.length < size)
a = (Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
这个方法有可能不需要生成新的数组,注意到如果数组a容量过大,只在size处设置为null。
public Object get(int index) {
RangeCheck(index);
return elementData[index];
}
可以看随机访问效率是很高的,和数组的索引访问是一样的,方式设计到索引值都会先检查。
public Object set(int index, Object element) {
RangeCheck(index);
Object oldValue = elementData[index];
elementData[index] = element;
return oldValue;
}
更新指定位置的值,并访问原来的值。
public boolean add(Object o) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = o;
return true;
}
添加一个新的元素到末尾,前面说道新增方法都要先调用ensureCapacity方法,这里没有调用add(size,o)方法。
public void add(int index, Object element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
在指定位置插入元素,指定元素和后面的元素后移。
public Object remove(int index) {
RangeCheck(index);
modCount++;
Object oldValue = elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
删除指定位置的元素,后面的元素前移,返回被删除的元素,这里要注意的elementData[--size]=null这条语句,
如果不这样的话,就有可能造成内存泄露,因为该对象其实已经没有用,但是内部还有一个它的引用,如果不设置
为null,GC就无法回收这个空间,积累多了就有可能造成内存泄露,这里只说有可能,而不是一定。
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
这段代码比较高效,这里也必须设置为空引用,理由同上。
public boolean addAll(Collection c) {
modCount++;
int numNew = c.size();
ensureCapacity(size + numNew);
Iterator e = c.iterator();
for (int i=0; i
elementData[size++] = e.next();
return numNew != 0;
}
添加集合c中的元素到ArrayList的末尾,添加成功返回true,如果集合c为空,返回false。
public boolean addAll(int index, Collection c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
int numNew = c.size();
ensureCapacity(size + numNew); // Increments modCount!!
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
Iterator e = c.iterator();
for (int i=0; i
elementData[index++] = e.next();
size += numNew;
return numNew != 0;
}
在指定位置插入集合中的所有元素,和上面一个方法基本差不多,指定位置元素和以后的都要后移。
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);
// Let gc do its work
int newSize = size - (toIndex-fromIndex);
while (size != newSize)
elementData[--size] = null;
}
这是一个保护方法,删除指定位置fromIndex到toIndex的元素,包括前面不包括后面。
private void RangeCheck(int index) {
if (index >= size || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}
不用解释。
private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
// Write out element count, and any hidden stuff
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i
s.writeObject(elementData[i]);
}
private synchronized void readObject(java.io.ObjectInputStream s) throws java.io.IOException,ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in array length and allocate array
int arrayLength = s.readInt();
elementData = new Object[arrayLength];
// Read in all elements in the proper order.
for (int i=0; i
elementData[i] = s.readObject();
}
这两个方法是为了实现串行化而写的,这里要注意几点:
1.这两个方法并不是java.io.Serializable中定义的方法,Serializable只是标记接口。
2.串行化对象的时候会先检查该对象是否实现了这两个方法,如果有实现就调用他们,如果没有的化就调用默认方法。
至于为什么可以调用该对象的private方法我也不清楚,这个问题确实比较奇怪,如果可以访问对象的private方法,那
也太不安全了。
3.因为elementData声明为transient,所以必须手动串行化化。
总结:
1.ArrayList的方法都没有同步,所以在多线程中是不安全的,必须自己同步,而且ArrayList很多方法声明对于多线程操作
都没有规定,就是说结果不可预料。
2.toArray()方法返回的是和原列表相同的对象,也就是说:
arrayList.toArray()[0]==arrayList.get(0)返回的是true(假定arrayList不空)。
3.clone()方法是一个浅拷贝。
4.可以通过自己调用ensureCapacity()提高效率。
- 作者: Yachun Miao 2006年12月19日, 星期二 17:51 回复(1) | 引用(0) 加入博采
Dom4j 使用介绍(转)
- 作者: Yachun Miao 2006年12月19日, 星期二 12:39 回复(0) | 引用(0) 加入博采
英语6级应试技巧
如何做阅读
1.首先到现在为止,词汇量对于阅读应该影响不太大了,因为读不懂可以用我讲的词根来猜.
2.提高阅读能力是个终身的问题,最后这个阶段所想提高太大很难,所以重点应放在找答案上做题的能力,因为这种能力是可以提高的
3,你在上课的会发现老师讲繁难的举例可以略过,这道题考的就是主题等,你当时是不是觉得太有道理了如果不选老师讲的答案就得自杀而课后自已做时却难得不得了,不要认为老师讲方法是马后炮,其实你平时做不出就是方法问题,所谓不读或略读其实是让你速读过去,在读的时候有所偏重而已,所以要认真按老师的方法去做,理会他的方法
4.但有的同学说做后答案都记忆住了,如何在做,我的体会是这样的,第一次按时间做 第二次,精读,第三次不做题只是在分析文章分析老师的讲课思路,这一点很重要!
文章分为启承转合四个部分,面启是指开头 承是指展开论述,转是指一个让步可转折,因为对任事都不可能完全否定或完全支持。合是结束语 而文章分为新老观点对比型,首段结论型,问题解决型,设问及其回答 。那新老观点对比型主题应出现在一段未可二段开头,否则就没有篇展开论述了 而首段结论型主题在第一段,那么第一段是必读第二段开头是必读的,在文章结束时三句话必然是合的部分,而往往会有考题,一般有一道题,所经以结束前三名话必读,其佘各段只读首句一般就够了,而对于做题时,在未读文章前就应该将各题的关键词画出来,还有文章的出题顺序与原文的出题顺序大致相同,找答案时按题索原文章就可以了!
1)在英语学习中,我们自然会遇到许多生词。这时,许多人立即翻阅字典,查找词义。 其实,这种做法是不科学的。它不但费时费力,而且影响阅读速度。事实上,阅读材 料中的每个词与它前后的词语或句子甚至段落有着互相制约的关系。我们可以利用语 境(各种已知信息)推测、判断某些生词的词义。猜测词义时,可利用以下线索:
一 针对性解释
针对性解释是作者为了更好的表达思想,在文章中对一些重要的概念、难懂的术语或 词汇等所作的解释。这些解释提供的信息具有明确的针对性,利用它们猜词义比较容易。
1. 根据定义猜测词义
如果生词是句子或段落所解释的定义,理解句子或段落本身就是推断词义。
例如:
anthropology is the scientific study of man.
由定义可知,anthropology就是“研究人类的科学”。
In slang the term "jam" constitutes a state of being in which a person
finds himself or herself in a difficult situation.
同样,从上下文的定义可知jam一词在俚语中的意思是“困境”。
定义句的谓语动词多为:be, mean, deal with, be considered, to be, refer to,
be called, be known as, define, represent, signify, constitute等。
2.根据复述猜测词义
虽然复述不如定义那样严谨、详细,但是提供的信息足以使阅读者猜出生词词义。 复述部分可以适当词、短语或是从句。
同位语
Semantics, the study of the meaning of words, is necessary if you are to
speak and read intelligently.
此例逗号中短语意为“对词意义进行研究的学科”。该短语与前面生词semanties式同位关系,因此我们不难猜出semanties指“语义学”。
在复述中构成同位关系的两部分之间多用逗号连接,有时也使用破折号,冒号,分 号,引号,和括号等。
Capacitance, or the ability to store electric charge, is one of the most common characteristics of electronic circuits.
由同位语我们很快猜出生词capacitance词义---电容量。需要注意的是:同位语前还常有or, similarly, that is to say, in other words, namely, or other,say i. e. 等副词或短语出现。
定语从句
Krabacber suffers from SAD, which is short for seasonal affective disorder,
大学英语四六级猜词技巧解读(2)
a syndrome characterized by severe seasonal mood swings.
根据生词SAD后面定语从句which is short for seasonal affective disorder和同位
语a syndrome characterized by severe seasonal mood swings, 我们可以推断出SAD含义,即“季节性情绪紊乱症”。
根据举例猜测词义
恰当的举例能够提供猜测生词的重要线索,
例如:
The consequences of epochal events such as wars and great scientific
discoveries are not confined to a small geographical area as they were in
the past.
句中“战争”和“重大科学发现”是生词的实例,通过它们我们可以猜出epochal的 大致词义“重要的”,这与其确切含义“划时代的”十分接近。
二 内在逻辑关系
根据内在逻辑关系推测词义是指运用语言知识分析和判断相关信息之间存在的逻辑关系,然后根据逻辑关系推断生词词义或大致义域。
1. 根据对比关系猜测词义
在一个句子或段落中,有对两个事物或现象进行对比性的描述,我们可以根据生词或难词的反义词猜测其词义。
例如:
Andrew is one of the most supercilious men I know. His brother, in contrast,
is quite humble and modest.
该例中supercilious对许多人来说可能是个生词,但是句中短语in contrast,(相对照的,相对比的)可以提示我们supercilious和后面词组humble and modest(谦卑又谦虚)是对比关系。分析出这种关系后,我们便能猜出supercilious意为“目空一切的,傲慢的”。
表示对比关系的词汇和短语主要是unlike,not,but,however,despite,in spite of,in contrast 和while 引导的并列句等。
A good supervisor can recognize instantly the adept workers from the unskilled ones.
该句中并未出现上面提到的表示对比关系的词或短语,但是通过上下文可以判断出句子前后是对比关系,即把熟练工人与非熟练工人区分开。这时我们也能够推断出生词adept的词义,“熟练的”。
2. 根据比较关系猜测词义
同对比关系相反,比较关系表示意义上的相似关系,
例如:
Green loves to talk,and his brothers are similarly loquacious.
该句中副词similarly表明短语loves to talk与生词loquacious之间的比较关系。以此可以推断出loquacious词义为“健谈的”。
大学英语四六级猜词技巧解读(3)
表示比较关系的词和短语主要是similarly,like,just as,also等。
3. 根据因果关系猜测词义
在句子或段落种,若两个事物、现象之间构成因果关系,我们可以根据这种逻辑关系推知生词词义。
例如:
Tom is considered an autocratic administrator because he makes decisions
without seeking the opinions of others.
根据原因状语从句的内容,我们可以推断出生词autocratic指“独断专行的”。
There were so many demonstrators in the Red Square that he had to elbow his
way through the crowd.
此句为结果状语从句,根据从句的描书“许多示威者”,我们便可推知elbow的词意“挤,挤过”。
4. 根据同义词的替代关系猜测词义
在句子或段落种,我们可以利用熟悉的词语,根据语言环境推断生词词义。
例如:
Although he often had the opportunity, Mr. Tritt was never able to steal
money from a customer. This would have endangered his position at the bank,
and he did not want to jeopardize his future.
作者为避免重复使用endanger一词,用其同义词jeopardize来替代它,由此推知其词义为“使. . . 陷入危险,危及、危害”。
Doctors believe that smoking cigarettes is detrimental to your health. They
also regard drinking as harmful.
句中detrimental四个生词,但判断出harmful替代detrimental后,不难推断出其词义为“不利的,有害的”。
三 外部相关因素
外部相关因素是指篇章(句子或段落)以外的其他知识。有时仅靠分析篇章内在逻辑关系无法猜出词义。这时,就需要运用生活经验和普通常识确定词义。例如:
Husband:it‘s really cold out tonight.
Wife: Sure it is. My hands are practically numb.
How about lighting the furnace?
根据生活经验,天气寒冷时,手肯定是“冻僵的,冻得麻木的”。
The snake slithered through the grass.
根据有关蛇的生活习性的知识,我们可以推断出slither词义为“爬行”。
在猜测词义过程中,除了使用上面提到的一些技巧,我们还可以依靠构词方面的知识,从生词本身猜测词义。
1. 根据前缀猜测词义
例如:
He fell into a ditch and lay there, semiconscious, for a few minutes.
根据词根conscious(清醒的,有意识的),结合前缀semi(半,部分的,不完全的), 我们便可猜出semiconscious词义“半清醒的,半昏迷的”。
I‘m illiterate about such things.
词根literate意为“有文化修养的,通晓的”,前缀il表示否定,因此illiterate指“一窍不通,不知道的”。
2. 根据后缀猜测词义
例如:
Insecticide is applied where it is needed.
后缀cide表示“杀者,杀灭剂”,结合大家熟悉的词根insect(昆虫),不难猜出insecticide意为“杀虫剂”。
Then the vapor may change into droplets.
后缀let表示“小的”,词根drop指“滴,滴状物”。将两个意思结合起来,便可推断出droplet词义“小滴,微滴”。
3.根据复合词的各部分猜测词义
例如:
Growing economic problems were highlighted by a slowdown in oil output.
Hightlight或许是一个生词,但是分析该词结构后,就能推测出其含义。它是由high
(高的,强的)和light(光线)两部分组成,合在一起便是“以强光照射,使突出”
的意思。
Bullfight is very popular in Spain.
Bull(公牛)和fight(打,搏斗)结合在一起,指一种在西班牙颇为流行的体育运
动---斗牛。
综上所述,利用各种已知信息推测、判断词义是一项重要的阅读技巧。在实践中,我 们可以灵活运用,综合运用上面提到的几种猜测技巧,排除生词的障碍,顺利理解文章的思想内容,提高阅读速度。
阅读必备千词
spirit, decline, climate, lend to, accuse, afford, anxiety, atmosphere, blame, bargain, calculate, circle, confidence, conscious, convince, custom, desperate, encourage, discourage, economic, economy, motive, promote, emotional, motion, fail to, flat, install, intimate, limit, major, no matter, neutral, outlet, perform, inform, formal, normal, radiate, authority,
companion, concept, create, creature, dentist, identify, identical, identity, engage, entitle, evaluate, fluid, influence, fortune, fulfill, general, intense, interpret, justice, adjust, judge, minor, observe, deserve, preserve, parallel, percept, stuff, surroundings, transport, transmit, transform, undergo, wander, wonder, widespread, ambition, approach, brief, conquer, result, consult, insult,
deliberate, despair, compare, emergence, emergency, establish, exhaust, expand, fade, frustrate, handy, incredible, inherit, conference, infer, offer, interfere, length, manufacture, mood, necessity, noble, occasion, occasional, output, oversea, pattern, plunge, practice, practical, recognize, release, rescue, maintain, remain, obtain, entertain, rural, urban, similar to, trial, trail, witness, absolute,
accumulate, in advance, advanced, advantage, agent, appeal, application, point, appoint, approximate, barrier, claim, climate, complicate, comprise, considerable, resume, consume, assume, contact, convict, crew, crucial, define, definite, deliver, disaster, disorder, diverse, economic, alert, exert, facility, faith, faithful, gap, sympathy, trace, track, visual, vision, worship, apparatus, brand, civil, clarity,
declare, combine, connect, consider, insert, desert, drown, external, frank, press, impress, pressure, depress, express, indispensable, inspect, respect, prospect, perspective, peculiar, particular, likely, inquire, require, acquire, request, result , consult, insult, spray, announce, pronounce, apparent, boundary, frontier, crash, debate, decrease, increase, reduce, include, conclude, exclude, federal,
firm, forecast, foresee, grateful, integrate, divide, individual, fence, defense, offend, portion, proper, property, reputation, restrict, loyal, royal, spot, terminal, alter, alternate, alternative, concrete, consequent, explode, explore, exploit, hesitate, imagine, influence, innocent, guilty, persuade, possess, sample, support, vital, burden, elect, select, collect, collective, contribute, distribute,
attribute, abuse, prejudice, avoid, escape, capture, compete, effort, enthusiasm, involve, evolve, imply, reply, multiply, issue, modify, occur, opponent, prefer, refer, publish, punish, rate, award, reward, shelter, skim, in spite of, temporary, vehicle, ancestor associate, refuse, confuse, describe, display, due to, error, essential, fresh, obvious, visible, invisible, previous, recall, relate, soil,
spoil, stimulate, suffer, tense, accompany, analyze, conflict, convenient, derive, deprive, thrive, dominate, element, gallery, interval, lock, lack, match, ideal, reality, ignore, ignorant, independent, inspire, motion, promote, remote, motive, numerous, relevant, retreat, straight, suicide, trick, weigh, tempt, attempt, contract, attract, candidate, commercial, communicate, contain, coordinate, respond,
correspond, emphasis, enclose, focus, instinct, investigate, invest, manner, mere, nerve, political, policy, reliable, resemble, assemble, shape, sophisticate, stain, strain, symbol, triumph, upright, vigorous, benefit, profit, characterize, circumstance, chain store, concentrate, delegate, diligent, dispute, estimate, factor, intentional, occupy, inject, reject, object, project, subject, scenery,
survive, revive, temper, vary, accelerate, behave, concern, continue, deny, distance, insure, invade, mature, mental, monitor, mysterious, neglect, purpose, raw, refresh, remove, restore, resort, scarcely, specific, target, alarm, approve, certify, certificate, frost, fund, genuine, intend, material, memory, primitive, propose, ruin, shadow, contrary, sufficient, accent, actually, adequate, anxious,
attach, awkward, budget, capture, channel, circulate, community, company, consent, cooperate, cultivate, depart, devote, dismiss, distant, enhance, eventually, fierce, manage, overnight, passion, passive, postpone, progress, pursue, react,
render, sense, treat, treaty, capacity, civilian, compel, contradiction, crack, curiosity, departure, devise, device, secure, accurate, depend, distinguish, plain, complain, explain, financial, hurt, injure, harm, destroy, ruin, instruct, construct, structure, isolate, labor, task, overlook, overcome, resist, insist, state, supreme, superior, volume, absorb, account, achieve, universal, convert,
reverse, apply, applicable, applicant, application, broad, board, cancel, casual, cancer, classify, comfort, submit, permit, emit, complex, conduct, constant, core, demand, command, comment, commend, mental, recommend, mention, design, signal, significant, protect, detect, point, appoint, appointment, disappoint, duration, durable, employ, excess, exceed, success, succeed, process, proceed, false, impose,
indicate, predict, opportunity, personal, pose, dispose, expose, suppose, oppose, compose, impose, deposit, poison, poverty, cautious, prove, improve, record, relieve, sincere, troop, upset, violent, welfare, abundant, advocate, affect, effect, effective, efficient, campaign, cause, commission, component, content, intention, crisis, critic, critical, destination, discard, discipline, anticipate,
participate, principal, principle, familiar, feature, feasible, indifferent, provide, recover, standard, thorough, morally, virtually, deal with, cope with, doubt, execute, density, corporation, currency, current, demonstrate, countless, discount , extraordinary, graduate, gradually, precious, precise, appreciate, reluctant, unwilling, sharp, shape, source, strength, length, supply, drop, endure,
evident, enormous, intensive, preference, produce, reflect, scale, semester, shift, talent, threat, typical, vain, volunteer, accomplish, analysis, assist, persist, insist, resist, consist, colony, confront, conventional, descend, donate, elementary, eliminate, frequent, incline, instant, merit, military, prevent, represent, restrain, academic, available, challenge, continent, elaborate, function,
generous, illustrate, measure, narrow, option, phenomenon, prohibit, panic, pretend, prompt, regulate, shrink, swallow, uniform, artificial, creature, determine, distinct, encounter, environment, fatigue
改错
首先要说的是:CET6每次改错要你指出错误的地方肯定有错,绝对不会没错,而且肯定在这行,不会到前面或者下面去!!
然后要说的是:从上次CET6改革来看,改错的判卷方法改变了。找出错误0。5分,改对错误0。5分
有错的一句话,最多10个英语单词,大家首先按词性来划分一次
名词错误的可能
(1)名词单复数
只有这1种可能,而且到现在的改错,我就记得就1次单复数没考,其他场次必考!
形容词错误的可能
(1)意思颠倒,要改成反义词
这个错错每次改错题目都有,所以看到错的句子有形容词,先上下文看看,有没有意思反了
(2)词性错误
2个形容词在一起,那肯定有一个是修饰另外一个的,所以要改成副词
介词错误的可能性
(1)固定短语的搭配问题,不如key to,answer to, be faced with等
这个错错每次改错题目都有,所以看到错的句子有介词,先看左右,有没有搭配错误
连词错误的可能性
(1)承上启下的错误
有时候,表示递进的,但题目中给出even if,所以要把if去掉;有时表原因的,但题目中是therefore,所以要改成because。。这样的错误也时常出现,但不是每次出现
(2)非限制性宾语从句只能由which引导,题目中很会用that来误倒
代词错误的可能性
(1)代词与先行词不一致
前面说了是单数,后面用了them,所以要改成it。 这类的错误也经常发生
动词错误的可能性 (大头!!)
(1)时态错误
明明文章在说过去的事情,但用了个is,所以要改成was
这个错错每次改错题目都有,所以看到错的句子有动词,先看上下,有没有时态问题
(2)主谓不一致
they was doing …… 这样的错误,找不出么,6级也别考了
(3)非谓语动词提前形式的错误
viewed……,they were doing…… 像这样的情况,viewed就要改成viewing
如果后面是it,前面是ing形式,也要注意的
(4)平行结构错误
前面连着2句都是to做什么,to做什么and do什么, 这时候就要在这个do前加to
如果是to doing,就要改成to do
以上这些错误在考试中占6-7分左右,大家可以一定要抓住,后面的分数就难拿了
另类错误
(1)易混淆的词
比如:His persistence was awarded when the car finally started
中间的awarded是错误的,应该改成rewarded
(2)从上下文来看,应该改动的词
一般发生在名词的身上!! 比如前面说美国人吃饭的习惯,后面写了chinese,那就要改成American
(3)固定词组用错一个,造成意思完全改变
比如:It also takes rise to a blurring of the dis tinction between science and
technology 要把takes 改成 gives
这样的难点错误,解决方法是,能改则改,不能改确定哪个词错,骗2个0。5分
总的评注:做改错题一定要具有一双“慧眼”。重要的不是自己会运用一个语法点或知识点,而是能够识别出错误的用法,以审查的眼光去面对每一个改错题。这就需要掌握必要的答题步骤和技巧。
答题步骤:
1.一般来说,做题时千万不要拿起来就改。先花一、两分钟从头到尾通读全文,对文章大 致内容有所了解,做到心中有数。
2.然后把重点放在有错误项的标题号行,寻找较容易辩认的语法错误,如主谓不一致、 时态、语态使用错误、非谓语动词错误等等。
3.如果错行中不存在上述明显错误,则应查看是否有词语搭配错误,易混词错误、词性错误等等细节错误。
4.如果错行中既不存在语法错误,也不存在词汇错误,则从整体上查看上下文意思是否连贯,连接词是否使用正确,是否有逻辑混乱的现象,如否定句误用成肯定句造成句意不通等。注意:有时没有错项的行对改错很有帮助。
5.找到错误项之后,按要求形式进行改正、删去或增添,并设法找到一个正确项使句子在 语法、语义和逻辑上都成立
3.改错题的具体解题方法
现在我们来讨论改错题的具体解题方法。如前所述,改错题可以大致分为六个类型,所以我们的讨论也针对这六个题型来进行。
A.时态错误的发现与解决
我们来看2001年第79题:Immediately before him was
a…he put it into his collar,so that it falls across his shirt.此处的上下文明显地表现出过去时态的特征,因为在本行之前有明确的过去式was,那么如果was无错误,falls就必然是错的。然后根据题目可知was所在行无错误,所以falls应改为fell
由此我们可以看到,发现时态错误的关键是确定文章背景时态。句子的时态一般情况下应与文章的背景时态相一致,如不一致,则就会是错的,这就要求我们在通读文章时要留心。此外,就像这道题所反映的,有关的动词或者时间状语也是有用的。在解决的问题上,要注意同类时态的选择问题。譬如发现文章中的过去时态是错误的,而应当选用现在时态,则要注意是用一般时还是完成时。
需要注意的一点是,在利用时间状语或是有关动词来 _进行判断时,应该肯定该判断的依据是正确的,否则会导致错误。因此,背景时态是进行判断的最可靠的标准。
B.介词错误的发现与解决
首先来看介词本身用法错误的题型:A very flat piece of bread that looked,to him,very much as a napkin.在本题中,介词as是错误的,应用like取而代之。就这种题而言,准确掌握介词本身的意义是最根本的。As一般当做“作为”讲,而like的意义则是“像”。如果能精确地掌握它们的区别,那就会对题目的错误一目了然了。此外,有些介词有其习惯的用法,如2001年6月第75题,at the country就应改为in the country。对于这些习惯的用法,应多练多总结以利掌握。
然后是介词搭配。在所判断的行中有介词时。首先要考虑的就是该介词是否可以与其他的名词或动词构成固定搭配。如1996年6月第75题,because of its large student body consisting in many people…该题中in无疑是错误的,因为consist of是一个固定的搭配。对于此类题,熟悉有关的搭配本身就是解题的方法。
C.主谓一致的错误发现及解决
前面已经说过,主谓一致主要是数的一致。我们特别
要注意的是一些既定的语法规则,考试往往是比较侧重于考这些规则的。下面是有关规则的总结:
the majority作主语,谓动用第三人称单数。
the majority+复数名词作主语,则用复数。
the flock of+复数,谓动用单数。
表示价值,重量,长度,时间等名词,尽管是复数形式,谓动也用单数。
a number of+复数,谓动用复数;the number of+复数,则用单数。
neither…nor…连接两个并列主语时,以第二个名词为准。
more than one+复数名词,谓动为单数。
kind,form,type+of+名词,以kind,form,type的数为准。
a series/species/portion+of+名词,用单数。 many a+单数,谓动用单数。名词+and+名词表示一种概念时,用单数。
neither of+复数,谓动用单数。
从句做主语,谓动用单数。
an average(total)of+复数后用复数,the average(total)of+复数后用单数。
a body of+复数,谓动用单数。
这些规则只是其中常考的一部分,其他的还有待于大家进行总结。总之,熟练地掌握这些规则,应付主谓一致类的考题就会很容易了。
D.动词错误的发现与解决
我们首先来看2001年1月的第80题:But perhaps we should look at both sides of the coin before arriving hasty conclusions.本题中所考查的是短语动词。英语中有许多的短语动词,是很难从字面上判断其意义的。其中与介词构成短语的动词是最常见的。例如本题中的arrive,必须与at搭配成为短语动词之后才能与conclusion搭配。我们在分析题目的时候,发现有动宾搭配的时候,应该考虑该动词是否是一个短语动词,并进而分析构成该短语动词的介词有无遗漏。对于短语动词的熟悉是一个长期积累的过程,需要随时总结与记忆。以下是六级考试中常见到的短语动词:
account for, add up to, back up, break down, break away from, bleak out, break up, break in, break off, bring about, “bring up, bring out, bring down, build up, call off, call for, call on, carry out, check in, clear up, come off, come around, count on, cut down, cut off, do away with, dwell on, fall back on, fall out, get over, get out of, get at, give out, give up, give off, hang on, hold back, keep back from, keep up with, lay off, lay down, live up to, look into , look forward to, look over, make up, make out, pass away, put up with ,role out, run out of, set about, take over, take up, take in, take off, turn up, turn out, work out等。
还有一种常考的动词的用法就是语态。如2000年1月第79题:One is surely justitied in his concern for the money and resources that they are poured into the space exploration.在本题中,由于从句的主语they与pour的关系是主动的,因而不可以用被动语态。在所分析的文字中包含谓语动词,那么应该分析是否是这方面的问题。其中应该重点分析主语与谓语动词的关系是主动还是被动。
E.连接词错误的发现与解决
我们首先来看2000年6月的第73题:However.a second person thougt that this was more a question of civilized behavior as good manners.本题所考查的就是比较连接词的用法。从前文的more我们就可以看出,下文的as是不恰当的,正确的答案应是比较连接词than。一般来说,比较连接词所考查的重点集中在88与than的用法上。句意中是否还有比较意义是很容易辨别的,我们一旦觉察到句子有比较的意思,就应考虑是否是as与than的用法混淆。
此外定语从句的连接词也是要特别注意的。我们看 2000年6月的第74题:Instead,this other person told us a story,it he said was quite well known.在这里,句子的后半段很明显是一个非限制性的定语从句,所以应将it改为which。非限制性的定语从句的连接词在近几年的考试中反复出现,应该引起我们的重视。解决此类问题的方法是。凡是发现有复合句的分析对象,首先就要考虑从句的连接词。首先看是否有连接词,其次看连接词是否正确,尤其是which与 that的不同应用。
F.语言环境类错误的发现与解决
从历年的考题来看,语言环境类的错误是最有章可循的。这类错误一般是一行中的关键词与上下文所体现出来的意思完全相反。例如2000年6月第72题:A well—man. nered person…walks down a street he or she is constantly un-aware of others.本句的文意是一个有礼貌的人在街上走的时候是不会旁若无人的。而句中的unaware明显与句意是不符的。此类题的解决也较简单,那就是将不符合文意的词改为它的反义词即可。
改错练习中应注意的问题
A.首先要注意的是,在阅读和分析的时候,不要仅仅针对有错误的行进行分析。分析时不 要以行为单位,而应当以句子为单位。理由很简单,以行为单位进行分析容易使我们割断上下文之间的联系,导致分析的片面性。
B.在分析时态是否恰当时,应考虑整个文章的时态,根据上文句子的时态来判断下文句子的时态时,必须保证上文的时态正确,以免一错再错。
C.关于做题的时间的问题。改错的练习有一点特殊,那就是一定要在练习中养成检查的习惯,因为很多题目的判断是依赖对于文章背景的把握的。务必保证检查的时间。考虑到这个题型的特点,全部的做题时间要在15分钟左右,其中的5分钟左右应该是用来检查的。
D.在检查时,对于增、删的地方要再三斟酌。一般而言,考题中改的地方多,而单纯的增删是很少的,要注意句子的完整和通顺。
E.对于真题的总结问题。真题是一定要总结的。因为六级改错的考点是有限的,它不可避免的要重复出现。总结以往的考点就意味着对将来考题的预习。总结的方法也是简单的,你可以仅仅将考过的题摘录下来,注意经常熟悉就可以了。
简短回答题的具体解题方法分析
下面将针对具体的题型进行分析。
A.概括总结题型
这类题型主要包括主旨型题目和推断型题目,我们先来分析主旨型题目。
我们来看1997年1月的Sl题。这道题是典型的文章大意题,它提问的形式是What is the passage mainly about? 对于这类题,首先要将文章的大意所包含的要点归纳出来。通过读文章我们知道,文章的主要内容有以下几个方面:汽车被盗;盗车犯;赃车如何处置;破案情况;对盗车犯的惩罚;如何防盗。因此答案就必须概括到以上几个方面。显而易见,以上几个方面的综合就是很简单的一句话,那就是“美国汽车盗窃情况”,译成英文,就是,the car theft in the US。这样,答案就搞定了。有的同学在做题的时候不喜欢归纳各段的意思,仅凭对文章的整体印象作答,这样很容易导致答案的覆盖面不全。还有一类题是段落大意题,如 1998年6月的s4题。题目是这样的:Apart from personal preferences。what determines one’s choice of the media and media content?应付这类题,一个行之有效的方法是在段落中寻找关键句。就本题而言,段落的首句就点明了主旨....quite a different sort of factor that affects your media experiences is the social context of exposure…而且该段的第三句也进一步使段落大意明显化:…that social context affects…the media and the media content to which you become exposed.所以,答案就应当是the social context of exposure。一般而言,对于段落够精确而影响分数。另外还需注意的一点是,段落大意题往往不直接用类似于文章大意题的方式提问,即用类似于 what…mainly about的形式提问,因此要注意题目的定性分析。如前例,很多同学不知道personal preference是对第三段所论述的因素的概括,没有读懂题意,所以给出的答案离题万里,这种情况是要避免的。
其次是推断性的题目。我们首先来看1999年1月的s2题。题目是这样的:
Many parents think that,instead of watching a lot of TV.their children should_____.本题需要综合一、二段内容作综合判断。一、二段讲很多家长抱怨孩子懒惰,只知道看电视或者依赖父母带他们去玩,而不知道自己想办法玩。可见,家长的意思是希望孩子自己想想应该怎么去玩。对这个意思加以概括,就是play with themselves。
这类题目常常是与文中某些人的观点有关的。解此类题目的关键就是根据提问,在文章中寻找与提问相关的部分并提炼其大意,根据所概括出来的内容进行推断。在推断的过程中要注意对题目要求把握的准确性,不要掺杂无关的内容。
B.细节类题目
我们也分几个部分进行分析。第一个部分是描述型题目。我们来看1999年1月的Sl题:According to many par-ents,without TV,their children would like them to .本题是一道典型的描述性题目。文章的第一句话就说,很多家长担心如果没有电视,孩子们会不停地要求家长陪他们玩。答案是显而易见的,用英语表述出来,就是play with them。我们再来看1997年1月的s3题:How serious did the author predict the annual vehicle theft could be in the United States in 1989?文章的第二段最后一句话告诉我们,…exper ts predict annual vehicle thefts could exceed 2,000,000 by the end of the decade.也就是说到80年代末,美国每年将有200万辆以上的汽车被盗。所以本题的答案应该是it could exceed two million。描述类的细节题的答案,一般在文章中都可以找到,我们只需根据问题,在文中按图索骥,找到与提问相关的内容并加以概括就可以了。在对所找到的内容加以概括的时候,要注意两个问题:其一,文中有的意义的表达方式与题目中不同,如上例提问中的in 1989和文章中的by the end of the decade,要搞清楚它们所表达的是同一个含义。这种表达方式的互换在简短回答中是屡见不鲜的。其二,作为概括对象的内容中的一些关键词汇在答案中一定要体现出来。比如上例中的exceed,如果忽略了这个单词,那么答案的意义就与问题所要求的有很大的差距了。
接着我们来看因果关系型的细节题。此类细节题所考查的对象是文中的因果关系。提问多用why问句的形式,并且该问题的答案一般在文章中是可以直接找到的。我们先来看1998年6月的s2题:Why ale newspapers considered as an important medium according to the passage?文章的第一段第四句中写道:…with newspapers a close second.at least as a source of news and other information.在这句话中,直接谈到了newspaper的作用,即是重要的新闻和其他信息的来源。用英文表达就是because they are a source of news and information.再看1998年1月的S2题:Why was it easy for boats to tumble over in the Colorado?在文中有两处关键的话,即:…all of us naturally set aside any pretenses and put out backs into ever stroke to keep the boat from tumbling over,…working together to cope with the unpredictable twists and turns of the river.前一句话暗示出小船极易”tumble over”,后一句话表明河流有”the unpredictable twists and turns“,综合这两句话我们可以知道答案应为because of the wilder rapids。
解决因果关系类的细节题,要注意在文中出现的表示因果关系的词汇。通常表示原因的词汇有for that reason, for,as,because,since,as a result of,owing to,thanks to等,表示结果的词汇有as a result,therefore,consequently,thus, accordingly, so。在阅读时要特别注意这些词后面的内容,它们通常就会是因果关系类题所考查的内容。在答案的组织上,一般使用because开头的句子或because of开头的短语来回答。
然后我们来分析举例型的题目。下面请看1998年6月的S3题:For one reason or another,people’s exposure to the media is often_______.在审题时,应当注意题目中的“for one reason or another”,这表明要填的内容应当具有概括性,也就是各种原因导致的结果。文中第二段首句就突出了主题:There are various factors that can cause you to expose yourself to the media selectively,…然后指出原因:…is probably due to the psychological pressure…However,…but to other factors, such as…在做题时,应紧紧扣住selective这一点来组织答案,也就是说,在答案中一定要体现selective或者与其意思相同的语句。我们再举一个例子,1998年1月S4题:What caused the sharp conflict in the GM plant in the late 1970s?此题的答案需要从文中所举例子前后经过的描述中才能概括出来。文中用For example道出事情经过:For example,in the late 1970s a General Motor plant in Fremont. Calif,was the scene of constant warfare between labor and man-agement.Distrust ran so high that the labor contract was hundreds of pages of tricky legal temm.在这段叙述中,“distrust”是一个关键词。此外,上一段“the teamwork is the key to making dreams come true”也很重要,归纳答案时也应该考虑。经过概括,我们就可以得出答案:Distrust and lack of teamwork.
举例型题目,解决的关键是将文章中所列举的比较分散的与问题有关的语句加以概括。在解题时应该注意,所总结的答案的覆盖面一定要足够包容或者代表文章中与此有关的语句。
最后我们来分析对照型的题目。我们看1999年1月的S5题:Developing children’s self-confidence helps bring them up to be _______.此题答案在短文最后一句能直接找到:Giving children the opportunity to develop new resources.to enlarge their horizons and discover the pleasures of doing things on their own is,on the other hand, a way to help children develop a confident feeling about themselves as capable and interesting people.句中on the other hand表示对照,暗示出与前面相反的结果。答案可以总结为:capable and interesting people。
再看同一张卷子的S4题:When parents show constant disappointment in their children。the destructive effect is that the children will________.文中有关这个问题的表述为:
Such disappointment, however, is not only unjust, it is also destructive. Sensing their parent’s disappointment, children come to believe that they are, indeed, lacking something, and that this makes them less worthy of admiration and respect. 首先要注意 however这个词,通过它的转折我们可以知道其后面的destructive是一个关键词。然后在后一句中寻找解释为什么 destructive的语句,那就是make them less worthy of admiration and respect,从而,答案就可以总结为think themselves not worthy of admiration and respect。
这种题型的特点是,答案在文中是以与其他事物进行对比或比较的形式出现的。在解决它们的时候,要注意严格审题,切勿混淆了对比的两个方面。此外,对于一些表示对比和比较的词语要特别关注,例如however,nevertheless,in contrast。on the other hand,but,yet,while和likewise,in the same way ,as if ,as等等,在它们的附近往往可以找到答案的原材料。
简短回答的常见错误总结
六级的简短回答题有一些错误是具有共同性的,几乎所有考生所犯的错误都集中在这几种错误上。而这些错误,在六级考试关于这个题型的评分标准上都得到了反映。下面我们通过对评分标准的分析来举出简短回答题的一些常见错误。英语六级考试的每个题型都有其评分标准,但是简短回答题的评分标准有特别重要的意义,它几乎就是针对这一题型的具体的应试指导。首先让我们来了解一下评分标准:
a:本题要求读后回答5个所提的问题或补足不完整的句子。每题2分,共10分。
b:本题虽为简答题,但回答不全面者扣分。
c:有自相矛盾之处扣分。
d:照抄原文者扣分,照抄一句扣半分,照抄两句和二句以上零分。
e:答非所问者扣分。
f:答多者扣分。
g:正确的回答里有语法错误部分扣分。
h:回答多余部分如有语法错误的同样扣分。
以上的这些要求就反映了我们在考试中常见的错误,
下面我们来进一步分析:
首先,回答不全面是不行的。我在前边的题型分析中强调过某些题型应当保证答案的涵盖面,也就是说,符合题目要求的所有要点都要在答案中体现出来,特别要注意关键性的词汇不被忽略。为达到此目的,在内容比较多,如果用句子表达的话就容易答多的情况下,可以考虑用短语,特别是并列形式的短语作答。
第二,答案不能自相矛盾。这一点大家都很清楚。在作答完毕之后,要考虑所拟出的答案在逻辑上是否无懈可击,防止出现矛盾的现象。尤其注意答案中的形容词和副词不要与它们的反义形式或反义词相混淆。
第三,不能照抄原文。但要注意,答题时必须紧密结合文章,答案中的关键词句尽量用文章中的词句来回答,这就要求将合理的利用与照抄原文区分开来。一般情况下,答案的关键词汇用原文中的词汇是没有问题的。在作答的时候要胆大心细,除非有十足的把握,不要自己创造新的关键词汇,以求答案的可靠性。
第四,问什么,答什么。答案必须要有覆盖面,但是决不要为了追求答案的覆盖面就东拉西扯。在考试中这是丢分的一个重要原因。答案必须要完整,但是绝对不要答题目不要求的东西。
第五,答案要在规定的字数之内。这就要求,回答时要简短,能用单词的不用短语,能用短语的不用句子,防止拖沓。而且,越是简短的表达方式,犯语法错误和其他错误的可能性就越小。很多同学在答题时惟恐答得太简略会导致意思表述不清,或者由于总想把问题回答得圆满一些,或者由于习惯的原因总要采用完整的句式,这些想法在一定程度上都是有害的。因为简短回答题的字数限制使你有时候无法采用完整句,而只好用省略句或者短语。大家应该明白,简短回答,就是回答越短越好,在答案的长短上不要有太多的顾虑。简单明确的答案往往会更好地切中题意。反之,冗长拖沓的答案有时会使判卷的老师不明就里,导致失分。
第六,绝对禁止犯语法错误。简短回答题的答案不可能是复杂句,只会是简单句、短语,甚至有时候是一个单词。在这么简单的表达方式里还要犯语法错误简直是不可忍受的。在拟定答案之后,要对答案进行语法分析,保证其在语法上正确。尤其要注意的是时态,例如文意要求答案用过去时态的,不要写成现在或将来时态。也就是说,在时态上要和文章匹配。其次是主谓一致。另外,还有一些细节问题要注意,例如用句子回答问题时,首字母要大写的问题。这些问题看起来很细小,但是如果不注意的话,后果是严重的。以上所列举的诸种要求,都是我们在考试时要认真注意的。在平时的练习中严格地按照这些要求去做,慢慢地养成好的解题习惯,是在考试时避免上述问题的根本解决方法。
写作
四六级考前必看写作句型与过渡词语【开篇句】
1) With the rapid improvement in.../growing awareness of..., more and more.../sth....
(e.g. With the considerable improvement in building industry, more and more structures are being erected to set the people's minds at ease.)
2) Recently, sth./the problem of...has been brought to popular attention/ has become the focus of public concern. A
(e.g. Recently, the problem of unemployment has been brought to such popular attention that governments at all levels place it on the agenda as the first matter.)
3) One of the universal issues we are faced with/that cause increasing concern is that...
(e.g. One of the universal issues that draw (cause) growing concern is whether it is wise of man to have invented the automobile.)
4) In the past few years, there has been a boom/sharp growth/decline in.. .
(e.g. In the past ten years, there has been a sharp decline in the number of species.)
5) Nowadays, more/most important/dangerous for our society is...
(e.g. Nowadays, most dangerous for our society is the tendency to take advantage of each other in political circles.)
6) According to the information given in the table/graph, we can find that...
7) As can be seen from the table/graph/figure, there is a marked increase /decline/favorable (an unfavorable) change in...
8) As we can see from the table/graph/figure above, drastic/considerable/ great changes have taken place in...over the period of time from...(年份)to...( 年份)
9) The table/graph shows that there is a(n) declining/increasing trend of ...from...(年份) to...(年份)
10) Anyone who takes a closer look at the data in the table/graph can be surprised to find that...
11) It is a traditional practice to...in our society.
(e.g. It is a traditional practice for young people to be financially dependent on their parents for anything like marriage and housing.)
12) It has long been considered only right and proper to...(in China.)
(e.g. It has long been considered only right and proper/perfectly justified for the old to assume full responsibility for the growth of the young.)
13) As things usually go against sb.'s will, his original intention was to...
(e.g. As things usually go against man's will, his original intention was to change the way people lived by inventing the internal combustion engine. Indeed, its birth has greatly enhanced their enjoyment of life, especially in traveling and transporting. But its dark side is presenting a growing worry to our society.)
14) The current situation of..., if approached from the opposite angle, reveals that...
(e.g. The current situation of our reforms is political structure, if approached from the opposite angle, reveals that much of the achievement is far from satisfactory. For example,...But...)
15) Everything about...seems (not) to be getting on smoothly/just as one wishes in...
(e.g. Everything about the economic reform seems to be getting on just as our government wishes in China.)
16) To sb.'s mind/In sb.'s eye(s), sth. seems/means...
(e.g. In the eyes of the public, official corruption means taking bribes,. ..But such corruption comes in many different forms.)
17) No one would deny that...
或:Everyone would agree that...
18) When it comes to...(sth.), most people (the public) maintain(s)/contend(s) that...
(e.g. When it comes to fake commodities, every consumer has much anger to pour upon them.)
19) Now it is widely believed that...
(e.g. Now it is widely believed that examinations are the best possible measure for the selection of the qualified.)
20) A public debate has arisen as to/over/concerning...
(e.g. A public debate has arisen as to whether one should step forward bravely in the event of crime.)
21) All that sth. has done for our society seems like a big step forward in the right/wrong direction, but it has also brought along with it a great worry /benefit to...(the average people.)
(sth.: cloning, the reform in managerial structures, etc.)
22) The birth/invention of...has made an enormous/essential difference to ...But it does not mean that...
(e.g. The birth of the computer has made a radical difference to the human progress. But it does not mean that this wonder does no threat to our society.)
23) Sth. has changed the way our society develops....But its bright side should not keep us from following closely its dark side.
(sth.: the genetic engineering, etc.)
24) Things about...are going on to our advantage, but a long cool look at ...reveals that...
(e.g. Things about the reform in state enterprises are not going on to the most workers' advantage....But a long cool look at this move reveals that it will produce some substantial benefits to our nation in a long run.)
25) No/Little doubt that...But...
(e.g.[There is] Little doubt that the traditional schooling has contributed much to our social development....But some grave defects in it begin to stand out against the modern times.)
26) What does sth. mean?
27) How/Why does sth. affect our life?
28) What is it like to do...?
(e.g. What is it like to have lost all confidence in oneself?)
29) What would our society be like if there were no...?
(e.g. What would our society be like if there were no public morals?)
30) Should we put sth. above sth. else?
或:Should we attach as much weight to A as to B?
(e.g. Should we put intellectual development above moral education?)
31) Sth. is often referred to as/defined as...
(e.g. Corrupt officials are often referred to as the most dangerous borers in our government bodies, who are nibbling away the healthy organism of our party by dishonest means.)
32) (Doing) Sth. is just the same as.../is compared to.../is likened to.. ./is like...
(e.g. Life in the middle of marriage is often compared to wire walking, for in the early years spouses attract each other and in late years they need each other.)
33) Sth. is to...what sth. else is to...
(e.g. An individual human existence is to the human society what a river is to the ocean, small and busy rushing past rocks at first, but gradually growing wider and quieter until it becomes merged in the ocean in the end.)
34) To/For/With most people/sb., sth. is/means...
(e.g. To dishonest people, a friend means a target or an object that is of some use to them at present or in the future.)
35) Sth. is the symbol/mark/equivalent of.../is symbolic of.../is equivalent to...(e.g. If selling one's sex is the mark of degeneration, selling one's power is equivalent to committing crime.)
36) Suppose/Imagine that.../Let's suppose/assume/imagine (that)...
(e.g. Suppose, by any chance, you heartily disagree with anything that is going on about you, you are less likely to stay on the good side of people around you.)
37) We often find ourselves caught/involved in a dilemma whether...
(e.g. We often find ourselves caught in a dilemma whether we should reach for the bear's paw or for the shark's fin/whether to reach for...)
38) If/In case/In the event that..., it is better to.../a better course is to.../sb. has no choice/option/alternative but to.../all we want to know is how...
(e.g. In the event that you fall in a love river, all we want to know is how you will swim in it, as you are no longer a fisherman.)
39) Unfortunately, sth. may affect sb.'s life to the point where...
(e.g. Unfortunately, the desire to be well thought of affects one's life to the point where he is reluctant to say no to anyone else regarding anything.)
40) In our life, there often appears such an occasion when.../on which... (或it often happens that...)(e.g. In our daily life, there appears such an occasion when we drink success to our work in one field but, at the same time, begin to do great damage to other fields.)
41) “...” That is how one of the great minds/scientists/writers remarked on...(e.g. “Happiness, like an old friend, is inclined to drop in unexpectedly— when you're working hard on something else.” That is how a famous writer once remarked on happiness.)
42) One of the great sociologists/psychologists has said:“...”
(e.g. The great classical ballerina Anna Pavlova has said: “No one can arrive from being talented alone. God gives talent; work transforms talent into genius.” In other words ...)
43) “...” Such is the accurate exposition/exposure of...frequently over heard in public.
(e.g. “Public morals are declining day by day.” Such is the correct exposure of the dark side of our society.)
44) How often nowadays we hear such remarks/complaints/words as this “...” or “...”!
(e.g. How often nowadays we hear such complaints from officials as this “I have too many social engagements to carry out” or “I have too many social engagements to carry out” or “I have to bear too many titles for our society”! Don't be misled by the complaints of this kind!)
45) One of the great men once said that...
(e.g. A gifted American psychologist once said that it is an illusion to believe in the Sunday-school truth—more comfort, more happiness.)
46) Once in a newspaper/magazine, I hit upon the report that...
(e.g. Once in a newspaper, I hit upon the news that a quick witted policeman spotted a suspect's spittle in the street blotted it up and ran a DNA test on it which led to the man's arrest for a murder. This case best counts as a practical application of the DNA technique.)
47) One day, I happened to witness the incident as follows:...
(e.g. 略)
- 作者: Yachun Miao 2006年12月17日, 星期日 23:21 回复(0) | 引用(0) 加入博采
六级听力对话应试技巧及策略
1)时间与数字题型
时间、数字和计算是听力测试中最常见的题型,几乎每年都有,而且题型也比较多。历年试题中虽然涉及的数字或数目并不多,但考生的答对率却不高。究其原因,一是对数字的表达方法不够熟悉,二是对技巧的运用不够熟练。
● 卷面线索
时间题的四个选项一般都是表达时刻的数字,或是星期、年、月等词,偶尔会有介词后跟数字。数字题的选项则有可能为纯粹的数字或带有货币符号的数字。
● 解题要点
一般来说,正确答案不会是直接听到的数字,而往往是在意思上与这相同或相近,或换了一种表达方式,或要求对听到的数字进行简单的四则运算。做这类题时,听清这些数字和它们之间的关系是解题的关键。这些数字之间的关系往往用more/less, late/early, fast/slow, before/after之类的形容词、副词或介词短语来表示,多为时间题。
【例1】
M: Could you tell me the timetable of the school bus?
W: Well, the bus leaves here for the campus every two hours from 6:30 am But on weekends it starts half an hour later.
Q: When did the second bus leave on Saturday?
A) 7:30 B) 8:00 C) 8:30 D) 9:00
答案D)。这是一道较为复杂的转换题。考生应听懂对话中的every two hours from 6:30和half an hour later两处,同时还要细心,捕捉到问题中的the second bus和Saturday。
【例2】
W:Have you heard about the air crash that occurred last Wednesday?
M:Yes The newspaper said six crew members and sixty four passengers were killed, and fifteen others were injured.
Q: How +many people suffered in the air crash?
A) 85. B)70 C)64. D)31.
这是一道简单的数字运算题,解题的关键是要听懂问句中的“suffer”一词,受害者应包括死者和伤者,故答案应为6+64+15=85,A)。
【例3】
W:Sorry,Mr Smith is not in May I have him return your call?
M:Yes, thank you I’m at 6330872…Sorry It’s 6338720.
Q: What’s the man’s telephone number?
A) 6330872. B)6380372. C) 6338720. D)6338726.
这道题要注意的是电话号码在口语中的读法。6338720的最常见的读法是:six double three eight seven two zero (或o)。另外还要注意不要受干扰信息的影响,如本题中的6330872以及其后的话语停顿。
【例5】
W: I’d like to make two reservations one Flight 651 for June 8th.
M: I’m sorry We’ve booked up on the 8th But we still have a few seats available on the 9th
Q: When does the man want to leave?
A) On the 6th of June B) On the 8th of June.
C) On the 9th of June D) On the 19th of June.
答案为(B)。与上一题类似,本题是考察考生抗干扰的能力。对话中干扰因素有Flight 651和the 9th两个数词。考生还要理解两个词:reservation意为“预定”,而book up则指“订完”。此外,还要熟悉日期的不同表达方法。
2) 地点与方向题型
在地点型试题中,有的地点在对话中直接提到,有的则需要根据对话内容来判断(这类问题一般是问对话发生在何处),还有的则是两者兼而有之。
● 卷面线索:选项通常由(介词+)地点名词构成,提问可分为两种情况。第一种问对话发生在何处,选项的形式通常为表示方位的介词(如in, at等)加上一个地点名词构成;第二种问的是方向,选项的形式通常为表示方向的介词(如to, from, out of等)加上一个地点名词或者只有地点名词构成。
● 解题要点:
(1) 首先要根据卷面线索判断出试题的类型。对于第一种试题,要重点听对话中的与特定地点有关的常用词或词组(即关键词),例如听到reserve,check in,check out,room之类的词的话即可判断对话是发生在旅馆里,听到size,color,pay,discount之类的词即可判断对话地点是在商店等。
(2) 与方向有关的词和词组并不多,所提出的问题在形式上也可能比较简单,但是这类问题出现时,往往需要几经思考才能得出答案。在这类问题中,务必要注意录音中的内容细节,不要单凭从对话中获得的孤立信息来确定答案。建议在听音的同时用笔在纸上画一些草图以帮助理解。
(3)与时间题不同,有的地点题可通过逻辑判断猜出正确的答案,平时要注意这方面能力的
培养。
【例1】
M:Good afternoon This is Edward Miller at the Sun Valley Health Center I’d like to speak to Mr Adams, please.
W:Mr Miller, my husband isn’t at home I can give you his business phone if you’d like to call him at work.
Q:Where is Mr Adams now?
A)At the Sun Valley. B) At the Health Center.
C) At home D) At Work.
答案D)。本题表面是很简单,因为对话中有直接提示词At work,它与D)项完全相同。此外,his buisness phone(他的办公电话)也能给我们一定的提示。
【例2】
M: I’ve just got back from the holiday you arranged for me, but I must tell you the hotel was really awful! It was miles from the sea. The food was awful too. The bedroom was dirty.
W: Sorry about that but it’s not really our fault. The contract does say that the hotel accommodation is not our responsibility
Q: Where is the conversation most probably taking place?
A) At the airport. B) In a travel agency.
C) In a hotel D) At home
此题对话中虽有地点名词出现(hotel),但明显不是答案。本题考的是推理能力。根据hotel等关键词,可推断对话的话题与旅行有关。而从W的话中的arrange, contract等词,可以判断出这是一家旅行社(B)。本题说明,在听对话时,要对关键词特别注意,但又不能认为关键词就是答案。
【例3】
M:Now, Miss, do you feel all right now? What happened?
W:Yes, I’m fine now. I was just at the motorway I was driving along the main road when suddenly right before the crossroads I met the car came out at the side street I didn’t see him until he hit me.
Q: Where was the car before it hit the woman?
A)In the side street. B) At the crossroads.
C) On the main road D) On the motorway.
本题属于较为特殊的地点题,考生不仅要听懂对话发生的地点,而且要能理解I met the car came out at the side street这句语法上有错误的话并推断出在汽车撞上W之前,它正在the side street (A)中。其他选项在对话中都曾出现过,但它们都是描述W本人所在的位置,而非肇事车辆所处的位置。
【例4】
W: I just stopped by at your office in the bank. They told me that you had quit where are you working now?
M: I am working for a lawyer now.The pay is better and the work is much more interesting.
Q: Where did the man work before?
A) In a court. B) On the farm.
C) In a bank. D) In a shop.
答案C)。此题的关键词bank在对话中出现了两次,考生不但可以从女士的话(…at your office in the bank,They told me that you had quit:),也可以从男士的回答(The pay is better than in the bank…)中得出本题的正确答案。而a lawyer 仅仅是一个难度不大的干扰词,考生只要听懂now这一词就可以排B)D)两项与本题对话明显无关。
3)观点、态度与反应题型
观点、态度与反应题是指对话双方对某事或某人的观点和看法。有时这种观点和看法在对话中直接表达出来了,但绝大部分情况需要考生从说话人的语调、语气以及所使用的词汇、短语等方面来进行推理和判别的。这种题型的卷面特征通常表现在以下两个方面:
(1) 选项一般为完整的句子。
(2) 某一选项可能含有think, should, agree (disagree), share … opinion , like (dislike), will等词。
其提问方式有:
What did the woman/man say about …?
What’s the woman’s/man’s attitude towards / opinion about …?
What does the woman/man mean/imply (by saying …)?
What did the woman/man think of …?
What does the man think the woman should do?
等等。这类题由于说话者表达观点和看法的方式比较含蓄,不能为选择书面答案提供直接的信息,而是需要考生听懂录音并利用逻辑思维来进行推理和判断,因此往往被认为较难(在托福听力中这种题居多,在近几年的六级考试题中亦多见)。为此,从卷面的角度考虑,我们建议采取以下策略:
(1)凡在选项中出现agree (disagree), share … opinion , like (dislike)之类的词语的题是判断第二个说话的人是否同意第一个说话的人的观点的,在听的时候要重点注意第二个人所说的话,尤其是言外之意 。平时还要掌握表示赞同和反对的习语,如:
You can say it again.
I can’t agree more.
I’m not sure…
I doubt …
I’d rather …
(2)选项中出现should, ought to等词语的题,其答案多出现在第一个人说的话里,但是要随机应变。例如,第一个人是女声,如果选项都是He should / the man should …的话,则重点在女声的话中;而如果选项中出现的是She should …的话,重点又在男声的话中了。
(3)对于选项中出现mean, think, will (表意愿)的题,通常有两种情况,其一是说话人用了虚拟语气来表达其愿望;其二是找一个借口(用but来引导)来拒绝对方的邀请、建议等。因此平时要对虚拟语气非常熟悉,考试时才能听得出来。此外,对话者的语调也可提供重要的信息,但要求较高,此处不予推荐,读者如有兴趣,可参考有关书籍。
【例1】
M: Isn’t that a new brand of typewriter you are working at?
W: Oh, Bill This isn’t the first time you’ve asked me about it.
Q: What does the woman imply?
A) The man is a forgetful person.
B) The typewriter is not new.
C) The man can have the typewriter later.
D) The man misunderstood her.
答案为A)。本题是一道间接回答的典型题。W并没有直接回答M的话,而是说:你已问了多次了。言外之意,是说M太健忘。捕捉言外之意是一项要求较高、难度较大的能力,它不仅要求具有扎实的语言基础与听的能力,还需要经过反复实践才能掌握。
【例2】
W: Now, would you please fill out these customs forms for each package? Please state clearly the contents and value of each, and the name and address of the returnee Better in block letters.
M: There I think I’ve filled out everything correctly.
Q: What’s the woman’s occupation?
A) Doctor B) Clerk.
C) Professor D) Waitress.
答案为B)。本题的关键词包括fill out … forms, customs, state the contents and value等。从以上的关键词中我们可以知道W是海关人员,所以其余选项皆不可能。
【例3】
W: Some people know a lot more than they tell.
M: Unfortunately the reverse is also true.
Q: What does the man mean?
A) Some people pretend to know what they really don’t.
B) What the woman said is true.
C) What the woman said is wrong.
D) He knows more than the woman does.
答案为A)。由于对话极短,仅就对话内容而言,本题难度较大。考生不仅要理解W所说的话,同样要理解M说的the reverse is also true (相反的情况同样存在,即说的比懂的多,不懂装懂)。所以,他不仅肯定了W的话,还提出了自己的观点,这样,只选B)就不够准确
了。
【例4】
M: May I ask the nature of your business, please?
W: I’m from the State Administration, and I need to talk with Mr Jones about his building project on 20 Street.
Q: What is the man’s probable occupation?
A) Waiter. B) Secretary.
C) Tailor D) Professor.
本题略难,对话中男士的职业要从女士的回答中去推断,女士说她来找Mr Jones是要与他谈在20号大街上的建筑项目,由此可知对话中的男士应该是Mr Jones的同事或下属,只有B)符合。
4)职业与身份题型
职业与身份类型的题包括人物的职业、身份以及与他人的关系。此类对话经常提供一个情节,反应所涉及的人物的关系或身份。大部分情况下,这类题的选择项是有固定格式的,要么是四个不同的职业,要么是四对不同身份的人。与地点题类似的是,我们在听音时要善于捕捉关键词,以便抓住简短对话中所提供的信息,作出准确的判断。因此,除了要熟悉选项出现的形式,还要对其相应的设问做到心中有数。常见的设问有:
1 Who (what) is the man/woman?
2 What is the man’s/woman’s profession/occupation?
3 What is the probable relationship between the two speakers?
对于前两种提问,其选项通常为四个表示职业的名词。而对于第三题,其选项则为由and连接起来的四对名词或名词复数。
5)原因与结果题型
原因与结果题与大多数其他类型的题所不同的是,听音前较难通过卷面线索判断出这一题属于原因与结果题,要想判断出正确答案就更困难了。但是题型判断对于捕捉关键信息非常重要,考生应通过平时大量的训练和细致的分析争取尽快地抓住一些蛛丝马迹,作出准确的判断。这类题的卷面特征可概括如下:
(1)四个选项通常都是完整的陈述句;
(2)至少有一个选项暗示出该事实能导致某种结果,通常有好几个选项的事实能导致同一
结果;
(3)选项中可能出现can’t, not be able to, too(… to), have to等词或词组。
对于因果题,只要判断出题的类型就足够了,不宜浪费时间推测正确答案。
此外,因果题主要集中在原因上,而且一般由对话中的第二个人说出,因此应试时要格外注意第二个人说的话。提问通常以why、what reason等引导。以下各例对考试中出现过的各种形式逐一分析。
【例1】
M: Mary, why isn’t Jane teaching here this term?
W: She can’t she was fired.
Q: What reason was given for Jane’s not teaching?
A) She is tired of teaching.
B) She was dismissed from her job.
C) She’s changing jobs.
D) The school is too hot.
请读者结合本题,理解上面所讲的卷面特征。本题中的D显然没有道理,剩下的三个选项都能引起Jane不再教书这一结果。判断出题型以后,就不会错过W话中的she was fired这一信息,答案B)自然就呼之欲出了。
【例2】
W:Suppose the company offered you a pay rise of 50%, would you be so determined to leave and look for a job elsewhere?
M:Yes I’ve set my mind on it, I’d like to find a job with scope to show my ability.
Q:Why has the man decided to leave the company?
A) He is not equal to the job.
B) He is not well paid for his work.
C) He doesn’t think the job is challenging enough.
D) He can not keep mind on his work.
本题要求根据表面话语推断出隐含的意义。在对话中,M最关键的一句是:I’d like to … ability。言外之意就是目前的工作对他来说缺乏挑战性,即C)项为正确。
【例3】
M: Helen isn’t here yet. Did you forget to invite her?
W: She was going to come, but then changed her mind.
Q: Why isn’t Helen present?
A) She decided not to come.
B) She forgot to come.
C) She wasn’t invited.
D) She altered her decision.
本题尽管没有出现具有提示作用的关键词,但综合四个(尤其是前三个)选项,明显可以看出,问题必然是why doesn’t Helen come这样的句子,这样在听懂第二个人的话语之后,这一题就很容易做出来了。正确答案为D。
【例4】
W: George, where were you yesterday evening? I expected to see you at the concert.
M: Oh, I waited for you at the corner of your street. Then I looked for you at your flat, but the housekeeper said you were out.
Q: Why didn’t they meet that evening?
A) They were both busy doing their own work.
B) They waited for each other at different places.
C) They went to the street corner at different times.
D) The man went to the concert but the woman didn’t.
尽管因果题的答题线索通常都出现在第二个人说的话中,即第一个人说出结果,第二个人解释原因,但本题是个例外,双方都说出了部分原因。这是为什么呢?我们看一下选项就明白了。在本题的选项中每一项都牵扯到了对话的双方,所以听的时候要注意双方话。
6)事件情景题型
在听力考试中,最令考生头疼的莫过于事件情景题,但恰恰这类题在六级考试中出现得最多,这也是四、六级听力考试的区别之一。事件与情景题是针对对话所涉及的事件、对话所发生的情景及谈话内容存在的背景等设计而成的考题。这种题的形式一般是:对话双方围绕某件事、某个主题进行讨论,在谈话中涉及情景、背景或描述环境,然后针对谈话内容提问,其设问通常由what、why、how等引出,如:
What are the two speakers discussing?
What does the man/woman say about …?
What can you learn from the conversation? 等。
事件与情景题可分为事件细节题、综合理解题和推理题。对于细节题,考生要听清整个谈话的内容以至个别的词语;对于其他两种题,考生还要能够将这些内容和细节连贯起来进行整体的综合理解和进行逻辑推理。由于其选项多为完整的句子,因此有效利用这种题的卷面线索的方法是:做题前对选项进行快速浏览,找出重复率较高的词,并对对话的内容和背景进行预测,甚至可用逻辑分析猜出正确答案。
【例1】
M: Did you hear Mike is in hospital with head injuries and a broken arm?
W: Yes, apparently he was struck by another vehicle and turned completely over.
Q: What happened to Mike?
A) His car was hit by another car.
B) He was hurt while playing volleyball.
C) He fell down the stairs.
D) While crossing the street, he was hit by a car.
答案为A)。此题考的是判断事实、关注细节的能力,有一定的难度。其实,考生如果听懂了M说的he was struck by another vehicle,就能推断出Mike在被撞时是开着车子的,否则就不可能说是another vehicle了,更何况选项A)实际上就是此句话的同义重复。就卷面来说,本题有两个选项与车祸有关(A、D),另外两个则是各自独立的,事先可基本确定答案将在A、D中产生。
【例2】
M: This has been the worst flood for the past 20 years It has caused much damage and destruction.
W: Look at the prices of fruits and vegetables. No wonder they are so expensive.
Q: What are they talking about?
A)The effects of the flood.
B) The heroic fight against a flood.
C) The cause of the flood
D) Floods of the past twenty years.
答案为A) 。本题要求根据对话内容推断所谈论的主题——洪水造成的影响。考生可从两方面得出答案,一是M的后一句话It has caused many damage and destruction,二是W话中涉及的事实,它们实际上就是由洪水引起的。
【例3】
W: I have to think about your offer I can’t say ‘yes’ or ‘no’ at the moment.
M: You can take your time It will do if you let me know your decision in a day or two.
Q: Which of the following is true?
A) The man thinks the woman is wasting her time.
B) The man thinks the woman should make full use of her time.
C) The man is eager to know the woman’s answer.
D) The man can wait and there is no need for her to hurry.
本题的选项极象观点、态度与反应题,但实际考的是把握细节的能力。当年(1991年6月)
很多考生都没能做对,主要原因是对take one’s time没有理解,而将其与选项B)的意思混淆了,而且也没能用M的后半句话来加以验证。事实上,M的话可分为两部分,把握住任何一个就足以选出正确答案D),如能相互验证那就更有把握了。从卷面的角度分析,我们可以看出,C)、D)两项为完全排斥的反义关系,而A)、B)并非如此。在这种情况下,可基本排除A)、B),本题的难度也就降低了50%。
- 作者: Yachun Miao 2006年12月17日, 星期日 23:20 回复(0) | 引用(0) 加入博采
javascript框架编程(转)
框架编程
一个HTML页面可以有一个或多个子框架,这些子框架以<iframe>来标记,用来显示一个独立的HTML页面。这里所讲的框架编程包括框架的自我控制以及框架之间的互相访问,例如从一个框架中引用另一个框架中的 JavaScript变量、调用其他框架内的函数、控制另一个框架中表单的行为等。
框架间的互相引用
一个页面中的所有框架以集合的形式作为window对象的属性提供,例如:window.frames就表示该页面内所有框架的集合,这和表单对象、链接对象、图片对象等是类似的,不同的是,这些集合是document的属性。因此,要引用一个子框架,可以使用如下语法:
window.frames["frameName"];
window.frames.frameName
window.frames[index]
其中,window字样也可以用self代替或省略,假设frameName为页面中第一个框架,则以下的写法是等价的:
self.frames["frameName"]
self.frames[0]
frames[0]
frameName
每个框架都对应一个HTML页面,所以这个框架也是一个独立的浏览器窗口,它具有窗口的所有性质,所谓对框架的引用也就是对window对象的引用。有了这个window对象,就可以很方便地对其中的页面进行操作,例如使用window.document对象向页面写入数据、使用 window.location属性来改变框架内的页面等。
下面分别介绍不同层次框架间的互相引用:
1.父框架到子框架的引用
知道了上述原理,从父框架引用子框架变的非常容易,即:
window.frames["frameName"];
这样就引用了页面内名为frameName的子框架。如果要引用子框架内的子框架,根据引用的框架实际就是window对象的性质,可以这样实现:
window.frames["frameName"].frames["frameName2"];
这样就引用到了二级子框架,以此类推,可以实现多层框架的引用。
2.子框架到父框架的引用
每个window对象都有一个parent属性,表示它的父框架。如果该框架已经是顶层框架,则window.parent还表示该框架本身。
3.兄弟框架间的引用
如果两个框架同为一个框架的子框架,它们称为兄弟框架,可以通过父框架来实现互相引用,例如一个页面包括2个子框架:
<frameset rows="50%,50%">
<frame src="1.html" name="frame1" />
<frame src="2.html" name="frame2" />
</frameset>
在frame1中可以使用如下语句来引用frame2:
self.parent.frames["frame2"];
4.不同层次框架间的互相引用
框架的层次是针对顶层框架而言的。当层次不同时,只要知道自己所在的层次以及另一个框架所在的层次和名字,利用框架引用的window对象性质,可以很容易地实现互相访问,例如:
self.parent.frames["childName"].frames["targetFrameName"];
5.对顶层框架的引用
和parent属性类似,window对象还有一个top属性。它表示对顶层框架的引用,这可以用来判断一个框架自身是否为顶层框架,例如:
//判断本框架是否为顶层框架
if(self==top){
//dosomething
}
改变框架的载入页面
对框架的引用就是对window对象的引用,利用window对象的location属性,可以改变框架的导航,例如:
window.frames[0].location="1.html";
这就将页面中第一个框架的页面重定向到1.html,利用这个性质,甚至可以使用一条链接来更新多个框架。
<frameset rows="50%,50%">
<frame src="1.html" name="frame1" />
<frame src="2.html" name="frame2" />
</frameset>
<!--somecode-->
<a href="frame1.location='3.html;frame2.location='4.html'" onclick="">link</a>
<!--somecode-->
引用其他框架内的JavaScript变量和函数
在介绍引用其他框架内JavaScript变量和函数的技术之前,先来看以下代码:
<script language="JavaScript" type="text/javascript">
<!--
function hello(){
alert("hello,ajax!");
}
window.hello();
//-->
</script>
如果运行了这段代码,会弹出“hello,ajax!”的窗口,这正是执行hello()函数的结果。那为什么hello()变成了window对象的方法呢?因为在一个页面内定义的所有全局变量和全局函数都是作为window对象的成员。例如:
var a=1;
alert(window.a);
就会弹出对话框显示为1。同样的原理,在不同框架之间共享变量和函数,就是要通过window对象来调用。
例如:一个商品浏览页面由两个子框架组成,左侧表示商品分类的链接;当用户单击分类链接时,右侧显示相应的商品列表;用户可以单击商品旁的【购买】链接将商品加入购物车。
在这个例子中,可以利用左侧导航页面来存储用户希望购买的商品,因为当用户单击导航链接时,变化的是另外一个页面,即商品展示页面,而导航页面本身是不变的,因此其中的JavaScript变量不会丢失,可以用来存储全局数据。其实现原理如下:
假设左侧页面为link.html,右侧页面为show.html,页面结构如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> New Document </title>
</head>
<frameset cols="20%,80%">
<frame src="link.html" name="link" />
<frame src="show.html" name="show" />
</frameset>
</html>
在show.html中展示的商品旁边可以加入这样一条语句:
<a href="void(0)" onclick="self.parent.link.addToOrders(32068)">加入购物车</a>
其中link表示导航框架,在link.html页面中定义了arrOrders数组来存储商品的id,函数addToOrders()用来响应商品旁边【购买】链接的单击事件,它接收的参数id表示商品的id,例子中是一个id为32068的商品:
<script language="JavaScript" type="text/javascript">
<!--
var arrOrders=new Array();
function addToOrders(id){
arrOrders.push(id);
}
//-->
</script>
这样,在结帐页面或是购物车浏览页面就可以用arrOrders来获取所有准备购买的商品。
框架可以使一个页面划分为功能独立的多个模块,每个模块之间彼此独立,但又可以通过window对象的引用来建立联系,是Web开发中的一个重要机制。在 Ajax开发中,还可以利用隐藏框架实现各种技巧,在后面介绍Ajax实例编程时可以发现,无刷新上传文件以及解决Ajax的前进后退问题都会用到这种技术。
- 作者: Yachun Miao 2006年12月15日, 星期五 17:16 回复(0) | 引用(0) 加入博采
JSTL 之变量赋值标签
/*
* Author Yachun Miao
* Created 11-Dec-06
*/
关于JSP核心库的set标签赋值变量,有两种方式:
1.<c:set var="date" value="日期" />
2.<c:set var="date"><bean:message key="some.key" /></c:set>
有种需求要把ApplicationResources_zh_CN.properties文件定义的key对应的值用
<bean:message key="some.key" />赋值给date变量。
第一种方式value属性值内无法直接写其他的标签,但可以写${}变量。
经过测试像<c:set var="date“ value="<bean:message key='some.key' />"这种方法是无法赋值成功;
第二种方式可以把ApplicationResources_zh_CN.properties文件定义的some.key的值用<bean:message key="some.key" />定义给date变量;
- 作者: Yachun Miao 2006年12月15日, 星期五 15:20 回复(0) | 引用(0) 加入博采
radio控制文本框disabled属性
- 作者: Yachun Miao 2006年12月14日, 星期四 01:02 回复(0) | 引用(0) 加入博采
UML中类之间的关系及其Java代码例子(转)
- 作者: Yachun Miao 2006年12月12日, 星期二 09:49 回复(0) | 引用(0) 加入博采
WEB页面标签属性:ID & NAME
可以说几乎每个做过Web开发的人都问过,到底元素的ID和Name有什么区别阿?为什么有了ID还要有Name呢?!
而同样我们也可以得到最classical的答案:ID就像是一个人的身份证号码,而Name就像是他的名字,ID显然是唯一的,而Name是可以重复的。
上周我也遇到了ID和Name的问题,在页面里输入了一个input type="hidden",只写了一个ID='SliceInfo',赋值后submit,在后台用Request.Params["SliceInfo"]却怎么也取不到值。后来恍然大悟因该用Name来标示,于是在input里加了个Name='SliceInfo',就一切ok了。
第一段里对于ID和Name的解答说的太笼统了,当然那个解释对于ID来说是完全对的,它就是Client端HTML元素的Identity。而Name其实要复杂的多,因为Name有很多种的用途,所以它并不能完全由ID来代替,从而将其取消掉。
具体用途有:
用途1: 作为可与服务器交互数据的HTML元素的服务器端的标示,比如input、select、textarea、和button等。我们可以在服务器端根据其Name通过Request.Params取得元素提交的值。
用途2: HTML元素Input type='radio'分组,我们知道radio button控件在同一个分组类,check操作是mutex的,同一时间只能选中一个radio,这个分组就是根据相同的Name属性来实现的。
用途3: 建立页面中的锚点,我们知道<a href="URL">link</a>是获得一个页面超级链接,如果不用href属性,而改用Name,如:<a name="PageBottom"></a>,我们就获得了一个页面锚点。
用途4: 作为对象的Identity,如Applet、Object、Embed等元素。比如在Applet对象实例中,我们将使用其Name来引用该对象。
用途5: 在IMG元素和MAP元素之间关联的时候,如果要定义IMG的热点区域,需要使用其属性usemap,使usemap="#name"(被关联的MAP元素的Name)。
用途6: 某些特定元素的属性,如attribute,和param。例如为Object定义参数<PARAM NAME = "appletParameter" VALUE = "value">。
显然这些用途都不是能简单的使用ID来代替掉的,所以HTML元素的ID和Name的却别并不是身份证号码和姓名这样的区别,它们根本就是不同作用的东西。
当然HTML元素的Name属性在页面中也可以起那么一点ID的作用,因为在DHTML对象树中,我们可以使用document.getElementsByName来获取一个包含页面中所有指定Name元素的对象数组。
在这里顺便说一下,要是页面中有n(n>1)个HTML元素的ID都相同了怎么办?在DHTML对象中怎么引用他们呢?如果我们使用ASPX页面,这样的情况是不容易发生的,因为aspnet进程在处理aspx页面时根本就不允许有ID非唯一,这是页面会被抛出异常而不能被正常的render。要是不是动态页面,我们硬要让ID重复那IE怎么搞呢?
这个时候我们还是可以继续使用document.getElementById获取对象,只不过我们只能获取ID重复的那些对象中在HTML Render时第一个出现的对象。而这时重复的ID会在引用时自动变成一个数组,ID重复的元素按Render的顺序依次存在于数组中
另外加一些常用的正则表达式:
Email : /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
Phone : /^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$/,
Mobile : /^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$/,
Url : /^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^\"\"])*$/,
IdCard : /^\d{15}(\d{2}[A-Za-z0-9])?$/,
Currency : /^\d+(\.\d+)?$/,
Number : /^\d+$/,
Zip : /^[1-9]\d{5}$/,
QQ : /^[1-9]\d{4,8}$/,
Integer : /^[-\+]?\d+$/,
Double : /^[-\+]?\d+(\.\d+)?$/,
English : /^[A-Za-z]+$/,
Chinese : /^[\u0391-\uFFE5]+$/,
Username : /^[a-z]\w{3,}$/i,
UnSafe : /^(([A-Z]*|[a-z]*|\d*|[-_\~!@#\$%\^&\*\.\(\)\[\]\{\}\?\\\/\'\"]*)|.{0,5})$|\s/,
- 作者: Yachun Miao 2006年12月12日, 星期二 09:38 回复(0) | 引用(0) 加入博采
图像显示
第2章图像显示
2.1 图像显示概述
1.目的:
通过图像的显示,用户可以监视图像处理分析过程,并可以控制这一过程。
2.视网膜细胞特点:
(1)人眼的结构:
(2)视网膜神经细胞的敏感性
细胞种类 何时活跃 对颜色 数量 锥细胞 亮 敏感 少 柱细胞 暗
不敏感 多 所以:天色暗时看到的物体都是黑白剪影。
3.影响人的色觉复杂,有三个特征量表示颜色。
(1)亮度:与物体对光的反射率成正比。
一幅数字图像
减少亮度后的数字图像
增加亮度后的数字图像
(2)色调:与光谱的波长相联系。 反映主波长。
(3)色饱和度:与色调的纯度相联系。纯白光的色彩饱和度为0,而纯彩色光的饱和度则为100%。
注意:彩色电视中有亮度、对比度、色度等可调键。此处的色度定义了颜色的两个方面—色调与饱和度
4、色彩空间模式有:RBG、CMYK、LAB
CMYK模式:是最佳的打印模式,RGB模式虽色彩多,但不能完全打印出来。
RGB与CMYK图示:
资料:打印彩色图像用CMY相减混色模型。用彩色墨水或颜料进行混合,这样得到的颜色称为相减色。在理论上说,任何一种颜色都可以用三种基本颜料按一定比例混合得到。这三种颜色是青色(Cyan)、品红(Magenta)和黄色(Yellow),通常写成CMY,称为CMY模型。用这种方法产生的颜色之所以称为相减色,是因为它减少了为视觉系统识别颜色所需要的反射光。
2.2 数字图像显示
1 图像的显示特性
(1)图像的尺寸
251*196
351*274:
(2)光度分辨率
光度:是指发光物体本身的发光本领的大小。
注:而亮度是指我们所看到的发光体有多亮。 物理学中有一个关于光度、亮度和距离关系的公式:S ∝ L0 / r 2.。测量出天体的光度L0和亮度S。
指系统在每个像素位置产生正确的亮度的精度。
(3)灰度线性
输出亮度正比于输入灰度级的程度。
(4)平坦能力
当像点与像点之间距离不大时,显示点之间必定会发生重叠。如:
x1=[-1.3 -1.2 -1.1 -1.0 -0.9 -0.8 -0.7 -0.6 -0.5 -0.4 -0.3 -0.2 -0.1 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 ];
y1=exp(-8*x1.^2);
y2=exp(-8*(x1+1).^2);
y3=exp(-8*(x1-1).^2);
plot(x1,y1,'-',x1,y2,'-',x1,y3,'-');
(5)噪声特性
显示系统一定会有电子噪声,电子噪声将引起亮度和位置两方面的变化。
2 图像的暂时显示 图
(1)光栅扫描的阴极射线管(CRT)。
(2)激光显示器。
(3)固态显示器(液晶和发光二极管技术)
3. 图像的永久显示
(1)使胶卷曝光的照相机。
(2)各种打印机。
2.3 MATLAB图像显示方法
1. MATLAB图像的读写和显示 (load imread imwrite image)
load clown %调入变量文件,即含有一幅小丑图像的矩阵
此时三个矩阵:X-200*320 map-81*3 caption-2*1
再学习什么是调色板map.
imshow(X,map) %显示这一索引图像
若用image(X)命令显示时,显示下图。不能完全表现此图。
imwrite(X,ma p,'clown_bmp.bmp') %保存在BMP文件。
i=imshow('clown_bmp.bmp')%再显示此保存的图文件
figure,imshow(i) %原图的色彩丢,说明存格式转变时,不能完全复原
imgae(i) %此显示会丢失相关信息
2. 二进制图像的显示方法
例1
(1)建立一个方格子的二进制图像
bw1=zeros(20:20) ; %20*20 矩阵 元素均为0
bw1(2:2:18,2:2:18)=1 ; %a:b:c为起始位、步长、结束位等元素置为1
(2)显示建立好的二进制图像
imshow(bw1) %此时能显示图像。注意bw1是双精度型的,所以认为是二值图像
bw2=uint8(bw1); %bw2是无符号8位整数,若显示bw2则几乎是黑的。为什么?
imshow(bw2);
bw3=bw2~=0;%此时bw3为逻辑阵列类型的数据。
imshow(bw3);
例2 使用调色板显示一幅二进制图像
(1)调入一幅图像并显示
bw=imread('circles.tif');
figure,imshow(bw);
(2)用调色板显示一幅图像
figure,imshow(bw,[1 0 0; 0 0 1]); %背景 1 0 0 为红,颜色0 0 1为蓝
figure,imshow(bw,[0 1 0; 0 0 1]);%背景 1 10 0 为绿
figure,imshow(bw,[1 1 0; 0 0 1]);%背景 1 10 0 为黄
3. 灰度图像的显示方法
i=imread('testpat1.tif');
figure,imshow(i,2) %显示2个灰度级
figure,imshow(i,4) %显示4个灰度级
figure,imshow(i,16) %显示16个灰度级
figure,imshow(i,64) %显示64个灰度级
figure,imshow(i,128) %显示128个灰度级
j=filter2([1 2;-1 -2],i); %对图像i进行滤波,其结果送到j,滤波器为[1 2;-1 -2]
imshow(j,[]) %[]表示自动地标度灰度
4. 索引图像的显示方法
load clown %调入一个小丑图像
imshow(X);%由于是索图像,调色板最多显示81种颜色,而X数组最大值为81,由于是双精度数,大于等于1均为白。
j=uint8(X) 若要当作灰度图显示,则应转化为无符号8位整数
imshow(j)
imshow(X,map) %索引显示
5. RGB图像的显示方法
RGB = imread('ngc6543a.jpg'); %RGB为650*600*3
imshow(RGB);
6. 磁盘图像的直接显示
imshow flowers.tif
2.4 MATLAB特殊显示技术
1 添加色带
例1:
i=imread('saturn.tif');
imshow(i),colorbar
例2:
load flujet
imshow(X,map),colorbar
2 显示多帧图像(了解)
mri=uint8(zeros(128,128,1,27));
for frame=1:27
[mri(:,:,:,frame),map]=imread('mri.tif',frame);
end
mov=immovie(mri,map);
movie(mov);
3 显示多幅图像
(1)开辟一个图像窗口用figure命令
[x1 map1]=imread('forest.tif');
[x2 map2]=imread('trees.tif');
imshow(x1,map1)
figure,imshow(x2,map2);
(2)将一个窗口划分为多个显示区域
[x1 map1]=imread('forest.tif');
[x2 map2]=imread('trees.tif');
figure
subplot(1,2,1),imshow(x1,map1)
subplot(1,2,2),imshow(x2,map2) %说明使用了新的调色板后,前一个调色板受影响了。这样共享调色板其图像显示不能接受
subplot(1,2,1),subimage(x1,map1)
subplot(1,2,2),subimage(x2,map2)
4 纹理映射
概念:使用插值法将一幅图像映射至一个曲面网格上。命令warp(x,y,x,i).
[x,y,z]=cylinder;
i=imread('testpat1.tif');
figure,imshow(i);
warp(x,y,z,i);
5 图像显示中的常见问题
(1)将彩色图像显示为灰度图像? 其原因是调色板未装载。
(2)二值图像显示为全黑图像? uint8类型的灰度图像变化范围为[0,255]而不是[0,1]
(3)装载的是多帧图像,而MATLAB却仅仅显示一帧图像。
2.5 补充作业
1.三种基本因素体现颜色(色觉的三个特征量)?
亮度、色调、色饱和度。
2.Matlab使用什么样的调色板来显示灰度图像?
答:R=G=B。
3.判断:索引图像不使用调色板?
答:错误
4.纯彩色光的饱和度则为多少?
答:100%
5._______与光谱的波长相联系?
A、亮度 B、对比度 C、色调 D、饱和度
答:C
6.试区别图像的分辨率与图像的尺寸?
答:
7.开辟一个图像窗口的命令是什么?
答:figure
8.subplot(3,2,2)是什么意思?
答:在3*2小块图中,显示第2个小图
9.要在显示窗口中,使用不同的调色板的命令是什么?
答:subimage(x,map),其中x图像变量、map为调色板
10. 指出下例命令的含义?
imread('name.tif') load imwrite('name.tif')
11. uint8是什么类型数据?表示的范围是多少?
答:无符号8位整数 0~255
12. uint16是什么类型数据?表示的范围是多少?
答:无符号16位整数 0~65535
13.用画笔画出这个图像,用MATLAB装入并检查。
答:略去操作过程
14.图像尺寸为400*300是什么意思?
答:宽400个像素,高300像素个像素
15.彩色电视调节的三个特征量是_____、______、____。
答:亮度、对比度、色度
16.判断:色调反映颜色的类别。( )
答:正确
17.判断:颜色的深浅程度即饱和度。( )
答:正确
18.
本次课安排2节课理论+1节课机房上机。上机安排如下:
(1)熟悉MATLAB环境。给变量赋值、加减运算
(2)调入几幅图像,以另一种格式加以保存
(3)用画笔画出50*50的图像,保存文件并在MATLAB中使用,查看矩阵的情况。
(4)将教材中的图像文件调入并显示。
重新调整:同学若有兴趣,可以安排(1节理论+2节上机)
2.6 习题 37
1. 上课实际操作演示(以下题的结果较好)。
2.将一幅世界地图变为类似于地球仪的图形。
i=imread('worldmap1.jpg');
figure;
imshow(i);
[x,y,z]=sphere;
figure;
warp(x,y,z,i)
- 作者: Yachun Miao 2006年12月8日, 星期五 21:08 回复(0) | 引用(0) 加入博采
数字图像复习资料
- 作者: Yachun Miao 2006年12月8日, 星期五 21:05 回复(1) | 引用(0) 加入博采
数字图像处理试验指导
- 作者: Yachun Miao 2006年12月8日, 星期五 20:34 回复(0) | 引用(0) 加入博采