|
2005年12月6日
Java实用工具类库中的类java.util.Random提供了产生各种类型随机数的方法。它可以产生int、long、float、double以及Goussian等类型的随机数。这也是它与java.lang.Math中的方法Random()最大的不同之处,后者只产生double型的随机数。 类Random中的方法十分简单,它只有两个构造方法和六个普通方法。 构造方法: (1)public Random() (2)public Random(long seed) Java产生随机数需要有一个基值seed,在第一种方法中基值缺省,则将系统时间作为seed。 普通方法: (1)public synonronized void setSeed(long seed) 该方法是设定基值seed。 (2)public int nextInt() 该方法是产生一个整型随机数。 (3)public long nextLong() 该方法是产生一个long型随机数。 (4)public float nextFloat() 该方法是产生一个Float型随机数。 (5)public double nextDouble() 该方法是产生一个Double型随机数。 (6)public synchronized double nextGoussian() 该方法是产生一个double型的Goussian随机数。 例2 RandomApp.java。 //import java.lang.*; import java.util.Random;
public class RandomApp{ public static void main(String args[]){ Random ran1=new Random(); Random ran2=new Random(12345); //创建了两个类Random的对象。 System.out.println("The 1st set of random numbers:"); System.out.println("\t Integer:"+ran1.nextInt()); System.out.println("\t Long:"+ran1.nextLong()); System.out.println("\t Float:"+ran1.nextFloat()); System.out.println("\t Double:"+ran1.nextDouble()); System.out.println("\t Gaussian:"+ran1.nextGaussian()); //产生各种类型的随机数 System.out.print("The 2nd set of random numbers:"); for(int i=0;i<5;i++){ System.out.println(ran2.nextInt()+" "); if(i==2) System.out.println(); //产生同种类型的不同的随机数。 System.out.println(); } } }
本章介绍Java的实用工具类库java.util包。在这个包中,Java提供了一些实用的方法和数据结构。例如,Java提供日期(Data)类、日历(Calendar)类来产生和获取日期及时间,提供随机数(Random)类产生各种类型的随机数,还提供了堆栈(Stack)、向量(Vector) 、位集合(Bitset)以及哈希表(Hashtable)等类来表示相应的数据结构。 图8.1给出了java.util包的基本层次结构图。下面我们将具体介绍其中几个重要的类。 ┌java.util.BitSet │java.util.Calendar │ └java.util.GregorianCalendar │java.util.Date │java.util.Dictionary │ └java.util.Hashtable │ └java.util.Properties │java.util.EventObject │java.util.ResourceBundle ┌普通类┤ ├java.util.ListResourceBundle │ │ └java.util.PropertyResourceBundle │ │java.util.Local │ │java.util.Observable │ │java.util.Random │ │java.util.StringTokenizer │ │java.util.Vector │ │ └java.util.Stack Java.util┤ └java.util.TimeZone │ └java.util.SimpleTimeZone │ ┌java.util.Enumeration ├接 口┤java.util.EventListener │ └java.util.Observer │ ┌java.util.EmptyStackException └异常类┤java.util.MissingResourceException │java.util.NoSuchElementException └java.util.TooManyListenersException 图8.1 java.util包的基本层次结构
8.2 日期类Date
Java在日期类中封装了有关日期和时间的信息,用户可以通过调用相应的方法来获取系统时间或设置日期和时间。Date类中有很多方法在JDK1.0公布后已经过时了,在8.3中我们将介绍JDK1.0中新加的用于替代Date的功能的其它类。 在日期类中共定义了六种构造函数。 (1)public Date() 创建的日期类对象的日期时间被设置成创建时刻相对应的日期时间。 例 Date today=new Date();//today被设置成创建时刻相对应的日期时间。 (2)public Date (long date) long 型的参数date可以通过调用Date类中的static方法parse(String s)来获得。 例 long l=Date.parse("Mon 6 Jan 1997 13:3:00"); Date day=new Date(l); //day中时间为1997年 1月6号星期一,13:3:00。 (3)public Date(String s) 按字符串s产生一日期对象。s的格式与方法parse中字符串参数的模式相同。 例 Date day=new Date("Mon 6 Jan 1997 13:3:00"); //day 中时间为1997年1月6号星期一,13:3:00. (4)public Date(int year,int month,int date) (5)public Date(int year,int month,int date,int hrs,int min) (6)public Date(int year,int month,int date,int hrs,int min,int sec) 按给定的参数创建一日期对象。 参数说明: year的值为:需设定的年份-1900。例如需设定的年份是1997则year的值应为97,即1997-1900的结果。所以Date中可设定的年份最小为1900; month的值域为0~11,0代表1月,11表代表12月; date的值域在1~31之间; hrs的值域在0~23之间。从午夜到次日凌晨1点间hrs=0,从中午到下午1点间hrs=12; min和sec的值域在0~59之间。 例 Date day=new Date(11,3,4); //day中的时间为:04-Apr-11 12:00:00 AM 另外,还可以给出不正确的参数。 例 设定时间为1910年2月30日,它将被解释成3月2日。 Date day=new Date(10,1,30,10,12,34); System.out.println("Day's date is:"+day); //打印结果为:Day's date is:Web Mar 02 10:13:34 GMT+08:00 1910 下面我们给出一些Date类中常用方法。 (1)public static long UTC(int year,int month,int date,int hrs. int min,int sec) 该方法将利用给定参数计算UTC值。UTC是一种计时体制,与GMT(格林威治时间)的计时体系略有差别。UTC计时体系是基于原子时钟的,而GTMT计时体系是基于天文学观测的。计算中使用的一般为GMT计时体系。 (2)public static long parse(String s) 该方法将字符串s转换成一个long型的日期。在介绍构造方法Date(long date)时曾使用过这个方法。 字符串s有一定的格式,一般为: (星期 日 年 时间GMT+时区) 若不注明时区,则为本地时区。 (3)public void setMonth(int month) (4)public int getMonth() 这两个方法分别为设定和获取月份值。 获取的月份的值域为0~11,0代表1月,11代表12月。 (5)public String toString() (6)public String toLocalString() (7)public String toGMTString() 将给定日期对象转换成不同格式的字符串。它们对应的具体的格式可参看例子8.1。 (8)public int getTimezoneOffset() 该方法用于获取日期对象的时区偏移量。 例8.1中对上面介绍的Date类中的基本方法进行了具体的应用,并打印了相应的结果。由于使用了一些过时的方法,所以编译时会有警告信息。另外,由于本例中的时间表示与平台有关,不同的JDK版本对此处理不完全相同,因此不同版本的JDK执行本例的结果可能有细微差异。 例8.1 DateApp.java import java.lang.System; import java.util.Date; public class DateApp{ public static void main(String args[]){ Date today=new Date(); //today中的日期被设成创建时刻的日期和时间,假设创建时刻为1997年3月 //23日17时51分54秒。 System.out.println("Today's date is "+today); //返回一般的时间表示法,本例中结果为 //Today's date is Fri May 23 17:51:54 1997 System.out.println("Today's date(Internet GMT)is:" +today.toGMTString()); //返回结果为GMT时间表示法,本例中结果为 //Today's date(Internet GMT)is: 23 May 1997 09:51:54:GMT System.out.println("Today's date(Locale) is:" +today.toLocaleString()); //返回结果为本地习惯的时间表示法,结果为 //Today's date(Locale)is:05/23/97 17:51:54 System.out.println("Today's year is: "+today.getYear()); System.out.println("Today's month is: "+(today.getMonth()+1)); System.out.println("Today's date is: "+today.getDate()); //调用Date类中方法,获取年月日的值。 //下面调用了不同的构造方法来创建Date类的对象。 Date day1=new Date(100,1,23,10,12,34); System.out.println("Day1's date is: "+day1); Date day2=new Date("Sat 12 Aug 1996 13:3:00"); System.out.println("Day2's date is: "+day2); long l= Date.parse("Sat 5 Aug 1996 13:3:00 GMT+0800"); Date day3= new Date(l); System.out.println("Day3's date(GMT)is: "+day3.toGMTString()); System.out.println("Day3's date(Locale)is: " +day3.toLocaleString()); System.out.println("Day3's time zone offset is:" +day3.getTimezoneOffset()); } }
运行结果(JDK1.3版,与原文不同,原文是JDK1.0版): E:\java\tutorial\java01>java DateApp Today's date is Thu Dec 27 17:58:16 CST 2001 Today's date(Internet GMT)is:27 Dec 2001 09:58:16 GMT Today's date(Locale) is:2001-12-27 17:58:16 Today's year is: 101 Today's month is: 12 Today's date is: 27 Day1's date is: Wed Feb 23 10:12:34 CST 2000 Day2's date is: Fri Aug 12 13:03:00 CST 1996 Day3's date(GMT)is: 5 Aug 1996 05:03:00 GMT Day3's date(Locale)is: 1996-8-5 13:03:00 Day3's time zone offset is:-480
E:\java\tutorial\java01>
8.3 日历类Calendar
在早期的JDK版本中,日期(Date)类附有两大功能:(1)允许用年、月、日、时、分、秒来解释日期:(2)允许对表示日期的字符串进行格式化和句法分析。在JDK1.1中提供了类Calendar来完成第一种功能,类DateFormat来完成第二项功能。dateFormat是java.text包中的一个类。与Date类有所不同的是,DateFormat类接受用各种语言和不同习惯表示的日期字符串。本节将介绍java.util包中的类Calendar及其它新增加的相关的类。 类Calendar是一个抽象类,它完成日期(Date)类和普通日期表示法(即用一组整型域如YEAR,MONTH,DAY,HOUR表示日期)之间的转换。 由于所使用的规则不同,不同的日历系统对同一个日期的解释有所不同。在JDK1.1中提供了Calendar类一个子类GregorianCalendar??它实现了世界上普遍使用的公历系统。当然用户也可以通过继承Calendar类,并增加所需规则,以实现不同的日历系统。 第GregorianCalendar继承了Calendar类。本节将在介绍类GregorianCalendar的同时顺带介绍Calendar类中的相关方法。 类GregorianCalendar提供了七种构造函数: (1)public GregorianCalendar() 创建的对象中的相关值被设置成指定时区,缺省地点的当前时间,即程序运行时所处的时区、地点的当前时间。 (2)public GregorianCalendar(TimeZone zone) 创建的对象中的相关值被设置成指定时区zone,缺省地点的当前时间。 (3)public GregorianCalendar(Locale aLocale) 创建的对象中的相关值被设置成缺省时区,指定地点aLocale的当前时间。 (4)public GregorianCalendar(TimeZone zone,Local aLocale) 创建的对象中的相关值被设置成指定时区,指定地点的当前时间。 上面使用到的类TimeZone的性质如下: TimeZone是java.util包中的一个类,其中封装了有关时区的信息。每一个时区对应一组ID。类TimeZone提供了一些方法完成时区与对应ID两者之间的转换。 (Ⅰ)已知某个特定的ID,可以调用方法 public static synchronized TimeZone getTimeZone(String ID) 来获取对应的时区对象。 例 太平洋时区的ID为PST,用下面的方法可获取对应于太平洋时区的时区对象: TimeZone tz=TimeZone.getTimeZone("PST"); 调用方法getDefault()可以获取主机所处时区的对象。 TimeZone tz=TimeZone.getDefault(); (Ⅱ)调用以下方法可以获取时区的ID ■public static synchronized String[] getavailableIDs(int rawOffset) 根据给定时区偏移值获取ID数组。同一时区的不同地区的ID可能不同,这是由于不同地区对是否实施夏时制意见不统一而造成的。 例String s[]=TimeZone.getAvailableIDs(-7*60*60*1000); 打印s,结果为s[0]=PNT,s[1]=MST ■public static synchronized String[] getAvailableIDs() 获取提供的所有支持的ID。 ■public String getID() 获取特定时区对象的ID。 例 TimeZone tz=TimeZone.getDefault(); String s=tz.getID(); 打印s,结果为s=CTT。 上面使用类的对象代表了一个特定的地理、政治或文化区域。Locale只是一种机制,它用来标识一类对象,Local本身并不包含此类对象。 要获取一个Locale的对象有两种方法: (Ⅰ)调用Locale类的构造方法 Locale(String language,String country) Locale(String language,String country,String variant) 参数说明:language??在ISO-639中定义的代码,由两个小写字母组成。 country??在ISO-3166中定义的代码,由两个大写字母组成。 variant??售货商以及特定浏览器的代码,例如使用WIN代表Windows。 (Ⅱ)调用Locale类中定义的常量 Local类提供了大量的常量供用户创建Locale对象。 例 Locale.CHINA 为中国创建一个Locale的对象。 类TimeZone和类Locale中的其它方法,读者可查阅API。 (5)public GregorianCalendar(int year,int month,int date) (6)public GregorianCalendar(int year,int month,int date,int hour,int minute) (7)public GregorianCalendar(int year,int month,int date,int hour,int minute,int second) 用给定的日期和时间创建一个GregorianCalendar的对象。 参数说明: year-设定日历对象的变量YEAR;month-设定日历对象的变量MONTH; date-设定日历对象的变量DATE;hour-设定日历对象的变量HOUR_OF_DAY; minute-设定日历对象的变量MINUTE;second-设定日历对象的变量SECOND。 与Date类中不同的是year的值没有1900这个下限,而且year的值代表实际的年份。month的含义与Date类相同,0代表1月,11代表12月。 例 GregorianCalendar cal=new GregorianCalendar(1991,2,4) cal的日期为1991年3月4号。 除了与Date中类似的方法外,Calendar类还提供了有关方法对日历进行滚动计算和数学计算。计算规则由给定的日历系统决定。进行日期计算时,有时会遇到信息不足或信息不实等特殊情况。Calendar采取了相应的方法解决这些问题。当信息不足时将采用缺省设置,在GregorianCalendar类中缺省设置一般为YEAR=1970,MONTH=JANUARY,DATE=1。 当信息不实时,Calendar将按下面的次序优先选择相应的Calendar的变量组合,并将其它有冲突的信息丢弃。 MONTH+DAY_OF_MONTH MONTH+WEEK_OF_MONTH+DAY_OF_WEEK MONTH+DAY_OF_WEEK_OF_MONTH+DAY_OF_WEEK DAY_OF+YEAR DAY_OF_WEEK_WEEK_OF_YEAR HOUR_OF_DAY
8.4 随机数类Random
Java实用工具类库中的类java.util.Random提供了产生各种类型随机数的方法。它可以产生int、long、float、double以及Goussian等类型的随机数。这也是它与java.lang.Math中的方法Random()最大的不同之处,后者只产生double型的随机数。 类Random中的方法十分简单,它只有两个构造方法和六个普通方法。 构造方法: (1)public Random() (2)public Random(long seed) Java产生随机数需要有一个基值seed,在第一种方法中基值缺省,则将系统时间作为seed。 普通方法: (1)public synonronized void setSeed(long seed) 该方法是设定基值seed。 (2)public int nextInt() 该方法是产生一个整型随机数。 (3)public long nextLong() 该方法是产生一个long型随机数。 (4)public float nextFloat() 该方法是产生一个Float型随机数。 (5)public double nextDouble() 该方法是产生一个Double型随机数。 (6)public synchronized double nextGoussian() 该方法是产生一个double型的Goussian随机数。 例8.2 RandomApp.java。 //import java.lang.*; import java.util.Random;
public class RandomApp{ public static void main(String args[]){ Random ran1=new Random(); Random ran2=new Random(12345); //创建了两个类Random的对象。 System.out.println("The 1st set of random numbers:"); System.out.println("\t Integer:"+ran1.nextInt()); System.out.println("\t Long:"+ran1.nextLong()); System.out.println("\t Float:"+ran1.nextFloat()); System.out.println("\t Double:"+ran1.nextDouble()); System.out.println("\t Gaussian:"+ran1.nextGaussian()); //产生各种类型的随机数 System.out.print("The 2nd set of random numbers:"); for(int i=0;i<5;i++){ System.out.println(ran2.nextInt()+" "); if(i==2) System.out.println(); //产生同种类型的不同的随机数。 System.out.println();//原文如此 } } }
运行结果: E:\java01>java RandomApp The 1st set of random numbers: Integer:-173899656 Long:8056223819738127077 Float:0.6293638 Double:0.7888394520265607 Gaussian:0.5015701094568733 The 2nd set of random numbers:1553932502 -2090749135 -287790814 -355989640 -716867186 E:\java01>
8.5 向量类Vector
Java.util.Vector提供了向量(Vector)类以实现类似动态数组的功能。在Java语言中。正如在一开始就提到过,是没有指针概念的,但如果能正确灵活地使用指针又确实可以大大提高程序的质量,比如在C、C++中所谓“动态数组”一般都由指针来实现。为了弥补这点缺陷,Java提供了丰富的类库来方便编程者使用,Vector类便是其中之一。事实上,灵活使用数组也可完成向量类的功能,但向量类中提供的大量方法大大方便了用户的使用。 创建了一个向量类的对象后,可以往其中随意地插入不同的类的对象,既不需顾及类型也不需预先选定向量的容量,并可方便地进行查找。对于预先不知或不愿预先定义数组大小,并需频繁进行查找、插入和删除工作的情况,可以考虑使用向量类。 向量类提供了三种构造方法: public vector() public vector(int initialcapacity,int capacityIncrement) public vector(int initialcapacity) 使用第一种方法,系统会自动对向量对象进行管理。若使用后两种方法,则系统将根据参数initialcapacity设定向量对象的容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时,系统会扩充向量对象的存储容量。参数capacityIncrement给定了每次扩充的扩充值。当capacityIncrement为0时,则每次扩充一倍。利用这个功能可以优化存储。 在Vector类中提供了各种方法方便用户使用: ■插入功能 (1)public final synchronized void addElement(Object obj) 将obj插入向量的尾部。obj可以是任何类的对象。对同一个向量对象,可在其中插入不同类的对象。但插入的应是对象而不是数值,所以插入数值时要注意将数值转换成相应的对象。 例 要插入一个整数1时,不要直接调用v1.addElement(1),正确的方法为: Vector v1=new Vector(); Integer integer1=new Integer(1); v1.addElement(integer1); (2)public final synchronized void setElementAt(object obj,int index) 将index处的对象设成obj,原来的对象将被覆盖。 (3)public final synchronized void insertElementAt(Object obj,int index) 在index指定的位置插入obj,原来对象以及此后的对象依次往后顺延。 ■删除功能 (1)public final synchronized void removeElement(Object obj) 从向量中删除obj。若有多个存在,则从向量头开始试,删除找到的第一个与obj相同的向量成员。 (2)public final synchronized void removeAllElement() 删除向量中所有的对象。 (3)public final synchronized void removeElementlAt(int index) 删除index所指的地方的对象。 ■查询搜索功能 (1)public final int indexOf(Object obj) 从向量头开始搜索obj ,返回所遇到的第一个obj对应的下标,若不存在此obj,返回-1。 (2)public final synchronized int indexOf(Object obj,int index) 从index所表示的下标处开始搜索obj。 (3)public final int lastIndexOf(Object obj) 从向量尾部开始逆向搜索obj。 (4)public final synchronized int lastIndexOf(Object obj,int index) 从index所表示的下标处由尾至头逆向搜索obj。 (5)public final synchronized Object firstElement() 获取向量对象中的首个obj。 (6)public final synchronized Object lastelement() 获取向量对象中的最后一个obj。 了解了向量的最基本的方法后,我们来看一下例8.3VectorApp.java。 例8.3 VectorApp.java。 import java.util.Vector; import java.lang.*;//这一句不应该要,但原文如此 import java.util.Enumeration; public class VectorApp{ public static void main(String[] args){ Vector v1=new Vector(); Integer integer1=new Integer(1); v1.addElement("one"); //加入的为字符串对象 v1.addElement(integer1); v1.addElement(integer1); //加入的为Integer的对象 v1.addElement("two"); v1.addElement(new Integer(2)); v1.addElement(integer1); v1.addElement(integer1); System.out.println("The vector v1 is:\n\t"+v1); //将v1转换成字符串并打印 v1.insertElementAt("three",2); v1.insertElementAt(new Float(3.9),3); System.out.println("The vector v1(used method insertElementAt()) is:\n\t "+v1); //往指定位置插入新的对象,指定位置后的对象依次往后顺延 v1.setElementAt("four",2); System.out.println("The vector v1(used method setElementAt()) is:\n\t "+v1); //将指定位置的对象设置为新的对象 v1.removeElement(integer1); //从向量对象v1中删除对象integer1由于存在多个integer1所以从头开始 //找,删除找到的第一个integer1 Enumeration enum=v1.elements(); System.out.print("The vector v1(used method removeElement())is:"); while(enum.hasMoreElements()) System.out.print(enum.nextElement()+" "); System.out.println(); //使用枚举类(Enumeration)的方法来获取向量对象的每个元素 System.out.println("The position of object 1(top-to-bottom):" + v1.indexOf(integer1)); System.out.println("The position of object 1(tottom-to-top):" +v1.lastIndexOf(integer1)); //按不同的方向查找对象integer1所处的位置 v1.setSize(4); System.out.println("The new vector(resized the vector)is:"+v1); //重新设置v1的大小,多余的元素被行弃 } } 运行结果: E:\java01>java VectorApp The vector v1 is: [one, 1, 1, two, 2, 1, 1] The vector v1(used method insertElementAt()) is: [one, 1, three, 3.9, 1, two, 2, 1, 1] The vector v1(used method setElementAt()) is: [one, 1, four, 3.9, 1, two, 2, 1, 1] The vector v1(used method removeElement())is:one four 3.9 1 two 2 1 1 The position of object 1(top-to-bottom):3 The position of object 1(tottom-to-top):7 The new vector(resized the vector)is:[one, four, 3.9, 1] E:\java01> 从例8.3运行的结果中可以清楚地了解上面各种方法的作用,另外还有几点需解释。 (1)类Vector定义了方法 public final int size() 此方法用于获取向量元素的个数。它的返回值是向是中实际存在的元素个数,而非向量容量。可以调用方法capactly()来获取容量值。 方法: public final synchronized void setsize(int newsize) 此方法用来定义向量大小。若向量对象现有成员个数已超过了newsize的值,则超过部分的多余元素会丢失。 (2)程序中定义了Enumeration类的一个对象 Enumeration是java.util中的一个接口类,在Enumeration中封装了有关枚举数据集合的方法。 在Enumeration中提供了方法hawMoreElement()来判断集合中是束还有其它元素和方法nextElement()来获取下一个元素。利用这两个方法可以依次获得集合中元素。 Vector中提供方法: public final synchronized Enumeration elements() 此方法将向量对象对应到一个枚举类型。java.util包中的其它类中也大都有这类方法,以便于用户获取对应的枚举类型。
8.6 栈类Stack
Stack类是Vector类的子类。它向用户提供了堆栈这种高级的数据结构。栈的基本特性就是先进后出。即先放入栈中的元素将后被推出。Stack类中提供了相应方法完成栈的有关操作。 基本方法: public Object push(Object Hem) 将Hem压入栈中,Hem可以是任何类的对象。 public Object pop() 弹出一个对象。 public Object peek() 返回栈顶元素,但不弹出此元素。 public int search(Object obj) 搜索对象obj,返回它所处的位置。 public boolean empty() 判别栈是否为空。 例8.4 StackApp.java使用了上面的各种方法。 例8.4 StackApp.java。 import java.lang.*; import java.util.*; public class StackApp{ public static void main(String args[]){ Stack sta=new Stack(); sta.push("Apple"); sta.push("banana"); sta.push("Cherry"); //压入的为字符串对象 sta.push(new Integer(2)); //压入的为Integer的对象,值为2 sta.push(new Float(3.5)); //压入的为Float的对象,值为3.5 System.out.println("The stack is,"+sta); //对应栈sta System.out.println("The top of stack is:"+sta.peek()); //对应栈顶元素,但不将此元素弹出 System.out.println("The position of object Cherry is:" +sta.search("cherry")); //打印对象Cherry所处的位置 System.out.print("Pop the element of the stack:"); while(!sta.empty()) System.out.print(sta.pop()+" "); System.out.println(); //将栈中的元素依次弹出并打印。与第一次打印的sta的结果比较,可看出栈 //先进后出的特点 } } 运行结果(略)
8.7 哈希表类Hashtable
哈希表是一种重要的存储方式,也是一种常见的检索方法。其基本思想是将关系码的值作为自变量,通过一定的函数关系计算出对应的函数值,把这个数值解释为结点的存储地址,将结点存入计算得到存储地址所对应的存储单元。检索时采用检索关键码的方法。现在哈希表有一套完整的算法来进行插入、删除和解决冲突。在Java中哈希表用于存储对象,实现快速检索。 Java.util.Hashtable提供了种方法让用户使用哈希表,而不需要考虑其哈希表真正如何工作。 哈希表类中提供了三种构造方法,分别是: public Hashtable() public Hashtable(int initialcapacity) public Hashtable(int initialCapacity,float loadFactor) 参数initialCapacity是Hashtable的初始容量,它的值应大于0。loadFactor又称装载因子,是一个0.0到0.1之间的float型的浮点数。它是一个百分比,表明了哈希表何时需要扩充,例如,有一哈希表,容量为100,而装载因子为0.9,那么当哈希表90%的容量已被使用时,此哈希表会自动扩充成一个更大的哈希表。如果用户不赋这些参数,系统会自动进行处理,而不需要用户操心。 Hashtable提供了基本的插入、检索等方法。 ■插入 public synchronized void put(Object key,Object value) 给对象value设定一关键字key,并将其加到Hashtable中。若此关键字已经存在,则将此关键字对应的旧对象更新为新的对象Value。这表明在哈希表中相同的关键字不可能对应不同的对象(从哈希表的基本思想来看,这也是显而易见的)。 ■检索 public synchronized Object get(Object key) 根据给定关键字key获取相对应的对象。 public synchronized boolean containsKey(Object key) 判断哈希表中是否包含关键字key。 public synchronized boolean contains(Object value) 判断value是否是哈希表中的一个元素。 ■删除 public synchronized object remove(object key) 从哈希表中删除关键字key所对应的对象。 public synchronized void clear() 清除哈希表 另外,Hashtalbe还提供方法获取相对应的枚举集合: public synchronized Enumeration keys() 返回关键字对应的枚举对象。 public synchronized Enumeration elements() 返回元素对应的枚举对象。 例8.5 Hashtable.java给出了使用Hashtable的例子。 例8.5 Hashtalbe.java。 //import java.lang.*; import java.util.Hashtable; import java.util.Enumeration; public class HashApp{ public static void main(String args[]){ Hashtable hash=new Hashtable(2,(float)0.8); //创建了一个哈希表的对象hash,初始容量为2,装载因子为0.8
hash.put("Jiangsu","Nanjing"); //将字符串对象“Jiangsu”给定一关键字“Nanjing”,并将它加入hash hash.put("Beijing","Beijing"); hash.put("Zhejiang","Hangzhou");
System.out.println("The hashtable hash1 is: "+hash); System.out.println("The size of this hash table is "+hash.size()); //打印hash的内容和大小
Enumeration enum1=hash.elements(); System.out.print("The element of hash is: "); while(enum1.hasMoreElements()) System.out.print(enum1.nextElement()+" "); System.out.println(); //依次打印hash中的内容 if(hash.containsKey("Jiangsu")) System.out.println("The capatial of Jiangsu is "+hash.get("Jiangsu")); hash.remove("Beijing"); //删除关键字Beijing对应对象 System.out.println("The hashtable hash2 is: "+hash); System.out.println("The size of this hash table is "+hash.size()); } }
运行结果: The hashtable hash1 is: {Beijing=Beijing, Zhejiang=Hangzhou, Jiangsu=Nanjing} The size of this hash table is 3 The element of hash is: Beijing Hangzhou Nanjing The capatial of Jiangsu is Nanjing The hashtable hash2 is: {Zhejiang=Hangzhou, Jiangsu=Nanjing} The size of this hash table is 2
Hashtable是Dictionary(字典)类的子类。在字典类中就把关键字对应到数据值。字典类是一个抽象类。在java.util中还有一个类Properties,它是Hashtable的子类。用它可以进行与对象属性相关的操作。
8.8 位集合类BitSet
位集合类中封装了有关一组二进制数据的操作。 我们先来看一下例8.6 BitSetApp.java。 例8.6 BitSetApp.java //import java.lang.*; import java.util.BitSet; public class BitSetApp{ private static int n=5; public static void main(String[] args){ BitSet set1=new BitSet(n); for(int i=0;i<n;i++) set1.set(i); //将set1的各位赋1,即各位均为true BitSet set2= new BitSet(); set2=(BitSet)set1.clone(); //set2为set1的拷贝 set1.clear(0); set2.clear(2); //将set1的第0位set2的第2位清零 System.out.println("The set1 is: "+set1); //直接将set1转换成字符串输出,输出的内容是set1中值true所处的位置 //打印结果为The set1 is:{1,2,3,4} System.out.println("The hash code of set2 is: "+set2.hashCode()); //打印set2的hashCode printbit("set1",set1); printbit("set2",set2); //调用打印程序printbit(),打印对象中的每一个元素 //打印set1的结果为The bit set1 is: false true true true true set1.and(set2); printbit("set1 and set2",set1); //完成set1 and set2,并打印结果 set1.or(set2); printbit("set1 or set2",set1); //完成set1 or set2,并打印结果 set1.xor(set2); printbit("set1 xor set2",set1); //完成set1 xor set2,并打印结果 } //打印BitSet对象中的内容 public static void printbit(String name,BitSet set){ System.out.print("The bit "+name+" is: "); for(int i=0;i<n;i++) System.out.print(set.get(i)+" "); System.out.println(); } }
运行结果: The set1 is: {1, 2, 3, 4} The hash code of set2 is: 1225 The bit set1 is: false true true true true The bit set2 is: true true false true true The bit set1 and set2 is: false true false true true The bit set1 or set2 is: true true false true true The bit set1 xor set2 is: false false false false false
程序中使用了BitSet类提供的两种构造方法: public BitSet(); public BitSet(int n); 参数n代表所创建的BitSet类的对象的大小。BitSet类的对象的大小在必要时会由系统自动扩充。 其它方法: public void set(int n) 将BitSet对象的第n位设置成1。 public void clear(int n) 将BitSet对象的第n位清零。 public boolean get(int n) 读取位集合对象的第n位的值,它获取的是一个布尔值。当第n位为1时,返回true;第n位为0时,返回false。 另外,如在程序中所示,当把一BitSet类的对象转换成字符串输出时,输出的内容是此对象中true所处的位置。 在BitSet中提供了一组位操作,分别是: public void and(BitSet set) public void or(BitSet set) public void xor(BitSet set) 利用它们可以完成两个位集合之间的与、或、异或操作。 BitSet类中有一方法public int size()来取得位集合的大小,它的返回值与初始化时设定的位集合大小n不一样,一般为64。
小结
本章我们介绍了Java的实用工具类库java.util中一些常用的类。java.util包中还有其它一些类。它们的具体用法用户可以自行查阅API。在下一章,我们将学习利用java.awt包进行窗口程序设计。在下章的例子中我们将使用到本章介绍的各种类。
(前言) 根据上一讲,你或许大概已经了解了Eclipse的组成,以及大致的运行机理. 这篇文章, 将 开始带您使用eclipse. (正文) [Eclipse的工作台使用指南] 这部分要写的话,其实要写很多,而且最好方式则是图文并茂,最好再有演示. 我这里只是 给一些总体的介绍,并给予一些使用上的指导. 个人感觉,如果你从来 没碰过eclipse, 启动之后,最好先看一下help, 这样会比较好. 具体的操作步骤是这样的, 启动eclipse后, 选 菜单里的help-> help contents, 此时 会弹出一个新的窗口,就是eclipse的帮助窗口, 这个窗口的左边是一个导航 条,选择Workbench User Guide, 里面分为Getting Started, Concepts, Tasks 和Refer ence. 可以先看一下 Getting Started里面的Basic Tutorial. 这份 tutorial可以在最短的时间内,让你熟悉eclipse的工作台. 其实,eclipse平台ui方面有这几个组件: 菜单, 工具栏, 视图, 透视图, 编辑器 菜单和工具栏不用说了, 地球人都知道的, 视图就是view, 比如 Navigator, Outline, Tasks 等等都是视图, 每一个视图都有自己相应的功能,你可以参看 workbench user guide来了解这些视图. 编辑器,就是editor, 比如有开发java的编辑器 , 编写文本的编辑器,等等, 最后还有一个叫透视图, 英文是perspective, 这个东东其实是不同的view,menu,toolbar,editor的排列组合. 比如你开发java, 你经 常会用到package explorer, tasks, outline等view和编写java的editor, 以及适合开发java的菜单(Source和Refactor), 那它就会布局一个适合开发java的透视 图, 以此类推. 下面我介绍一下常用菜单项: + File - New: 新建文件,项目,或者其他都是从这里进入 - Import: 这个也是很有用的,比如别人开发的eclipse项目,你copy到 你机器上,可以通过import把这个项目导入工作区 - Export: 这个是导出功能, 比如你开发了一个项目,最后想导出一个 运行库,jar包之类的都可以用这个,这样你就不用自己手动的去把那些class 文件打包了 + Window - New Window: 你如果觉得在一个window下开发东西太挤的话,可以再开一 个,等于冒出两个workbench,其实操作的resource都是一样的. - Open Perspective: eclipse有很多透视图如resource, java, java browsing, cvs, debug等等,你可以根据当前开发的需要,选择你要的透视图进行开发 - Open View: 透视图毕竟有限,不可能把所有的view都帮你开好, 所以你 如果发觉你要用某一个view,但是它没有开,就用这个选项 - Customize Perspective: 毕竟每个人都有自己的习惯, 你觉得这个透视图用 的不爽, 可以自己定制的 - Reset Perspective: 给你定制的一塌糊涂, 唉, 没办法, 还原成老样子把, 就用这个 - Preference: 这个是非常重要的选项, 偶是没事就进去改的, 它保存了 很多配置方面的东西, 比如字体, 快捷键设置, 很多很多方面都要用到这个 的, 这个东西的详细介绍, 会稍后介绍. + Help - Welcome: eclipse很多插件都做了welcome page,这个page对很多初学 者来说,很有用的,否则很多情况下,新的东西是无从下手的. - Help Content: 前面讲过了,用来启动帮助系统 - Update: 这个以后会具体介绍 - About Platform: 你可以从这里了解你装了哪些features和plugins [利用 eclipse 开发简单的 java 程序] 好了,我们一起step by step来学一下把, 很easy的 1. 菜单 new -> project, 然后在new project 对话框里选 java ->java project, 按n ext 按钮 2. 输入 project 的名字, 按 next 按钮, 当然如果你不想把项目的根目录建在默认的 地方,也可以取消掉use default ,然后自己设定目录 3. 之后就 finish 把, 都用默认配置 4. 如果你没有在java透视图下面,它会提示你是否跳到java透视图,选择是 5. ok 一个项目就建好了 6. 之后,你就可以 new class 开始写java程序了 7. 比如你的new一个class,如下 public class A{
public static void main(String args[]){
System.out.println("Hello World");
}
}
8. 编辑好保存, 然后跳到菜单 -> run -> run as -> java application 9. 你可以看到console view中就冒出Hello World了 很easy把, 当然, 你会在开发中会遇到很多问题, 这是必然的, 那这些问题只能在实践 中积累才能得到解决, 所以不用急,多用用,多玩玩,遇到问题经常到版上来问 问. 还有如果你发现问题的话, 找问题解决方案的第一个地方,应该是eclipse的帮助系统里 的Java Development User Guide, 几乎绝大多数问题,上面都有答案. 所以 有空的话还是要多读一读. 不过,很多人都说用了eclipse之后,就抛弃了其他的java ide, 说明它必有爽的地方, 那 我来介绍一下用eclipse 开发java,有哪些爽的地方 [Java Development Tool (JDT) 特色] 其实特色有很多,我也只是凭我的开发经验, 介绍一下jdt的突出功能. 1. 自动修饰代码功能 这个功能很大程度上, 把平时一些开发代码中的琐碎的工作给自动化了 打开菜单 + Source - Comment: 这个比较有用,比如你写java代码,发觉你有一段代码要注 释掉,那就选中那一块代码,然后选这项,他就会自动把这段代码注释掉. 快捷 健是 "Ctrl+/" - Uncomment: 反注释, 操作方法和Comment差不多, 快捷键是 "Ctrl+\" - Format: 这个操作项是我一直推荐的, 非常方便, 比如你写了段格 式很烂的代码, 乱七八糟的, 那你就选择这项,你会发觉, 哇~~, 我的代码怎 么一下子变漂亮了, 不信你可以试试, 快捷键是"Ctrl+Shift+F", 所以我现在都养成习 惯了,写一会儿代码,就c+s+f一下, 呵呵,很方便的. - Sort Member: 这个不是很常用,但是如果你觉得代码太长,老是找不到函 数,,也可以试试. 他会帮你把你写的函数,变量重新排序 - Organize Import: 这个功能也是一个不用不爽的功能, 比如你编一个项目, 发现有很多import都没有用到,或者说你引用了一个类,但是你没有import, 结果编译不通过, 那都没关系, 一用这个,所有的问题迎刃而解. 至少我用这个之后, 就 从来没有写过import这类语句了. 快捷键是"Ctrl+Shift+O(是字母O,不是数 字0)", 我经常把这个和c+s+f一起用, 呵呵,人也变懒了不少 - Override/Implement Method: 这个比如你写一个类, 实现了某一个接口,但是 你还没有实现那个接口的函数, 那就用这个,它会自动搜索父类和接口的方 法,你可以选择要覆盖还是实现哪些函数 - Generate Getter and Setter: 如果你加了一个类变量, 要为它写getter和se tter,不用那么麻烦, 用这个把,都是自动的 - Generate Delegating Method: 如果某一个field要生成代理函数,用这个把, 选一下就ok - Add Javadoc Comment: 点中某一个你想要加javadoc的函数或类或变量,然后 选这项,它会自动帮你加好javadoc的头,包括你用的那些param或return - surround with try/catch block: 比如你有一段代码要处理某些exception, 可你又忘了用try/catch来写,别急,选中那段代码,然后选这项,你会发觉它会 自动针测你这段代码里要抛出哪些exception,并且自动生成好所有代码 - externalize string: 这个是在做国际化的时候用的,简单的说,就是把string 包在resource bundle里, 这也是i18n的一个解决方案,我想我以后会详细介 绍这方面的东西. 2. 重构功能: 重构这两年很热, 那让我们看看jdt里面的重构到底有多强. 呵呵, 这也是eclipse最吸 引我的一个地方. 打开菜单 + Refactor - Rename: 如果你写了一个类,你发觉这个类某个类变量的名字起的不太好听 , 你觉得不爽, 于是你就想改名字, 那怎么改呢, 就把那个变量名改了 ? 呵呵,没那么简单,因为你这个变量如果已经在某些函数里引用到,那编译要出错了, 而 且你根本就不知道你哪里引用了, 写了那么多代码,脑子都晕了, 那不是死 菜了吗? 别紧张, 用这个rename可以帮你解决一切问题, 它不仅可以帮你把变量的名字 改了,而且它还会自动搜寻所有这个变量被引用到的地方,然后把那些地方也 一起改了, 爽不爽啊, 给你省了很大的劳动力不是. - Move: 同样, 要移动一个实现了的静态函数或变量到别的类的话, 用这个移动, 保证不出错 - Modify Method Signature: 你设计函数不可能一下子就定型的,比如你一开始这 个函数有一个参数,后来发觉不对,要用两个, 那你就要用这个来改, 这 样它还会搜寻所有已经引用这个函数的地方, 并且把这些应用的地方也改掉,否则编译也 要出错的 - Extract Interface: 它可以帮你把一个类抽象成一个接口, 规范你的代码 - Extract Method: 如果你写了一段很长的函数, 但是这个函数有些代码有 重复利用性, 你就可以把给分割出来, 选中那段代码,然后选这项,系统会问 你抽出来的函数的定义, 然后它就会生成这段函数,把实现从原来函数那里抽出来,并在 原来函数那里写一个对这个新函数的引用,以保证程序不变性. - Extract Local Variable: 如果你发觉你函数里有些值都是通过一样的表达 式得到的,你就可以通过这项把这段表达式变成一个变量,并且把这个变量替 换到引用到表达式的地方 - Extract Constant: 抽取常量, 比如一个string= "eclipse", 你觉得很多 地方要用到, 那就抽出来变成一个常量 ECLIPSE, 就这么简单 - convert local variable to field : 这个看名字就知道, 不多说了 - encapsulate field: 这个和生成getter,setter有点类似, 但是不同的在于, 如果你有一个public的常量 var,并且已经在别处引用到了, 那你用生成 getter,setter肯定有问题, 因为引用的地方没改过来, 用这个的话,不仅会生成getter, setter,而且还会改掉所有引用的地方, 比如把var = ...;的地方改成 setvar(...), 把 ... = var地方改成 ... = getvar(); , 呵呵,够强把... 3. 敏感帮助: 这个jb之类的ide也有, 启动方式为"alt+/" , 你如果觉得这个不爽,可以选preference- >workbench->keys->edit->content assist 修改键值 而且,这个敏感帮助还有一个强的地方在于: 你如果想写一个for语句, 呵呵, 就打 for, 然后alt+/, 选一个for的生成方式, 一个完 成的for语句就出来了, 呵呵,eclipse多用用, 人都会变懒的 这个功能其实是jdt的模板功能,你也可以加自己用的模板, 具体在 preference-> java -> editor ->templates 加 4. Quick Fix功能: 比如我们来编一个类A, 如下: public class A {
public static void main(String args[]){
System.out.prin("Hello World");
}
}
编译是不通过的,print方法打错了, 打成prin了 所有你会发觉那一行的左边有个红差差,说明这行有错误,你把鼠标移到那个红差差上,它 会有一个提示出来,告诉你出了什么错了 这还不止,你还发觉在红差差左边有个电灯泡, 你点那个电灯泡,它会弹出框问你是chang e to print 还是change to println, 选择change to print, 它就自动帮 你改好了,呵呵,连改错都这么方便,太爽了. 当然,如果没有电灯泡的话,你就只能手动改 了,毕竟这东西没这么智能,可以帮你自动解决所有的问题
(前言) 今天这篇侧重于eclipse的内部结构剖析,对于想开发插件的同志们,这些都是基础知识, 可以好好看看. 由于我写这个东西,也是随性发挥,想到什么就写什么. 而关于eclipse的 如何使用,如何用eclipse来开发一个java项目或其他项目之类的文章, 我想我会在以后 的文章中写到. 但我想对于要在开发eclipse上开发web项目,c项目或其他的话,你也要会 自己能安装相应的插件,才能开发,所以,这些基础知识的对于这些人来说还是很有必要的 . (正文) 上一回我们且说到eclipse的下载,安装,启动. 其中讲到eclipse目录结构时,你会发现有 两个目录,一个叫plugins,一个叫features,而且你会发觉就这两个目录就占了整个eclip se项目的9x%的空间,如果少了这两个目录,呵呵,eclipse根本就是空架子. 那这里面到底 存放了些什么东西呢, 让我们来研究一下. [什么是 plug-in] 我们来做一个比喻, 你买了一套新房子,买过来是毛坯房,然后你稍微装修一下,铺了地板 ,上了墙纸, 当然现在大家都要用家用电器, 没电咋行, 所以我们就要布好电线,装好电 源插座. 这个时候,你可以把这个房子想像成eclipse这个平台. 之后, 我们或许就要添 置家用电器了,比如电视, 音响等等, 等我们买好回家, 然后把电源往插座上一插, 那我 们就抱着孩子, 搂着老婆, 看电视, 听音乐, 舒舒服服的过上幸福美满的小生活了~~~ 同样的, eclipse的plug-in 也是同样的工作原理, plug-in 只要放到 /p lugins目录下, eclipse启动后就会自动给所有在这个目录下的plug-ins, 通上电, 那这 些plug-ins就会自动的运行起来, 美妙的eclipse界面也随之呈现在你眼前. 那接下来,让我们看看eclipse这个由插件组成的平台,到底是个什么样的架构 [Eclipse 平台架构] --------------------------------------------- | Eclipse Platform | -------- | | ---\ | | | ----------------------- ------------ |==| _ |______| JDT | | |Workbench | | | |==| | | | | | | | | | ---/ -------- | | | | | | | | ---------------| | Help | | | | | jface | | | | | |----------- | | | | | | SWT | | | | | | | | | ------------ | | ----------------------- | | ------------ | -------- | ------------------ | | | ---\ | | | |Workspace | | | |==| _ |______| PDE | | | | | Team | |==| | | | | ------------------ | | | ---/ -------- | | | | | ------------ | ---------------------------------------------
Eclipse Platform 就是一个房子, workbench,jface, swt, workspace, help, team, jdt, pde都是基于这个平台的插件.
下面我介绍一下这些基础插件的基本功能:
*: workbench用来控制工作台, 负责控制工作台上包括菜单,视图,透视图等等的控制和 操作 *: SWT是一个类似AWT,SWING的java组件,是一个轻量级的组件,而且和awt,swing不同的 是,它底层实现不是基于jre,而是根据不同操作系统,有相应的动态链接库实现,所以作出 来的效果很专业, SWT主要用于workbench的ui绘制 *: jface是基于SWT的一个插件, 对SWT进行了封装, 封装实现了对话框, 视图等东东 *: workspace是用来控制工作区的,(有别于工作台), 包括对工作区内的项目的控制,删 除,添加,编译项目资源等等都由它来控制 *: help是一个eclipse帮助系统, eclipse的菜单->Help-> Help Content,就可以打开这 个帮助系统, 这个系统不是封闭的, 可以进行扩展(以后会介绍做eclipse帮助的插件) *: team是一个cvs系统,可以和CVS server协调使用,进行版本控制 *: jdt 是 Java Development Tools, 开发java的插件 *: pde 是 plug-in development environment, 开发插件的平台
[plug-in 的基本结构]
每一个plug-in都用一个目录包起来, 而且起目录名也是有讲究的,比如plug-in的名字叫 edu.sjtu.bbs.eclipse,版本是1.0.0, 那这个目录名就是edu.sjtu.bbs.eclipse_1.0.0.
而且随便打开一个plugin目录,可以发现总有一个文件叫一个叫plugin.xml,这个文件对 于plugin来说十分重要, 它相当于定义了plugin的运行参数,没有这个,plugin无法启动, 就像你家的电冰箱如果不知道是用110V还是220V的,你也不敢乱往插座上插,所以总要有 个说明,这个说明就是plugin.xml. 至于这个文件有些什么具体结构,我想在以后介绍编 写插件的时候,我会详细介绍.
[什么是 feature]
feature是功能部件,它里面没有实际的运行的库,它只是eclipse用来管理plugins的一种 途径. 比如你家装了电灯,总要有开关控制把,比如大堂的灯有一个开关控制, 卧室的灯 也有一个开关控制, 它们分别用来控制灯的亮与灭. 同样,功能部件就是用来控制插件的启动与否. eclipse的update透视图可以设定各个功 能部件的启用或禁用状态, 所以你可以通过禁用功能部件,来禁止插件的启动. 这样有一 个好处,比如你装了很多插件在eclipse上,但是装的越多,加载就越多,启动也会变慢, 你 不信的话,可以玩玩wsad, 就知道我说的话不是假的了. 所以,我们可以把功能部件看作是插件或插件集合的开关, 用来控制插件的状态. 如果pl ugins目录有插件没有被任何一个功能部件包络的话, 我称之为"野插件", 就是eclipse 启动,它也一定会启动, 就相当于没有开关, 电源一直连通一样.
当然,功能部件还有很多其他方面的用处,以后会有详细介绍.
(前言)
Eclipse这个新的东东,大家都很感兴趣,为了帮助初学者揭开Eclipse的神秘面纱,也为了 总结一下自己的使用开发经验,所以打算写下来共享给大家,当然我也是在不断的摸索中, 文中如有不对之处,还望大家指正.
(正文)
Eclipse项目是IBM在2001年捐献的一个开发平台,当时此项目评估价值为40million USD. 此东东如此值钱,可见自是有过人之处. 接下来直接转入正题, 哪里可以下到Eclip se呢.
[Eclipse 下载]
下载Eclipse的官方网站:
http://www.eclipse.org/downloads/
这个网站上有很多mirror,你可以根据网路,选择自己最方便的下载mirror 如果你在交大的校园的话,呵呵,那就去这里
ftp://ftp.sjtu.edu.cn/mirror/sites/download.eclipse.org/
这个目录是也是一个Eclipse网站的镜像,但是为什么没有公布在Eclipse.org网站上,这 我就不清楚了 你可以从上述网站上找到Eclipse的下载包 上了网站,你会发现有许多Eclipse版本,有1.x, 2.x, 3.x,那到底该用什么版本呢?
[Eclipse 版本 (以及WSAD版本)]
IBM有个深受大家欢迎的产品,叫做Webshpere Studio Application Developer (WSAD), 当时WSAD4.0发布时用的Eclipse是1.0版本的,后来IBM把Eclipse捐献出来后,就陆续开发 了2.x版本,而且也应用到了后来的WSAD产品,现在WSAD5.0用的是Eclipse 2.0, WSAD 5.1 用的是Eclipse 2.1.1. 现在Eclipse.org Release的最近Release的版本是Eclipse 2.1.3, 相信大家平时要用Ec lipse做开发的话,用2.1.3比较合适. 同时,如果你如果想尝尝鲜的话,就试试Eclipse 3.0版本,现在3.0还没有Release, Eclipse 3.0的开发过程是分成10个Milestone来分的,现在已经开发到第8个Milestone, 就是Eclipse 3.0M8. 大家可以试试. 预计3.0的正式版本要到今年6月发布, 相信到时候 发布不久后, WSAD6.0就会发布了. 呵呵,期待啊 @_@
另外对于Eclipse的开发,还有一种版本标记方式,是分别已 N, I, R 打头的, 比如 N20040101之类的, 大家会觉得纳闷,这到底代表什么意思呢. 其实, N 是 Nightly Build , I 是 Integration Build, R 是 Release Build, 特别是对于大型软件, 有时 编译一个项目要花很多时间, 所以很多时候编译的工作就放在了夜里, 所以就有了 Nightly Build 这一说, 而Nightly版本在经过集成测试,就生成了 Integration 版本. Integration版本再经过严格的测试,最后就发布Release版本. 所以 稳定性程度: R > I > N.
相信大家了解了这个版本后,就可以当自己想要的版本了. 等当好了之后,我们就可以安装了
[Eclipse 安装] (以 R2.1.3 版本为例)
当下来的东东是一个压缩包, 然后你要做的就是把这个压缩包解压都某一个目录,为了方 便介绍,我们叫这个目录为.
让我们展开一下这个目录看看:
| --- /plugins 存放插件的目录 (稍后介绍) --- /features 存放功能部件的目录 (稍后介绍) --- /links 其他plugins和features的连接地址的存放目录 (稍后介绍) --- /readme --- eclipse.exe 启动Eclipse程序 --- ...
[启动 Eclipse]
但是如果你机器还没有装jre的话,那eclipse还是不能启动的 如果没有的话,去java.sun.com当一个jre,安装好jre之后才能启动 如果你同时有几个jre, 那eclipse会自动搜索注册表,并找到版本高的jre使用.
提示: 如果想指定eclipse使用你想它使用的jre的话,可以设置eclipse.exe的启动参数:
eclipse.exe -vm
然后你就可以启动eclipse了, 慢慢等, 还是挺慢的, 你可以顺便喝口茶, 欣赏欣赏eclipse的splash画面, 呵呵 启动好后, eclipse的开发平台就展现你的眼前了.
在这个启动的时候, eclipse自动创建了一个workspace, 你可以在的目 录下看到一个workspace的目录,这个目录下还有一个.metadata的目录,这个目录存着你 这个启动的工作区的所有配置.
当然,如果你不想把workspace这个工作区目录放到别的目录下,也没有问题,设置一下eclipse的启动参数: eclipse.exe -data
以后,你如果同时要开发好几个项目, 当时有不想放一个工作区里, 学会启动的不同的工作区是必要的
- Vector 还是ArrayList,哪一个更好,为什么?
要回答这个问题不能一概而论,有时候使用Vector比较好;有时是ArrayList,有时候这两个都不是最好的选择。你别指望能够获得一个简单肯定答案,因为这要看你用它们干什么。下面有4个要考虑的因素:
(1)API
(2)同步处理
(3)数据增长性
(4)使用模式
下面针对这4个方面进行一一探讨
API 在由Ken Arnold等编著的《Java Programming Language》(Addison-Wesley, June 2000)一书中有这样的描述,Vector类似于ArrayList.。所有从API的角度来看这两个类非常相似。但他们之间也还是有一些主要的区别的。
同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
使用模式
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的—O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。
http://forum.javaeye.com/viewtopic.php?p=17108&highlight=Serializable+generate+SessionImplementor+sessionImplementor%2C#17108
看了看。真是万分高兴,可能别人用了一个星期才解决的问题。我现在就可以不劳而获。惭愧啊。。
由于担心Hibernate自身提供的increment id generator有性能影响,我对increment进行了扩展,改为InMemoryIncrement:
InMemoryIncrement.java
代码: //Declare Classes Import here,Do not use .* to import all the classes in package. import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties;
import java.util.Map; import java.util.HashMap;
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;
import net.sf.hibernate.HibernateException; import net.sf.hibernate.MappingException; import net.sf.hibernate.dialect.Dialect; import net.sf.hibernate.engine.SessionImplementor; import net.sf.hibernate.type.Type; import net.sf.hibernate.id.IdentifierGenerator; import net.sf.hibernate.id.PersistentIdentifierGenerator; import net.sf.hibernate.id.Configurable;
/** * Hibernate high performence id generator extend IncrementGenerator.<br> * This generator use ’select max(column) from table ’ to get id’s init value * in first time.Then generate increment id in memory . * <p> * 原理: * <li>使用long[]数组在内存中存储每个表的最大值,使用Map存储表名在数组中对应的索引值。 * <li>第一次访问某个表的id的最大值时会从数据库中取出并存放到long数组中, * 同时在Map中保存索引值 * <li>第二次以后访问会首先从Map中读出索引值,根据索引值把long数组对应的值 * 加上递增步长并返回 * </p> * @author fenghm * @version $Revision$ */ public final class InMemoryIncrement implements IdentifierGenerator, Configurable {
private static final Log log = LogFactory.getLog(InMemoryIncrement.class); //存储最大值的数组的容量 private static final int MAX_CAPACITY = 200; /**同步锁*/ private static final Object lock = new Object();
//存储表存储在数组中的索引值 private static Map map = new HashMap(); //递增步长,默认加1 private int step = 1; //最大值数组 private static long[] seqs = new long[MAX_CAPACITY]; //最大值数组已经使用的容量 private static int lastIndex; private String key; private String sql; private Connection connection; private Class returnClass; /** * (none java doc) * @see net.sf.hibernate.id.IdentifierGenerator# * generate(net.sf.hibernate.engine.SessionImplementor, java.lang.Object) */ public Serializable generate(SessionImplementor session, Object object) throws SQLException, HibernateException { connection = session.connection(); long seq = -1; //找到索引值 int index = findIndex(); //把最大值加1 seqs[index] = seqs[index] + step; seq = seqs[index]; return new Long(seq); }
/** * 找到表中自动增长字段存储在数组中的索引值 * @return 索引值 */ private int findIndex(){ int index = 0; //首先中缓存中取出索引值 Integer integer = (Integer)map.get(key); //如果没有找到就从数据库中读出最大值并进行cache if(null == integer){ //double check lock synchronized(lock){ integer = (Integer)map.get(key); if(null == integer){ long maxvalue = 1; try{ maxvalue = getMaxvalue(); }catch(SQLException e){ log.error(e); } integer = new Integer(lastIndex++); seqs[integer.intvalue()] = maxvalue; map.put(key,integer); } } } index = integer.intvalue(); return index; } /** * (none java doc) * @see net.sf.hibernate.id.Configurable#configure(net.sf.hibernate.type.Type, * java.util.Properties, net.sf.hibernate.dialect.Dialect) */ public void configure(Type type, Properties params, Dialect d) throws MappingException { //取出table参数 String table = params.getProperty("table"); if (table == null){ table = params.getProperty(PersistentIdentifierGenerator.TABLE); } //取出column参数 String column = params.getProperty("column"); if (column == null){ column = params.getProperty(PersistentIdentifierGenerator.PK); } //表的sehcma参数 String schema = params.getProperty(PersistentIdentifierGenerator.SCHEMA); returnClass = type.getReturnedClass(); //取出step参数 String stepvalue = params.getProperty("step"); if(null != stepvalue && !"".equals(stepvalue.trim())){ try{ step = Integer.parseInt(stepvalue); }catch(Exception e){ log.error(e); } }
//构造存储在Map中的索引值的key name key = table + "_$_" + column; //根据参数构造取最大值的SQL sql = "select max(" + column + ") from "; if(null != schema){ sql += schema + "."; } sql += table; } /** * 取指定表中id字段的最大值,不存在记录返回0 * @return 最大值 * @throws SQLException if sql error occurs. */ private long getMaxvalue() throws SQLException { long maxvalue = 0; PreparedStatement st = connection.prepareStatement(sql); ResultSet rs = null; try { rs = st.executeQuery(); if(rs.next()) { maxvalue = rs.getLong(1); } sql = null; }finally { if (rs != null){ rs.close(); } st.close(); } return maxvalue; }
测试代码: Data.java 代码: public class Data {
private long id; private String name; private String email;
/** * @return */ public String getEmail() { return email; }
/** * @return */ public long getId() { return id; }
/** * @return */ public String getName() { return name; }
/** * @param string */ public void setEmail(String string) { email = string; }
/** * @param l */ public void setId(long l) { id = l; }
/** * @param string */ public void setName(String string) { name = string; } }
Data.hbm.xml 代码: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping package="com.kmview.test.hibernate.model"> <class name="Data" table="data"> <id name="id" type="long" column="id"> <generator class="com.kmview.test.hibernate.Generator"/> </id> <property name="name" type="java.lang.String" length="20" column="name"/> <property name="email" type="java.lang.String" column="email"/> </class> </hibernate-mapping>
ThreadMain.java 代码:
public class ThreadMain extends Thread {
public static void main(String[] args) { try{ Configuration cfg = new Configuration().configure(); SchemaExport schema = new SchemaExport(cfg); schema.create(false,true);
for(int i=0;i<100;i++){ new ThreadMain().start(); } }catch(Exception e){ e.printStackTrace(); } } public void run(){ try{ for(int i=0;i<20;i++){ Session session = HibernateSession.openSession();
Data data = new Data(); data.setName("sdfsfsdf"); data.setEmail("sdflkas;lfdalsfjasljf@d.d");
session.save(data); session.flush(); HibernateSession.closeSession(); } }catch(Exception e){ e.printStackTrace(); } } }
HibernateSession.java 代码: public final class HibernateSession {
private HibernateSession(){} //synchronized lock for getSessionFactory private static Object lock = new Object(); //common log object private static Log log = LogFactory.getLog(HibernateSession.class); //Hibernate SessionFactory private static SessionFactory sessionFactory; //Implement Hibatenate ThreadLocal pattern private static final ThreadLocal session = new ThreadLocal(); /** * 自动在ClassPath的根路径寻找hibernate.properties与hibernate.cfg.xml文件<br> * 并初始化SessionFactory * @return SessionFactory */ private static SessionFactory buildSessionFactory(){ SessionFactory factory = null; try{ Configuration config = new Configuration(); config = config.configure(); factory = config.buildSessionFactory(); log.info("ok,SessionFactory builded!"); }catch(HibernateException e){ log.error(e); } return factory; } /** * 取得当前会话的Hibernae Session对象,并打开数据库连接 * @return Hibernate Session Object for Data Access * @exception HibernateException throw HibernateException */ public static Session openSession() throws HibernateException{ Session s = (Session)session.get(); if(null == s){ getSessionFactory(); if(null != sessionFactory){ s = sessionFactory.openSession(); session.set(s); }else{ log.error("Error,SessionFactory object is not inited!"); } }else if(!s.isConnected()){ s.reconnect(); } return s; } /** * 关闭数据库连接,在每次Session用完的时候,应该马上调用closeSession,<br> * 以把数据库连接归还到数据库连接池中,提高并发性能。 */ public static void closeSession(){ try{ Session s = (Session)session.get(); if(null != s && s.isConnected()){ s.disconnect(); } }catch(HibernateException e ){ log.error(e); } } /** * 释放Hibernate Session对象,此方法应该在当前线程消亡之前调用。<br> * 例如:在Web应用中可以使用filter统一在最后进行调用<br> * <code><pre> * public class HibernateFilter extends HttpServlet implements Filter{ * private FilterConfig filterConfig; * public void init(FilterConfig filterConfig){ * this.filterConfig = filterConfig; * } * * public void doFilter(ServletRequest request,ServletResponse response, * FilterChain filterChain){ * try{ * filterChain.doFilter(request,response); * } catch(ServletException sx){ * filterConfig.getServletContext().log(sx.getMessage()); * } catch(IOException iox){ * filterConfig.getServletContext().log(iox.getMessage()); * }finally{ * HibernateSession.releaseSession(); * } * } * } * </pre></code> * 在Hibernate的API文档提到,Session的close()方法不是必须要被调用的,<br> * 但disconnect()方法是必须的;所以此方法也不是必须要被调用的,但是<br> * closeSession()方法是必须的。 * @exception HibernateException throw HibernateException */ public static void releaseSession() throws HibernateException{ Session s = (Session)session.get(); session.set(null); if(null != s){ s.close(); } } /** * 设置SessionFactory,提供此方法是为了可以在外部初始化SessionFactory. * @param factory SessionFactory */ public static void setSessionFactory(SessionFactory factory){ if(null == sessionFactory){ sessionFactory = factory; } } /** * 获取SessionFactory,第一次访问时会自动在ClassPath的根路径寻找<br> * hibernate.properties与hibernate.cfg.xml文件并初始化。 * @return SessionFactory */ public static SessionFactory getSessionFactory(){ //Double check lock. if(null == sessionFactory){ synchronized(lock){ if(null == sessionFactory){ sessionFactory = buildSessionFactory(); } } } return sessionFactory; } }
从网上当了一个例子,发觉上传图片文件时图片文件中内容发生了变化,最大的变化时所有的FF变成了3F
也就是说输出时把字符当成有符号传送了?(是这样把?)
使用代码如下:
PrintWriter pw = new PrintWriter(new BufferedWriter(new
FileWriter((savePath==null? "" : savePath) + filename)));
while (i != -1 && !newLine.startsWith(boundary)) {
// 文件内容的最后一行包含换行字符
// 因此我们必须检查当前行是否是最
// 后一行
i = in.readLine(line, 0, 1280);
if ((i==boundaryLength+2 || i==boundaryLength+4)
&& (new String(line, 0, i).startsWith(boundary)))
pw.print(newLine.substring(0, newLine.length()-2));
else
pw.print(newLine);
newLine = new String(line, 0, i);
}
pw.close();
但是总是不行,后来我把PrintWriter用FileOutputStream替代,代码如下:
FileOutputStream pw=new FileOutputStream((savePath==null? "" : savePath) + filename);
//PrintWriter pw = new PrintWriter(new BufferedWriter(new
//FileWriter((savePath==null? "" : savePath) + filename)));
while (i != -1 && !newLine.startsWith(boundary)) {
// 文件内容的最后一行包含换行字符
// 因此我们必须检查当前行是否是最
// 后一行
i = in.readLine(line, 0, 1280);
if ((i==boundaryLength+2 || i==boundaryLength+4)
&& (new String(line, 0, i).startsWith(boundary)))
pw.write(newLine.substring(0, newLine.length()-2).getBytes("ISO8859_1"));
else
pw.write(newLine.getBytes("ISO8859_1"));
newLine = new String(line, 0, i,"ISO8859_1");
}
pw.close();
竟然就可以了,那个转换的编码还一定要上才行,不然也不干活的,真是郁闷,这跨平台就不能让写CODE的人解放解放,一个输出都N多类,看着心寒,想着简直想死啊
哎 忙了我一个下午,原来就是这回事
下面把原代码附上,是把别人的代码改吧改吧放上来的,还请原作者不要见意:
Java bean 文件:
package cr.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;
import java.util.Dictionary;
import java.util.Hashtable;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.*;
public class FileUploadBean {
private String savePath, filepath, filename, contentType;
private byte[] b;
byte t;
private Dictionary fields;
public String getFilename() {
return filename;
}
public String getFilepath() {
return filepath;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public String getContentType() {
return contentType;
}
public String getFieldValue(String fieldName) {
if (fields == null || fieldName == null)
return null;
return (String) fields.get(fieldName);
}
private void setFilename(String s) {
if (s==null)
return;
int pos = s.indexOf("filename=\"");
if (pos != -1) {
filepath = s.substring(pos+10, s.length()-1);
// Windows浏览器发送完整的文件路径和名字
// 但Linux/Unix和Mac浏览器只发送文件名字
pos = filepath.lastIndexOf("\\");
if (pos != -1)
filename = filepath.substring(pos + 1);
else
filename = filepath;
}
}
private void setContentType(String s) {
if (s==null)
return;
int pos = s.indexOf(": ");
if (pos != -1)
contentType = s.substring(pos+2, s.length());
}
public void getByte(HttpServletRequest request)
{
DataInputStream is;
int i=0;
try
{
is=new DataInputStream(request.getInputStream());
b=new byte[request.getContentLength()];
while (true)
{
try
{
t=is.readByte();
b[i]=t;
i++;
}
catch(EOFException e)
{ break;}
}
is.close();}
catch(IOException e)
{}
}
public void doUpload1(HttpServletRequest request) throws
IOException {
byte[] line=new byte[128];
FileOutputStream os=new FileOutputStream("c:\\Demo.out");
ServletInputStream in = request.getInputStream();
getByte(request);
String temp="";
temp=new String(b,"ISO8859_1");
byte[] img=temp.getBytes("ISO8859_1");
for (int i=0;i<img.length;i++)
{ os.write(img[i]); }
os.close();
}
public void doUpload(HttpServletRequest request) throws IOException {
request.setCharacterEncoding("GB2312");
ServletInputStream in = request.getInputStream();
byte[] line = new byte[1280];
int i = in.readLine(line, 0, 1280);
if (i < 3)
return;
int boundaryLength = i - 2;
String boundary = new String(line, 0, boundaryLength); //-2丢弃换行字符
fields = new Hashtable();
while (i != -1) {
String newLine = new String(line, 0, i);
if (newLine.startsWith("Content-Disposition: form-data; name=\"")) {
if (newLine.indexOf("filename=\"") != -1) {
setFilename(new String(line, 0, i-2));
if (filename==null)
return;
//文件内容
i = in.readLine(line, 0, 1280);
setContentType(new String(line, 0, i-2));
i = in.readLine(line, 0, 1280);
//空行
i = in.readLine(line, 0, 1280);
newLine = new String(line, 0, i,"ISO8859_1");
FileOutputStream pw=new FileOutputStream((savePath==null? "" : savePath) + filename);
//PrintWriter pw = new PrintWriter(new BufferedWriter(new
//FileWriter((savePath==null? "" : savePath) + filename)));
while (i != -1 && !newLine.startsWith(boundary)) {
// 文件内容的最后一行包含换行字符
// 因此我们必须检查当前行是否是最
// 后一行
i = in.readLine(line, 0, 1280);
if ((i==boundaryLength+2 || i==boundaryLength+4)
&& (new String(line, 0, i).startsWith(boundary)))
pw.write(newLine.substring(0, newLine.length()-2).getBytes("ISO8859_1"));
else
pw.write(newLine.getBytes("ISO8859_1"));
newLine = new String(line, 0, i,"ISO8859_1");
}
pw.close();
}
else {
// 普通表单输入元素
// 获取输入元素名字
int pos = newLine.indexOf("name=\"");
String fieldName = newLine.substring(pos+6, newLine.length()-3);
i = in.readLine(line, 0, 1280);
i = in.readLine(line, 0, 1280);
newLine = new String(line, 0, i);
StringBuffer fieldValue = new StringBuffer(1280);
while (i != -1 && !newLine.startsWith(boundary)) {
// 最后一行包含换行字符
// 因此我们必须检查当前行是否是最后一行
i = in.readLine(line, 0, 1280);
if ((i==boundaryLength+2 || i==boundaryLength+4)
&& (new String(line, 0, i).startsWith(boundary)))
fieldValue.append(newLine.substring(0, newLine.length()-2));
else
fieldValue.append(newLine);
newLine = new String(line, 0, i);
}
fields.put(fieldName, fieldValue.toString());
}
}
i = in.readLine(line, 0, 1280);
}
}
}
HTML页面:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>文件上载</title>
</head>
<body>
<form action=jsp1.jsp enctype="MULTIPART/FORM-DATA" method=post>
作者: <input type=text name=author>
<br>
公司: <input type=text name=company>
<br>
说明: <textarea name=comment></textarea>
<br>
选择要上载的文件<input type=file name=filename>
<br>
文件描述: <input type=text name=description>
<br>
<input type=submit value="Upload">
</form>
</body>
</html>
JSP页面:
<%@ page contentType="text/html;charset=gb2312"%>
<jsp:useBean id="TheBean" scope="page" class="cr.web.FileUploadBean" />
<%
TheBean.setSavePath("d:\\");
//TheBean.doUpload1(request);
TheBean.doUpload(request);
out.println("Filename:" + TheBean.getFilename());
out.println("<BR>内容类型:" + TheBean.getContentType());
out.println("<BR>作者:" + TheBean.getFieldValue("Author"));
out.println("<BR>公司:" + TheBean.getFieldValue("Company"));
out.println("<BR>说明:" + TheBean.getFieldValue("Comment"));
%>
单值类型是与现存类型(它的“源”类型)共享其内部表示的用户定义数据类型,但对于大多数运算来说,认为单值类型是独立和不兼容的。例如,您可能想定义年龄类型、重量类型以及高度类型,所有这些类型都有相当不同的语义,但都使用内部数据类型 INTEGER 作为它们的内部表示。
下列示例说明了命名为 PAY 的单值类型的创建: CREATE DISTINCT TYPE PAY AS DECIMAL(9,2) WITH COMPARISONS
虽然 PAY 有与内部数据类型 DECIMAL(9,2)相同的表示,但还是认为它是与 DECIMAL(9,2)或任何其他类型不可比的独立类型。它只能与相同的单值类型比较。并且,会影响到按 DECIMAL 使用的运算符和函数将在此不适用。例如,具有 PAY 数据类型的值不能与具有 INTEGER 数据类型的值相乘。因此,您必须编写只应用于 PAY 数据类型的函数。
使用单值数据类型可限制偶然错误。例如,如果将 EMPLOYEE 表的 SALARY 列定义为 PAY 数据类型,则不能将该列添加至 COMM,即使它们的源类型相同。
单值数据类型支持类型转换。源类型可以转换为单值数据类型,单值数据类型也可以转换为源类型。例如,如果将表 EMPLOYEE 的 SALARY 列定义为 PAY 数据类型,则下列示例将不会在比较运算符处出错。 SELECT * FROM EMPLOYEE
WHERE DECIMAL(SALARY) = 41250
DECIMAL(SALARY)返回一个十进制数据类型。相反地,数字数据类型可以转换为 PAY 类型。例如,可以使用 PAY(41250) 来转换数字 41250 的类型。
如使用函数中所述,DB2 通用数据库提供内部函数和用户定义函数 (UDF)。然而,此函数集从不会满足所有需求。您常常需要为特别的任务创建定制函数。用户定义函数允许您创建定制函数。
用户定义函数有两种类型:源和外部函数。
源用户定义函数允许允许用户定义类型有选择地引用另一个已为数据库所知的内部函数或用户定义函数。您可以既使用标量函数又使用列函数。
在下一示例中,基于内部 MAX 列函数来创建称为 MAX 的用户定义函数,它采用 DECIMAL 数据类型作为输入。MAX UDF 采用 PAY 类型作为输入且返回一个 PAY 类型作为输出。 CREATE FUNCTION MAX(PAY) RETURNS PAY
SOURCE MAX(DECIMAL)
外部用户定义函数由用户用程序设计语言编写。有外部标量函数和外部表函数,在 SQL Reference 中讨论了这两个函数。
假定您已编写了一个计算字符串中字数的函数,则您可以使用 CREATE FUNCTION 语句以名称 WORDCOUNT 向数据库注册该函数。然后就可在 SQL 语句中使用此函数。
例如,下列语句返回雇员编号和他们简历的 ASCII 格式的字数。WORDCOUNT 是用户已向数据库注册并且现正在语句中使用的外部标量函数。
SELECT EMPNO, WORDCOUNT(RESUME)
FROM EMP_RESUME
WHERE RESUME_FORMAT = 'ascii'
有关编写用户定义函数的更详细信息,参考 Application Development Guide。
术语大对象及其缩写词 LOB 用于表示三种数据类型:BLOB、CLOB 或 DBCLOB。这些类型可以包含诸如音频、图片以及文档等对象的大量数据。
二进制大对象(BLOB)是变长字符串,以字节进行量度,最大长度可达 2 吉字节。BLOB 主要用来保存非传统数据,如图片、声音以及混合媒体等。
字符大对象(CLOB)是变长字符串,以字节进行量度,最大长度可达 2 吉字节。 CLOB 用于存储大的单字节字符集数据,如文档等。CLOB 被认为是字符串。
双字节字符大对象(DBCLOB)是最大长度可达 2 吉字节的双字节字符变长字符串(1 073 741 823 双字节字符)。 DBCLOB 用于存储大的双字节字符集数据,如文档等。DBCLOB 被认为是图形字符串。
由于 LOB 值可以很大,所以将它们从数据库服务器传送至客户机应用程序可能要花费一些时间。然而,一般一次处理 LOB 值的一部分,而不是将它们作为一个整体处理。对于应用程序不需要(或不想要)将整个 LOB 值存储在应用程序内存中的那些情况,应用程序可以通过大对象定位器变量引用此值。
然后后续语句可以使用定位器对数据执行操作,而不必检索整个大对象。定位器变量用来减少应用程序的存储器需求,并通过减少客户机与服务器之间的数据流而改进性能。
另一个机制是文件引用变量。它们用来直接对文件检索大对象或直接从文件来更新表中的大对象。文件引用变量用来减少应用程序的存储器需求,因为这些变量不必存储大对象数据。有关更多信息,参考 Application Development Guide 和 SQL Reference。
专用寄存器是由数据库管理程序为连接定义的存储区,用于存储可以用 SQL 语句引用的信息。下列是几个较常用的专用寄存器的示例。有关所有专用寄存器的列表和更详细的信息,参考 SQL Reference。
- CURRENT DATE:保存对应于执行 SQL 语句时的日时钟的日期。
- CURRENT FUNCTION PATH:保存一个指定用于分解函数和数据类型引用的函数路径的值。
- CURRENT SERVER:指定当前应用程序服务器。
- CURRENT TIME:保存对应于执行 SQL 语句时的日时钟的时间。
- CURRENT TIMESTAMP:指定对应于执行 SQL 语句时的日时钟的时间戳记。
- CURRENT TIMEZONE:指定世界时与应用程序服务器本地时间之间的差别。
- USER:指定运行期权限 ID。
您可以用 VALUES 语句显示专用寄存器的内容。例如: VALUES (CURRENT TIMESTAMP)
也可以使用: SELECT CURRENT TIMESTAMP FROM ORG
这将为表中的每一行项返回 TIMESTAMP。
DB2 为每个数据库创建并维护一个系统目录表扩充集。这些表包含关于数据库对象(如表、视图、程序包、参考完整性关系、函数、单值类型以及触发器等)的逻辑结构和物理结构的信息。这些表是在创建数据库时创建的,并在正常操作过程中得到更新。不能显式创建或卸下它们,但可以查询和查看其内容。
有关更多信息,参考 SQL Reference。
目录视图象任何其他数据库视图一样。可以使用 SQL 语句来查看数据,确切地说,就是使用查看系统中任何其他视图的相同方式。
可以在 SYSCAT.TABLES 目录中找到关于表的非常有用的信息。要寻找已创建的现存表的名称,发出一个类似以下的语句:
SELECT TABNAME, TYPE, CREATE_TIME
FROM SYSCAT.TABLES
WHERE DEFINER = USER
此语句产生下列结果: TABNAME TYPE CREATE_TIME
------------------ ---- --------------------------
ORG T 1997-05-22-11.15.27.850000
STAFF T 1997-05-22-11.15.29.470000
DEPARTMENT T 1997-05-22-11.15.30.850000
EMPLOYEE T 1997-05-22-11.15.31.310000
EMP_ACT T 1997-05-22-11.15.32.850000
PROJECT T 1997-05-22-11.15.34.410007
EMP_PHOTO T 1997-05-22-11.15.35.190000
EMP_RESUME T 1997-05-22-11.15.40.600000
SALES T 1997-05-22-11.15.43.000000
下列列表包括与本书讨论的与主题有关的目录视图。还有很多其他目录视图,它们详细地在 SQL Reference 和管理指南中列出。
说明 |
目录视图 |
检查约束 |
SYSCAT.CHECKS |
列 |
SYSCAT.COLUMNS |
检查约束引用的列 |
SYSCAT.COLCHECKS |
关键字中使用的列 |
SYSCAT.KEYCOLUSE |
数据类型 |
SYSCAT.DATATYPES |
函数参数或函数结果 |
SYSCAT.FUNCPARMS |
参考约束 |
SYSCAT.REFERENCES |
模式 |
SYSCAT.SCHEMATA |
表约束 |
SYSCAT.TABCONST |
表 |
SYSCAT.TABLES |
触发器 |
SYSCAT.TRIGGERS |
用户定义函数 |
SYSCAT.FUNCTIONS |
视图 |
SYSCAT.VIEWS |
在商界,我们的确通常需要确保始终实施某些规则。例如,参与项目的雇员必须被雇用。或者想要某些事件有计划地发生。例如,如果销售员售出一批商品,则应增加其佣金。
DB2 通用数据库为此提供了一套有用的方法。 唯一约束是禁止在表的一列或多列中出现重复值的规则。 参考完整性约束确保在整个指定的表中数据一致性。 表检查约束是一些条件,它们定义为表定义的一部分,限制一列或多列中使用的值。触发器允许您定义一组操作,这些操作通过对指定的表进行删除、插入或更新操作来执行或触发。触发器可用于写入其他表、修改输入值以及发布警报信息。
第一节提供关键字的概念性概述。接着,通过示例和图表进一步探讨参考完整性、约束以及触发器。
关键字是可用来标识或存取特定行的一组列。
由不止一列组成的关键字称为组合关键字。在具有组合关键字的表中,组合关键字中各列的排序不受这些列在表中排序的约束。
唯一关键字被定义为它的任何值都不相同。唯一关键字的列不能包含空值。在执行 INSERT 和 UPDATE 语句期间,数据库管理程序强制执行该约束。一个表可以有多个唯一关键字。唯一关键字是可选的,并且可在 CREATE TABLE 或 ALTER TABLE 语句中定义。
主关键字是一种唯一关键字,表定义的一部分。一个表不能有多个主关键字,并且主关键字的列不能包含空值。主关键字是可选的,并且可在 CREATE TABLE 或 ALTER TABLE 语句中定义。
外部关键字在参考约束的定义中指定。一个表可以有零个或多个外部关键字。如果组合外部关键字的值的任何部分为空,则该值为空。外部关键字是可选的,并且可在 CREATE TABLE 语句或 ALTER TABLE 语句中定义。
唯一约束确保关键字的值在表中是唯一的。唯一约束是可选的,并且可以通过使用指定 PRIMARY KEY 或 UNIQUE 子句的 CREATE TABLE 或 ALTER TABLE 语句来定义唯一约束。例如,可在一个表的雇员编号列上定义一个唯一约束,以确保每个雇员有唯一的编号。
通过定义唯一约束和外部关键字,可以定义表与表之间的关系,从而实施某些商业规则。唯一关键和外部关键字约束的组合通常称为参考完整性约束。外部关键字所引用的唯一约束称为父关键字。外部关键字表示特定的父关键字,或与特定的父关键字相关。例如,某规则可能规定每个雇员(EMPLOYEE 表)必须属于某现存的部门(DEPARTMENT 表)。因此,将 EMPLOYEE 表中的“部门号”定义为外部关键字,而将 DEPARTMENT 表中的“部门号”定义为主关键字。下列图表提供参考完整性约束的直观说明。
图 4. 外部约束和主约束定义关系并保护数据
表检查约束指定对于表的每行都要进行判定的条件。可对个别列指定检查约束。可使用 CREATE 或 ALTER TABLE 语句添加检查约束。
下列语句创建具有下列约束的表:
- 部门编号的值必须在范围 10 至 100 内
- 雇员的职务只能为下列之一: "Sales"、"Mgr"或"Clerk"
- 1986 年之前雇用的每个雇员的工资必须超过 $40,500。
CREATE TABLE EMP
(ID SMALLINT NOT NULL,
NAME VARCHAR(9),
DEPT SMALLINT CHECK (DEPT BETWEEN 10 AND 100),
JOB CHAR(5) CHECK (JOB IN ('Sales', 'Mgr', 'Clerk')),
HIREDATE DATE,
SALARY DECIMAL(7,2),
COMM DECIMAL(7,2),
PRIMARY KEY (ID),
CONSTRAINT YEARSAL CHECK (YEAR(HIREDATE) >= 1986 OR SALARY > 40500) )
仅当条件判定为假时才会违反约束。例如,如果插入行的 DEPT 为空值,则插入继续进行而不出错,尽管 DEPT 的值应该象约束中定义的那样在 10 和 100 之间。
下列语句将一个约束添加至名为 COMP 的 EMPLOYEE 表中,该约束为雇员的总报酬必须超过 $15,000:
ALTER TABLE EMP
ADD CONSTRAINT COMP CHECK (SALARY + COMM > 15000)
将检查表中现存的行以确保这些行不违反新约束。可通过使用如下的 SET CONSTRAINTS 语句将此检查延期: SET CONSTRAINTS FOR EMP OFF
ALTER TABLE EMP ADD CONSTRAINT COMP CHECK (SALARY + COMM > 15000)
SET CONSTRAINTS FOR EMP IMMEDIATE CHECKED
首先使用 SET CONSTRAINTS 语句以延期对表的约束检查。然后可将一个或多个约束添加至表而不检查这些约束。接着再次发出 SET CONSTRAINTS 语句,反过来将约束检查打开并执行任何延期的约束检查。
一个触发器定义一组操作,这组操作通过修改指定基表中数据的操作来激活。
可使用触发器来执行对输入数据的验证;自动生成新插入行的值;为了交叉引用而读取其他表;为了审查跟踪而写入其他表;或通过电子邮件信息支持警报。使用触发器将导致应用程序开发及商业规则的全面实施更快速并且应用程序和数据的维护更容易。
DB2 通用数据库支持几种类型的触发器。可定义触发器在 DELETE、INSERT 或 UPDATE 操作之前或之后激活。每个触发器包括一组称为触发操作的 SQL 语句,这组语句可包括一个可选的搜索条件。
可进一步定义后触发器以对每一行都执行触发操作,或对语句执行一次触发操作,而前触发器总是对每一行都执行触发操作。
在 INSERT、UPDATE 或 DELETE 语句之前使用触发器,以便在执行触发操作之前检查某些条件,或在将输入值存储在表中之前更改输入值。使用后触发器,以便在必要时传播值或执行其他任务,如发送信息等,这些任务可能是触发器操作所要求的。
下列示例说明了前触发器和后触发器的使用。考虑一个记录并跟踪股票价格波动的应用程序。该数据库包含两个表,CURRENTQUOTE 和 QUOTEHISTORY,定义如下:
CREATE TABLE CURRENTQUOTE
(SYMBOL VARCHAR(10),
QUOTE DECIMAL(5,2),
STATUS VARCHAR(9))
CREATE TABLE QUOTEHISTORY
(SYMBOL VARCHAR(10),
QUOTE DECIMAL(5,2),
TIMESTAMP TIMESTAMP)
当使用如下语句更新 CURRENTQUOTE 的 QUOTE 列时:
UPDATE CURRENTQUOTE
SET QUOTE = 68.5
WHERE SYMBOL = 'IBM'
应更新 CURRENTQUOTE 的 STATUS 列以反映股票是否:
- 在升值
- 处于本年度的新高
- 在下跌
- 处于本年度的新低
- 价位稳定
这通过使用下列前触发器来实现:
(1) CREATE TRIGGER STOCK_STATUS
NO CASCADE BEFORE UPDATE OF QUOTE ON CURRENTQUOTE
REFERENCING NEW AS NEWQUOTE OLD AS OLDQUOTE
FOR EACH ROW MODE DB2SQL
(2) SET NEWQUOTE.STATUS =
(3) CASE
(4) WHEN NEWQUOTE.QUOTE >=
(SELECT MAX(QUOTE)
FROM QUOTEHISTORY
WHERE SYMBOL = NEWQUOTE.SYMBOL
AND YEAR(TIMESTAMP) = YEAR(CURRENT DATE) )
THEN 'High'
(5) WHEN NEWQUOTE.QUOTE <=
(SELECT MIN(QUOTE)
FROM QUOTEHISTORY
WHERE SYMBOL = NEWQUOTE.SYMBOL
AND YEAR(TIMESTAMP) = YEAR(CURRENT DATE) )
THEN 'Low'
(6) WHEN NEWQUOTE.QUOTE > OLDQUOTE.QUOTE
THEN 'Rising'
WHEN NEWQUOTE.QUOTE < OLDQUOTE.QUOTE
THEN 'Dropping'
WHEN NEWQUOTE.QUOTE = OLDQUOTE.QUOTE
THEN 'Steady'
END
- (1)
- 此代码块将名为 STOCK_STATUS 的触发器定义为一个应该在更新 CURRENTQUOTE 表的 QUOTE 列之前激活的触发器。第二行指定,在将 CURRENTQUOTE 表的实际更新所引起的任何更改应用于数据库之前,要应用触发操作。第二行也意味着触发操作将不会激活任何其他触发器。第三行指定一些名称,必须将这些名称作为列名的限定符用于新值 (NEWQUOTE) 和旧值 (OLDQUOTE)。用这些相关名(NEWQUOTE 和 OLDQUOTE)限定的列名称为转换变量。第四行表示应对每一行都执行触发操作。
- (2)
- 这标记此触发器的触发操作中第一个也是唯一的一个 SQL 语句的开始。 SET 转换变量语句在一个触发器中用来将值赋给表的行中的列,该表正在由激活该触发器的语句进行更新。此语句正将一个值赋给 CURRENTQUOTE 表的 STATUS 列。
- (3)
- 该赋值语句右边使用的表达式为 CASE 表达式。 CASE 表达式扩充为 END 关键字。
- (4)
- 第一种情况检查新报价 (NEWQUOTE.QUOTE) 是否超过当前日历年度中股票符号的最高价。子查询正在使用由跟在后面的后触发器更新的 QUOTEHISTORY 表。
- (5)
- 第二种情况检查新报价 (NEWQUOTE.QUOTE) 是否小于当前日历年度中股票符号的最低价。子查询正在使用由跟在后面的后触发器更新的 QUOTEHISTORY 表。
- (6)
- 最后三种情况将新报价 (NEWQUOTE.QUOTE) 与表 (OLDQUOTE.QUOTE) 中的报价比较,以确定新报价是大于、小于还是等于旧报价。 SET 转换变量语句在此处结束。
除了更新 CURRENTQUOTE 表中的项之外,还需要通过将新报价连同时间戳记一起复制到 QUOTEHISTORY 表中来创建一个审查记录。这通过使用下列后触发器来实现:
(1) CREATE TRIGGER RECORD_HISTORY
AFTER UPDATE OF QUOTE ON CURRENTQUOTE
REFERENCING NEW AS NEWQUOTE
FOR EACH ROW MODE DB2SQL
BEGIN ATOMIC
(2) INSERT INTO QUOTEHISTORY
VALUES (NEWQUOTE.SYMBOL, NEWQUOTE.QUOTE, CURRENT TIMESTAMP);
END
- (1)
- 此代码块将命名为 RECORD_HISTORY 的触发器定义为应该在更新 CURRENTQUOTE 表的 QUOTE 列之后激活的触发器。第三行指定应该作为列名的限定符用于新值 (NEWQUOTE) 的名称。第四行表示应对每一行都执行触发操作。
- (2)
- 此触发器的触发操作包括单个 SQL 语句,该语句使用已更新的行中的数据(NEWQUOTE.SYMBOL 和 NEWQUOTE.QUOTE)和当前的时间戳记将该行插入 QUOTEHISTORY 表。
CURRENT TIMESTAMP 是包含时间戳记的专用寄存器。专用寄存器中提供了列表和解释。
从两个或更多个表中组合数据的过程称为连接表。数据库管理程序从指定的表中形成行的所有组合。对于每个组合,它都测试连接条件。连接条件是带有一些约束的搜索条件。有关约束的列表,参考 SQL Reference。
注意:连接条件涉及的列的数据类型不必相同;然而,这些数据类型必须相容。计算连接条件的方式与计算其他搜索条件的方式相同,并且使用相同的比较规则。
如果未指定连接条件,则返回在 FROM 子句中列出的表中行的所有组合,即使这些行可能完全不相关。该结果称为这两个表的交叉积。
本节中的示例基于下面两个表。这两个表只是样本数据库中表的简化形式,在样本数据库中并不存在。这两个表一般用来概述关于连接的重点。 SAMP_STAFF 列出未作为合同工雇用的雇员的姓名以及这些雇员的职务说明,而 SAMP_PROJECT 则列出雇员(合同工和全职人员)的姓名以及这些雇员所参与的项目。
这些表如下:
图 5. SAMP_PROJECT 表
图 6. SAMP_STAFF 表
下列示例产生两个表的交叉积。因未指定连接条件,所以给出了行的所有组合: SELECT SAMP_PROJECT.NAME,
SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT, SAMP_STAFF
此语句产生下列结果: NAME PROJ NAME JOB
---------- ------ ---------- --------
Haas AD3100 Haas PRES
Thompson PL2100 Haas PRES
Walker MA2112 Haas PRES
Lutz MA2111 Haas PRES
Haas AD3100 Thompson MANAGER
Thompson PL2100 Thompson MANAGER
Walker MA2112 Thompson MANAGER
Lutz MA2111 Thompson MANAGER
Haas AD3100 Lucchessi SALESREP
Thompson PL2100 Lucchessi SALESREP
Walker MA2112 Lucchessi SALESREP
Lutz MA2111 Lucchessi SALESREP
Haas AD3100 Nicholls ANALYST
Thompson PL2100 Nicholls ANALYST
Walker MA2112 Nicholls ANALYST
Lutz MA2111 Nicholls ANALYST
两个主要的连接类型是内连接和外连接。到目前为止,所有示例中使用的都是内连接。内连接只保留交叉积中满足连接条件的那些行。如果某行在一个表中存在,但在另一个表中不存在,则结果表中不包括该信息。
下列示例产生两个表的内连接。该内连接列出分配给某个项目的全职雇员信息: SELECT SAMP_PROJECT.NAME,
SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT, SAMP_STAFF
WHERE SAMP_STAFF.NAME = SAMP_PROJECT.NAME
或者,也可以指定如下内连接: SELECT SAMP_PROJECT.NAME,
SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT INNER JOIN SAMP_STAFF
ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
结果是: NAME PROJ NAME JOB
---------- ------ ---------- --------
Haas AD3100 Haas PRES
Thompson PL2100 Thompson MANAGER
注意:该内连接的结果由右表和左表中姓名列的值匹配的行组成- 'Haas' 和 'Thompson' 都包括在列出所有全职雇员的 SAMP_STAFF 表中以及列出分配给某个项目的专职和合同雇员的 SAMP_PROJECT 表中。
外连接是内连接和左表和/或右表中未包括内连接中的那些行的并置。当对两个表执行外连接时,可任意将一个表指定为左表而将另一个表指定为右表。外连接有三种类型:
- 左外连接包括内连接和左表中未包括在内连接中的那些行。
- 右外连接包括内连接和右表中未包括在内连接中的那些行。
- 全外连接包括内连接以及左表和右表中未包括在内连接中的行。
使用 SELECT 语句来指定要显示的列。在 FROM 子句中,列出后跟关键字 LEFT OUTER JOIN、RIGHT OUTER JOIN 或 FULL OUTER JOIN 的第一个表的名称。接着需要指定后跟 ON 关键字的第二个表。在 ON 关键字后面,指定表示要连接的表之间关系的连接条件。
在下列示例中,将 SAMP_STAFF 指定为右表,而 SAMP_PROJECT 则被指定为左表。通过使用 LEFT OUTER JOIN,列出所有全职和合同雇员(在 SAMP_PROJECT 中列出)的姓名和项目编号,如果是全职雇员(在 SAMP_STAFF 中列出),还列出这些雇员的职位: SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ,
SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT LEFT OUTER JOIN SAMP_STAFF
ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
此语句产生下列结果: NAME PROJ NAME JOB
---------- -------------------- ---------- --------------------
Haas AD3100 Haas PRES
Lutz MA2111 - -
Thompson PL2100 Thompson MANAGER
Walker MA2112 - -
所有列中都具有值的那些行是该内连接的结果。这些都是满足连接条件的行: 'Haas' 和 'Thompson' 既在 SAMP_PROJECT(左表)中列出又在 SAMP_STAFF(右表)中列出。对于不满足连接条件的行,右表的列上出现空值: 'Lutz' 和 'Walker' 都是在 SAMP_PROJECT 表中列出的合同雇员,因而未在 SAMP_STAFF 表中列出。注意:左表中的所有行都包括在结果集中。
在下一个示例中,将 SAMP_STAFF 指定为右表而 SAMP_PROJECT 则被指定为左表。通过使用 RIGHT OUTER JOIN 列出所有专职雇员(在 SAMP_STAFF 中列出)的姓名和工作职位,如果将这些雇员分配给了某个项目(在 SAMP_PROJECT 中列出),还列出他们的项目编号: SELECT SAMP_PROJECT.NAME,
SAMP_PROJECT.PROJ, SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT RIGHT OUTER JOIN SAMP_STAFF
ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
结果为: NAME PROJ NAME JOB
---------- -------------------- ---------- --------------------
Haas AD3100 Haas PRES
- - Lucchessi SALESREP
- - Nicholls ANALYST
Thompson PL2100 Thompson MANAGER
象在左外连接中一样,所有列中都具有值的那些行是内连接的结果。这些都是满足连接条件的行: 'Haas'和'Thompson'既在 SAMP_PROJECT(左表)中列出又在 SAMP_STAFF(右表)中列出。对于不满足连接条件的行,右表的列上出现空值: 'Lucchessi'和'Nicholls'都是未分配项目的专职雇员。虽然他们在 SAMP_STAFF 中列出,但未在 SAMP_PROJECT 中列出。注意:右表中的所有行都包括在结果集中。
下一个示例对 SAMP_PROJECT 表和 SAMP_STAFF 表使用 FULL OUTER JOIN。该示例列出所有专职雇员(包括未分配项目的雇员)和合同雇员的姓名: SELECT SAMP_PROJECT.NAME, SAMP_PROJECT.PROJ,
SAMP_STAFF.NAME, SAMP_STAFF.JOB
FROM SAMP_PROJECT FULL OUTER JOIN SAMP_STAFF
ON SAMP_STAFF.NAME = SAMP_PROJECT.NAME
结果为: NAME PROJ NAME JOB
---------- -------------------- ---------- --------------------
Haas AD3100 Haas PRES
- - Lucchessi SALESREP
- - Nicholls ANALYST
Thompson PL2100 Thompson MANAGER
Lutz MA2111 - -
Walker MA2112 - -
此结果包括左外连接、右外连接以及内连接。列出所有专职雇员和合同雇员。正如左外连接和右外连接一样,对于不满足连接条件的值,相应列中出现空值。 SAMP_STAFF 和 SAMP_PROJECT 中的每一行都包括在结果集中。
DB2 通用数据库允许您通过使用 ROLLUP 和 CUBE 分组、合并及查看单个结果集中的多列。这种新型而强大的功能增强并简化了基于数据分析的 SQL。
有很多方法可从数据库中抽取有用信息。可执行递归查询从现存数据集中产生结果表。
在查询的 GROUP BY 子句中指定 ROLLUP 和 CUBE 运算。 ROLLUP 分组产生包含常规分组行和小计行的结果集。CUBE 分组产生包含来自 ROLLUP 和交叉制表行中的行的结果集。所以对于 ROLLUP,可获取每人每月的销售量以及每月销售总量和总部总量。对于 CUBE,将包括每人销售总量的附加行。参见 SQL Reference 以了解更详细的情况。
递归查询是迭代使用结果数据来确定进一步结果的查询。可以把这想象成在一棵树上或一幅图中来回移动。使用递归查询的常见示例包括材料单应用程序、订票系统、网络计划和调度。递归查询是使用包括引用自己名称的的公共表表达式来编写的。参见 SQL Reference 以获取递归查询的示例。
UNION、EXCEPT 以及 INTERSECT 集合运算符使您能够将两个或更多外层查询组合成单个查询。执行用这些集合运算符连接的每个查询并组合各个查询的结果。根据运算符不同,产生不同的结果。
UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表。当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行。两种情况下,派生表的每一行不是来自 TABLE1 就是来自 TABLE2。
在下列 UNION 运算符的示例中,查询返回薪水高于 $21,000、有管理责任且工龄少于 8 年的人员的姓名:
(1) SELECT ID, NAME FROM STAFF WHERE SALARY > 21000
UNION
(2) SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8
ORDER BY ID
各个查询的结果如下:
(1)
ID NAME
------ ---------
140 Fraye
160 Molinare
260 Jones
(2) ID NAME
------ ---------
10 Sanders
30 Marenghi
100 Plotz
140 Fraye
160 Molinare
240 Daniels
数据库管理程序组合这两个查询的结果,消除重复行,并按升序返回最终结果。
ID NAME
------ ---------
10 Sanders
30 Marenghi
100 Plotz
140 Fraye
160 Molinare
240 Daniels
260 Jones
如果在带有任何集合运算符的查询中使用 ORDER BY 子句,则必须在最后一个查询之后写该子句。系统对组合的回答集进行排序。如果两个表中的列名不同,则组合的结果表没有相应列的名称。替代地,将这些列按其出现的顺序编号。因此,如果想要对结果表进行排序,则必须在 ORDER BY 子句中指定列号。
EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所有重复行而派生出一个结果表。当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。
在下列 EXCEPT 运算符的示例中,查询返回收入超过 $21,000 但没有经理职位且工龄为 8 年或更长的所有人员的姓名。 SELECT ID, NAME FROM STAFF WHERE SALARY > 21000
EXCEPT
SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8
各个查询的结果在关于 UNION 的一节中列出。上面的语句产生下列结果: ID NAME
------ ---------
260 Jones
INTERSECT 运算符通过只包括 TABLE1 和 TABLE2 中都有的行并消除所有重复行而派生出一个结果表。当 ALL 随 INTERSECT 一起使用时 (INTERSECT ALL),不消除重复行。
在下列 INTERSECT 运算符的示例中,查询返回收入超过 $21,000 有管理责任且工龄少于8年的雇员的姓名和 ID。 SELECT ID, NAME FROM STAFF WHERE SALARY > 21000
INTERSECT
SELECT ID, NAME FROM STAFF WHERE JOB='Mgr' AND YEARS < 8
各个查询的结果在关于 UNION 的一节中列出。这两个使用 INTERSECT 的查询的结果为: ID NAME
------ ---------
140 Fraye
160 Molinare
当使用 UNION、EXCEPT 以及 INTERSECT 运算符时,记住下列事项:
- 运算符的查询选择列表中的所有对应项必须是相容的。有关更多信息,参见 SQL Reference 中的数据类型相容性表。
- ORDER BY 子句(如果使用该子句的话)必须放在最后一个带有集合运算符的查询后面。对于每个运算符来说,如果列的名称与查询的选择列表中对应项的名称相同,则该列名只能在 ORDER BY 子句中使用。
- 在具有相同数据类型和相同长度的列之间进行的运算会产生一个具有该类型和长度的列。针对 UNION、EXCEPT 以及 INTERSECT 集合运算符的结果,参见 SQL Reference 中结果数据类型的规则。
谓词允许您构造条件,以便只处理满足这些条件的那些行。基本谓词在选择行讨论。本节讨论 IN、BETWEEN、LIKE、EXISTS 以及定量谓词。
使用 IN 谓词将一个值与其他几个值进行比较。例如: SELECT NAME
FROM STAFF
WHERE DEPT IN (20, 15)
此示例相当于: SELECT NAME
FROM STAFF
WHERE DEPT = 20 OR DEPT = 15
当子查询返回一组值时,可使用 IN 和 NOT IN 运算符。例如,下列查询列出负责项目 MA2100 和 OP2012 的雇员的姓: SELECT LASTNAME
FROM EMPLOYEE
WHERE EMPNO IN
(SELECT RESPEMP
FROM PROJECT
WHERE PROJNO = 'MA2100'
OR PROJNO = 'OP2012')
计算一次子查询,并将结果列表直接代入外层查询。例如,上面的子查询选择雇员编号 10 和 330,对外层查询进行计算,就好象 WHERE 子句如下: WHERE EMPNO IN (10, 330)
子查询返回的值列表可包含零个、一个或多个值。
使用 BETWEEN 谓词将一个值与某个范围内的值进行比较。范围两边的值是包括在内的,并考虑 BETWEEN 谓词中用于比较的两个表达式。
下一示例寻找收入在 $10,000 和 $20,000 之间的雇员的姓名: SELECT LASTNAME
FROM EMPLOYEE
WHERE SALARY BETWEEN 10000 AND 20000
这相当于: SELECT LASTNAME
FROM EMPLOYEE
WHERE SALARY >= 10000 AND SALARY <= 20000
下一个示例寻找收入少于 $10,000 或超过 $20,000 的雇员的姓名: SELECT LASTNAME
FROM EMPLOYEE
WHERE SALARY NOT BETWEEN 10000 AND 20000
使用 LIKE 谓词搜索具有某些模式的字符串。通过百分号和下划线指定模式。
- 下划线字符(_)表示任何单个字符。
- 百分号(%)表示零或多个字符的字符串。
- 任何其他表示本身的字符。
下列示例选择以字母'S'开头长度为 7 个字母的雇员名: SELECT NAME
FROM STAFF
WHERE NAME LIKE 'S_ _ _ _ _ _'
下一个示例选择不以字母'S'开头的雇员名: SELECT NAME
FROM STAFF
WHERE NAME NOT LIKE 'S%'
可使用子查询来测试满足某个条件的行的存在性。在此情况下,谓词 EXISTS 或 NOT EXISTS 将子查询链接到外层查询。
当用 EXISTS 谓词将子查询链接到外层查询时,该子查询不返回值。相反,如果子查询的回答集包含一个或更多个行,则 EXISTS 谓词为真;如果回答集不包含任何行,则 EXISTS 谓词为假。
通常将 EXISTS 谓词与相关子查询一起使用。下面示例列出当前在项目(PROJECT) 表中没有项的部门: SELECT DEPTNO, DEPTNAME
FROM DEPARTMENT X
WHERE NOT EXISTS
(SELECT *
FROM PROJECT
WHERE DEPTNO = X.DEPTNO)
ORDER BY DEPTNO
可通过在外层查询的 WHERE 子句中使用 AND 和 OR 将 EXISTS 和 NOT EXISTS 谓词与其他谓词连接起来。
定量谓词将一个值和值的集合进行比较。如果全查询返回多个值,则必须通过附加后缀 ALL、ANY 或 SOME 来修改谓词中的比较运算符。这些后缀确定如何在外层谓词中处理返回的这组值。使用>比较运算符作为示例(下面的注释也适用于其他运算符):
- 表达式 > ALL (全查询)
- 如果该表达式大于由全查询返回的每个单值,则该谓词为真。如果全查询未返回值,则该谓词为真。如果指定的关系至少对一个值为假,则结果为假。注意:<>ALL 定量谓词相当于 NOT IN 谓词。
下列示例使用子查询和> ALL 比较来寻找收入超过所有经理的所有雇员的姓名和职业: SELECT LASTNAME, JOB
FROM EMPLOYEE
WHERE SALARY > ALL
(SELECT SALARY
FROM EMPLOYEE
WHERE JOB='MANAGER')
- 表达式 > ANY (全查询)
- 如果表达式至少大于由全查询返回的值之一,则该谓词为真。如果全查询未返回值,则该谓词为假。注意:=ANY 定量运算符相当于 IN 谓词。
- 表达式 > SOME(全查询)
- SOME 与 ANY 同义。
标量全查询是在括号中的全查询,该查询返回的一行只包含一个列值。标量全查询对从数据库中检索数据值供表达式使用是很有用的。
- 下列示例列出其薪水超过全部雇员平均薪水的雇员的姓名:
SELECT LASTNAME, FIRSTNME
FROM EMPLOYEE
WHERE SALARY > (SELECT AVG(SALARY)
FROM EMPLOYEE)
- 下列示例在两个不同的表中查寻雇员的平均薪水:
SELECT AVG(SALARY) AS "Average_Employee",
(SELECT AVG(SALARY) AS "Average_Staff" FROM STAFF)
FROM EMPLOYEE
有时可能需要将一些值从一种数据类型转换成另一种数据类型,例如,从数字值转换成字符串。要将一个值转换成另一个不同的类型,使用 CAST 说明。
转换说明的另一个可能用途是截断很长的字符串。在 EMP_RESUME 表中,RESUME 列是 CLOB(5K)。您可能只想显示包含应聘者个人信息的前 370 个字符。要从 EMP_RESUME 表中显示简历的 ASCII 格式的前 370 个字符,发出下列请求:
SELECT EMPNO, CAST(RESUME AS VARCHAR(370))
FROM EMP_RESUME
WHERE RESUME_FORMAT = 'ascii'
会发出一个警告,通知您超过 370 个字符的值被截断。
可将空值转换为更便于在查询中进行处理的其他数据类型。 公共表表达式是一个为此目的使用转换的示例。
可在 SQL 语句中使用 CASE 表达式以便于处理表的数据表示。这提供了一种功能强大的条件表达式能力,在概念上与某些程序设计语言中的 CASE 语句类似。
- 要从 ORG 表中的 DEPTNAME 列将部门编号更改为有意义的词,输入下列查询:
SELECT DEPTNAME,
CASE DEPTNUMB
WHEN 10 THEN 'Marketing'
WHEN 15 THEN 'Research'
WHEN 20 THEN 'Development'
WHEN 38 THEN 'Accounting'
ELSE 'Sales'
END AS FUNCTION
FROM ORG
结果为: DEPTNAME FUNCTION
-------------- -----------
Head Office Marketing
New England Research
Mid Atlantic Development
South Atlantic Accounting
Great Lakes Sales
Plains Sales
Pacific Sales
Mountain Sales
- 可使用 CASE 表达式来防止出现异常情况,如被零除等。在下列示例中,如果雇员没有奖金或佣金报酬,则语句条件通过避免除法运算来防止出错:
SELECT LASTNAME, WORKDEPT FROM EMPLOYEE
WHERE(CASE
WHEN BONUS+COMM=0 THEN NULL
ELSE SALARY/(BONUS+COMM)
END ) > 10
- 可在单个语句中使用 CASE 表达式,根据一个列中值的子集的总和与该列中所有值的总和的比来产生一个比率。使用 CASE 表达式的语句只需要传送数据一次。在没有 CASE 表达式的情况下,执行同样的计算至少需要传送两次。
下列示例使用 CASE 表达式计算部门 20 的薪水之和与全部薪水总额的比率:
SELECT CAST(CAST (SUM(CASE
WHEN DEPT = 20 THEN SALARY
ELSE 0
END) AS DECIMAL(7,2))/
SUM(SALARY) AS DECIMAL (3,2))
FROM STAFF
结果为 0.11。注意:CAST 函数确保结果的精度得到保持。
- 可使用 CASE 表达式来计算简单的函数,而不必调用函数本身,调用函数将需要额外开销。例如:
CASE
WHEN X<0 THEN -1
WHEN X=0 THEN 0
WHEN X>0 THEN 1
END
此表达式与 SYSFUN 模式中 SIGN 用户定义函数有相同的结果。
如果只需要单个查询的视图定义,可使用表表达式。
表表达式是临时的,只在 SQL 语句的使用期限内有效;表表达式不能共享,但它们比视图更灵活。任何授权的用户都可共享视图定义。
本节描述如何在查询中使用公共表表达式和嵌套表表达式。
嵌套表表达式是一个临时视图,其中的定义被嵌套(直接定义)在主查询的 FROM子 句中。
下列查询使用嵌套表表达式来寻找那些教育级别超过 16 的雇员的平均总收入、教育级别以及雇用年份:
SELECT EDLEVEL, HIREYEAR, DECIMAL(AVG(TOTAL_PAY), 7,2)
FROM (SELECT YEAR(HIREDATE) AS HIREYEAR, EDLEVEL,
SALARY+BONUS+COMM AS TOTAL_PAY
FROM EMPLOYEE
WHERE EDLEVEL > 16 ) AS PAY_LEVEL
GROUP BY EDLEVEL, HIREYEAR
ORDER BY EDLEVEL, HIREYEAR
结果如下: EDLEVEL HIREYEAR 3
------- ----------- ---------
17 1967 28850.00
17 1973 23547.00
17 1977 24430.00
17 1979 25896.50
18 1965 57970.00
18 1968 32827.00
18 1973 45350.00
18 1976 31294.00
19 1958 51120.00
20 1975 42110.00
此查询使用嵌套表表达式,首先从 HIREDATE 列中抽取雇用年份,以便可以在后续的 GROUP BY 子句中使用该雇用年份。您可能不想将此查询作为视图来创建,因为您打算用不同的 EDLEVEL 值来执行相似查询。
此示例中使用了标量内部函数 DECIMAL。 DECIMAL 返回数字或字符串的十进制表示。有关函数的更多详情,参考 SQL Reference。
公共表表达式是在全查询的开头使用 WITH 关键字定义的命名结果表。公共表表达式是您创建以在复杂查询之中使用的表表达式。在查询的开头使用 WITH 子句定义并命名公共表表达式。对公共表表达式的重复引用使用同一个结果集。相比之下,如果使用嵌套表表达式或视图,则每次都将重新生成结果集,其结果可能各不相同。
下列示例列出公司中教育级别大于 16、平均工资比那些同时雇用的且有同样教育级别的人低的所有人。在该查询后面会更详细地描述查询的各个部分。
(1) WITH
PAYLEVEL AS
(SELECT EMPNO, YEAR(HIREDATE) AS HIREYEAR, EDLEVEL,
SALARY+BONUS+COMM AS TOTAL_PAY
FROM EMPLOYEE
WHERE EDLEVEL > 16),
(2) PAYBYED (EDUC_LEVEL, YEAR_OF_HIRE, AVG_TOTAL_PAY) AS
(SELECT EDLEVEL, HIREYEAR, AVG(TOTAL_PAY)
FROM PAYLEVEL
GROUP BY EDLEVEL, HIREYEAR)
(3) SELECT EMPNO, EDLEVEL, YEAR_OF_HIRE, TOTAL_PAY, DECIMAL(AVG_TOTAL_PAY,7,2)
FROM PAYLEVEL, PAYBYED
WHERE EDLEVEL=EDUC_LEVEL
AND HIREYEAR = YEAR_OF_HIRE
AND TOTAL_PAY < AVG_TOTAL_PAY
- (1)
- 这是名为 PAYLEVEL 的公共表表达式。此结果表包括雇用某个人的年份、该雇员的总收入以及他(或她)的教育级别。只包括雇员的教育级别大于 16 的那些行。
- (2)
- 这是名为 PAYBYED(或 PAY by education)的公共表表达式。该表达式使用在前一个公共表表达式中创建的 PAYLEVEL 表来确定每个教育级别同一年雇用的雇员的教育级别、雇用年份以及平均收入。此表返回的列被赋予的名称与选择列表中所使用的列名不同(如 EDUC_LEVEL)。这会生成命名为 PAYBYED 的结果集,与嵌套表表达式示例中产生的结果相同。
- (3)
- 最后,我们获得能产生期望结果的实际查询。连接这两个表(PAYLEVEL,PAYBYED)以确定总收入比同年雇用的人的平均收入低的那些人。注意:PAYBYED 是以 PAYLEVEL 为基础。所以在完整语句中有效地存取了 PAYLEVEL 两次。两次都使用同一组行来计算查询。
最终结果如下: EMPNO EDLEVEL YEAR_OF_HIRE TOTAL_PAY 5
------ ------- ------------ ------------- ---------
000210 17 1979 20132.00 25896.50
相关名是用于识别一个对象的多种用途的标识符。可在查询的 FROM 子句中和 UPDATE 或 DELETE 语句的第一个子句中定义相关名。相关名可与表、视图或嵌套表表达式关联,但只限于定义相关名的上下文中。
例如,子句 FROM STAFF S、ORG O 分别指定 S 和 O 作为 STAFF 和 ORG 的相关名。 SELECT NAME, DEPTNAME
FROM STAFF S, ORG O
WHERE O.MANAGER = S.ID
一旦定义了相关名,则只可以使用相关名来限定对象。例如,上例中如果写成 ORG.MANAGER=STAFF.ID 的话,则该语句就会失效。
也可以使用相关名作为表示数据库对象的简称。只输入 S 比输入 STAFF 更容易。
通过使用相关名,可复制对象。这在需要将表中各项与自己本身作比较时很有用。在下列示例中,EMPLOYEE 表与它自己的另一个实例比较以寻找所有雇员的经理。该示例显示非设计员的雇员的姓名、这些雇员的经理的姓名以及部门编号。 SELECT E2.FIRSTNME, E2.LASTNAME,
E2.JOB, E1.FIRSTNME, E1.LASTNAME, E1.WORKDEPT
FROM EMPLOYEE E1, EMPLOYEE E2
WHERE E1.WORKDEPT = E2.WORKDEPT
AND E1.JOB = 'MANAGER'
AND E2.JOB <> 'MANAGER'
AND E2.JOB <> 'DESIGNER'
此语句产生下列结果: FIRSTNME LASTNAME JOB FIRSTNME LASTNAME WORKDEPT
------------ --------------- -------- ------------ --------------- --------
DOLORES QUINTANA ANALYST SALLY KWAN C01
HEATHER NICHOLLS ANALYST SALLY KWAN C01
JAMES JEFFERSON CLERK EVA PULASKI D21
MARIA PEREZ CLERK EVA PULASKI D21
SYBIL JOHNSON CLERK EVA PULASKI D21
DANIEL SMITH CLERK EVA PULASKI D21
SALVATORE MARINO CLERK EVA PULASKI D21
ETHEL SCHNEIDER OPERATOR EILEEN HENDERSON E11
MAUDE SETRIGHT OPERATOR EILEEN HENDERSON E11
PHILIP SMITH OPERATOR EILEEN HENDERSON E11
JOHN PARKER OPERATOR EILEEN HENDERSON E11
RAMLAL MEHTA FIELDREP THEODORE SPENSER E21
JASON GOUNOT FIELDREP THEODORE SPENSER E21
WING LEE FIELDREP THEODORE SPENSER E21
允许引用先前提到的任何表的子查询称为相关子查询。我们也说该子查询具有对主查询中表的相关引用。
下列示例是一个不相关子查询,该子查询列出部门 'A00' 中薪水超过该部门平均薪水的雇员的雇员编号和姓名:
SELECT EMPNO, LASTNAME
FROM EMPLOYEE
WHERE WORKDEPT = 'A00'
AND SALARY > (SELECT AVG(SALARY)
FROM EMPLOYEE
WHERE WORKDEPT = 'A00')
如果想要知道每个部门的平均薪水,则需要对每个部门计算一次子查询。对在外层查询中标识的表的每一行,各使用一次 SQL 的相关功能(该能力允许您编写重复执行的子查询),就可做到这一点。此类型的相关子查询用来计算外层表的每一行的某个特性,该特性是在子查询中计算谓词所需要的。
此示例显示薪水高于部门平均薪水的所有雇员:
SELECT E1.EMPNO, E1.LASTNAME, E1.WORKDEPT
FROM EMPLOYEE E1
WHERE SALARY > (SELECT AVG(SALARY)
FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT)
ORDER BY E1.WORKDEPT
在此查询中,对每个部门计算一次子查询。结果为: EMPNO LASTNAME WORKDEPT
------ --------------- --------
000010 HAAS A00
000110 LUCCHESSI A00
000030 KWAN C01
000060 STERN D11
000220 LUTZ D11
000200 BROWN D11
000170 YOSHIMURA D11
000150 ADAMSON D11
000070 PULASKI D21
000270 PEREZ D21
000240 MARINO D21
000090 HENDERSON E11
000280 SCHNEIDER E11
000100 SPENSER E21
000340 GOUNOT E21
000330 LEE E21
要编写带有相关子查询的查询,使用与带有子查询的普通外部查询相同的基本格式。然而,在外部查询的 FROM 子句中,只是在表名后面放置一个相关名。于是子查询可能包含由该相关名限定的列引用。例如,如果 E1 是相关名,则 E1.WORKDEPT 表示外部查询中表的当前行的工作部门值。在外部查询中对表的每一行(概念上)重新计算子查询。
通过使用相关子查询,可以使系统为您工作并减少需要在应用程序中编写的代码量。
DB2 中允许非限定相关引用。例如,表 EMPLOYEE 有一个命名为 LASTNAME 的列,表 SALES 有一个命名为 SALES_PERSON 的列,但没有命名为 LASTNAME 的列。
SELECT LASTNAME, FIRSTNME, COMM
FROM EMPLOYEE
WHERE 3 > (SELECT AVG(SALES)
FROM SALES
WHERE LASTNAME = SALES_PERSON)
在此示例中,系统检查最内层的 FROM 子句,以获取 LASTNAME 列。如果未找到 LASTNAME 列,则系统检查次最内层的 FROM 子句(此情况下为外部 FROM 子句)。虽然不总是必要的,还是建议限定相关引用以改进查询的可读性并确保获取想要的结果。
想何时使用相关子查询?列函数的使用有时是一条线索。
假定您想要列出教育级别高于部门平均值的雇员。
首先,您必须确定选择列表项。问题为 "List the employees"。这隐含着来自 EMPLOYEE 表中的 EMPNO 应该足以唯一标识雇员。该问题也将 EDLEVEL 和雇员的部门 WORKDEPT 说明为条件。当问题未明确要求显示列时,在选择列表中包括这些列将会有助于说明解法。现在可构造查询的一部分: SELECT LASTNAME, WORKDEPT, EDLEVEL
FROM EMPLOYEE
接着需要搜索条件(WHERE子句)。问题语句为 "...whose level of education is higher than the average for that employee's department"。这意味着对于表中每个雇员,必须计算该雇员所在部门的平均教育级别。此语句适合相关子查询的说明。正在对每行计算某个特性(当前雇员所在部门的平均教育级别)。 EMPLOYEE 表需要相关名: SELECT LASTNAME, WORKDEPT, EDLEVEL
FROM EMPLOYEE E1
需要的子查询较简单。该子查询计算每个部门的平均教育级别。完整的 SQL 语句为: SELECT LASTNAME, WORKDEPT, EDLEVEL
FROM EMPLOYEE E1
WHERE EDLEVEL > (SELECT AVG(EDLEVEL)
FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT)
结果为: LASTNAME WORKDEPT EDLEVEL
--------------- -------- -------
HAAS A00 18
KWAN C01 20
PULASKI D21 16
HENDERSON E11 16
LUCCHESSI A00 19
PIANKA D11 17
SCOUTTEN D11 17
JONES D11 17
LUTZ D11 18
MARINO D21 17
JOHNSON D21 16
SCHNEIDER E11 17
MEHTA E21 16
GOUNOT E21 16
假定不列出雇员的部门编号,则应列出部门名称。需要的信息(DEPTNAME)在独立表(DEPARTMENT)中。定义相关变量的外层查询也可以是连接查询(参见从多个表中选择数据以了解详情)。
当在外层查询中使用连接时,列出要在 FROM 子句中连接的表,并将相关名放在这些表名的任何一个表名旁边。
要修改查询以列出部门名称而不是部门编号,在选择列表中用 DEPTNAME 替换 WORKDEPT。 FROM 子句现在也必须包括 DEPARTMENT 表,并且 WHERE 子句必须表示适当的连接条件。
以下是修改的查询: SELECT LASTNAME, DEPTNAME, EDLEVEL
FROM EMPLOYEE E1, DEPARTMENT
WHERE E1.WORKDEPT = DEPARTMENT.DEPTNO
AND EDLEVEL > (SELECT AVG(EDLEVEL)
FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT)
上例显示,必须在包含相关子查询的某个查询的 FROM 子句中定义用于子查询中的相关名。然而,这种包含可能涉及若干层嵌套。
假定某些部门只有几个雇员,因此这些部门的平均教育级别可能是错误的。可以决定,为了使平均教育级别在用于比较雇员时是有意义的数字,一个部门中必须至少有 5 个雇员。因此现在必须列出教育级别高于雇员所在部门平均值的雇员,并只考虑至少有 5 个雇员的部门。
该问题暗含另一个子查询,因为对于外层查询中每个雇员来说,必须计算该雇员所在部门的雇员总数: SELECT COUNT(*)
FROM EMPLOYEE E3
WHERE E3.WORKDEPT = E1.WORKDEPT
仅当计数大于或等于 5 时才计算平均值: SELECT AVG(EDLEVEL)
FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT
AND 5 <= (SELECT COUNT(*)
FROM EMPLOYEE E3
WHERE E3.WORKDEPT = E1.WORKDEPT)
最后,只包括其教育级别高于部门平均值的那些雇员: SELECT LASTNAME, DEPTNAME, EDLEVEL
FROM EMPLOYEE E1, DEPARTMENT
WHERE E1.WORKDEPT = DEPARTMENT.DEPTNO
AND EDLEVEL >
(SELECT AVG(EDLEVEL)
FROM EMPLOYEE E2
WHERE E2.WORKDEPT = E1.WORKDEPT
AND 5 <=
(SELECT COUNT(*)
FROM EMPLOYEE E3
WHERE E3.WORKDEPT = E1.WORKDEPT))
此语句产生下列结果: LASTNAME DEPTNAME EDLEVEL
--------------- ----------------------------- -------
PIANKA MANUFACTURING SYSTEMS 17
LUTZ MANUFACTURING SYSTEMS 18
JONES MANUFACTURING SYSTEMS 17
SCOUTTEN MANUFACTURING SYSTEMS 17
PULASKI ADMINISTRATION SYSTEMS 16
JOHNSON ADMINISTRATION SYSTEMS 16
MARINO ADMINISTRATION SYSTEMS 17
HENDERSON OPERATIONS 16
SCHNEIDER OPERATIONS 17
您必须先与数据库连接,才能使用 SQL 语句来查询或操作该数据库。CONNECT 语句使数据库连接与用户名相关联。
例如,要连接 SAMPLE 数据库,在 DB2 命令行处理器中输入下列命令: CONNECT TO SAMPLE USER USERID USING PASSWORD
(确保选择服务器系统上有效的 USER 和 USING 值)。
在此示例中,USER 的值为 USERID,USING 的值为 PASSWORD。
下列信息告诉您连接成功: 数据库连接信息
数据库产品 = DB2/6000 6.0.0
SQL 权限 ID = USERID
本地数据库别名 = SAMPLE
通过 CONNECT 语句设置连接时,建立显式连接。在隐式连接中已设置缺省服务器。在此情况下可以使用 CONNECT,或开始发出语句,将自动建立连接。
一旦连接上,就可以开始操作数据库。有关隐式和显式连接的详情,参考 SQL Reference 中的 CONNECT 语句。
无论何时在任何示例中输入出错时,或者如果执行 SQL 语句期间出错,则数据库管理程序返回错误信息。错误信息由信息标识符、简要说明以及 SQLSTATE 组成。
SQLSTATE 是 DB2 系列产品的公共错误码。 SQLSTATE 符合 ISO/ANSI SQL92 标准。
例如,如果 CONNECT 语句中用户名或口令不正确,则数据库管理程序将返回信息标识符 SQL1403N 和 SQLSTATE 为 08004。该信息如下: SQL1403N 提供的用户名和/或口令不正确。 SQLSTATE=08004
可以通过输入一个问号(?),然后输入信息标识符或 SQLSTATE 来获取关于错误信息的更多信息: ? SQL1403N
或
? SQL1403
或
? 08004
注意:错误 SQL1403N 的说明中倒数第二行表明 SQLCODE为-1403。 SQLCODE 为产品特定错误码。以 N(通知)或 C(严重)结尾的信息标识符表示一个错误,并且具有负 SQLCODE。以 W(警告)结尾的信息标识符表示一个警告,并且具有正 SQLCODE。
使用 SELECT 语句从表中选择特定的列。在该语句中指定用逗号分隔的列名列表。此列表称为选择列表。
下列语句从 SAMPLE 数据库的 ORG 表中选择部门名称 (DEPTNAME) 和部门号 (DEPTNUMB):
SELECT DEPTNAME, DEPTNUMB
FROM ORG
上面语句产生下列结果:
DEPTNAME DEPTNUMB
-------------- --------
Head Office 10
New England 15
Mid Atlantic 20
South Atlantic 38
Great Lakes 42
Plains 51
Pacific 66
Mountain 84
通过使用星号 (*) 可从表中选择所有列。下一个示例列出 ORG 表中的所有的列和行:
SELECT *
FROM ORG
此语句产生下列结果: DEPTNUMB DEPTNAME MANAGER DIVISION LOCATION
-------- -------------- ------- ---------- -------------
10 Head Office 160 Corporate New York
15 New England 50 Eastern Boston
20 Mid Atlantic 10 Eastern Washington
38 South Atlantic 30 Eastern Atlanta
42 Great Lakes 100 Midwest Chicago
51 Plains 140 Midwest Dallas
66 Pacific 270 Western San Francisco
84 Mountain 290 Western Denver
要从表中选择特定行,在 SELECT 语句之后使用 WHERE 子句指定要选择的行必须满足的条件。从表中选择行的标准是搜索条件。
搜索条件由一个或多个谓词组成。谓词指定关于某一行是真或是假(或未知)的条件。可使用下列基本谓词在 WHERE 子句中指定条件:
谓词 |
功能 |
x = y |
x 等于 y |
x <> y |
x 不等于 y |
x < y |
x 小于 y |
x > y |
x 大于 y |
x <= y |
x 小于或等于 y |
x >= y |
x 大于或等于 y |
IS NULL/IS NOT NULL |
测试空值 |
在构造搜索条件时,要注意只对数字数据类型执行算术运算,并只在相容数据类型之间进行比较。例如,不能将字符串与数字进行比较。
如果正在基于字符值来选择行,则该值必须用单引号括起来(例如,WHERE JOB = 'Clerk'),并且输入的每个字符值必须与数据库中的完全一样。如果数据值在数据库中是小写的,而您用大写形式来输入它,则将不选择行。如果正在基于数字值来选择行,则该值不得用引号括起来(例如,WHERE DEPT = 20)。
下列示例只从 STAFF 表中选择部门 20 的行: SELECT DEPT, NAME, JOB
FROM STAFF
WHERE DEPT = 20
此语句产生下列结果: DEPT NAME JOB
------ --------- -----
20 Sanders Mgr
20 Pernal Sales
20 James Clerk
20 Sneider Clerk
下一示例使用 AND 来指定多个条件。可以指定任意多个条件。该示例从 STAFF 表中选择部门 20 中的 clerk:
SELECT DEPT, NAME, JOB
FROM STAFF
WHERE JOB = 'Clerk'
AND DEPT = 20
此语句产生下列结果: DEPT NAME JOB
------ --------- -----
20 James Clerk
20 Sneider Clerk
未在其中输入值且不支持缺省值的列中出现空值。将值特别设置为空值的地方也可以出现空值。空值只能在定义为支持空值的列中出现。在表中定义和支持空值在创建表中讨论。
使用谓词 IS NULL 和 IS NOT NULL 来检查空值。
下列语句列出佣金未知的雇员: SELECT ID, NAME
FROM STAFF
WHERE COMM IS NULL
此语句产生下列结果: ID NAME
------ ---------
10 Sanders
30 Marenghi
50 Hanes
100 Plotz
140 Fraye
160 Molinare
210 Lu
240 Daniels
260 Jones
270 Lea
290 Quill
值零与空值不相同。下列语句选择表中佣金为零的每个人: SELECT ID, NAME
FROM STAFF
WHERE COMM = 0
因为样本表中的 COMM 列中没有零值,所以返回的结果集为空。
下一个示例选择 STAFF 表中 YEARS 的值大于 9 的所有行: SELECT NAME, SALARY, YEARS
FROM STAFF
WHERE YEARS > 9
此语句产生下列结果: NAME SALARY YEARS
--------- --------- ------
Hanes 20659.80 10
Lu 20010.00 10
Jones 21234.00 12
Quill 19818.00 10
Graham 21000.00 13
您可能想要信息按特定次序返回。使用 ORDER BY 子句将信息按一个或多个列中的值进行排序。
下列语句显示部门 84 中按雇用年数排序的雇员: SELECT NAME, JOB, YEARS
FROM STAFF
WHERE DEPT = 84
ORDER BY YEARS
此语句产生下列结果: NAME JOB YEARS
--------- ----- ------
Davis Sales 5
Gafney Clerk 5
Edwards Sales 7
Quill Mgr 10
指定 ORDER BY 作为整个 SELECT 语句中的最后一个子句。在此子句中命名的列可以是表达式或表的任何列。ORDER BY 子句中的列名不必在选择列表中指定。
可通过在 ORDER BY 子句中显式指定 ASC 或 DESC 将行按升序或降序进行排序。如果既未指定 ASC,也未指定 DESC,则自动按升序将行进行排序。下列语句按雇用年数以降序显示部门 84 中的雇员: SELECT NAME, JOB, YEARS
FROM STAFF
WHERE DEPT = 84
ORDER BY YEARS DESC
此语句产生下列结果: NAME JOB YEARS
--------- ----- ------
Quill Mgr 10
Edwards Sales 7
Davis Sales 5
Gafney Clerk 5
可以按字符值以及数字值将行进行排序。下列语句按姓名字母顺序显示部门 84 的雇员: SELECT NAME, JOB, YEARS
FROM STAFF
WHERE DEPT = 84
ORDER BY NAME
此语句产生下列结果:
NAME JOB YEARS
--------- ----- ------
Davis Sales 5
Edwards Sales 7
Gafney Clerk 5
Quill Mgr 10
当使用 SELECT 语句时,您可能不想要返回重复信息。例如,STAFF 有一个其中多次列出了几个部门编号的 DEPT 列,以及一个其中多次列出了几个工作说明的 JOB 列。
要消除重复行,在 SELECT 子句上使用 DISTINCT 选项。例如,如果将 DISTINCT 插入该语句,则部门中的每项工作仅列出一次: SELECT DISTINCT DEPT, JOB
FROM STAFF
WHERE DEPT < 30
ORDER BY DEPT, JOB
此语句产生下列结果: DEPT JOB
------ -----
10 Mgr
15 Clerk
15 Mgr
15 Sales
20 Clerk
20 Mgr
20 Sales
DISTINCT 已消除了在 SELECT 语句中指定的一组列中所有包含重复数据的行。
考虑运算次序是很重要的。一个子句的输出是下一个子句的输入,如下面列表中所示。给表达式命名中给出一个要考虑其中运算次序的示例。
并且注意,此说明允许以一种更直观的方式对查询进行考虑。此说明不一定是在内部执行运算的方式。运算顺序如下:
- FROM 子句
- WHERE 子句
- GROUP BY 子句
- HAVING 子句
- SELECT 子句
表达式是包括在语句中的计算或函数。下列语句计算,如果部门 38 中每个雇员都收到 $500 的奖金,则每人的薪水将是多少:
SELECT DEPT, NAME, SALARY + 500
FROM STAFF
WHERE DEPT = 38
ORDER BY 3
此结果为: DEPT NAME 3
------ --------- ----------------
38 Abrahams 12509.75
38 Naughton 13454.75
38 Quigley 17308.30
38 Marenghi 18006.75
38 O'Brien 18506.00
注意第三列的列名是一个数字。这是一个系统生成的数字,因为 SALARY+500 未指定列名。以后此数字在 ORDER BY 子句中用来表示第三列。给表达式命名论及如何给表达式取有意义的名称。
可使用基本算术运算符加(+)、减(-)、乘(*)、除(/)来形成算术表达式。
这些运算符可对几种不同类型操作数的值进行运算,其中某些操作数为:
- 列名(例如在 RATE*HOURS 中)
- 常数值(例如在 RATE*1.07 中)
- 标量函数(例如在 LENGTH(NAME) + 1 中)。
可选的 AS 子句允许您给表达式指定有意义的名称,这就使得以后再引用该表达式更容易。可使用 AS 子句为选择列表中的任何项提供名称。
下列语句显示其薪水加佣金少于 $13,000 的所有雇员。表达式 SALARY + COMM 命名为 PAY: SELECT NAME, JOB, SALARY + COMM AS PAY
FROM STAFF
WHERE (SALARY + COMM) < 13000
ORDER BY PAY
此语句产生下列结果: NAME JOB PAY
--------- ----- ----------
Yamaguchi Clerk 10581.50
Burke Clerk 11043.50
Scoutten Clerk 11592.80
Abrahams Clerk 12246.25
Kermisch Clerk 12368.60
Ngan Clerk 12714.80
通过使用 AS 子句,可以在 ORDER BY 子句中引用特定的列名而不是系统生成的数字。在此示例中,我们在 WHERE 子句中将(SALARY + COMM)与 13000 进行比较,而不是使用名称 PAY。这是运算次序的结果。在将(SALARY + COMM)命名为 PAY 之前计算 WHERE 子句的值。因此,不能在该谓词中使用 PAY。
可使用 SELECT 语句从两个或多个表中生成包含信息的报告。这通常称为 连接。例如,可以连接 STAFF 和 ORG 表中的数据以形成一个新表。要连接两个表,在 SELECT 子句中指定想要显示的列,在 FROM 子句中指定表名,在 WHERE 子句中指定搜索条件。WHERE 子句是可选的。
下一个示例使每个经理的姓名与部门名称关联。需要从两个表中选择信息,因为雇员信息(STAFF 表)和部门信息(ORG 表)是独立存储的。下列查询分别选择 STAFF 和 ORG 表的 NAME 和 DEPTNAME 列。搜索条件将选择范围缩小为 MANAGER 列中的值与 ID 列中的值相同的行: SELECT DEPTNAME, NAME
FROM ORG, STAFF
WHERE MANAGER = ID
图 3演示如何比较两个不同表中的列。加框线的值表示满足搜索条件的匹配项。
图 3. 从 STAFF 和 ORG 表中选择
SELECT 语句产生下列结果: DEPTNAME NAME
-------------- ---------
Mid Atlantic Sanders
South Atlantic Marenghi
New England Hanes
Great Lakes Plotz
Plains Fraye
Head Office Molinare
Pacific Lea
Mountain Quill
该结果列出每个经理的姓名和他或她的部门。
在编写 SELECT 语句时,可在 WHERE 子句中放置另一个 SELECT 语句。每个附加的 SELECT 启动一个子查询。
子查询本身又可包括其值代入其 WHERE 子句的另一个子查询。另外,WHERE 子句可将子查询包括在多个搜索条件中。子查询可引用与主查询中所使用的不同的表和列。
下列语句从 ORG 表中选择 STAFF 表中其 ID 为 280 的雇员的分部和位置: SELECT DIVISION, LOCATION
FROM ORG
WHERE DEPTNUMB = (SELECT DEPT
FROM STAFF
WHERE ID = 280)
在处理此语句时,DB2 首先确定子查询的结果。结果为 66,因为具有 ID 280 的雇员在部门 66。则最终结果从其部门号列具有值 66 的 ORG 表的行中得出。最终结果是: DIVISION LOCATION
---------- -------------
Western San Francisco
当使用子查询时,数据库管理程序计算该子查询并将结果值直接代入 WHERE 子句。
在相关子查询中进一步讨论子查询。
本节简要介绍了将用于全书示例的函数。 数据库函数是一组输入数据值和一个结果值之间的关系。
函数可以是内部的或用户定义的。 DB2 通用数据库提供很多内部函数和预安装的用户定义函数。可找到 SYSIBM 模式的内部函数和 SYSFUN 模式的预安装的用户定义函数。SYSIBM 和 SYSFUN 是保留模式。
内部函数和预安装的用户定义函数从不会满足所有需求。所以应用程序开发者可能需要创建自己的一套特定于他们的应用程序的函数。用户定义函数使这成为可能,例如通过扩展 DB2 通用数据库的范围以包括定制的商业或科学函数。这在用户定义函数中进一步讨论。
列函数对列中的一组值进行运算以得到单个结果值。下列就是一些列函数的示例。有关完整列表,参考 SQL Reference。
- AVG
- 返回某一组中的值除以该组中值的个数的和
- COUNT
- 返回一组行或值中行或值的个数
- MAX
- 返回一组值中的最大值
- MIN
- 返回一组值中的最小值
下列语句从 STAFF 表中选择最高薪水: SELECT MAX(SALARY)
FROM STAFF
此语句从员工 STAFF 样本表中返回值 22959.20。
下一个示例选择其收入超过平均收入但在公司的年数少于平均年数的雇员姓名和薪水。 SELECT NAME, SALARY
FROM STAFF
WHERE SALARY > (SELECT AVG(SALARY) FROM STAFF)
AND YEARS < (SELECT AVG(YEARS) FROM STAFF)
此语句产生下列结果: NAME SALARY
--------- ---------
Marenghi 17506.75
Daniels 19260.25
Gonzales 16858.20
在上面示例中的 WHERE 子句中,与直接实现列函数 (WHERE SALARY > AVG(SALARY)) 相反,在子查询中说明列函数。不能在 WHERE 子句中说明列函数。这是由于运算次序的结果。可认为 WHERE 子句是在 SELECT 子句之前进行计算的。因此,当正在计算 WHERE 子句时,列函数没有对该组值的存取权。稍后由 SELECT 子句选择这组值。
可指定 DISTINCT 作为列函数自变量的一部分,以在应用函数之前消除重复值。因此,COUNT(DISTINCT WORKDEPT) 计算不同部门的个数。
标量函数对值进行某个运算以返回另一个值。下列就是一些由DB2 通用数据库提供的标量函数的示例。
- ABS
- 返回数的绝对值
- HEX
- 返回值的十六进制表示
- LENGTH
- 返回自变量中的字节数(对于图形字符串则返回双字节字符数。)
- YEAR
- 抽取日期时间值的年份部分
有关标量函数的详细列表和说明,参考 SQL Reference。
下列语句返回 ORG 表中的部门名称以及其每个名称的长度: SELECT DEPTNAME, LENGTH(DEPTNAME)
FROM ORG
此语句产生下列结果: DEPTNAME 2
-------------- -----------
Head Office 11
New England 11
Mid Atlantic 12
South Atlantic 14
Great Lakes 11
Plains 6
Pacific 7
Mountain 8
注意:由于未使用 AS 子句给 LENGTH(DEPTNAME) 取一个有意义的名称,所以第二列中出现系统生成的数字。
DB2 通用数据库具有基于表的特定列对数据进行分析的能力。
可按照在 GROUP BY 子句中定义的组对行进行分组。以其最简单的形式,组由称为分组列的列组成。 SELECT 子句中的列名必须为分组列或列函数。列函数对于 GROUP BY 子句定义的每个组各返回一个结果。下列示例产生一个列出每个部门编号的最高薪水的结果: SELECT DEPT, MAX(SALARY) AS MAXIMUM
FROM STAFF
GROUP BY DEPT
此语句产生下列结果: DEPT MAXIMUM
------ ---------
10 22959.20
15 20659.80
20 18357.50
38 18006.00
42 18352.80
51 21150.00
66 21000.00
84 19818.00
注意:计算的是每个部门(由 GROUP BY 子句定义的组)而不是整个公司的 MAX(SALARY)。
分组查询可以在形成组和计算列函数之前具有消除非限定行的标准 WHERE 子句。必须在GROUP BY 子句之前指定 WHERE 子句。例如: SELECT WORKDEPT, EDLEVEL, MAX(SALARY) AS MAXIMUM
FROM EMPLOYEE
WHERE HIREDATE > '1979-01-01'
GROUP BY WORKDEPT, EDLEVEL
ORDER BY WORKDEPT, EDLEVEL
结果为: WORKDEPT EDLEVEL MAXIMUM
-------- ------- -----------
D11 17 18270.00
D21 15 27380.00
D21 16 36170.00
D21 17 28760.00
E11 12 15340.00
E21 14 26150.00
注意:在 SELECT 语句中指定的每个列名也在 GROUP BY 子句中提到。未在这两个地方提到的列名将产生错误。GROUP BY 子句对 WORKDEPT 和 EDLEVEL 的每个唯一组合各返回一行。
可应用限定条件进行分组,以便系统仅对满足条件的组返回结果。为此,在GROUP BY 子句后面包含一个 HAVING 子句。 HAVING 子句可包含一个或多个用 AND 和 OR 连接的谓词。每个谓词将组特性(如 AVG(SALARY))与下列之一进行比较:
例如,下列查询寻找雇员数超过 4 的部门的最高和最低薪水: SELECT WORKDEPT, MAX(SALARY) AS MAXIMUM, MIN(SALARY) AS MINIMUM
FROM EMPLOYEE
GROUP BY WORKDEPT
HAVING COUNT(*) > 4
ORDER BY WORKDEPT
此语句产生下列结果: WORKDEPT MAXIMUM MINIMUM
-------- ----------- -----------
D11 32250.00 18270.00
D21 36170.00 17250.00
E11 29750.00 15340.00
有可能(虽然很少见)查询有 HAVING 子句但没有 GROUP BY 子句。在此情况下,DB2 将整个表看作一个组。因为该表被看作是单个组,所以最多可以有一个结果行。如果 HAVING 条件对整个表为真,则返回选择的结果(该结果必须整个由列函数组成);否则不返回任何行。 | |
使用 CREATE TABLE 语句创建自己的表,指定列名和类型以及约束。约束在用约束和触发器实施商业规则中讨论。
下列语句创建一个命名为 PERS 的表,该表与 STAFF 表类似,但有一个附加列 BIRTH_DATE 。 CREATE TABLE PERS
( ID SMALLINT NOT NULL,
NAME VARCHAR(9),
DEPT SMALLINT WITH DEFAULT 10,
JOB CHAR(5),
YEARS SMALLINT,
SALARY DECIMAL(7,2),
COMM DECIMAL(7,2),
BIRTH_DATE DATE)
此语句创建一个其中无数据的表。下一节描述如何将数据插入新表。
如示例中所示,为每一列都指定名称和数据类型。数据类型在数据类型中讨论。 NOT NULL 是可选的,可以指定它以表示列中不允许有空值。缺省值也是可选的。
可以在 CREATE TABLE 语句中指定许多其他选项,如唯一约束或参考约束。有关所有选项的详情,参见 SQL Reference 中的 CREATE TABLE 语句。
当创建新表时,新表不包含任何数据。要将新的行输入表中,使用 INSERT 语句。此语句有两种一般格式:
- 一种格式,使用 VALUES 子句来指定一行或多行的列值。下面三个示例使用此一般格式将数据插入表中。
- 另一种格式,指定全查询而非指定 VALUES 来标识来自包含在其他表和/或视图中的行的列。
全查询是 INSERT 或 CREATE VIEW 语句中所使用的选择语句、或者是跟在谓词后面的选择语句。括在括号中的全查询一般称为子查询。
根据创建表时已选择的缺省选项,对于每个插入的行,为每一列提供一个值或者接受一个缺省值。各种数据类型的缺省值在 SQL Reference 中讨论。
下列语句使用 VALUES 子句将一行数据插入 PERS 表中:
INSERT INTO PERS
VALUES (12, 'Harris', 20, 'Sales', 5, 18000, 1000, '1950-1-1')
下列语句使用 VALUES 子句将三行插入其中只有 ID、名称以及工作是已知的 PERS 表中。如果列定义为 NOT NULL 且没有缺省值,则必须为该列指定一个值。 CREATE TABLE 语句中的列定义上的 NOT NULL 子句可以用单词 WITH DEFAULT 扩充。如果某一列定义为 NOT NULL WITH DEFAULT 或常数缺省值(如 WITH DEFAULT 10),并且您未在列列表中指定该列,则缺省值插入至已插入行的该列中。例如,在 CREATE TABLE 语句中,仅为 DEPT 列指定了缺省值并将该值定义为 10。因此,DEPT 设置为 10 而所有其他列都为空。
INSERT INTO PERS (NAME, JOB, ID)
VALUES ('Swagerman', 'Prgmr', 500),
('Limoges', 'Prgmr', 510),
('Li', 'Prgmr', 520)
下列语句返回插入的结果: SELECT *
FROM PERS
ID NAME DEPT JOB YEARS SALARY COMM BIRTH_DATE
------ --------- ------ ----- ------ --------- --------- ----------
12 Harris 20 Sales 5 18000.00 1000.00 01/01/1950
500 Swagerman 10 Prgmr - - - -
510 Limoges 10 Prgmr - - - -
520 Li 10 Prgmr - - - -
注意:在此情况下,并未给每个列指定值。空值显示为 -。为此,列名列表的次序和数据类型都必须与 VALUES 子句中提供的值对应。如果省略列名列表(如第一个示例中那样),则 VALUES 之后的数据值列表的次序必须与它们所插入至的表中的列次序相同,值的数目必须等于表中列的数目。
每个值必须与它所插入至的列的数据类型相容。如果某列定义为可空,且未指定该列的值,则将空值赋给插入行中的该列。
下列示例将空值插入 YEARS、COMM 和 BIRTH_DATE 中,因为未给行中的那些列指定值。 INSERT INTO PERS (ID, NAME, JOB, DEPT, SALARY)
VALUES (410, 'Perna', 'Sales', 20, 20000)
INSERT 语句的第二种格式对于用来自另一表中行的值填充表非常方便。如所述的那样,指定全查询而非指定 VALUES 以标识来自包含在其他表和/或视图中的行中的列。
下列示例从员工 STAFF 表中选择部门 38 的成员的数据,并将它插入 PERS 表中:
INSERT INTO PERS (ID, NAME, DEPT, JOB, YEARS, SALARY)
SELECT ID, NAME, DEPT, JOB, YEARS, SALARY
FROM STAFF
WHERE DEPT = 38
在此插入之后,下列 SELECT 语句与 INSERT 语句中全查询产生的结果相同。 SELECT ID, NAME, DEPT, JOB, YEARS, SALARY
FROM PERS
WHERE DEPT = 38
结果为:
ID NAME DEPT JOB YEARS SALARY
------ --------- ------ ----- ------ ---------
30 Marenghi 38 Mgr 5 17506.75
40 O'Brien 38 Sales 6 18006.00
60 Quigley 38 Sales - 16808.30
120 Naughton 38 Clerk - 12954.75
180 Abrahams 38 Clerk 3 12009.75
使用 UPDATE 语句来更改表中的数据。使用此语句,可以更改满足 WHERE 子句搜索条件的每行中的一列或多列的值。
下列示例更新其 ID 为 410 的雇员的信息: UPDATE PERS
SET JOB='Prgmr', SALARY = SALARY + 300
WHERE ID = 410
SET 子句指定要更新的列并提供值。
WHERE 子句是可选的,它指定要更新的行。如果省略 WHERE 子句,则数据库管理程序用您提供的值更新表或视图中的每一行。
在此示例中,首先命名表 (PERS),然后指定要更新行的条件。雇员编号 410 的信息已更改:该雇员的工作职位更改为 Prgmr,它的薪水增加了 300$。
可以通过包括应用于两行或更多行的 WHERE 子句来更改多行数据。下列示例给每个销售员的薪水增加 15%: UPDATE PERS
SET SALARY = SALARY * 1.15
WHERE JOB = 'Sales'
使用 DELETE 语句,基于在 WHERE 子句中指定的搜索条件从表中删除数据行。下列示例删除其中雇员 ID 为 120 的行:
DELETE FROM PERS
WHERE ID = 120
WHERE 子句是可选的,它指定要删除的行。如果省略 WHERE 子句,则数据库管理程序删除表或视图中的所有行。
可以使用 DELETE 语句删除多行。下列示例删除其中雇员部门 (DEPT) 为 20 的所有行: DELETE FROM PERS
WHERE DEPT = 20
当删除某一行时,是除去整行,而不是除去行中的特定列值。
要删除表的定义及其内容,发出 DROP TABLE 语句,如 SQL Reference 中所述。
如视图中所讨论的,视图提供在一个或多个表中查看数据的替代方法。通过创建视图,可以对想要各种用户查看的信息进行限制。下列图表显示视图和表之间的关系。
图 2. 表和视图之间的关系
在图 2中,View_A 被限制仅存取 TABLE_A 的列 AC1 和 AC2。 View_AB 允许存取 TABLE_A 中的列 AC3 和 TABLE_B 中的列 BC2。通过创建 View_A,将用户可以具有的存取权限制于 TABLE_A,通过创建 VIEW_AB,将存取权限制于某些列并创建查看数据的替代方式。
下列语句创建 STAFF 表 中 20 部门的非经理人员视图,其中薪水和佣金不通过基表显示。
CREATE VIEW STAFF_ONLY
AS SELECT ID, NAME, DEPT, JOB, YEARS
FROM STAFF
WHERE JOB <> 'Mgr' AND DEPT=20
在创建视图之后,下列语句显示视图的内容: SELECT *
FROM STAFF_ONLY
此语句产生下列结果:
ID NAME DEPT JOB YEARS
------ --------- ------ ----- ------
20 Pernal 20 Sales 8
80 James 20 Clerk -
190 Sneider 20 Clerk 8
早些时候,我们把 STAFF 和 ORG 表连接起来产生一个列出每个部门名称及其部门经理姓名的结果。下列语句创建可重复用于相同目的的视图:
CREATE VIEW DEPARTMENT_MGRS
AS SELECT NAME, DEPTNAME
FROM STAFF, ORG
WHERE MANAGER = ID
创建视图时,可以使用 WITH CHECK OPTION 子句将附加约束添加到通过视图插入和更新表。此子句导致数据库管理程序验证对视图的任何更新或插入是否符合该视图的定义,并拒绝那些不符合定义的更新或插入。如果省略此子句,则不检查违反视图定义的插入和更新。有关 WITH CHECK OPTION 如何起作用的详情,参考 SQL Reference 中的 CREATE VIEW 语句。
象 SELECT 语句一样,INSERT、DELETE 以及 UPDATE 语句可以应用于视图,就好象视图是一个实表一样。这些语句处理基本基表中的数据。因此当再次存取该视图时,使用最新的基表对它进行计算。如果未使用 WITH CHECK OPTION,则使用视图修改的数据可能由于不再满足原始视图定义而不在视图的重复存取中出现。
下列是一个将更新应用于视图 FIXED_INCOME 的示例: FIXED_INCOME 的视图定义:
CREATE VIEW FIXED_INCOME (LNAME, DEPART, JOBTITLE, NEWSALARY)
AS SELECT NAME, DEPT, JOB, SALARY
FROM PERS
WHERE JOB <> 'Sales' WITH CHECK OPTION
UPDATE FIXED_INCOME
SET NEWSALARY = 19000
WHERE LNAME = 'Li'
除了校验选项以外,先前视图中的更新等效于对基表 PERS 的更新: UPDATE PERS
SET SALARY = SALARY * 1.10
WHERE NAME = 'Li'
AND JOB <> 'Sales'
注意:由于视图是在 CREATE VIEW FIXED_INCOME 中对约束 JOB <> 'Sales'使用 WITH CHECK OPTION 创建的,所以当 Limoges 调去做销售时不允许下列更新: UPDATE FIXED_INCOME
SET JOBTITLE = 'Sales'
WHERE LNAME = 'Limoges'
不能更新由表达式 SALARY + COMM or SALARY * 1.25 定义的列。如果定义的视图包含一列或多个这样的列,则拥有者不接受对这些列的更新(UPDATE)特权。在包含这样的列的视图上不允许 INSERT 语句,但允许 DELETE 语句。
考虑一个没有一列定义为 NOT NULL 的 PERS 表。可以通过 FIXED_INCOME 视图将行插入 PERS 表中,即使该视图不包含基本表 PERS 的 ID、YEARS、COMM 或 BIRTHDATE。整个视图中看不到的列被适当地设置为空值或缺省值。
然而,PERS 表确实已将列 ID 定义为 NOT NULL。如果尝试通过 FIXED_INCOME 视图插入行,则系统试图将空值插入在整个视图中“看不到”的所有 PERS 列。由于 ID 列未包括在视图中并且该列不允许空值,所以系统不允许通过该视图进行插入。
表是由定义的列数和可变的行数组成的逻辑结构。列是一组相同数据类型的值。在表中不必对行进行排序。要对结果集进行排序,必须在从表中选择数据的 SQL 语句中显式指定排序。在每个列和行的相交处是一个称为值的特定数据项。在图 1中,'Sanders' 是表中值的一个示例。
基表是用 CREATE TABLE 语句创建的,用于保存用户数据。结果表是一组行,数据库管理程序从一个或多个基表选择或生成这组行以满足查询要求。
图 1说明了表的一部分。列和行已标记。
图 1. 表的可视化
视图提供了在一个或多个表中查看数据的替代方法。它是表上的一个动态窗口。
视图允许多个用户查看同一数据的不同表示。例如,几个用户可以同时存取一个关于雇员的数据表。一个用户可以查看关于某些雇员而非其他雇员的数据,而另一个用户可以查看关于所有雇员的某些数据而非他们的薪水。这些用户的每一个都在操作一个从该实表派生的视图。每个视图都显示为一个表并有自己的名称。
使用视图的优点是您可以使用它们来控制对敏感数据的存取。所以,不同的人可以存取数据的不同列或行。
模式是命名对象的集合,并提供了数据库中对象的逻辑分类。模式可以包含数据库对象,如表和视图等。
模式本身也可以认为是数据库中的一个对象。当创建表或视图时隐式创建了模式。或者,可以使用 CREATE SCHEMA 语句显式创建它。
创建对象时,可以用特定模式的名称来限定对象的名称。命名对象有两部分名称,其中第一部分名称是指定给对象的模式名。如果未指定模式名,则给对象指定其名称是用户执行语句的权限 ID 的缺省模式。对于交互式 SQL,该方法用于执行本书中的示例,权限 ID 为用 CONNECT 语句指定的用户 ID。例如,如果表名为 STAFF,CONNECT 语句中指定的用户 ID 为 USERID,则限定名为 USERID.STAFF。参见连接数据库以获取关于 CONNECT 语句的详情。
某些模式名是系统保留的。例如,当预安装的用户定义函数 属于 SYSFUN 模式时,内部函数处于 SYSIBM 模式。参考 SQL Reference 以获取关于 CREATE SCHEMA 语句的详情。
数据类型定义常数、列、宿主变量、函数、表达式以及专用寄存器可接受的值。本节描述示例中引用的数据类型。有关其他数据类型的完整列表和说明,参考 SQL Reference。
- 字符串
-
字符串为一个字节序列。字符串的长度为序列中的字节数。如果长度为零,则该字符串的值称为空字符串。 定长字符串
-
CHAR(x)是定长字符串。长度属性 x 必须在 1 和 254 之间,并包括 1 和 254。
- 变长字符串
-
变长字符串有三种类型:VARCHAR、LONG VARCHAR 以及 CLOB。 VARCHAR(x)类型是变长字符串,因此,可以将长度为 9 的字符串插入 VARCHAR(15)中,而该字符串的长度将仍然为 9。参见大对象 (LOB)以获取关于 CLOB 的详情。
- 图形字符串
-
图形字符串是一个双字节字符数据序列。
- 定长图形字符串
-
GRAPHIC(x)是定长字符串。长度属性 x 必须在 1 和 127 之间,并包括 1 和 127。
- 变长图形字符串
-
变长图形字符串有三种类型:VARGRAPHIC、LONG VARGRAPHIC 以及 DBCLOB。参见大对象 (LOB)以获取关于 DBCLOB 的详情。
- 二进制字符串
-
二进制字符串是一个字节序列。它用于保存非传统数据,如图象等。“二进制大对象”(BLOB)是二进制字符串。参见大对象 (LOB)以了解更多信息。
- 数字
-
所有数字都有符号和精度。精度是除符号位以外的位数或数字数。
- SMALLINT
- SMALLINT(小型整数)是精度为 5 位的两字节整数。
- INTEGER
- INTEGER(大型整数)是精度为 10 位的四字节整数。
- REAL
- REAL(单精度浮点数)是实数的 32 位近似值。
- DOUBLE
- DOUBLE(双精度浮点数)是实数的 64 位近似值。 DOUBLE 也称 FLOAT。
- DECIMAL(p,s)
-
DECIMAL 是一个十进制数。小数点的位置由数字的 精度(p)和小数位(s) 确定。精度是数字的总位数,必须小于 32。小数位是小数部分数字的位数且总是小于或等于精度值。如果未指定精度和小数位,则十进制值的缺省精度为 5,缺省小数位为 0。
- 日期时间值
-
日期时间值是日期、时间以及时间戳记的表示。日期时间值可以用于某些算术运算和字符串运算并且与某些字符串是相容的,然而它们既非字符串也非数字。 (1)
- 日期
- 日期值分为三个部分(年、月以及日)。
- 时间
- 时间是用 24 小时制式来指定一天内的时间的值,分为三个部分(小时、分钟以及秒)。
- 时间戳记
- 时间戳记为指定日期和时间的值,分为七个部分(年、月、日、小时、分钟、秒以及微秒)。
空值是一个区别于所有非空值的特殊值。它意味着行中的那一列无任何其他值。所有数据类型都存在空值。
下表突出显示示例中所使用的数据类型的特性。所有数字数据类型都定义在某一确定范围内。该数字数据类型范围也包括在此表中。可以使用此表作为正确数据类型用法的快速参考。
数据类型 |
类型 |
特性 |
示例或范围 |
CHAR(15)
|
定长字符串
|
最大长度为 254
|
'Sunny day '
|
VARCHAR(15)
|
变长字符
|
最大长度为 4000
|
'Sunny day'
|
SMALLINT
|
数字
|
长度为 2 字节
精度为 5 位
|
范围为-32768 至 32767
|
INTEGER
|
数字
|
长度为 4 字节
精度为 10 位
|
范围为-2147483648 至 2147483647
|
REAL
|
数字
|
单精度
浮点
32 位近似值
|
范围为
-3.402E+38 至-1.175E-37
或 1.175E-37 至-3.402E+38
或零
|
DOUBLE
|
数字
|
双精度
浮点
64 位近似值 |
范围为
-1.79769E+308 至-2.225E-307
或 2.225E-307 至 1.79769E+308
或零
|
DECIMAL(5,2)
|
数字
|
精度为 5
小数位为 2
|
范围为
-10**31+1 至 10**31-1 |
DATE
|
日期时间
|
三部分值
|
1991-10-27
|
TIME
|
日期时间
|
三部分值
|
13.30.05
|
TIMESTAMP
|
日期时间
|
七部分值
|
1991-10-27-13.30.05.000000
|
关系数据库中,数据存储在表中。表是行和列的集合。 结构化查询语言(SQL)通过指定列、表以及它们之间的各种关系来检索或更新数据。 SQL 是在关系数据库中定义和处理数据的标准化语言。SQL 语句由数据库管理程序执行。数据库管理程序是管理数据的计算机程序。
分区关系数据库是在多个分区(也称为节点)上管理数据的关系数据库。本书中我们将把重点集中在单个分区数据库上。
可以使用一个象命令行处理器(CLP)或命令中心那样的界面,通过交互式 SQL 来存取样本数据库并试验本书中的所有示例。 | |
final关键字最基本的含义就是表明“这个东西不能改变”。之所以这样,可能是考虑到两方面的因素:设计或效率。
final关键字可应用在三种场合: 数据、方法以及类
1. final数据
表明某个数据是“常数”,永远不会改变。使用final定义,编译器可以直接将常数值封装到需要的计算过程里。也就是说,计算可以在编译期前执行,从而节省运行时的开销。在JAVA中,这些常数必须属于基本数据类型。并且在对这样的一个常数进行定义的时候,必须给出一个值。
而当对象句柄使用final时,必须将句柄初始化到一个具体的对象,而且永远不能将句柄指向另一个对象。然而,对象本身可以修改。
空白final
Java允许创建“空白final”。即不赋初始值,但空白final必须在实际使用前得到正确的初始化。如果将赋初始值的语句放在不同的构造器中,则final字段可以随着调用不同的构造器而获得不同的初始值。但一旦确定,值将无法改变。
final数据无法更改:
{ final int i=5; i=6; }
以上语句会在编译期间出错: :cannot assign a value to final variable i=6;
final自变量
JAVA允许将自变量设成final属性。即在方法的变量列表中进行声明。这样意味着在一个方法内部,不能改变自变量句柄所指向的东西。
2. final 方法
将一个方法标识为final,则可防止任何继承类改变这个方法的本来含义。即不可被覆盖或改写。
同时将一个方法设置成final后,编译器可以把对那个方法的所有调用都嵌入到调用里,即采用常规的代码插入方法。因此可以提高程序效率。当然,如果方法体积太大,那么程序就会变的臃肿。
因此,通常只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设置为final 。
类中所有的private方法都自动成为final。因为我们不能从类外访问final方法,所以它绝对不会被其他方法覆盖。
3. final类
若在整个类定义前冠以final关键字,表明不希望从这个类继承。而final类的数据成员既可以是final的,也可以不是。因为final类禁止了继承,所以final类中的所有方法都默认为final。因为此时不可能覆盖它们。(当然可以再为final类中的方法添加final指示符号,但这样做没有任何意义)
ArticleContent1_ArticleContent1_lblContent">ArrayList是List接口的一个可变长数组实现。实现了所有List接口的操作,并允许存储null值。除了没有进行同步,ArrayList基本等同于Vector。在Vector中几乎对所有的方法都进行了同步,但ArrayList仅对writeObject和readObject进行了同步,其它比如add(Object)、remove(int)等都没有同步。
1.存储 ArrayList使用一个Object的数组存储元素。 private transient Object elementData[]; ArrayList实现了java.io.Serializable接口,这儿的transient标示这个属性不需要自动序列化。下面会在writeObject()方法中详细讲解为什么要这样作。
2.add和remove
public boolean add(Object o) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = o; return true; }
注意这儿的ensureCapacity()方法,它的作用是保证elementData数组的长度可以容纳一个新元素。在“自动变长机制”中将详细讲解。
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; }
RangeCheck()的作用是进行边界检查。由于ArrayList采用一个对象数组存储元素,所以在删除一个元素时需要把后面的元素前移。删除一个元素时只是把该元素在elementData数组中的引用置为null,具体的对象的销毁由垃圾收集器负责。 modCount的作用将在下面的“iterator()中的同步”中说明。 注:在前移时使用了System提供的一个实用方法:arraycopy(),在本例中可以看出System.arraycopy()方法可以对同一个数组进行操作,这个方法是一个native方法,如果对同一个数组进行操作时,会首先把从源部分拷贝到一个临时数组,在把临时数组的元素拷贝到目标位置。
3.自动变长机制 在实例化一个ArrayList时,你可以指定一个初始容量。这个容量就是elementData数组的初始长度。如果你使用:
ArrayList list = new ArrayList();
则使用缺省的容量:10。
public ArrayList() { this(10); }
ArrayList提供了四种add()方法,
public boolean add(Object o)
public void add(int index, Object element)
public boolean addAll(Collection c)
public boolean addAll(int index, Collection c)
在每一种add()方法中,都首先调用了一个ensureCapacity(int miniCapacity)方法,这个方法保证elementData数组的长度不小于miniCapacity。ArrayList的自动变长机制就是在这个方法中实现的。
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每次扩容,都扩大到原来大小的1.5倍。 每种add()方法的实现都大同小异,下面给出add(Object)方法的实现:
public boolean add(Object o) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = o; return true; }
4.iterator()中的同步 在父类AbstractList中定义了一个int型的属性:modCount,记录了ArrayList结构性变化的次数。
protected transient int modCount = 0;
在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。 注:add()及addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。
AbstractList中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,生成一个Itr对象(Iterator接口)返回:
public Iterator iterator() { return new Itr(); }
Itr实现了Iterator()接口,其中也定义了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。
int expectedModCount = modCount;
注:内部成员类Itr也是ArrayList类的一个成员,它可以访问所有的AbstractList的属性和方法。理解了这一点,Itr类的实现就容易理解了。
在Itr.hasNext()方法中:
public boolean hasNext() { return cursor != size(); }
调用了AbstractList的size()方法,比较当前光标位置是否越界。
在Itr.next()方法中,Itr也调用了定义在AbstractList中的get(int)方法,返回当前光标处的元素:
public Object next() { try { Object next = get(cursor); checkForComodification(); lastRet = cursor++; return next; } catch(IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
注意,在next()方法中调用了checkForComodification()方法,进行对修改的同步检查:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
现在对modCount和expectedModCount的作用应该非常清楚了。在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作,这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。在AbstractList中,使用了一个简单的机制来规避这些风险。这就是modCount和expectedModCount的作用所在。
5.序列化支持 ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。ArrayList的主要属性定义如下:
private static final long serialVersionUID = 8683452581122892189L;
private transient Object elementData[];
private int size;
可以看出serialVersionUID和size都将自动序列化到介质中,但elementData数组对象却定义为transient了。也就是说ArrayList中的所有这些元素都不会自动系列化到介质中。为什么要这样实现?因为elementData数组中存储的“元素”其实仅是对这些元素的一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。
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<size; i++) s.writeObject(elementData[i]); }
这样元素数组elementData中的所以元素对象就可以正确地序列化到存储介质了。 对应的readObject()也按照writeObject()方法的顺序从输入流中读取:
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<size; i++) elementData[i] = s.readObject(); }
META标签是HTML语言HEAD区的一个辅助性标签,它位于HTML文档头部的<HEAD>标记和<TITLE>标记之间,它提供用户不可见的信息。meta标签通常用来为搜索引擎robots定义页面主题,或者是定义用户浏览器上的cookie;它可以用于鉴别作者,设定页面格式,标注内容提要和关键字;还可以设置页面使其可以根据你定义的时间间隔刷新自己,以及设置RASC内容等级,等等。
详细介绍
下面介绍一些有关 标记的例子及解释。
META标签分两大部分:HTTP标题信息(HTTP-EQUIV)和页面描述信息(NAME)。
★HTTP-EQUIV
HTTP-EQUIV类似于HTTP的头部协议,它回应给浏览器一些有用的信息,以帮助正确和精确地显示网页内容。常用的HTTP-EQUIV类型有:
1、Content-Type和Content-Language (显示字符集的设定)
说明:设定页面使用的字符集,用以说明主页制作所使用的文字已经语言,浏览器会根据此来调用相应的字符集显示page内容。
用法:<Meta http-equiv="Content-Type" Content="text/html; Charset=gb2312"> <Meta http-equiv="Content-Language" Content="zh-CN">
注意: 该META标签定义了HTML页面所使用的字符集为GB2132,就是国标汉字码。如果将其中的“charset=GB2312”替换成“BIG5”,则该页面所用的字符集就是繁体中文Big5码。当你浏览一些国外的站点时,IE浏览器会提示你要正确显示该页面需要下载xx语支持。这个功能就是通过读取HTML页面META标签的Content-Type属性而得知需要使用哪种字符集显示该页面的。如果系统里没有装相应的字符集,则IE就提示下载。其他的语言也对应不同的charset,比如日文的字符集是“iso-2022-jp ”,韩文的是“ks_c_5601”。 Content-Type的Content还可以是:text/xml等文档类型; Charset选项:ISO-8859-1(英文)、BIG5、UTF-8、SHIFT-Jis、Euc、Koi8-2、us-ascii, x-mac-roman, iso-8859-2, x-mac-ce, iso-2022-jp, x-sjis, x-euc-jp,euc-kr, iso-2022-kr, gb2312, gb_2312-80, x-euc-tw, x-cns11643-1,x-cns11643-2等字符集;Content-Language的Content还可以是:EN、FR等语言代码。
2、Refresh (刷新)
说明:让网页多长时间(秒)刷新自己,或在多长时间后让网页自动链接到其它网页。 用法:<Meta http-equiv="Refresh" Content="30"> <Meta http-equiv="Refresh" Content="5; Url=http://www.xia8.net"> 注意:其中的5是指停留5秒钟后自动刷新到URL网址。
3、Expires (期限)
说明:指定网页在缓存中的过期时间,一旦网页过期,必须到服务器上重新调阅。 用法:<Meta http-equiv="Expires" Content="0"> <Meta http-equiv="Expires" Content="Wed, 26 Feb 1997 08:21:57 GMT"> 注意:必须使用GMT的时间格式,或直接设为0(数字表示多少时间后过期)。
4、Pragma (cach模式)
说明:禁止浏览器从本地机的缓存中调阅页面内容。 用法:<Meta http-equiv="Pragma" Content="No-cach"> 注意:网页不保存在缓存中,每次访问都刷新页面。这样设定,访问者将无法脱机浏览。
5、Set-Cookie (cookie设定)
说明:浏览器访问某个页面时会将它存在缓存中,下次再次访问时就可从缓存中读取,以提高速度。当你希望访问者每次都刷新你广告的图标,或每次都刷新你的计数器,就要禁用缓存了。通常HTML文件没有必要禁用缓存,对于ASP等页面,就可以使用禁用缓存,因为每次看到的页面都是在服务器动态生成的,缓存就失去意义。如果网页过期,那么存盘的cookie将被删除。 用法:<Meta http-equiv="Set-Cookie" Content="cookievalue=xxx; expires=Wednesday, 21-Oct-98 16:14:21 GMT; path=/"> 注意:必须使用GMT的时间格式。
6、Window-target (显示窗口的设定)
说明:强制页面在当前窗口以独立页面显示。 用法:<Meta http-equiv="Widow-target" Content="_top"> 注意:这个属性是用来防止别人在框架里调用你的页面。Content选项:_blank、_top、_self、_parent。
7、Pics-label (网页RSAC等级评定) 说明:在IE的Internet选项中有一项内容设置,可以防止浏览一些受限制的网站,而网站的限制级 别就是通过该参数来设置的。 用法:<META http-equiv="Pics-label" Contect= "(PICS-1.1'http://www.rsac.org/ratingsv01.html' I gen comment 'RSACi North America Sever' by 'inet@microsoft.com' for 'http://www.microsoft.com' on '1997.06.30T14:21-0500' r(n0 s0 v0 l0))">
注意:不要将级别设置的太高。RSAC的评估系统提供了一种用来评价Web站点内容的标准。用户可以设置Microsoft Internet Explorer(IE3.0以上)来排除包含有色情和暴力内容的站点。上面这个例子中的HTML取自Microsoft的主页。代码中的(n 0 s 0 v 0 l 0)表示该站点不包含不健康内容。级别的评定是由RSAC,即美国娱乐委员会的评级机构评定的,如果你想进一步了解RSAC评估系统的等级内容,或者你需要评价自己的网站,可以访问RSAC的站点:http://www.rsac.org/。
8、Page-Enter、Page-Exit (进入与退出)
说明:这个是页面被载入和调出时的一些特效。 用法:<Meta http-equiv="Page-Enter" Content="blendTrans(Duration=0.5)"> <Meta http-equiv="Page-Exit" Content="blendTrans(Duration=0.5)"> 注意:blendTrans是动态滤镜的一种,产生渐隐效果。另一种动态滤镜RevealTrans也可以用于页面进入与退出效果:
<Meta http-equiv="Page-Enter" Content="revealTrans(duration=x, transition=y)"> <Meta http-equiv="Page-Exit" Content="revealTrans(duration=x, transition=y)">
Duration 表示滤镜特效的持续时间(单位:秒) Transition 滤镜类型。表示使用哪种特效,取值为0-23。
0 矩形缩小 1 矩形扩大 2 圆形缩小 3 圆形扩大 4 下到上刷新 5 上到下刷新 6 左到右刷新 7 右到左刷新 8 竖百叶窗 9 横百叶窗 10 错位横百叶窗 11 错位竖百叶窗 12 点扩散 13 左右到中间刷新 14 中间到左右刷新 15 中间到上下 16 上下到中间 17 右下到左上 18 右上到左下 19 左上到右下 20 左下到右上 21 横条 22 竖条 23 以上22种随机选择一种
9、MSThemeCompatible (XP主题) 说明:是否在IE中关闭 xp 的主题 用法:<Meta http-equiv="MSThemeCompatible" Content="Yes"> 注意:关闭 xp 的蓝色立体按钮系统显示样式,从而和win2k 很象。
10、IE6 (页面生成器) 说明:页面生成器generator,是ie6 用法:<Meta http-equiv="IE6" Content="Generator"> 注意:用什么东西做的,类似商品出厂厂商。
11、Content-Script-Type (脚本相关) 说明:这是近来W3C的规范,指明页面中脚本的类型。 用法:<Meta http-equiv="Content-Script-Type" Content="text/javascript"> 注意:
★NAME变量
name是描述网页的,对应于Content(网页内容),以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)。 name的value值(name="")指定所提供信息的类型。有些值是已经定义好的。例如description(说明)、keyword(关键字)、refresh(刷新)等。还可以指定其他任意值,如:creationdate(创建日期) 、 document ID(文档编号)和level(等级)等。 name的content指定实际内容。如:如果指定level(等级)为value(值),则Content可能是beginner(初级)、intermediate(中级)、advanced(高级)。
1、Keywords (关键字) 说明:为搜索引擎提供的关键字列表 用法:<Meta name="Keywords" Content="关键词1,关键词2,关键词3,关键词4,……"> 注意:各关键词间用英文逗号“,”隔开。META的通常用处是指定搜索引擎用来提高搜索质量的关键词。当数个META元素提供文档语言从属信息时,搜索引擎会使用lang特性来过滤并通过用户的语言优先参照来显示搜索结果。例如: <Meta name="Kyewords" Lang="EN" Content="vacation,greece,sunshine"> <Meta name="Kyewords" Lang="FR" Content="vacances,grè:ce,soleil">
2、Description (简介) 说明:Description用来告诉搜索引擎你的网站主要内容。 用法:<Meta name="Description" Content="你网页的简述"> 注意:
3、Robots (机器人向导) 说明:Robots用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引。Content的参数有all、none、index、noindex、follow、nofollow。默认是all。 用法:<Meta name="Robots" Content="All|None|Index|Noindex|Follow|Nofollow"> 注意:许多搜索引擎都通过放出robot/spider搜索来登录网站,这些robot/spider就要用到meta元素的一些特性来决定怎样登录。
all:文件将被检索,且页面上的链接可以被查询; none:文件将不被检索,且页面上的链接不可以被查询;(和 "noindex, no follow" 起相同作用) index:文件将被检索;(让robot/spider登录) follow:页面上的链接可以被查询; noindex:文件将不被检索,但页面上的链接可以被查询;(不让robot/spider登录) nofollow:文件将不被检索,页面上的链接可以被查询。(不让robot/spider顺着此页的连接往下探找)
4、Author (作者) 说明:标注网页的作者或制作组 用法:<Meta name="Author" Content="张三,abc@sina.com"> 注意:Content可以是:你或你的制作组的名字,或Email
5、Copyright (版权) 说明:标注版权 用法:<Meta name="Copyright" Content="本页版权归Zerospace所有。All Rights Reserved"> 注意:
6、Generator (编辑器) 说明:编辑器的说明 用法:<Meta name="Generator" Content="PCDATA|FrontPage|"> 注意:Content="你所用编辑器"
7、revisit-after (重访) 说明: 用法:<META name="revisit-after" CONTENT="7 days" > 注意:
★Head中的其它一些用法
1、scheme (方案) 说明:scheme can be used when name is used to specify how the value of content should be interpreted. 用法:<meta scheme="ISBN" name="identifier" content="0-14-043205-1" /> 注意:
2、Link (链接) 说明:链接到文件 用法:<Link href="soim.ico" rel="Shortcut Icon"> 注意:很多网站如果你把她保存在收件夹中后,会发现它连带着一个小图标,如果再次点击进入之后还会发现地址栏中也有个小图标。现在只要在你的页头加上这段话,就能轻松实现这一功能。<LINK> 用来将目前文件与其它 URL 作连结,但不会有连结按钮,用於 <HEAD> 标记间, 格式如下: <link href="URL" rel="relationship"> <link href="URL" rev="relationship">
3、Base (基链接) 说明:插入网页基链接属性 用法:<Base href="http://www.xia8.net/" target="_blank"> 注意:你网页上的所有相对路径在链接时都将在前面加上“http://www.cn8cn.com/”。其中target="_blank"是链接文件在新的窗口中打开,你可以做其他设置。将“_blank”改为“_parent”是链接文件将在当前窗口的父级窗口中打开;改为“_self”链接文件在当前窗口(帧)中打开;改为“_top”链接文件全屏显示。
以上是META标签的一些基本用法,其中最重要的就是:Keywords和Description的设定。为什么呢?道理很简单,这两个语句可以让搜索引擎能准确的发现你,吸引更多的人访问你的站点!根据现在流行搜索引擎(Google,Lycos,AltaVista等)的工作原理,搜索引擎先派机器人自动在WWW上搜索,当发现新的网站时,便于检索页面中的Keywords和Description,并将其加入到自己的数据库,然后再根据关键词的密度将网站排序。
由此看来,我们必须记住添加Keywords和Description的META标签,并尽可能写好关键字和简介。否则, 后果就会是: ● 如果你的页面中根本没有Keywords和Description的META标签,那么机器人是无法将你的站点加入数 据库,网友也就不可能搜索到你的站点。
● 如果你的关键字选的不好,关键字的密度不高,被排列在几十甚至几百万个站点的后面被点击的可 能性也是非常小的。
写好Keywords(关键字)要注意以下几点:
● 不要用常见词汇。例如www、homepage、net、web等。
● 不要用形容词,副词。例如最好的,最大的等。
● 不要用笼统的词汇,要尽量精确。例如“爱立信手机”,改用“T28SC”会更好。
“三人之行,必有我师”,寻找合适关键词的技巧是:到Google、Lycos、Alta等著名搜索引擎,搜索与 你的网站内容相仿的网站,查看排名前十位的网站的META关键字,将它们用在你的网站上,效果可想而知了。
★小窍门
为了提高搜索点击率,这里还有一些“捷径”可以帮得到你:
● 为了增加关键词的密度,将关键字隐藏在页面里(将文字颜色定义成与背景颜色一样)。
● 在图像的ALT注释语句中加入关键字。如:<IMG SRC="xxx.gif" Alt="Keywords">
● 利用HTML的注释语句,在页面代码里加入大量关键字。用法: <!-- 这里插入关键字 -->
<head> <title>文件头,显示在浏览器标题区</title> <meta http-equiv="Content-Language" content="zh-cn"> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <meta name="GENERATOR" content="Microsoft FrontPage 4.0"> <meta name="ProgId" content="FrontPage.Editor.Document"> <meta name="制作人" content="唐蓉生"> <meta name="主题词" content="HTML 网页制作 课件"></head>
Expires 属性 Expires 属性指定了在浏览器上缓冲存储的页距过期还有多少时间。如果用户在某个页过期之前又回到此页,就会显示缓冲区中的版本
语法 Response.Expires [= number] 参数 number 距过期还有多少分钟。将此参数设置为 0 可使缓存的页立即过期。 注释 若此属性在一页上设置了多次,则使用最短的时间。 应用于 Response 对象
Response.expires=0也表示立即过期,但如果client和server不在一个时区或者client的时间早于server上的时间,则不能立即过期。所以用负数或者用Response.ExpiresAbsolute=now()-1来表示立即过期,response.expires=1表示在1分钟后过期。
不知道大家对Buffer了解多少,很多人对这个概念都比较模糊,尤其是在asp中。很多初学者在编写asp程序时很少用到这条语句,下面我就来说说Buffer的用途以及它在asp程序中的作用。
一、Buffer
Buffer从英文直译过来的意思是“缓冲区”,这里我们将它称为缓冲,因为它不仅是个名词,还是个动词。 缓冲区是存储一系列的数据的地方,客户端所获得的数据可以从程序的执行结果直接输出,也可以从缓冲区输出。但是这两种方式在速度上是有差异的:在web中,当一个asp程序被请求的次数不多时,二者基本上没有什么差异,至少我们感觉不出来。但是当有很多人请求一个asp程序时,速度可就不一样了。如果没有缓冲区,那么每个请求asp程序的人的客户端所得到的结果都是asp程序执行一次所得到的结果,而如果预先将asp程序缓冲,那么每个客户端所得到的结果就是缓冲区的结果,不是执行一次程序的结果。比如有1000个用户同时访问一个asp页面,如果这个asp程序没有缓冲,那么程序将被执行一千次,这样服务器的负荷就回加大,从而导致客户端打开页面速度变慢;如果这个asp程序被缓冲了,那么结果就不一样了,每个客户端直接从缓冲区获得数据,服务器将不会因为访问增加而增加程序执行次数,因此客户端打开页面的速度也就比上一种情况要快。这就是Buffer的好处。
二、如何将asp程序缓冲
这个问题其实很简单,只要在asp程序的第一行加上: <% Response.Buffer = True %> 就可以了。 这句话的意思就是指明输出页面是否被缓冲,当属性值为True时,服务器将不会向客户端发送任何信息,直到所有程序执行完或者遇到 <% Response.Flush %>或<% Response.End %> 语句,才会释放缓冲区的信息。
三、总结
Response的Buffer属性虽然能够提高页面显示速度,但是也要分什么情况。如果你正在制作一个普通的个人主页,访问量不是很高,并且没有什么复杂的执行程序,那么用不用这个属性就不是很重要,因为将数据缓冲也需要一段时间,只不过我们感觉不到罢了;但是如果你正在制作一个大型论坛或者一个产品展示或其他的商务站点,并且访问量很高,那么我建议在程序的第一行加入 <% Response.Buffer = True %> 这句话,因为这样能够让客户在有效的时间内获得更多的数据。
在我们学习Java的过程中,掌握其中的基本概念对我们的学习无论是J2SE,J2EE,J2ME都是很重要的,J2SE是Java的基础,所以有必要对其中的基本概念做以归纳,以便大家在以后的学习过程中更好的理解java的精髓,在此我总结了30条基本的概念。
Java概述:
目前Java主要应用于中间件的开发(middleware)---处理客户机于服务器之间的通信技术,早期的实践证明,Java不适合pc应用程序的开发,其发展逐渐变成在开发手持设备,互联网信息站,及车载计算机的开发.Java于其他语言所不同的是程序运行时提供了平台的独立性,称许可以在windows,solaris,linux其他操作系统上使用完全相同的代码.Java的语法与C++语法类似,C++/C程序员很容易掌握,而且Java是完全的彻底的面向对象的,其中提出了很好的GC(Garbage Collector)垃圾处理机制,防止内存溢出。
Java的白皮书为我们提出了Java语言的11个关键特性。
(1)Easy:Java的语法比C++的相对简单,另一个方面就是Java能使软件在很小的机器上运行,基础解释其和类库的支持的大小约为40kb,增加基本的标准库和线程支持的内存需要增加125kb。
(2)分布式:Java带有很强大的TCP/IP协议族的例程库,Java应用程序能够通过URL来穿过网络来访问远程对象,由于servlet机制的出现,使Java编程非常的高效,现在许多的大的web server都支持servlet。
(3)O面向对象设计是把重点放在对象及对象的接口上的一个编程技术.其面向对象和C++有很多不同,在与多重继承的处理及Java的原类模型。
(4)健壮特性:Java采取了一个安全指针模型,能减小重写内存和数据崩溃的可能型。
(5)安全:Java用来设计网路和分布系统,这带来了新的安全问题,Java可以用来构建防病毒和防攻击的System.事实证明Java在防毒这一方面做的比较好。
(6)中立体系结构:Java编译其生成体系结构中立的目标文件格式可以在很多处理器上执行,编译器产生的指令字节码(Javabytecode)实现此特性,此字节码可以在任何机器上解释执行。
(7)可移植性:Java中对基本数据结构类型的大小和算法都有严格的规定所以可移植性很好。
(8)多线程:Java处理多线程的过程很简单,Java把多线程实现交给底下操作系统或线程程序完成.所以多线程是Java作为服务器端开发语言的流行原因之一。
(9)Applet和servlet:能够在网页上执行的程序叫Applet,需要支持Java的浏览器很多,而applet支持动态的网页,这是很多其他语言所不能做到的。
基本概念:
1.OOP中唯一关系的是对象的接口是什么,就像计算机的销售商她不管电源内部结构是怎样的,他只关系能否给你提供电就行了,也就是只要知道can or not而不是how and why.所有的程序是由一定的属性和行为对象组成的,不同的对象的访问通过函数调用来完成,对象间所有的交流都是通过方法调用,通过对封装对象数据,很大限度上提高复用率。
2.OOP中最重要的思想是类,类是模板是蓝图,从类中构造一个对象,即创建了这个类的一个实例(instance)。
3.封装:就是把数据和行为结合起在一个包中)并对对象使用者隐藏数据的实现过程,一个对象中的数据叫他的实例字段(instance field)。
4.通过扩展一个类来获得一个新类叫继承(inheritance),而所有的类都是由Object根超类扩展而得,根超类下文会做介绍。
5.对象的3个主要特性
behavior---说明这个对象能做什么. state---当对象施加方法时对象的反映. identity---与其他相似行为对象的区分标志. 每个对象有唯一的indentity 而这3者之间相互影响.
6.类之间的关系:
use-a :依赖关系 has-a :聚合关系 is-a :继承关系--例:A类继承了B类,此时A类不仅有了B类的方法,还有其自己的方法.(个性存在于共性中)
7.构造对象使用构造器:构造器的提出,构造器是一种特殊的方法,构造对象并对其初始化。
例:Data类的构造器叫Data
new Data()---构造一个新对象,且初始化当前时间. Data happyday=new Data()---把一个对象赋值给一个变量happyday,从而使该对象能够多次使用,此处要声明的使变量与对象变量二者是不同的.new返回的值是一个引用。
构造器特点:构造器可以有0个,一个或多个参数 构造器和类有相同的名字 一个类可以有多个构造器 构造器没有返回值 构造器总是和new运算符一起使用.
8.重载:当多个方法具有相同的名字而含有不同的参数时,便发生重载.编译器必须挑选出调用哪个方法。
9.包(package)Java允许把一个或多个类收集在一起成为一组,称作包,以便于组织任务,标准Java库分为许多包.java.lang java.util java,net等,包是分层次的所有的java包都在java和javax包层次内。
10.继承思想:允许在已经存在的类的基础上构建新的类,当你继承一个已经存在的类时,那么你就复用了这个类的方法和字段,同时你可以在新类中添加新的方法和字段。
11.扩展类:扩展类充分体现了is-a的继承关系. 形式为:class (子类) extends (基类)。
12.多态:在java中,对象变量是多态的.而java中不支持多重继承。
13.动态绑定:调用对象方法的机制。
(1)编译器检查对象声明的类型和方法名。
(2)编译器检查方法调用的参数类型。
(3)静态绑定:若方法类型为priavte static final 编译器会准确知道该调用哪个方法。
(4)当程序运行并且使用动态绑定来调用一个方法时,那么虚拟机必须调用x所指向的对象的实际类型相匹配的方法版本。
(5)动态绑定:是很重要的特性,它能使程序变得可扩展而不需要重编译已存代码。
14.final类:为防止他人从你的类上派生新类,此类是不可扩展的。
15.动态调用比静态调用花费的时间要长。
16.抽象类:规定一个或多个抽象方法的类本身必须定义为abstract。
例: public abstract string getDescripition
17.Java中的每一个类都是从Object类扩展而来的。
18.object类中的equal和toString方法。
equal用于测试一个对象是否同另一个对象相等。
toString返回一个代表该对象的字符串,几乎每一个类都会重载该方法,以便返回当前状态的正确表示. (toString 方法是一个很重要的方法)
19.通用编程:任何类类型的所有值都可以同object类性的变量来代替。
20.数组列表:ArrayList动态数组列表,是一个类库,定义在java.uitl包中,可自动调节数组的大小。
21.class类 object类中的getclass方法返回ckass类型的一个实例,程序启动时包含在main方法的类会被加载,虚拟机要加载他需要的所有类,每一个加载的类都要加载它需要的类。
22.class类为编写可动态操纵java代码的程序提供了强大的功能反射,这项功能为JavaBeans特别有用,使用反射Java能支持VB程序员习惯使用的工具。
能够分析类能力的程序叫反射器,Java中提供此功能的包叫Java.lang.reflect反射机制十分强大.
1.在运行时分析类的能力。 2.在运行时探察类的对象。 3.实现通用数组操纵代码。 4.提供方法对象。
而此机制主要针对是工具者而不是应用及程序。
反射机制中的最重要的部分是允许你检查类的结构.用到的API有:
java.lang.reflect.Field 返回字段. java.reflect.Method 返回方法. java.lang.reflect.Constructor 返回参数.
方法指针:java没有方法指针,把一个方法的地址传给另一个方法,可以在后面调用它,而接口是更好的解决方案。
23.接口(Interface)说明类该做什么而不指定如何去做,一个类可以实现一个或多个interface。
24.接口不是一个类,而是对符合接口要求的类的一套规范。
若实现一个接口需要2个步骤:
1.声明类需要实现的指定接口。 2.提供接口中的所有方法的定义。
声明一个类实现一个接口需要使用implements 关键字
class actionB implements Comparable 其actionb需要提供CompareTo方法,接口不是类,不能用new实例化一个接口.
25.一个类只有一个超类,但一个类能实现多个接口。Java中的一个重要接口:Cloneable
26.接口和回调.编程一个常用的模式是回调模式,在这种模式中你可以指定当一个特定时间发生时回调对象上的方法。
例:ActionListener 接口监听. 类似的API有:java.swing.JOptionPane
java.swing.Timer java.awt.Tookit
27.对象clone:clone方法是object一个保护方法,这意味着你的代码不能简单的调用它。
28.内部类:一个内部类的定义是定义在另一个内部的类。
原因是:
1.一个内部类的对象能够访问创建它的对象的实现,包括私有数据。
2.对于同一个包中的其他类来说,内部类能够隐藏起来。
3.匿名内部类可以很方便的定义回调。
4.使用内部类可以非常方便的编写事件驱动程序。
29.代理类(proxy):
1.指定接口要求所有代码
2.object类定义的所有的方法(toString equals)
30.数据类型:Java是强调类型的语言,每个变量都必须先申明它都类型,java中总共有8个基本类型.4种是整型,2种是浮点型,一种是字符型,被用于Unicode编码中的字符,布尔型。
1.document.write(""); 输出语句 2.JS中的注释为// 3.传统的HTML文档顺序是:document->html->(head,body) 4.一个浏览器窗口中的DOM顺序是:window->(navigator,screen,history,location,document) 5.得到表单中元素的名称和值:document.getElementById("表单中元素的ID号").name(或value) 6.一个小写转大写的JS: document.getElementById("output").value = document.getElementById("input").value.toUpperCase(); 7.JS中的值类型:String,Number,Boolean,Null,Object,Function 8.JS中的字符型转换成数值型:parseInt(),parseFloat() 9.JS中的数字转换成字符型:(""+变量) 10.JS中的取字符串长度是:(length) 11.JS中的字符与字符相连接使用+号. 12.JS中的比较操作符有:==等于,!=不等于,>,>=,<.<= 13.JS中声明变量使用:var来进行声明 14.JS中的判断语句结构:if(condition){}else{} 15.JS中的循环结构:for([initial expression];[condition];[upadte expression]) {inside loop} 16.循环中止的命令是:break 17.JS中的函数定义:function functionName([parameter],...){statement[s]} 18.当文件中出现多个form表单时.可以用document.forms[0],document.forms[1]来代替. 19.窗口:打开窗口window.open(), 关闭一个窗口:window.close(), 窗口本身:self 20.状态栏的设置:window.status="字符"; 21.弹出提示信息:window.alert("字符"); 22.弹出确认框:window.confirm(); 23.弹出输入提示框:window.prompt(); 24.指定当前显示链接的位置:window.location.href="URL" 25.取出窗体中的所有表单的数量:document.forms.length 26.关闭文档的输出流:document.close(); 27.字符串追加连接符:+= 28.创建一个文档元素:document.createElement(),document.createTextNode() 29.得到元素的方法:document.getElementById() 30.设置表单中所有文本型的成员的值为空: var form = window.document.forms[0] for (var i = 0; i<form.elements.length;i++){ if (form.elements.type == "text"){ form.elements.value = ""; } } 31.复选按钮在JS中判断是否选中:document.forms[0].checkThis.checked (checked属性代表为是否选中返回TRUE或FALSE) 32.单选按钮组(单选按钮的名称必须相同):取单选按钮组的长度document.forms[0].groupName.length 33.单选按钮组判断是否被选中也是用checked. 34.下拉列表框的值:document.forms[0].selectName.options[n].value (n有时用下拉列表框名称加上.selectedIndex来确定被选中的值) 35.字符串的定义:var myString = new String("This is lightsword"); 36.字符串转成大写:string.toUpperCase(); 字符串转成小写:string.toLowerCase(); 37.返回字符串2在字符串1中出现的位置:String1.indexOf("String2")!=-1则说明没找到. 38.取字符串中指定位置的一个字符:StringA.charAt(9); 39.取出字符串中指定起点和终点的子字符串:stringA.substring(2,6); 40.数学函数:Math.PI(返回圆周率),Math.SQRT2(返回开方),Math.max(value1,value2)返回两个数中的最在值,Math.pow(value1,10)返回value1的十次方,Math.round(value1)四舍五入函数,Math.floor(Math.random()*(n+1))返回随机数 41.定义日期型变量:var today = new Date(); 42.日期函数列表:dateObj.getTime()得到时间,dateObj.getYear()得到年份,dateObj.getFullYear()得到四位的年份,dateObj.getMonth()得到月份,dateObj.getDate()得到日,dateObj.getDay()得到日期几,dateObj.getHours()得到小时,dateObj.getMinutes()得到分,dateObj.getSeconds()得到秒,dateObj.setTime(value)设置时间,dateObj.setYear(val)设置年,dateObj.setMonth(val)设置月,dateObj.setDate(val)设置日,dateObj.setDay(val)设置星期几,dateObj.setHours设置小时,dateObj.setMinutes(val)设置分,dateObj.setSeconds(val)设置秒 [注意:此日期时间从0开始计] 43.FRAME的表示方式: [window.]frames[n].ObjFuncVarName,frames["frameName"].ObjFuncVarName,frameName.ObjFuncVarName 44.parent代表父亲对象,top代表最顶端对象 45.打开子窗口的父窗口为:opener 46.表示当前所属的位置:this 47.当在超链接中调用JS函数时用:(javascript :)来开头后面加函数名 48.在老的浏览器中不执行此JS:<!-- //--> 49.引用一个文件式的JS:<script type="text/javascript" src="aaa.js"></script> 50.指定在不支持脚本的浏览器显示的HTML:<noscript></noscript> 51.当超链和onCLICK事件都有时,则老版本的浏览器转向a.html,否则转向b.html.例:<a href="a.html" onclick="location.href='b.html';return false">dfsadf</a> 52.JS的内建对象有:Array,Boolean,Date,Error,EvalError,Function,Math,Number,Object,RangeError,ReferenceError,RegExp,String,SyntaxError,TypeError,URIError 53.JS中的换行:\n 54.窗口全屏大小:<script>function fullScreen(){ this.moveTo(0,0);this.outerWidth=screen.availWidth;this.outerHeight=screen.availHeight;}window.maximize=fullScreen;</script> 55.JS中的all代表其下层的全部元素 56.JS中的焦点顺序:document.getElementByid("表单元素").tabIndex = 1 57.innerHTML的值是表单元素的值:如<p id="para">"how are <em>you</em>"</p>,则innerHTML的值就是:how are <em>you</em> 58.innerTEXT的值和上面的一样,只不过不会把<em>这种标记显示出来. 59.contentEditable可设置元素是否可被修改,isContentEditable返回是否可修改的状态. 60.isDisabled判断是否为禁止状态.disabled设置禁止状态 61.length取得长度,返回整型数值 62.addBehavior()是一种JS调用的外部函数文件其扩展名为.htc 63.window.focus()使当前的窗口在所有窗口之前. 64.blur()指失去焦点.与FOCUS()相反. 65.select()指元素为选中状态. 66.防止用户对文本框中输入文本:onfocus="this.blur()" 67.取出该元素在页面中出现的数量:document.all.tags("div(或其它HTML标记符)").length 68.JS中分为两种窗体输出:模态和非模态.window.showModaldialog(),window.showModeless() 69.状态栏文字的设置:window.status='文字',默认的状态栏文字设置:window.defaultStatus = '文字.'; 70.添加到收藏夹:external.AddFavorite(" http://www.dannyg.com";,"jaskdlf"); 71.JS中遇到脚本错误时不做任何操作:window.onerror = doNothing; 指定错误句柄的语法为:window.onerror = handleError; 72.JS中指定当前打开窗口的父窗口:window.opener,支持opener.opener...的多重继续. 73.JS中的self指的是当前的窗口 74.JS中状态栏显示内容:window.status="内容" 75.JS中的top指的是框架集中最顶层的框架 76.JS中关闭当前的窗口:window.close(); 77.JS中提出是否确认的框:if(confirm("Are you sure?")){alert("ok");}else{alert("Not Ok");} 78.JS中的窗口重定向:window.navigate(" http://www.sina.com.cn";); 79.JS中的打印:window.print() 80.JS中的提示输入框:window.prompt("message","defaultReply"); 81.JS中的窗口滚动条:window.scroll(x,y) 82.JS中的窗口滚动到位置:window.scrollby 83.JS中设置时间间隔:setInterval("expr",msecDelay)或setInterval(funcRef,msecDelay)或setTimeout 84.JS中的模态显示在IE4+行,在NN中不行:showModalDialog("URL"[,arguments][,features]); 85.JS中的退出之前使用的句柄:function verifyClose(){event.returnValue="we really like you and hope you will stay longer.";}} window.onbeforeunload=verifyClose; 86.当窗体第一次调用时使用的文件句柄:onload() 87.当窗体关闭时调用的文件句柄:onunload() 88.window.location的属性: protocol(http:),hostname(www.example.com),port(80),host(www.example.com:80),pathname("/a/a.html"),hash("#giantGizmo",指跳转到相应的锚记),href(全部的信息) 89.window.location.reload()刷新当前页面. 90.window.history.back()返回上一页,window.history.forward()返回下一页,window.history.go(返回第几页,也可以使用访问过的URL) 91.document.write()不换行的输出,document.writeln()换行输出 92.document.body.noWrap=true;防止链接文字折行. 93.变量名.charAt(第几位),取该变量的第几位的字符. 94."abc".charCodeAt(第几个),返回第几个字符的ASCii码值. 95.字符串连接:string.concat(string2),或用+=进行连接 96.变量.indexOf("字符",起始位置),返回第一个出现的位置(从0开始计算) 97.string.lastIndexOf(searchString[,startIndex])最后一次出现的位置. 98.string.match(regExpression),判断字符是否匹配. 99.string.replace(regExpression,replaceString)替换现有字符串. 100.string.split(分隔符)返回一个数组存储值. 101.string.substr(start[,length])取从第几位到指定长度的字符串. 102.string.toLowerCase()使字符串全部变为小写. 103.string.toUpperCase()使全部字符变为大写. 104.parseInt(string[,radix(代表进制)])强制转换成整型. 105.parseFloat(string[,radix])强制转换成浮点型. 106.isNaN(变量):测试是否为数值型. 107.定义常量的关键字:const,定义变量的关键字:var
CSS是“Cascading Style Sheets”的简称,中文翻译为“串接样式表”,也有人翻译 为“样式表”。CSS用以作为网页的排版和风格设计,在web标准建站中,对CSS的熟悉和使用 是相当重要的一个内容。CSS的作用是弥补HTML的不足,让网页的设计更为灵活。
这个文章只是为您介绍CSS的基础应用,指引您的一个入门的基础教程,主要目的是为 推进web标准贡献自己的微薄之力。
说点我自己的体会,现在有好多人都在推广WEB标准,其实对初学者来说,不需要刚学 的时候就学标准,学点简单的还是容易入门的,因为现在HTML还在照样用啊,所以我希望初学 者学习时不要非遵循标准,当你入门之后,你做网页的时候,发现使用表格特麻烦的时候你就 该去寻求简单方法了,到时再学也不晚啊,如果你喜欢新技术那么你初学时就学标准吧,按个 人的实际来行动吧。下面开始学习了
一、如何在HTML中应用CSS。
您可以利用下列 3 种方式将 CSS 指定的格式加入到HTML中:
1. 在 HTML 文件里加一个超级连结,连接到外部的 CSS 文档。(外部连结 CSS)
这个方法最方便管理整个网站的网页风格,它让网页的文字内容与版面设计分开。您 只要在一个 CSS 文档内(扩展名为 .CSS)定义好网页的风格,然后在网页中加一个超级连接 连接到该文档,那么你的网页会按在CSS文档内定义好的风格显示出来了。
具体的使用防范是:
<HTML>
<HEAD>
<TITLE>网页文件的标题</TITLE>
<LINK REL="stylesheet" HREF="style.css" TYPE="text/css">
</HEAD>
注意:style.css文件的位置。
2. 在 HTML 文件内的 <HEAD>.......</HEAD> 标签之间,加一段 CSS 的描述内容。(内定义CSS)
这个方法适用于指定某个网页,除了表现外部的 CSS 文档定义好的网页风格外,同时 还要表现本身 HTML 文档内指定的 CSS 。
如果内在添加的 CSS 描述与外部连接的 CSS 描述相冲突的话,网页的表现将以内在 添加的 CSS 描述为主,也就是外部的描述就不再起作用了。
具体使用方法是:
<HTML>
<HEAD>
<TITLE>网页标题</TITLE>
<STYLE TYPE="text/css">
<!--
BODY {font: 12pt}
H1 {font: 16pt}
P {font-weight: bold;
color: green}
-->
</STYLE>
</HEAD>
<BODY>
网页内容…
</BODY>
</HTML>
值得注意的是,为了防止不支持 CSS 的浏览器误将标签间的 CSS 风格描述当成普通 字串,而表现于网页上,您最好将 CSS 的叙述文字插入在<!--和-->之间。
3. 在 HTML 文件的文本内容中,随时有需要,随时加一小段 CSS 的描述指定风格。( 文本间CSS)
这个方法适用于指定网页内的某一小段文字的呈现风格。
外部CSS与内定义CSS如果和此定义有相同的项,那么以此定义的 CSS 风格表现,外部 CSS文档与内定义CSS和此定义的没相同的项时那么还会正常显示,同时还会显示文本内容间的 CSS 风格。
具体使用方法是:
<HTML>
<HEAD>
<TITLE>网页标题</TITLE>
</HEAD>
<BODY>
<P STYLE="color: red">
本页内容…
</P>
</BODY>
</HTML>
上述的 3 种 CSS,可以同时并用,也可以择您所好,单一或成双地使用。如果各 CSS 间的叙述相冲突,则内在定义的 CSS 会覆盖外部连结的 CSS ,文本间的 CSS 会覆盖内在定 义的 CSS 。
二、挑选者、属性和值。
先举个例子:H3{ COLOR : BLUE }表示在文本中只要使用H3标签的文字的颜色都是绿 色。其中H3为挑选者,COLOR为属性,BLUE为COLOR属性的值。挑选者是套用样式的元件,通常 为外部CSS或内定义CSS定义的风格的一个名字,在这个初级教程里理解为一个标签的名字也可 以。属性是用语描述挑选者的特性,相当于HTML语法中的标签的属性。值就是属性的具体内容 。
在CSS中当我们使用到属性值的时候,通常值是有一个度量依据的,也就是说值是有单 位的。比如我们通常说你从家到学校走1,1什么呢?米,公里,还是走1小时。通常在CSS中的 单位有:相对单位与绝对单位两种单位具体如下:
“em” (比如 font-size: 2em) :相对于字母高度的比例因子。
“%” (比如 font-size: 80%): 相对于长度单位(通常是目前字型的大小)的百分 比例。
'px' (比如 font-size: 12px) :像素(系统预设单位)。
'pt' (比如 font-size: 12pt): 像点。
此外还有 'pc' (印刷活字单位), 'cm' (公分), 'mm' (公厘) 和 'in' (英寸)等单位 。
当值为0时,我们就不需要设置单位了,比如你不想要边框那么我们直接设置border=0 。
在这我多说一句,就是强调单位的使用时,当我自己制作的网页想在分辨率改变时, 字体大小也随着改变那么我们就使用单位%和em,如果你想时你的网页不管怎么调分辨率都是 固定大小的那么我们使用px、pt等元素了。呵呵!
三、颜色的设置和使用。
CSS提供了16,777,216种颜色可以供我们来使用,通常表现颜色的方式有三种:颜色名 字、RGB(red/green/blue) 数值和十六进制数形式,具体表现如下:
红色可以表示为:red、RGB(255,0,0)、rgb(100%,0%,0%)、#ff0000 和 #f00 五种方 式。
#RRGGBB:以三个00到FF的十六进位值分别表示0到255十进位值的红、绿、蓝三原色数 值。
#RGB:简略表示法,只用三个0到F的十六进位值分别表示红、绿、蓝三原色数值。而 事实上,浏览器会自动扩展为六个十六进位的值,如『#ABC』将变为『#AABBCC』。但是,显 见这样的 表示法并不精确。
rgb(R,G,B):以0到255十进位值的红、绿、蓝三原色数值来表示颜色。
rgb(R%,G%,B%):以红、绿、蓝彼此相对的数值比例来表示颜色,如rgb (60%,100%,75%)。
Color_Name: 直接以颜色名称来表示颜色,共有141种标准的颜色名称。
通常我们在设置颜色的时候通常是设置文字的颜色还有一个就是背景色。如按下图进 行设置:
我们可以保存一下文荡然后浏览你就可以看到效果了。
四、关于文本的设置。
我们可以使用多种属性来改变网页文本的大小和形状,以使网页文本内容看起来更加美 观。
font-family:设定文字字型 可以取family-name值,范例:SPAN { family-name : " 楷体" }或范例:<SPAN style="family-name:楷体">。
font-style:设定字体样式,可以取的值有normal 普通字、italic 斜体字;范例: SPAN { font-style : italic }。
font-weight:设定字型份量;可以取的值有normal 普通字 、bold 粗体字 、bolder 相对于父元素稍粗 、lighter 相对于父元素稍细 、100,200,300,400,500,600,700,800,900 数字由小到大代表笔画由细到粗,例如:normal=400 bold=700 ;范例:SPAN { font- weight : bolder }。
font-size:设定文字大小。
text-decoration:设定文字修饰,可能值有 none 普通字 、underline 文字加底线 、overline 文字加顶线 、line-through 文字加删除线 、blink 设定文字闪烁 ;范例: SPAN { text-decoration : blink }
text-transform:设定文字转换 ;可能值有none 普通字 、capitalize 将英文单字 地一个字母转换为大写 、uppercase 将所有文字转换为大写 、lowercase 将所有文字转换为 小写 ;范例:SPAN { text-transform : uppercase }。
五、边缘和区块的设置。
MARGIN:边缘,虽然是通透的部份,但是可以藉由边缘宽度的调整来达到内容元素位 置调整的目的。PADDING:补白,也就是内容元素与框架之间的这段距离与空间,也可以利用 CSS指令去控制大小。
把代码改为如图:
他们的属性有:margin-top(上边缘宽度), margin-right(右边缘宽度), margin- bottom(下边缘宽度), margin-left(左边缘宽度), padding-top(上方补白宽度), padding- right(右方补白宽度), padding-bottom(下方补白宽度) 和 padding-left(左方补白宽度)。
下面通过一个图来给大家说明:
看看上图理解点了吧!下面我们开始讲讲边框。
六、边框border性质设定。
边框也能应用到大多数的HTML标签中,可以来使网页更加美观,边框的具体属性有 border-top:综合设定上边框性质 、border-right:综合设定右边框性质 、border-bottom :综合设定下边框性质、 border-left:综合设定左边框性质。
border-style 综合设定边框样式 ,可能值:solid(实线), dotted(虚线), dashed( 短直线), double(双直线), groove (3d凹线), ridge (3d凸线), inset (3d嵌入) 和 outset (3d隆起)。
border-width 综合设定边框宽度,可以设置的有 border-top-width(设定上边框宽度 ), border-right-width(设定右边框宽度), border-bottom-width(设定下边框宽度) 和 border-left-width(设定左边框宽度).
border-color 综合设定边框颜色。
把下面代码加到你的网页中可以看到效果了:
这片文章就介绍到这里了,因为是一个初学者的入门教程,所以内容显得少的可怜。 以后会给大家介绍一些比较高级的内容,希望大家支持我啊!
属性名称
字体属性(Font) font-family font-style font-variant font-weight font-size
颜色和背景属性 Color Background-color Background-image Background-repeat Background-attachment Background-position
文本属性 Word-spacing Letter-spacing Text-decoration
Vertical-align
Text-transform
Text-align Text-indent Line-height
边距属性 Margin-top Margin-right Margin-bottom Margin-left
填充距属性 Padding-top Padding-right Padding-bottom Padding-left
边框属性 Border-top-width Border-right-width Border-bottom-width Border-left-width Border-width Border-color Border-style Border-top Border-right Border-bottom Border-left Width Height Float Clear
分级属性 Display White-space List-style-type List-style-image List-style-position List-style
鼠标(Cursor)属性 |
属性含义
使用什么字体 字体是否斜体 是否用小体大写 字体的粗细 字体的大小
定义前景色 定义背景色 定义背景图案 重复方式 设置滚动 初始位置
单词之间的间距 字母之间的间距 文字的装饰样式
垂直方向的位置
文本转换
对齐方式 首行的缩进方式 文本的行高
顶端边距 右侧边距 底端边距 左侧边距
顶端填充距 右侧填充距 底端填充距 左侧填充距
顶端边框宽度 右侧边框宽度 底端边框宽度 左侧边框宽度 一次定义宽度 设置边框颜色 设置边框样式 一次定义顶端 一次定义右侧 一次定义底端 一次定义左侧 定义宽度属性 定义高度属性 文字环绕 哪一边环绕
定义是否显示 怎样处理空白 加项目编号 加图案 第二行起始位置 一次定义列表
自动 定位“十”字 默认指针 手形 移动 箭头朝右方 箭头朝右上方 箭头朝左上方 箭头朝上方 箭头朝右下方 箭头朝左下方 箭头朝下方 箭头朝左方 文本“I”形 等待 帮助
|
MANIFEST.MF 文件内容详解 |
|
打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目录,这个目录下会有一些文件,其中必有一个MANIFEST.MF,这个文件描述了该Jar文件的很多信息,下面将详细介绍MANIFEST.MF文件的内容,先来看struts.jar中包含的MANIFEST.MF文件内容:
Manifest-Version: 1.0 Created-By: Apache Ant 1.5.1 Extension-Name: Struts Framework Specification-Title: Struts Framework Specification-Vendor: Apache Software Foundation Specification-Version: 1.1 Implementation-Title: Struts Framework Implementation-Vendor: Apache Software Foundation Implementation-Vendor-Id: org.apache Implementation-Version: 1.1 Class-Path: commons-beanutils.jar commons-collections.jar commons-dig ester.jar commons-logging.jar commons-validator.jar jakarta-oro.jar s truts-legacy.jar
如果我们把MANIFEST中的配置信息进行分类,可以归纳出下面几个大类:
一. 一般属性
1. Manifest-Version 用来定义manifest文件的版本,例如:Manifest-Version: 1.0 2. Created-By 声明该文件的生成者,一般该属性是由jar命令行工具生成的,例如:Created-By: Apache Ant 1.5.1 3. Signature-Version 定义jar文件的签名版本 4. Class-Path 应用程序或者类装载器使用该值来构建内部的类搜索路径
二. 应用程序相关属性
1. Main-Class 定义jar文件的入口类,该类必须是一个可执行的类,一旦定义了该属性即可通过 java -jar x.jar来运行该jar文件。 三. 小程序(Applet)相关属性
1. Extendsion-List 该属性指定了小程序需要的扩展信息列表,列表中的每个名字对应以下的属性 2. <extension>-Extension-Name 3. <extension>-Specification-Version 4. <extension>-Implementation-Version 5. <extension>-Implementation-Vendor-Id 5. <extension>-Implementation-URL
四. 扩展标识属性
1. Extension-Name 该属性定义了jar文件的标识,例如Extension-Name: Struts Framework 五. 包扩展属性 1. Implementation-Title 定义了扩展实现的标题 2. Implementation-Version 定义扩展实现的版本 3. Implementation-Vendor 定义扩展实现的组织 4. Implementation-Vendor-Id 定义扩展实现的组织的标识 5. Implementation-URL : 定义该扩展包的下载地址(URL) 6. Specification-Title 定义扩展规范的标题 7. Specification-Version 定义扩展规范的版本 8. Specification-Vendor 声明了维护该规范的组织 9. Sealed 定义jar文件是否封存,值可以是true或者false (这点我还不是很理解)
六. 签名相关属性
签名方面的属性我们可以来参照JavaMail所提供的mail.jar中的一段
Name: javax/mail/Address.class Digest-Algorithms: SHA MD5 SHA-Digest: AjR7RqnN//cdYGouxbd06mSVfI4= MD5-Digest: ZnTIQ2aQAtSNIOWXI1pQpw==
这段内容定义类签名的类名、计算摘要的算法名以及对应的摘要内容(使用BASE64方法进行编码)
七.自定义属性
除了前面提到的一些属性外,你也可以在MANIFEST.MF中增加自己的属性以及响应的值,例如J2ME程序jar包中就可能包含着如下信息
MicroEdition-Configuration: CLDC-1.0 MIDlet-Name: J2ME_MOBBER Midlet Suite MIDlet-Info-URL: http://www.javayou.com MIDlet-Icon: /icon.png MIDlet-Vendor: Midlet Suite Vendor MIDlet-1: mobber,/icon.png,mobber MIDlet-Version: 1.0.0 MicroEdition-Profile: MIDP-1.0 MIDlet-Description: Communicator
关键在于我们怎么来读取这些信息呢?其实很简单,JDK给我们提供了用于处理这些信息的API,详细的信息请见java.util.jar包中,我们可以通过给JarFile传递一个jar文件的路径,然后调用JarFile的getManifest方法来获取Manifest信息。
更详细关于JAR文件的规范请见 http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html
中文说明 http://www-900.ibm.com/developerWorks/cn/java/j-jar/ |
ActionMapping类 将特定请求映射到特定Action的相关信息存储在ActionMapping中,ActionServelt将ActionMapping传送到Action类的execute()方法,Action将使用ActionMapping的findForward()方法,此方法返回一个指定名称的ActionForward,这样Action就完成了本地转发。若没有找到具体的ActionForward,就返回一个null. ActionMapping的方法: public ExceptionConfig findException(Class type) ,查找异常对象 public ActionForward findForward(String name) 可在映射中动态添加ActionForward: public String[] findForwards()找到一个Action可以使用的actionForward列表 public ActionForward getInputForward() 得到本action的输入ActionForard. ActionMapping继承于org.apache.struts.config.ActionConfig Action类 Action类真正实现应用程序的事务逻辑,它们负责处理请求。在收到请求后,ActionServlet会: 1.为这个请求选择适当的Action 2.如果需要,创建Action的一个实例 3.调用Action的execute()方法 如果ActionServlet不能找到有效的映射,它会调用默认的Action类(在配置文件中定义)。如果找到了ActionServlet将适当的ActionMapping类转发给Action,这个Action使用ActionMapping找到本地转发,然后获得并设置ActionMapping属性。根据servlet的环境和被覆盖的execute ()方法的签名,ActionServlet也会传送ServletRequest对象或HttpServletRequest对象。 所有Action类都扩展org.apache.struts.action.Action类,并且覆盖类中定义的某一个execute ()方法。有两个execute ()方法: 处理非HTTP(一般的)请求: public ActionForward execute (ActionMapping action, Acionform form, ServletRequest request, ServletResponse response) throws java.lang.Exception 处理HTTP请求: public ActionForward execute (ActionMapping action, Acionform form, HttpServletRequest request, HttpServletResponse response) throws java.lang.Exception Action类必须以”线程安全”的方式进行编程,因为控制器会令多个同时发生的请求共享同一个实例,相应的,在设计Action类时就需要注意以下几点: 不能使用实例或静态变量存储特定请求的状态信息,它们会在同一个操作中 共享跨越请求的全局资源 如果要访问的资源(如JavaBeans和会话变量)在并行访问时需要进行保护,那么访问就要进行同步 Action类的方法 除了execute ()方法外,还有以下方法: 可以获得或设置与请求相关联的区域: public Locale getLocale(HttpServletRequest request) public void setLocale(HttpServletRequest request,Locale locale) 为应用程序获得消息资源: protected MessageResources getResources(HttpServletRequest request) protected MessageResources getResources(HttpServletRequest request,String key) 检查用户是否点击表单上的”取消”键,如果是,将返回true: public Boolean isCancelled(HttpServletRequest request) 当应用程序发生错误时,Action类能够使用下面方法存储错误信息: public void saveErrors(HttpServletRequest request,ActionErrors errors) public void saveMessages(HttpServletRequest request,ActionMessages messages) ActionError实例被用来存储错误信息,这个方法在错误关键字下的请求属性列表中存储ActionError对象。通过使用在struts标记库中定义的自定义标记,JSP页能够显示这些错误信息。ActionMessages 用来存储一些提示信息,不是错误,在jsp页面可以使用标记现实这些提示信息。 请求有效性处理,使用令牌可以有效的防止重复提交。 protected String generateToken(HttpServletRequest request) 创建一个令牌. protected boolean isTokenValid(HttpServletRequest request) 检查令牌是否有效 protected boolean isTokenValid(HttpServletRequest request,Boolean reset) 检查令牌是否有效,并且重置令牌(如果reset 是true) protected void resetToken(HttpServletRequest request) 重置令牌 protected void saveToken(HttpServletRequest request) 添加令牌 获取数据库连接 protected DataSource getDataSource(HttpServletRequest request) protected DataSource getDataSource(HttpServletRequest request, String key) 其他的 ActionServlet getServlet() 可以获得本action的配置信息.
DispatchAction类 DispatchAction是Action的子类,主要功能可以实现,动态的方法调用。例如action中有一个方法update(ActionMapping mapping, Actionform form, HttpServletRequest request, HttpServletResponse response), 可以通过 saveSubs cription.do?method=update来调用update方法。这个类不需要我们实现其他方法,我们只要实现 XXX(ActionMapping mapping, Actionform form, HttpServletRequest request, HttpServletResponse response)就可以了。 在http://www.chinajavaworld.net/forum/topic.cgi?forum=48&topic=1166&show=150和 http://www.chinajavaworld.net/forum/topic.cgi?forum=48&topic=1129有对DispatchAction和LookupDispatchAction的详细介绍
SwitchAction类 SwitchAction是Action的子类,主要功能是将请求在不同的模块之间转发。对于大的项目很有用。具体看http://www.chinajavaworld.net/forum/topic.cgi?forum=48&topic=1029&show=0
Actionform类 假设用户在应用程序中为每个表单都创建了一个Actionform bean,对于每个在struts-config.xml文件中定义的bean,框架在调用Action类的execute()方法之前会进行以下操作: 在相关联的关键字下,它检查用于适当类的bean实例的用户会话(或请求),如果在会话(或请求)中没有可用的bean,它就会自动创建一个新的bean并添加到用户的会话(或请求)中。至于是在会话还是请求取决于struts-config.xml 中Action 的scope属性。在创建Actionform的时候,系统会将请求中的值,进行相应的类型转换以后对Actionform进行初始化。 对于请求中每个与bean属性名称对应的参数,Action调用相应的设置方法。 当Action execute()被调用时,最新的Actionform bean传送给它,参数值就可以立即使用了。 Actionform类扩展org.apache.struts.action.Actionform类,程序开发人员创建的bean能够包含额外的属性,而且ActionServlet可能使用反射(允许从已加载的对象中回收信息)访问它。 Actionform类提供了另一种处理错误的手段,提供两个方法: Public ActionErrors validate(ActionMappin mapping, ServletRequest request) Public ActionErrors validate(ActionMappin mapping, HttpServletRequest request) 你应该在自己的bean里覆盖validate()方法,并在配置文件里设置<action>元素的validate为true。在ActionServlet调用Action类前,它会调用validate(),如果返回的ActionErrors不是null,则Actinform会根据错误关键字将ActionErrors存储在请求属性列表中。 如果返回的不是null,而且长度大于0,则根据错误关键字将实例存储在请求的属性列表中,然后ActionServlet将响应转发到配置文件<action>元素的input属性所指向的目标。 如果需要执行特定的数据有效性检查,最好在Action类中进行这个操作,而不是在Actionform类中进行。 方法reset()可将bean的属性恢复到默认值: public void reset(ActionMapping mapping,HttpServletRequest request) public void reset(ActionMapping mapping,ServletRequest request) 典型的ActionFrom bean只有属性的设置与读取方法(getXXX),而没有实现事务逻辑的方法。只有简单的输入检查逻辑,使用的目的是为了存储用户在相关表单中输入的最新数据,以便可以将同一网页进行再生,同时提供一组错误信息,这样就可以让用户修改不正确的输入数据。而真正对数据有效性进行检查的是Action类或适当的事务逻辑bean。 Actionform中属性允许的类型boolean,byte,short,char,int,long,float,double,Boolean,Btye,Short, Character,Integer,Long,Float,Double,String,Date,Time,Timestamp,Object,以及以上类型的数组。 如果Actionform bean 的属性是一个数组则相应的设置和读取方法要做部分修改。对于数组setXXX(…),和getXXX()在jsp页面中意义不大。应该将添加setXXX( int index , …. ) 方法和getXXX ( int index )方法。这两个方法对于jsp页面来说更有意义。jsp中的property应该是XXX[0]。 如果Actionform bean的属性是一个 Map则应该提供方法setXXX( String key , … )和getXXX(String key),使得jsp页面可以访问Map属性。jsp中的properry应该是XXX(keyname)。 通过getXXX(int index),setXXX (int index,…),getXXX(String key),setXXX(String key,…)可以方便的实现重复html输入框。 例如: public class Fooform extends Actionform { private String yourName; public String getYourName() { return yourName; } public void setYourName(String yourName) { this.yourName = yourName; } private final Map values = new HashMap(); public void setvalue(String key, Object value) { values.put(key, value); } public Object getvalue(String key) { if ( values.containsKey(key)){ return values.get(key); }else{ return ""; } } … }
Validatorform类 org.apache.struts.validator.Validatorform类继承了Actionform类。使用本类可以方便的实现表单参数的校验。在校验的时候,使用在struts-config.xml中action元素中的name属性,确定要在validation.xml中取得校验规则的依据。 使用本类可以方便的解决同一个form在不同的Action中使用不同的校验规则的问题。在继承了Validatorform的类中不再需要我们去写validate方法。而是由Validatorform中的validate方法通过读取validation.xml中的描述信息来进行数据的校验。使用Validatorform也可以方便的实现在浏览器端实现利用脚本的校验。 Validatorform中的新增加的方法: int getPage() java.util.Map getResultvalueMap() ValidatorResults getValidatorResults() void setPage(int page) void setValidatorResults() 使用本类可以大大提高我们的编程效率。 ValidatorActionform类 org.apache.struts.validator.ValidatorActionform类继承了Validatorform类。使用本类可以方便的实现表单参数的校验。在校验的时候,使用在struts-config.xml中action元素中的path属性,确定要在validation.xml中取得校验规则的依据。
DynaActionform类 org.apache.struts.action.DynaActionform类继承了Actionform类。使用本类可以方便的实现动态表单。创建不确定的表单,如果jsp发生了变化只需要修改jsp页面和struts-config.xml文件中的form-bean元素就可以了。我们的程序中完全可以不用手工书写actionform的类了。 DynaActionform中的方法: boolean contains(String name, String key) 检测name(key)在actionform中是否存在。 Object get(String name) 从actionform中取得name的值。 Object get(String name,int index) 从actionform中取得 name对象的index个值。 Object get(String name,String key)从actionform中取得name对象的key对应的值。 Map getMap() 返回对象中包含的对象属性名列表。 void remove(String name, String key) 删除一个元素。 void set(String name, int index,Object value) 对actionform中的属性进行赋值。 void set(String name,Object value) void set(String name, Strign key ,Object value)
DynaValidatorform类 org.apache.struts.validator.DynaValidatorform类继承了DynaActionform类。使用本类可以方便的实现表单参数的校验。在校验的时候,使用在struts-config.xml中action元素中的name属性,确定要在validation.xml中取得校验规则的依据。 至于校验,同Validatorform。 DynaValidatorform中的新增加的方法: int getPage() java.util.Map getResultvalueMap() ValidatorResults getValidatorResults() void setPage(int page) void setValidatorResults()
DynaValidatorActionform类 org.apache.struts.validator.DynaValidatorActionform类继承了DynaValidatorform类。使用本类可以方便的实现表单参数的校验。在校验的时候,使用在struts-config.xml中action元素中的path属性,确定要在validation.xml中取得校验规则的依据。 至于校验,同Validatorform。
ActionForward类 ActionForward类继承了org.apache.struts.config.ForwardConfig。 ForwardConfig的方法: String getName() 虚名字 String getPath() 实际路径 boolean getRedirect() 是否重定向 void setName(String name) void setPath(String path) void setRedirect(boolean redirect)
ActionForward目的是控制器将Action类的处理结果转发至目的地。 Action类获得ActionForward实例的句柄,然后可用两种方法返回到ActionServlet, ActionMapping实例被传送到execute()方法,使用actionMapping的findForward(String name)根据名称获取一个全局转发或本地转发。 另一种是调用下面的一个构造器来创建它们自己的一个实例: public ActionForward() public ActionForward(String path) public ActionForward(String path,Boolean redirect) public ActionForward(String name,String path,Boolean redirect) public ActionForward(String name,String path,Boolean redirect, boolean contextRelative) 或下面的构造方法(下面是ActionForward的子类) ForwardingActionForward() ForwardingActionForward(String path) RedirectingActionForward() RedirectingActionForward(String path)
一些初学JAVA的朋友可能会遇到JAVA的数据类型之间转换的苦恼,例如,整数和float,double型之间的转换,整数和String类型之间的转换,以及处理、显示时间方面的问题等。下面笔者就开发中的一些体会介绍给大家。 我们知道,Java的数据类型分为三大类,即布尔型、字符型和数值型,而其中数值型又分为整型和浮点型;相对于数据类型,Java的变量类型为布尔型boolean;字符型char;整型byte、short、int、long;浮点型float、double。其中四种整型变量和两种浮点型变量分别对应于不同的精度和范围。此外,我们还经常用到两种类变量,即String和Date。对于这些变量类型之间的相互转换在我们编程中经常要用到,在下面的论述中,我们将阐述如何实现这些转换。 1 数据类型转换的种类 java数据类型的转换一般分三种,分别是: (1). 简单数据类型之间的转换 (2). 字符串与其它数据类型的转换 (3). 其它实用数据类型转换 下面我们对这三种类型转换分别进行论述。 2 简单数据类型之间的转换 在Java中整型、实型、字符型被视为简单数据类型,这些类型由低级到高级分别为 [center](byte,short,char)--int--long--float--double[/center] 简单数据类型之间的转换又可以分为: ●低级到高级的自动类型转换 ●高级到低级的强制类型转换 ●包装类过渡类型能够转换 2.1自动类型转换 低级变量可以直接转换为高级变量,笔者称之为自动类型转换,例如,下面的语句可以在Java中直接通过:
byte b;int i=b;long l=b;float f=b;double d=b;
如果低级类型为char型,向高级类型(整型)转换时,会转换为对应ASCII码值,例如
char c='c'; int i=c; System.out.println("output:"+i);
输出:output:99; 对于byte,short,char三种类型而言,他们是平级的,因此不能相互自动转换,可以使用下述的强制类型转换。
short i=99;char c=(char)i;System.out.println("output:"+c);
输出:output:c; 但根据笔者的经验,byte,short,int三种类型都是整型,因此如果操作整型数据时,最好统一使用int型。 2.2强制类型转换 将高级变量转换为低级变量时,情况会复杂一些,你可以使用强制类型转换。即你必须采用下面这种语句格式:
int i=99;byte b=(byte)i;char c=(char)i;float f=(float)i;
可以想象,这种转换肯定可能会导致溢出或精度的下降,因此笔者并不推荐使用这种转换。 2.3包装类过渡类型转换 在我们讨论其它变量类型之间的相互转换时,我们需要了解一下Java的包装类,所谓包装类,就是可以直接将简单类型的变量表示为一个类,在执行变量类型的相互转换时,我们会大量使用这些包装类。Java共有六个包装类,分别是Boolean、Character、Integer、Long、Float和Double,从字面上我们就可以看出它们分别对应于 boolean、char、int、long、float和double。而String和Date本身就是类。所以也就不存在什么包装类的概念了。 在进行简单数据类型之间的转换(自动转换或强制转换)时,我们总是可以利用包装类进行中间过渡。 一般情况下,我们首先声明一个变量,然后生成一个对应的包装类,就可以利用包装类的各种方法进行类型转换了。例如: 例1,当希望把float型转换为double型时:
float f1=100.00f; Float F1=new float(f1); Double d1=F1.doubleValue();//F1.doubleValue()为Float类的返回double值型的方法
当希望把double型转换为int型时:
double d1=100.00; Double D1=new Double(d1); int i1=D1.intValue();
当希望把int型转换为double型时,自动转换:
int i1=200; double d1=i1;
简单类型的变量转换为相应的包装类,可以利用包装类的构造函数。即: Boolean(boolean value)、Character(char value)、Integer(int value)、Long(long value)、Float(float value)、Double(double value) 而在各个包装类中,总有形为××Value()的方法,来得到其对应的简单类型数据。利用这种方法,也可以实现不同数值型变量间的转换,例如,对于一个双精度实型类,intValue()可以得到其对应的整型变量,而doubleValue()可以得到其对应的双精度实型变量。 3 字符串型与其它数据类型的转换 通过查阅类库中各个类提供的成员方法可以看到,几乎从java.lang.Object类派生的所有类提供了toString()方法,即将该类转换为字符串。例如:Characrer,Integer,Float,Double,Boolean,Short等类的toString()方法toString()方法用于将字符、整数、浮点数、双精度数、逻辑数、短整型等类转换为字符串。如下所示:
int i1=10;float f1=3.14f;double d1=3.1415926;Integer I1=new Integer(i1);//生成Integer类Float F1=new Float(f1); //生成Float类Double D1=new Double(d1); //生成Double类//分别调用包装类的toString()方法转换为字符串String si1=I1.toString();String sf1=F1.toString();String sd1=D1.toString();Sysytem.out.println("si1"+si1);Sysytem.out.println("sf1"+sf1);Sysytem.out.println("sd1"+sd1);
五、将字符型直接做为数值转换为其它数据类型 将字符型变量转换为数值型变量实际上有两种对应关系,在我们在第一部分所说的那种转换中,实际上是将其转换成对应的ASCII码,但是我们有时还需要另一种转换关系,例如,'1'就是指的数值1,而不是其ASCII码,对于这种转换,我们可以使用Character的getNumericValue(char ch)方法。 六、Date类与其它数据类型的相互转换 整型和Date类之间并不存在直接的对应关系,只是你可以使用int型为分别表示年、月、日、时、分、秒,这样就在两者之间建立了一个对应关系,在作这种转换时,你可以使用Date类构造函数的三种形式: Date(int year, int month, int date):以int型表示年、月、日 Date(int year, int month, int date, int hrs, int min):以int型表示年、月、日、时、分 Date(int year, int month, int date, int hrs, int min, int sec):以int型表示年、月、日、时、分、秒 在长整型和Date类之间有一个很有趣的对应关系,就是将一个时间表示为距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数。对于这种对应关系,Date类也有其相应的构造函数:Date(long date) 获取Date类中的年、月、日、时、分、秒以及星期你可以使用Date类的getYear()、getMonth()、getDate()、getHours()、getMinutes()、getSeconds()、getDay()方法,你也可以将其理解为将Date类转换成int。 而Date类的getTime()方法可以得到我们前面所说的一个时间对应的长整型数,与包装类一样,Date类也有一个toString()方法可以将其转换为String类。 有时我们希望得到Date的特定格式,例如20020324,我们可以使用以下方法,首先在文件开始引入,
import java.text.SimpleDateFormat;import java.util.*;java.util.Date date = new java.util.Date();//如果希望得到YYYYMMDD的格式SimpleDateFormat sy1=new SimpleDateFormat("yyyyMMDD");String dateFormat=sy1.format(date);//如果希望分开得到年,月,日SimpleDateFormat sy=new SimpleDateFormat("yyyy");SimpleDateFormat sm=new SimpleDateFormat("MM");SimpleDateFormat sd=new SimpleDateFormat("dd");String syear=sy.format(date);String smon=sm.format(date);String sday=sd.format(date);
结束语: 当然,笔者的论述只是一人之见,如果希望更多了解有关JAVA数据类型转换的知识,笔者建议参考JAVA类库java.util.*下面的 Integer类 Boolean类 Character类 Float类 Double类 String类 Date类 根据自己的需要,参考不同类的各种成员方法进行数据类型之间的转换。 大家可以参考JAVA的网上类库或下载一份,来更灵活的利用其中的成员方法进行数据类型之间的转换,IP地址: http://java.sun.com/j2se/1.3/docs/api/index.html
|
|
一. Input和Output 1. stream代表的是任何有能力产出数据的数据源,或是任何有能力接收数据的接收源。在Java的IO中,所有的stream(包括Input和Out stream)都包括两种类型: 1.1 以字节为导向的stream 以字节为导向的stream,表示以字节为单位从stream中读取或往stream中写入信息。以字节为导向的stream包括下面几种类型: 1) input stream: 1) ByteArrayInputStream:把内存中的一个缓冲区作为InputStream使用 2) StringBufferInputStream:把一个String对象作为InputStream 3) FileInputStream:把一个文件作为InputStream,实现对文件的读取操作 4) PipedInputStream:实现了pipe的概念,主要在线程中使用 5) SequenceInputStream:把多个InputStream合并为一个InputStream 2) Out stream 1) ByteArrayOutputStream:把信息存入内存中的一个缓冲区中 2) FileOutputStream:把信息存入文件中 3) PipedOutputStream:实现了pipe的概念,主要在线程中使用 4) SequenceOutputStream:把多个OutStream合并为一个OutStream 1.2 以Unicode字符为导向的stream 以Unicode字符为导向的stream,表示以Unicode字符为单位从stream中读取或往stream中写入信息。以Unicode字符为导向的stream包括下面几种类型: 1) Input Stream 1) CharArrayReader:与ByteArrayInputStream对应 2) StringReader:与StringBufferInputStream对应 3) FileReader:与FileInputStream对应 4) PipedReader:与PipedInputStream对应 2) Out Stream 1) CharArrayWrite:与ByteArrayOutputStream对应 2) StringWrite:无与之对应的以字节为导向的stream 3) FileWrite:与FileOutputStream对应 4) PipedWrite:与PipedOutputStream对应 以字符为导向的stream基本上对有与之相对应的以字节为导向的stream。两个对应类实现的功能相同,字是在操作时的导向不同。如CharArrayReader:和ByteArrayInputStream的作用都是把内存中的一个缓冲区作为InputStream使用,所不同的是前者每次从内存中读取一个字节的信息,而后者每次从内存中读取一个字符。 1.3 两种不现导向的stream之间的转换 InputStreamReader和OutputStreamReader:把一个以字节为导向的stream转换成一个以字符为导向的stream。 2. stream添加属性 2.1 “为stream添加属性”的作用 运用上面介绍的Java中操作IO的API,我们就可完成我们想完成的任何操作了。但通过FilterInputStream和FilterOutStream的子类,我们可以为stream添加属性。下面以一个例子来说明这种功能的作用。 如果我们要往一个文件中写入数据,我们可以这样操作: FileOutStream fs = new FileOutStream(“test.txt”); 然后就可以通过产生的fs对象调用write()函数来往test.txt文件中写入数据了。但是,如果我们想实现“先把要写入文件的数据先缓存到内存中,再把缓存中的数据写入文件中”的功能时,上面的API就没有一个能满足我们的需求了。但是通过FilterInputStream和FilterOutStream的子类,为FileOutStream添加我们所需要的功能。 2.2 FilterInputStream的各种类型 2.2.1 用于封装以字节为导向的InputStream 1) DataInputStream:从stream中读取基本类型(int、char等)数据。 2) BufferedInputStream:使用缓冲区 3) LineNumberInputStream:会记录input stream内的行数,然后可以调用getLineNumber()和setLineNumber(int) 4) PushbackInputStream:很少用到,一般用于编译器开发 2.2.2 用于封装以字符为导向的InputStream 1) 没有与DataInputStream对应的类。除非在要使用readLine()时改用BufferedReader,否则使用DataInputStream 2) BufferedReader:与BufferedInputStream对应 3) LineNumberReader:与LineNumberInputStream对应 4) PushBackReader:与PushbackInputStream对应 2.3 FilterOutStream的各种类型 2.2.3 用于封装以字节为导向的OutputStream 1) DataIOutStream:往stream中输出基本类型(int、char等)数据。 2) BufferedOutStream:使用缓冲区 3) PrintStream:产生格式化输出 2.2.4 用于封装以字符为导向的OutputStream 1) BufferedWrite:与对应 2) PrintWrite:与对应 3. RandomAccessFile 1) 可通过RandomAccessFile对象完成对文件的读写操作 2) 在产生一个对象时,可指明要打开的文件的性质:r,只读;w,只写;rw可读写 3) 可以直接跳到文件中指定的位置 4. I/O应用的一个例子 import java.io.*; public class TestIO{ public static void main(String[] args) throws IOException{ //1.以行为单位从一个文件读取数据 BufferedReader in = new BufferedReader( new FileReader("F:\\nepalon\\TestIO.java")); String s, s2 = new String(); while((s = in.readLine()) != null) s2 += s + "\n"; in.close();
//1b. 接收键盘的输入 BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Enter a line:"); System.out.println(stdin.readLine());
//2. 从一个String对象中读取数据 StringReader in2 = new StringReader(s2); int c; while((c = in2.read()) != -1) System.out.println((char)c); in2.close();
//3. 从内存取出格式化输入 try{ DataInputStream in3 = new DataInputStream( new ByteArrayInputStream(s2.getBytes())); while(true) System.out.println((char)in3.readByte()); } catch(EOFException e){ System.out.println("End of stream"); }
//4. 输出到文件 try{ BufferedReader in4 = new BufferedReader( new StringReader(s2)); PrintWriter out1 = new PrintWriter( new BufferedWriter( new FileWriter("F:\\nepalon\\ TestIO.out"))); int lineCount = 1; while((s = in4.readLine()) != null) out1.println(lineCount++ + ":" + s); out1.close(); in4.close(); } catch(EOFException ex){ System.out.println("End of stream"); }
//5. 数据的存储和恢复 try{ DataOutputStream out2 = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("F:\\nepalon\\ Data.txt"))); out2.writeDouble(3.1415926); out2.writeChars("\nThas was pi:writeChars\n"); out2.writeBytes("Thas was pi:writeByte\n"); out2.close(); DataInputStream in5 = new DataInputStream( new BufferedInputStream( new FileInputStream("F:\\nepalon\\ Data.txt"))); BufferedReader in5br = new BufferedReader( new InputStreamReader(in5)); System.out.println(in5.readDouble()); System.out.println(in5br.readLine()); System.out.println(in5br.readLine()); } catch(EOFException e){ System.out.println("End of stream"); }
//6. 通过RandomAccessFile操作文件 RandomAccessFile rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "rw"); for(int i=0; i<10; i++) rf.writeDouble(i*1.414); rf.close();
rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "r"); for(int i=0; i<10; i++) System.out.println("Value " + i + ":" + rf.readDouble()); rf.close();
rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "rw"); rf.seek(5*8); rf.writeDouble(47.0001); rf.close();
rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "r"); for(int i=0; i<10; i++) System.out.println("Value " + i + ":" + rf.readDouble()); rf.close(); } } 关于代码的解释(以区为单位): 1区中,当读取文件时,先把文件内容读到缓存中,当调用in.readLine()时,再从缓存中以字符的方式读取数据(以下简称“缓存字节读取方式”)。 1b区中,由于想以缓存字节读取方式从标准IO(键盘)中读取数据,所以要先把标准IO(System.in)转换成字符导向的stream,再进行BufferedReader封装。 2区中,要以字符的形式从一个String对象中读取数据,所以要产生一个StringReader类型的stream。 4区中,对String对象s2读取数据时,先把对象中的数据存入缓存中,再从缓冲中进行读取;对TestIO.out文件进行操作时,先把格式化后的信息输出到缓存中,再把缓存中的信息输出到文件中。 5区中,对Data.txt文件进行输出时,是先把基本类型的数据输出屋缓存中,再把缓存中的数据输出到文件中;对文件进行读取操作时,先把文件中的数据读取到缓存中,再从缓存中以基本类型的形式进行读取。注意in5.readDouble()这一行。因为写入第一个writeDouble(),所以为了正确显示。也要以基本类型的形式进行读取。 6区是通过RandomAccessFile类对文件进行操作。
目前网络上关于对象序列化的文章不少,但是我发现详细叙述用法和原理的文章太少。本人把自己经过经验总结和实际运用中的体会写成的学习笔记贡献给大家。希望能为整个java社区的繁荣做一点事情。 序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。 一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。 二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。 从上面的叙述中,我们知道了对象序列化是java编程中的必备武器,那么让我们从基础开始,好好学习一下它的机制和用法。
java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
序列化机制:
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:
处理对象流:
(序列化过程和反序列化过程)
java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。 我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。 writeObject()方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。 下面,让我们从例子中来了解ObjectOutputStream这个类吧。
// 序列化 today's date 到一个文件中. FileOutputStream f = new FileOutputStream("tmp"); ObjectOutputStream s = new ObjectOutputStream(f); s.writeObject("Today"); s.writeObject(new Date()); s.flush();
现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。 例子如下:
//从文件中反序列化 string 对象和 date 对象 FileInputStream in = new FileInputStream("tmp"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject();
定制序列化过程:
序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。 例子:一个非常简单的序列化类。
public class simpleSerializableClass implements Serializable{ String sToday="Today:"; transient Date dtToday=new Date(); }
序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。 关于如何使用定制序列化的部分代码如下:
//重写writeObject()方法以便处理transient的成员。 public void writeObject(ObjectOutputStream outputStream) throws IOException{ outputStream.defaultWriteObject();//使定制的writeObject()方法可以 利用自动序列化中内置的逻辑。 outputStream.writeObject(oSocket.getInetAddress()); outputStream.writeInt(oSocket.getPort()); } //重写readObject()方法以便接收transient的成员。 private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{ inputStream.defaultReadObject();//defaultReadObject()补充自动序列化 InetAddress oAddress=(InetAddress)inputStream.readObject(); int iPort =inputStream.readInt(); oSocket = new Socket(oAddress,iPort); iID=getID(); dtToday =new Date(); }
完全定制序列化过程:
如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序 列化的高级教程,以后再述。
参考资料:http://java.sun.com/j2se/1.3/docs/guide/serialization/spec/serialTOC.doc.html
定义: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
如何使用? 因为Java中的提供clone()方法来实现对象的克隆(具体了解clone()按这里),所以Prototype模式实现一下子变得很简单.
以勺子为例:
public abstract class AbstractSpoon implements Cloneable { String spoonName;
public void setSpoonName(String spoonName) {this.spoonName = spoonName;} public String getSpoonName() {return this.spoonName;}
public Object clone() { Object object = null; try { object = super.clone(); } catch (CloneNotSupportedException exception) { System.err.println("AbstractSpoon is not Cloneable"); } return object; } }
有两个具体实现(ConcretePrototype):
public class SoupSpoon extends AbstractSpoon { public SoupSpoon() { setSpoonName("Soup Spoon"); } }
public class SaladSpoon extends AbstractSpoon { public SaladSpoon() { setSpoonName("Salad Spoon"); } }
调用Prototype模式很简单:
AbstractSpoon spoon = new SoupSpoon(); AbstractSpoon spoon = new SaladSpoon();
当然也可以结合工厂模式来创建AbstractSpoon实例。
在Java中Prototype模式变成clone()方法的使用,由于Java的纯洁的面向对象特性,使得在Java中使用设计模式变得很自然,两者已经几乎是浑然一体了。这反映在很多模式上,如Interator遍历模式。
Command模式是最让我疑惑的一个模式,我在阅读了很多代码后,才感觉隐约掌握其大概原理,我认为理解设计模式最主要是掌握起原理构造,这样才对自己实际编程有指导作用.Command模式实际上不是个很具体,规定很多的模式,正是这个灵活性,让人有些confuse.
Command定义 不少Command模式的代码都是针对图形界面的,它实际就是菜单命令,我们在一个下拉菜单选择一个命令时,然后会执行一些动作.
将这些命令封装成在一个类中,然后用户(调用者)再对这个类进行操作,这就是Command模式,换句话说,本来用户(调用者)是直接调用这些命令的,如菜单上打开文档(调用者),就直接指向打开文档的代码,使用Command模式,就是在这两者之间增加一个中间者,将这种直接关系拗断,同时两者之间都隔离,基本没有关系了.
显然这样做的好处是符合封装的特性,降低耦合度,Command是将对行为进行封装的典型模式,Factory是将创建进行封装的模式, 从Command模式,我也发现设计模式一个"通病":好象喜欢将简单的问题复杂化, 喜欢在不同类中增加第三者,当然这样做有利于代码的健壮性 可维护性 还有复用性.
如何使用? 具体的Command模式代码各式各样,因为如何封装命令,不同系统,有不同的做法.下面事例是将命令封装在一个Collection的List中,任何对象一旦加入List中,实际上装入了一个封闭的黑盒中,对象的特性消失了,只有取出时,才有可能模糊的分辨出:
典型的Command模式需要有一个接口.接口中有一个统一的方法,这就是"将命令/请求封装为对象":
public interface Command { public abstract void execute ( ); }
具体不同命令/请求代码是实现接口Command,下面有三个具体命令
public class Engineer implements Command {
public void execute( ) { //do Engineer's command } }
public class Programmer implements Command {
public void execute( ) { //do programmer's command } }
public class Politician implements Command {
public void execute( ) { //do Politician's command } }
按照通常做法,我们就可以直接调用这三个Command,但是使用Command模式,我们要将他们封装起来,扔到黑盒子List里去:
public class producer{ public static List produceRequests() { List queue = new ArrayList(); queue.add( new DomesticEngineer() ); queue.add( new Politician() ); queue.add( new Programmer() ); return queue; }
}
这三个命令进入List中后,已经失去了其外表特征,以后再取出,也可能无法分辨出谁是Engineer 谁是Programmer了,看下面如何调用Command模式:
public class TestCommand { public static void main(String[] args) { List queue = Producer.produceRequests(); for (Iterator it = queue.iterator(); it.hasNext(); ) //取出List中东东,其他特征都不能确定,只能保证一个特征是100%正确, // 他们至少是接口Command的"儿子".所以强制转换类型为接口Command
((Command)it.next()).execute();
} }
由此可见,调用者基本只和接口打交道,不合具体实现交互,这也体现了一个原则,面向接口编程,这样,以后增加第四个具体命令时,就不必修改调用者TestCommand中的代码了.
理解了上面的代码的核心原理,在使用中,就应该各人有自己方法了,特别是在如何分离调用者和具体命令上,有很多实现方法,上面的代码是使用"从List过一遍"的做法.这种做法只是为了演示.
使用Command模式的一个好理由还因为它能实现Undo功能.每个具体命令都可以记住它刚刚执行的动作,并且在需要时恢复.
Command模式在界面设计中应用广泛.Java的Swing中菜单命令都是使用Command模式,由于Java在界面设计的性能上还有欠缺,因此界面设计具体代码我们就不讨论,网络上有很多这样的示例.
Template定义: 定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中.
其实Java的抽象类本来就是Template模式,因此使用很普遍.而且很容易理解和使用,我们直接以示例开始:
public abstract class Benchmark { /** * 下面操作是我们希望在子类中完成 */ public abstract void benchmark();
/** * 重复执行benchmark次数 */ public final long repeat (int count) { if (count <= 0) return 0; else { long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) benchmark();
long stopTime = System.currentTimeMillis(); return stopTime - startTime; } } }
在上例中,我们希望重复执行benchmark()操作,但是对benchmark()的具体内容没有说明,而是延迟到其子类中描述:
public class MethodBenchmark extends Benchmark { /** * 真正定义benchmark内容 */ public void benchmark() {
for (int i = 0; i < Integer.MAX_VALUE; i++){ System.out.printtln("i="+i); } } }
至此,Template模式已经完成,是不是很简单?看看如何使用:
Benchmark operation = new MethodBenchmark(); long duration = operation.repeat(Integer.parseInt(args[0].trim())); System.out.println("The operation took " + duration + " milliseconds");
也许你以前还疑惑抽象类有什么用,现在你应该彻底明白了吧? 至于这样做的好处,很显然啊,扩展性强,以后Benchmark内容变化,我只要再做一个继承子类就可以,不必修改其他应用代码.
Builder模式定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.
Builder模式是一步一步创建一个复杂的对象,它允许用户可以只通过指定复杂对象的类型和内容就可以构建它们.用户不知道内部的具体构建细节.Builder模式是非常类似抽象工厂模式,细微的区别大概只有在反复使用中才能体会到.
为何使用? 是为了将构建复杂对象的过程和它的部件解耦.注意: 是解耦过程和部件.
因为一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮 方向盘 发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开.
如何使用? 首先假设一个复杂对象是由多个部件组成的,Builder模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示.
首先,需要一个接口,它定义如何创建复杂对象的各个部件:
public interface Builder {
//创建部件A 比如创建汽车车轮 void buildPartA(); //创建部件B 比如创建汽车方向盘 void buildPartB(); //创建部件C 比如创建汽车发动机 void buildPartC();
//返回最后组装成品结果 (返回最后装配好的汽车) //成品的组装过程不在这里进行,而是转移到下面的Director类中进行. //从而实现了解耦过程和部件 Product getResult();
}
用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说Director的内容是如何将部件最后组装成成品:
public class Director {
private Builder builder;
public Director( Builder builder ) { this.builder = builder; } // 将部件partA partB partC最后组成复杂对象 //这里是将车轮 方向盘和发动机组装成汽车的过程 public void construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC();
}
}
Builder的具体实现ConcreteBuilder: 通过具体完成接口Builder来构建或装配产品的部件; 定义并明确它所要创建的是什么具体东西; 提供一个可以重新获取产品的接口:
public class ConcreteBuilder implements Builder {
Part partA, partB, partC; public void buildPartA() { //这里是具体如何构建partA的代码
}; public void buildPartB() { //这里是具体如何构建partB的代码 }; public void buildPartC() { //这里是具体如何构建partB的代码 }; public Product getResult() { //返回最后组装成品结果 };
}
复杂对象:产品Product:
public interface Product { }
复杂对象的部件:
public interface Part { }
我们看看如何调用Builder模式: ConcreteBuilder builder = new ConcreteBuilder(); Director director = new Director( builder );
director.construct(); Product product = builder.getResult();
Builder模式的应用 在Java实际使用中,我们经常用到"池"(Pool)的概念,当资源提供者无法提供足够的资源,并且这些资源需要被很多用户反复共享时,就需要使用池.
"池"实际是一段内存,当池中有一些复杂的资源的"断肢"(比如数据库的连接池,也许有时一个连接会中断),如果循环再利用这些"断肢",将提高内存使用效率,提高池的性能.修改Builder模式中Director类使之能诊断"断肢"断在哪个部件上,再修复这个部件.
Visitor定义 作用于某个对象群中各个对象的操作. 它可以使你在不改变这些对象本身的情况下,定义作用于这些对象的新操作.
在Java中,Visitor模式实际上是分离了collection结构中的元素和对这些元素进行操作的行为.
为何使用Visitor? Java的Collection(包括Vector和Hashtable)是我们最经常使用的技术,可是Collection好象是个黑色大染缸,本来有各种鲜明类型特征的对象一旦放入后,再取出时,这些类型就消失了.那么我们势必要用If来判断,如:
Iterator iterator = collection.iterator() while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof Collection) messyPrintCollection((Collection)o); else if (o instanceof String) System.out.println("'"+o.toString()+"'"); else if (o instanceof Float) System.out.println(o.toString()+"f"); else System.out.println(o.toString()); } 在上例中,我们使用了 instanceof来判断 o的类型.
很显然,这样做的缺点代码If else if 很繁琐.我们就可以使用Visitor模式解决它.
如何使用Visitor? 针对上例,我们设计一个接口visitor访问者:
public interface Visitor { public void visitCollection(Collection collection); public void visitString(String string); public void visitFloat(Float float); }
在这个接口中,将我们认为Collection有可能的类的类型放入其中.
有了访问者,我们需要被访问者,被访问者就是我们Collection的每个元素Element,我们要为这些Element定义一个可以接受访问的接口(访问和被访问是互动的,只有访问者,被访问者如果表示不欢迎,访问者就不能访问),
我们定义这个接口叫Visitable,用来定义一个Accept操作,也就是说让Collection每个元素具备可访问性.
public interface Visitable { public void accept(Visitor visitor); }
好了,有了两个接口,我们就要定义他们的具体实现(Concrete class):
public class ConcreteElement implements Visitable { private String value; public ConcreteElement(String string) { value = string; } //定义accept的具体内容 这里是很简单的一句调用 public void accept(Visitor visitor) { visitor.visitString(this); } }
再看看访问者的Concrete实现:
public class ConcreteVisitor implements Visitor { //在本方法中,我们实现了对Collection的元素的成功访问 public void visitCollection(Collection collection) { Iterator iterator = collection.iterator() while (iterator.hasNext()) { Object o = iterator.next(); if (o instanceof Visitable) ((Visitable)o).accept(this); }
public void visitString(String string) { System.out.println("'"+string+"'"); }
public void visitFloat(Float float) { System.out.println(float.toString()+"f"); } }
在上面的visitCollection我们实现了对Collection每个元素访问,只使用了一个判断语句,只要判断其是否可以访问.
至此,我们完成了Visitor模式基本架构.
使用Visitor模式的前提 对象群结构中(Collection) 中的对象类型很少改变,也就是说访问者的身份类型很少改变,如上面中Visitor中的类型很少改变,如果需要增加新的操作,比如上例中我们在ConcreteElement具体实现外,还需要新的ConcreteElement2 ConcreteElement3.
可见使用Visitor模式是有前提的,在两个接口Visitor和Visitable中,确保Visitor很少变化,变化的是Visitable,这样使用Visitor最方便.
如果Visitor也经常变化, 也就是说,对象群中的对象类型经常改变,一般建议是,不如在这些对象类中逐个定义操作.但是Java的Reflect技术解决了这个问题.
定义: Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
在很多操作中,比如建立目录 数据库连接都需要这样的单线程操作。
还有, singleton能够被状态化; 这样,多个单态类在一起就可以作为一个状态仓库一样向外提供服务,比如,你要论坛中的帖子计数器,每次浏览一次需要计数,单态类能否保持住这个计数,并且能synchronize的安全自动加1,如果你要把这个数字永久保存到数据库,你可以在不修改单态接口的情况下方便的做到。
另外方面,Singleton也能够被无状态化。提供工具性质的功能,
Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。
我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。
如何使用? 一般Singleton模式通常有几种形式:
public class Singleton {
private Singleton(){}
//在自己内部定义自己一个实例,是不是很奇怪? //注意这是private 只供内部调用
private static Singleton instance = new Singleton();
//这里提供了一个供外部访问本class的静态方法,可以直接访问 public static Singleton getInstance() { return instance; } }
第二种形式:
public class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次 //使用时生成实例,提高了效率! if (instance==null) instance=new Singleton(); return instance; }
}
使用Singleton.getInstance()可以访问单态类。
上面第二中形式是lazy initialization,也就是说第一次调用时初始Singleton,以后就不用再生成了。
注意到lazy initialization形式中的synchronized,这个synchronized很重要,如果没有synchronized,那么使用getInstance()是有可能得到多个Singleton实例。关于lazy initialization的Singleton有很多涉及double-checked locking (DCL)的讨论,有兴趣者进一步研究。
一般认为第一种形式要更加安全些。
使用Singleton注意事项: 有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的。
我们以SUN公司的宠物店源码(Pet Store 1.3.1)的ServiceLocator为例稍微分析一下:
在Pet Store中ServiceLocator有两种,一个是EJB目录下;一个是WEB目录下,我们检查这两个ServiceLocator会发现内容差不多,都是提供EJB的查询定位服务,可是为什么要分开呢?仔细研究对这两种ServiceLocator才发现区别:在WEB中的ServiceLocator的采取Singleton模式,ServiceLocator属于资源定位,理所当然应该使用Singleton模式。但是在EJB中,Singleton模式已经失去作用,所以ServiceLocator才分成两种,一种面向WEB服务的,一种是面向EJB服务的。
Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类 线程 内存等概念有相当的了解。
定义:提供创建对象的接口.
为何使用? 工厂模式是我们最常用的模式了,著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。
为什么工厂模式是如此常用?因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑实用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
我们以类Sample为例, 如果我们要创建Sample的实例对象:
Sample sample=new Sample();
可是,实际情况是,通常我们都要在创建sample实例时做点初始化的工作,比如赋值 查询数据库等。
首先,我们想到的是,可以使用Sample的构造函数,这样生成实例就写成:
Sample sample=new Sample(参数);
但是,如果创建sample实例时所做的初始化工作不是象赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了(就需要Refactor重整)。
为什么说代码很难看,初学者可能没有这种感觉,我们分析如下,初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也是有背于Java面向对象的原则,面向对象的封装(Encapsulation)和分派(Delegation)告诉我们,尽量将长的代码分派“切割”成每段,将每段再“封装”起来(减少段和段之间偶合联系性),这样,就会将风险分散,以后如果需要修改,只要更改每段,不会再发生牵一动百的事情。
在本例中,首先,我们需要将创建实例的工作与使用实例的工作分开, 也就是说,让创建实例所需要的大量初始化工作从Sample的构造函数中分离出去。
这时我们就需要Factory工厂模式来生成对象了,不能再用上面简单new Sample(参数)。还有,如果Sample有个继承如MySample, 按照面向接口编程,我们需要将Sample抽象成一个接口.现在Sample是接口,有两个子类MySample 和HisSample .我们要实例化他们时,如下:
Sample mysample=new MySample(); Sample hissample=new HisSample();
随着项目的深入,Sample可能还会"生出很多儿子出来", 那么我们要对这些儿子一个个实例化,更糟糕的是,可能还要对以前的代码进行修改:加入后来生出儿子的实例.这在传统程序中是无法避免的.
但如果你一开始就有意识使用了工厂模式,这些麻烦就没有了.
工厂方法 你会建立一个专门生产Sample实例的工厂:
public class Factory{
public static Sample creator(int which){
//getClass 产生Sample 一般可使用动态类装载装入类。 if (which==1) return new SampleA(); else if (which==2) return new SampleB();
}
}
那么在你的程序中,如果要实例化Sample时.就使用
Sample sampleA=Factory.creator(1);
这样,在整个就不涉及到Sample的具体子类,达到封装效果,也就减少错误修改的机会,这个原理可以用很通俗的话来比喻:就是具体事情做得越多,越容易范错误.这每个做过具体工作的人都深有体会,相反,官做得越高,说出的话越抽象越笼统,范错误可能性就越少.好象我们从编程序中也能悟出人生道理?呵呵.
使用工厂方法 要注意几个角色,首先你要定义产品接口,如上面的Sample,产品接口下有Sample接口的实现类,如SampleA,其次要有一个factory类,用来生成产品Sample,如下图,最右边是生产的对象Sample:
进一步稍微复杂一点,就是在工厂类上进行拓展,工厂类也有继承它的实现类concreteFactory了。
抽象工厂 工厂模式中有: 工厂方法(Factory Method) 抽象工厂(Abstract Factory).
这两个模式区别在于需要创建对象的复杂程度上。如果我们创建对象的方法变得复杂了,如上面工厂方法中是创建一个对象Sample,如果我们还有新的产品接口Sample2.
这里假设:Sample有两个concrete类SampleA和SamleB,而Sample2也有两个concrete类Sample2A和SampleB2
那么,我们就将上例中Factory变成抽象类,将共同部分封装在抽象类中,不同部分使用子类实现,下面就是将上例中的Factory拓展成抽象工厂:
public abstract class Factory{
public abstract Sample creator();
public abstract Sample2 creator(String name);
}
public class SimpleFactory extends Factory{
public Sample creator(){ ......... return new SampleA }
public Sample2 creator(String name){ ......... return new Sample2A }
}
public class BombFactory extends Factory{
public Sample creator(){ ...... return new SampleB }
public Sample2 creator(String name){ ...... return new Sample2B }
}
从上面看到两个工厂各自生产出一套Sample和Sample2,也许你会疑问,为什么我不可以使用两个工厂方法来分别生产Sample和Sample2?
抽象工厂还有另外一个关键要点,是因为 SimpleFactory内,生产Sample和生产Sample2的方法之间有一定联系,所以才要将这两个方法捆绑在一个类中,这个工厂类有其本身特征,也许制造过程是统一的,比如:制造工艺比较简单,所以名称叫SimpleFactory。
在实际应用中,工厂方法用得比较多一些,而且是和动态类装入器组合在一起应用,
举例
我们以Jive的ForumFactory为例,这个例子在前面的Singleton模式中我们讨论过,现在再讨论其工厂模式:
public abstract class ForumFactory {
private static Object initLock = new Object(); private static String className = "com.jivesoftware.forum.database.DbForumFactory"; private static ForumFactory factory = null;
public static ForumFactory getInstance(Authorization authorization) { //If no valid authorization passed in, return null. if (authorization == null) { return null; on 单态模式 if (factory == null) { synchronized(initLock) { if (factory == null) { ......
try { //动态转载类 Class c = Class.forName(className); factory = (ForumFactory)c.newInstance(); } catch (Exception e) { return null; } } } }
//Now, 返回 proxy.用来限制授权对forum的访问 return new ForumFactoryProxy(authorization, factory, factory.getPermissions(authorization)); }
//真正创建forum的方法由继承forumfactory的子类去完成. public abstract Forum createForum(String name, String des cription) throws UnauthorizedException, ForumAlreadyExistsException;
....
}
因为现在的Jive是通过数据库系统存放论坛帖子等内容数据,如果希望更改为通过文件系统实现,这个工厂方法ForumFactory就提供了提供动态接口:
private static String className = "com.jivesoftware.forum.database.DbForum} //以下使用了SingletFactory";
你可以使用自己开发的创建forum的方法代替com.jivesoftware.forum.database.DbForumFactory就可以.
在上面的一段代码中一共用了三种模式,除了工厂模式外,还有Singleton单态模式,以及proxy模式,proxy模式主要用来授权用户对forum的访问,因为访问forum有两种人:一个是注册用户 一个是游客guest,那么那么相应的权限就不一样,而且这个权限是贯穿整个系统的,因此建立一个proxy,类似网关的概念,可以很好的达到这个效果.
看看Java宠物店中的CatalogDAOFactory:
public class CatalogDAOFactory {
/**
* 本方法制定一个特别的子类来实现DAO模式。 * 具体子类定义是在J2EE的部署描述器中。 */
public static CatalogDAO getDAO() throws CatalogDAOSysException {
CatalogDAO catDao = null;
try {
InitialContext ic = new InitialContext(); //动态装入CATALOG_DAO_CLASS //可以定义自己的CATALOG_DAO_CLASS,从而在无需变更太多代码 //的前提下,完成系统的巨大变更。
String className =(String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);
catDao = (CatalogDAO) Class.forName(className).newInstance();
} catch (NamingException ne) {
throw new CatalogDAOSysException(" CatalogDAOFactory.getDAO: NamingException while getting DAO type : \n" + ne.getMessage());
} catch (Exception se) {
throw new CatalogDAOSysException(" CatalogDAOFactory.getDAO: Exception while getting DAO type : \n" + se.getMessage());
}
return catDao;
}
}
CatalogDAOFactory是典型的工厂方法,catDao是通过动态类装入器className获得CatalogDAOFactory具体实现子类,这个实现子类在Java宠物店是用来操作catalog数据库,用户可以根据数据库的类型不同,定制自己的具体实现子类,将自己的子类名给与CATALOG_DAO_CLASS变量就可以。
由此可见,工厂方法确实为系统结构提供了非常灵活强大的动态扩展机制,只要我们更换一下具体的工厂方法,系统其他地方无需一点变换,就有可能将系统功能进行改头换面的变化。
2.工厂模式的简单用例
工厂模式是J2EE核心模式中较简单的一种,听说Jive论坛中对数种设计模式有着广泛的应用,工厂模式也包括其中,本人就从该模式起步,希望有一天能对各种设计模式运用自如。
现给出一简单用例,模拟一火腿(Ham)生产工厂,工厂可以生产数种类型的Ham,那么在创建了一个工厂实例后,只需要告诉它请你生产何种Ham,它就会给你生产出来: package test;
interface Ham { //定义接口,用于辅助实现工厂模式 void show();//由Ham工厂生产出的各种Ham将有show()的能力 } public class FatoryModule {//工厂类,用于产品多种不同种类的Ham public Ham getHam(String HamType) throws Exception{//工厂类对象生产Ham的动作 if (HamType.equals("HamA")) { return new HamA(); } else if (HamType.equals("HamB")) { return new HamB(); } else if (HamType.equals("HamC")) { return new HamC(); } else throw new Exception();//如果该工厂收到生产某种Ham的指令但暂不支持,则抛出异常 }
public static void main(String[] args) {//测试代码 FatoryModule fatorymodule = new FatoryModule(); try { Ham myHam = fatorymodule.getHam("HamB");//可以方便地创建各种类型的Ham,而程序结构和代码的修改量达到最小 myHam.show(); } catch (Exception ex) { ex.printStackTrace();//应进一步处理异常 } } }
class HamA implements Ham { //工厂中生产的一种产品HamA public void show() { System.out.println("You got a HamA."); } }
class HamB implements Ham { //工厂生产的另一种产品HamB public void show() { System.out.println("You got a HamB."); } }
class HamC implements Ham { //工厂生产的第三种产品HamC public void show() { System.out.println("You got a HamC."); } } 通过程序的演示我得到了以下结论:
由于应用了工厂模式,我只需要告诉工厂对象给我生产一种什么样的Ham(即getHam()方法中的参数),便可以得到此类Ham,而不用写HamX hamX=new HamX(); 要是有数十种不同的Ham对象需要在程序的不同地方被创建,那么使用工厂模式代码将显得简单而统一,每个需要不同Ham的地方仅是getHam()的参数不同。否则程序一旦需要扩展和维护,数十处的new语句将让人头痛。而如果需要添加新的Ham类型,只需要在工厂类中添加即可。 我还这样理解:如果说每种Ham对象的创建都是一条生产线,那么我们用一个工厂将多个生产线封装起来了,从而用户就好比只与工厂传达室的人打交道,告诉他请你们工厂生产一种什么产品,而不是与每一条生产线打交道--这样说来当然是有好处的。 敬请各位发表评论:)
|