|
2005年12月5日
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对象的创建都是一条生产线,那么我们用一个工厂将多个生产线封装起来了,从而用户就好比只与工厂传达室的人打交道,告诉他请你们工厂生产一种什么产品,而不是与每一条生产线打交道--这样说来当然是有好处的。 敬请各位发表评论:)
正则表达式使用详解
如果我们问那些UNIX系统的爱好者他们最喜欢什么,答案除了稳定的系统和可以远程启动之外,十有八九的人会提到正则表达式;如果我们再问他们最头痛的是什么,可能除了复杂的进程控制和安装过程之外,还会是正则表达式。那么正则表达式到底是什么?如何才能真正的掌握正则表达式并正确的加以灵活运用?本文将就此展开介绍,希望能够对那些渴望了解和掌握正则表达式的读者有所助益。
入门简介
简单的说,正则表达式是一种可以用于模式匹配和替换的强有力的工具。我们可以在几乎所有的基于UNIX系统的工具中找到正则表达式的身影,例如,vi编辑器,Perl或PHP脚本语言,以及awk或sed shell程序等。此外,象JavaScript这种客户端的脚本语言也提供了对正则表达式的支持。由此可见,正则表达式已经超出了某种语言或某个系统的局限,成为人们广为接受的概念和功能。
正则表达式可以让用户通过使用一系列的特殊字符构建匹配模式,然后把匹配模式与数据文件、程序输入以及WEB页面的表单输入等目标对象进行比较,根据比较对象中是否包含匹配模式,执行相应的程序。
举例来说,正则表达式的一个最为普遍的应用就是用于验证用户在线输入的邮件地址的格式是否正确。如果通过正则表达式验证用户邮件地址的格式正确,用户所填写的表单信息将会被正常处理;反之,如果用户输入的邮件地址与正则表达的模式不匹配,将会弹出提示信息,要求用户重新输入正确的邮件地址。由此可见正则表达式在WEB应用的逻辑判断中具有举足轻重的作用。
基本语法
在对正则表达式的功能和作用有了初步的了解之后,我们就来具体看一下正则表达式的语法格式。
正则表达式的形式一般如下:
/love/
其中位于“/”定界符之间的部分就是将要在目标对象中进行匹配的模式。用户只要把希望查找匹配对象的模式内容放入“/”定界符之间即可。为了能够使用户更加灵活的定制模式内容,正则表达式提供了专门的“元字符”。所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符(即位于元字符前面的字符)在目标对象中的出现模式。
较为常用的元字符包括: “+”, “*”,以及 “?”。其中,“+”元字符规定其前导字符必须在目标对象中连续出现一次或多次,“*”元字符规定其前导字符必须在目标对象中出现零次或连续多次,而“?”元字符规定其前导对象必须在目标对象中连续出现零次或一次。
下面,就让我们来看一下正则表达式元字符的具体应用。
/fo+/
因为上述正则表达式中包含“+”元字符,表示可以与目标对象中的 “fool”, “fo”, 或者 “football”等在字母f后面连续出现一个或多个字母o的字符串相匹配。
/eg*/
因为上述正则表达式中包含“*”元字符,表示可以与目标对象中的 “easy”, “ego”, 或者 “egg”等在字母e后面连续出现零个或多个字母g的字符串相匹配。
/Wil?/
因为上述正则表达式中包含“?”元字符,表示可以与目标对象中的 “Will”, 或者 “Wilson”,等在字母i后面连续出现零个或一个字母l的字符串相匹配。
除了元字符之外,用户还可以精确指定模式在匹配对象中出现的频率。例如,
/jim{2,6}/
上述正则表达式规定字符m可以在匹配对象中连续出现2-6次,因此,上述正则表达式可以同jimmy或jimmmmmy等字符串相匹配。
在对如何使用正则表达式有了初步了解之后,我们来看一下其它几个重要的元字符的使用方式。
s:用于匹配单个空格符,包括tab键和换行符;
S:用于匹配除单个空格符之外的所有字符;
d:用于匹配从0到9的数字;
w:用于匹配字母,数字或下划线字符;
W:用于匹配所有与w不匹配的字符;
. :用于匹配除换行符之外的所有字符。
(说明:我们可以把s和S以及w和W看作互为逆运算) 下面,我们就通过实例看一下如何在正则表达式中使用上述元字符。
/s+/
上述正则表达式可以用于匹配目标对象中的一个或多个空格字符。
/d000/
如果我们手中有一份复杂的财务报表,那么我们可以通过上述正则表达式轻而易举的查找到所有总额达千元的款项。
除了我们以上所介绍的元字符之外,正则表达式中还具有另外一种较为独特的专用字符,即定位符。定位符用于规定匹配模式在目标对象中的出现位置。
较为常用的定位符包括: “^”, “$”, “\b” 以及 “\B”。其中,“^”定位符规定匹配模式必须出现在目标字符串的开头,“$”定位符规定匹配模式必须出现在目标对象的结尾,\b定位符规定匹配模式必须出现在目标字符串的开头或结尾的两个边界之一,而“\B”定位符则规定匹配对象必须位于目标字符串的开头和结尾两个边界之内,即匹配对象既不能作为目标字符串的开头,也不能作为目标字符串的结尾。同样,我们也可以把“^”和“$”以及“\b”和“\B”看作是互为逆运算的两组定位符。举例来说:
/^hell/
因为上述正则表达式中包含“^”定位符,所以可以与目标对象中以 “hell”, “hello”或 “hellhound”开头的字符串相匹配。
/ar$/
因为上述正则表达式中包含“$”定位符,所以可以与目标对象中以 “car”, “bar”或 “ar” 结尾的字符串相匹配。
/\bbom/
因为上述正则表达式模式以“\b”定位符开头,所以可以与目标对象中以 “bomb”, 或 “bom”开头的字符串相匹配。
/man\b/
因为上述正则表达式模式以“\b”定位符结尾,所以可以与目标对象中以 “human”, “woman”或 “man”结尾的字符串相匹配。
为了能够方便用户更加灵活的设定匹配模式,正则表达式允许使用者在匹配模式中指定某一个范围而不局限于具体的字符。例如:
/[A-Z]/
上述正则表达式将会与从A到Z范围内任何一个大写字母相匹配。
/[a-z]/
上述正则表达式将会与从a到z范围内任何一个小写字母相匹配。
/[0-9]/
上述正则表达式将会与从0到9范围内任何一个数字相匹配。
/([a-z][A-Z][0-9])+/
上述正则表达式将会与任何由字母和数字组成的字符串,如 “aB0” 等相匹配。这里需要提醒用户注意的一点就是可以在正则表达式中使用 “()” 把字符串组合在一起。“()”符号包含的内容必须同时出现在目标对象中。因此,上述正则表达式将无法与诸如 “abc”等的字符串匹配,因为“abc”中的最后一个字符为字母而非数字。
如果我们希望在正则表达式中实现类似编程逻辑中的“或”运算,在多个不同的模式中任选一个进行匹配的话,可以使用管道符 “|”。例如:
/to|too|2/
上述正则表达式将会与目标对象中的 “to”, “too”, 或 “2” 相匹配。
正则表达式中还有一个较为常用的运算符,即否定符 “[^]”。与我们前文所介绍的定位符 “^” 不同,否定符 “[^]”规定目标对象中不能存在模式中所规定的字符串。例如:
/[^A-C]/
上述字符串将会与目标对象中除A,B,和C之外的任何字符相匹配。一般来说,当“^”出现在 “[]”内时就被视做否定运算符;而当“^”位于“[]”之外,或没有“[]”时,则应当被视做定位符。
最后,当用户需要在正则表达式的模式中加入元字符,并查找其匹配对象时,可以使用转义符“”。例如:
/Th*/
上述正则表达式将会与目标对象中的“Th*”而非“The”等相匹配。 使用实例
在对正则表达式有了较为全面的了解之后,我们就来看一下如何在Perl,PHP,以及JavaScript中使用正则表达式。
通常,Perl中正则表达式的使用格式如下:
operator / regular-expression / string-to-replace / modifiers
运算符一项可以是m或s,分别代表匹配运算和替换运算。
其中,正则表达式一项是将要进行匹配或替换操作的模式,可以由任意字符,元字符,或定位符等组成。替换字符串一项是使用s运算符时,对查找到的模式匹配对象进行替换的字符串。最后的参数项用来控制不同的匹配或替换方式。例如:
s/geed/good/
将会在目标对象中查找第一个出现的geed字串,并将其替换为good。如果我们希望在目标对象的全局范围内执行多次查找—替换操作的话,可以使用参数 “g”,即s/love/lust/g。
此外,如果我们不需要限制匹配的大小写形式的话,可以使用参数 “i ”。例如,
m/JewEL/i
上述正则表达式将会与目标对象中的jewel,Jewel,或JEWEL相匹配。
在Perl中,使用专门的运算符“=~”指定正则表达式的匹配对象。例如:
$flag =~ s/abc/ABC/
上述正则表达式将会把变量$flag中的字串abc替换为ABC。
下面,我们就在Perl程序中加入正则表达式,验证用户邮件地址格式的有效性。代码如下:
#!/usr/bin/perl # get input print “What's your email address? ”; $email = <> chomp($email); # match and display result if($email =~ /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/) { print(“Your email address is correct! ”); } else { print(“Please try again! ”); } 如果用户更偏爱PHP的话,可以使用ereg()函数进行模式匹配操作。ereg()函数的使用格式如下: ereg(pattern, string)
其中,pattern代表正则表达式的模式,而string则是执行查找替换操作的目标对象。同样是验证邮件地址,使用PHP编写的程序代码如下:
<?php if (ereg(“^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+”,$email)) { echo “Your email address is correct!”;} else { echo “Please try again!”;} ?> 最后,我们在来看一下JavaScript。JavaScript 1.2中带有一个功能强大的RegExp()对象,可以用来进行正则表达式的匹配操作。其中的test()方法可以检验目标对象中是否包含匹配模式,并相应的返回true或false。
我们可以使用JavaScript编写以下脚本,验证用户输入的邮件地址的有效性。
<html> <head> < language="Javascript1.2"> <!-- start hiding function verifyAddress(obj) { var email = obj.email.value; var pattern = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/; flag = pattern.test(email); if(flag) { alert(“Your email address is correct!”); return true; } else { alert(“Please try again!”); return false; } } // stop hiding --> </script> </head> <body> <form onSubmit="return verifyAddress(this);"> <input name="email" type="text"> <input type="submit" value="提交"> </form> </body> </html>
正则表达式语法
正则表达式是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”)。模式描述在搜索文本时要匹配的一个或多个字符串。
下面是正则表达式的一些示例:
表达式 |
匹配 |
/^\s*$/ |
匹配空行。 |
/\d{2}-\d{5}/ |
验证由两位数字、一个连字符再加 5 位数字组成的 ID 号。 |
/<\s*(\S+)(\s[^>]*)?>[\s\S]*<\s*\/\1\s*>/ |
匹配 HTML 标记。 |
下表包含了元字符的完整列表以及它们在正则表达式上下文中的行为:
字符 |
说明 |
\ |
将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,“n”匹配字符“n”。“\n”匹配换行符。序列“\\”匹配“\”,“\(”匹配“(”。 |
^ |
匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与“\n”或“\r”之后的位置匹配。 |
$ |
匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与“\n”或“\r”之前的位置匹配。 |
* |
零次或多次匹配前面的字符或子表达式。例如,zo* 匹配“z”和“zoo”。* 等效于 {0,}。 |
+ |
一次或多次匹配前面的字符或子表达式。例如,“zo+”与“zo”和“zoo”匹配,但与“z”不匹配。+ 等效于 {1,}。 |
? |
零次或一次匹配前面的字符或子表达式。例如,“do(es)?”匹配“do”或“does”中的“do”。? 等效于 {0,1}。 |
{n} |
n 是非负整数。正好匹配 n 次。例如,“o{2}”与“Bob”中的“o”不匹配,但与“food”中的两个“o”匹配。 |
{n,} |
n 是非负整数。至少匹配 n 次。例如,“o{2,}”不匹配“Bob”中的“o”,而匹配“foooood”中的所有 o。'o{1,}' 等效于 'o+'。'o{0,}' 等效于 'o*'。 |
{n,m} |
m 和 n 是非负整数,其中 n <= m。至少匹配 n 次,至多匹配 m 次。例如,“o{1,3}”匹配“fooooood”中的头三个 o。'o{0,1}' 等效于 'o?'。注意:您不能将空格插入逗号和数字之间。 |
? |
当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是“非贪心的”。“非贪心的”模式匹配搜索到的、尽可能短的字符串,而默认的“贪心的”模式匹配搜索到的、尽可能长的字符串。例如,在字符串“oooo”中,“o+?”只匹配单个“o”,而“o+”匹配所有“o”。 |
. |
匹配除“\n”之外的任何单个字符。若要匹配包括“\n”在内的任意字符,请使用诸如“[\s\S]”之类的模式。 |
(pattern) |
匹配 pattern 并捕获该匹配的子表达式。可以使用 $0...$9 属性从结果“匹配”集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用“\(”或者“\)”。 |
(?:pattern) |
匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用“或”字符 (|) 组合模式部件的情况很有用。例如,与“industry|industries”相比,“industr(?:y| ies)”是一个更加经济的表达式。 |
(?=pattern) |
执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,“Windows (?=95| 98| NT| 2000)”与“Windows 2000”中的“Windows”匹配,但不与“Windows 3.1”中的“Windows”匹配。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。 |
(?!pattern) |
执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,“Windows (?!95| 98| NT| 2000)”与“Windows 3.1”中的“Windows”匹配,但不与“Windows 2000”中的“Windows”匹配。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。 |
x| y |
与 x 或 y 匹配。例如,“z| food”与“z”或“food”匹配。“(z| f)ood”与“zood”或“food”匹配。 |
[xyz] |
字符集。匹配包含的任一字符。例如,“[abc]”匹配“plain”中的“a”。 |
[^xyz] |
反向字符集。匹配未包含的任何字符。例如,“[^abc]”匹配“plain”中的“p”。 |
[a-z] |
字符范围。匹配指定范围内的任何字符。例如,“[a-z]”匹配“a”到“z”范围内的任何小写字母。 |
[^a-z] |
反向范围字符。匹配不在指定的范围内的任何字符。例如,“[^a-z]”匹配任何不在“a”到“z”范围内的任何字符。 |
\b |
匹配一个字边界,即字与空格间的位置。例如,“er\b”匹配“never”中的“er”,但不匹配“verb”中的“er”。 |
\B |
非字边界匹配。“er\B”匹配“verb”中的“er”,但不匹配“never”中的“er”。 |
\cx |
匹配由 x 指示的控制字符。例如,\cM 匹配一个 Control-M 或回车符。x 的值必须在 A-Z 或 a-z 之间。如果不是这样,则假定 c 就是“c”字符本身。 |
\d |
数字字符匹配。等效于 [0-9]。 |
\D |
非数字字符匹配。等效于 [^0-9]。 |
\f |
换页符匹配。等效于 \x0c 和 \cL。 |
\n |
换行符匹配。等效于 \x0a 和 \cJ。 |
\r |
匹配一个回车符。等效于 \x0d 和 \cM。 |
\s |
匹配任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r\t\v] 等效。 |
\S |
匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\t |
制表符匹配。与 \x09 和 \cI 等效。 |
\v |
垂直制表符匹配。与 \x0b 和 \cK 等效。 |
\w |
匹配任何字类字符,包括下划线。与“[A-Za-z0-9_]”等效。 |
\W |
任何非字字符匹配。与“[^A-Za-z0-9_]”等效。 |
\xn |
匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,“\x41”匹配“A”。“\x041”与“\x04”&“1”等效。允许在正则表达式中使用 ASCII 代码。 |
\num |
匹配 num,此处的 num 是一个正整数。到捕获匹配的反向引用。例如,“(.)\1”匹配两个连续的相同字符。 |
\n |
标识一个八进制转义码或反向引用。如果 \n 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。 |
\nm |
标识一个八进制转义码或反向引用。如果 \nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 \nm 前面至少有 n 个捕获,那么 n 是反向引用,后面跟 m。如果前面的条件均不存在,那么当 n 和 m 是八进制数 (0-7) 时,\nm 匹配八进制转义码 nm。 |
\nml |
当 n 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml。 |
\un |
匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (?)。 |
作者:王和全 来自:IBM
作为基于MVC模式的Web应用最经典框架,Struts已经正式推出了1.1版本,该版本在以往版本的基础上,提供了许多激动人心的新功能。本文就将带你走进Struts 1.1去深入地了解这些功能。 说明:希望本文的读者能有一定的Struts使用基础。
1、Model 2
Struts是基于Model 2之上的,而Model 2是经典的MVC(模型-视图-控制器)模型的Web应用变体,这个改变主要是由于网络应用的特性--HTTP协议的无状态性引起的。Model 2的目的和MVC一样,也是利用控制器来分离模型和视图,达到一种层间松散耦合的效果,提高系统灵活性、复用性和可维护性。在多数情况下,你可以将Model 2与MVC等同起来。
下图表示一个基于Java技术的典型网络应用,从中可以看出Model 2中的各个部分是如何对应于Java中各种现有技术的。
在利用Model 2之前,我们是把所有的表示逻辑和业务逻辑都集中在一起(比如大杂烩似的JSP),有时也称这种应用模式为Model 1,Model 1的主要缺点就是紧耦合,复用性差以及维护成本高。
2、Struts 1.1 和Model 2
既然Struts 1.1是基于Model 2之上,那它的底层机制也就是MVC,下面是Struts 1.1中的MVC实现示意图:
图解说明:其中不同颜色代表MVC的不同部分:红色(控制器)、紫色(模型)和绿色(视图)
首先,控制器(ActionServlet)进行初始化工作,读取配置文件(struts-config.xml),为不同的Struts模块初始化相应的ModuleConfig对象。比如配置文件中的Action映射定义都保存在ActionConfig集合中。相应地有ControlConfig集合、FormBeanConfig集合、ForwardConfig集合和MessageResourcesConfig集合等。
提示:模块是在Struts 1.1中新提出的概念,在稍后的内容中我们将详细介绍,你现在可以简单地把模块看作是一个子系统,它们共同组成整个应用,同时又各自独立。Struts 1.1中所有的处理都是在特定模块环境中进行的。模块的提出主要是为了解决Struts 1.0中单配置文件的问题。
控制器接收HTTP请求,并从ActionConfig中找出对应于该请求的Action子类,如果没有对应的Action,控制器直接将请求转发给JSP或者静态页面。否则控制器将请求分发至具体Action类进行处理。
在控制器调用具体Action的execute方法之前,ActionForm对象将利用HTTP请求中的参数来填充自己(可选步骤,需要在配置文件中指定)。具体的ActionForm对象应该是ActionForm的子类对象,它其实就是一个JavaBean。此外,还可以在ActionForm类中调用validate方法来检查请求参数的合法性,并且可以返回一个包含所有错误信息的ActionErrors对象。如果执行成功,ActionForm自动将这些参数信息以JavaBean(一般称之为form bean)的方式保存在Servlet Context中,这样它们就可以被其它Action对象或者JSP调用。
Struts将这些ActionForm的配置信息都放在FormBeanConfig集合中,通过它们Struts能够知道针对某个客户请求是否需要创建相应的ActionForm实例。
Action很简单,一般只包含一个execute方法,它负责执行相应的业务逻辑,如果需要,它也进行相应的数据检查。执行完成之后,返回一个ActionForward对象,控制器通过该ActionForward对象来进行转发工作。我们主张将获取数据和执行业务逻辑的功能放到具体的JavaBean当中,而Action只负责完成与控制有关的功能。遵循该原则,所以在上图中我将Action对象归为控制器部分。
提示:其实在Struts 1.1中,ActionMapping的作用完全可以由ActionConfig来替代,只不过由于它是公共API的一部分以及兼容性的问题得以保留。ActionMapping通过继承ActionConfig来获得与其一致的功能,你可以等同地看待它们。同理,其它例如ActionForward与ForwardConfig的关系也是如此。
下图给出了客户端从发出请求到获得响应整个过程的图解说明。
下面我们就来详细地讨论一下其中的每个部分,在这之前,先来了解一下模块的概念。
3、模块
我们知道,在Struts 1.0中,我们只能在web.xml中为ActionServlet指定一个配置文件,这对于我们这些网上的教学例子来说当然没什么问题,但是在实际的应用开发过程中,可能会有些麻烦。因为许多开发人员都可能同时需要修改配置文件,但是配置文件只能同时被一个人修改,这样肯定会造成一定程度上的资源争夺,势必会影响开发效率和引起开发人员的抱怨。
在Struts 1.1中,为了解决这个并行开发的问题,提出了两种解决方案:
1. 多个配置文件的支持 2. 模块的支持
支持多个配置文件,是指你能够为ActionServlet同时指定多个xml配置文件,文件之间以逗号分隔,比如Struts提供的MailReader演示例子中就采用该种方法。
<!-- Action Servlet Configuration --> <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml, /WEB-INF/struts-config-registration.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
这种方法可以很好地解决修改冲突的问题,不同的开发人员可以在不同的配置文件中设置自己的Action、ActionForm等等(当然不是说每个开发人员都需要自己的配置文件,可以按照系统的功能模块进行划分)。但是,这里还是存在一个潜在的问题,就是可能不同的配置文件之间会产生冲突,因为在ActionServlet初始化的时候这几个文件最终还是需要合并到一起的。比如,在struts-config.xml中配置了一个名为success的<forward>,而在struts-config-registration.xml中也配置了一个同样的<forward>,那么执行起来就会产生冲突。
为了彻底解决这种冲突,Struts 1.1中引进了模块(Module)的概念。一个模块就是一个独立的子系统,你可以在其中进行任意所需的配置,同时又不必担心和其它的配置文件产生冲突。因为前面我们讲过,ActionServlet是将不同的模块信息保存在不同的ModuleConfig对象中的。要使用模块的功能,需要进行以下的准备工作:
1、为每个模块准备一个配置文件 2、配置web.xml文件,通知控制器
决定采用多个模块以后,你需要将这些信息告诉控制器,这需要在web.xml文件进行配置。下面是一个典型的多模块配置:
<init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>config/customer</param-name> <param-value>/WEB-INF/struts-config-customer.xml</param-value> </init-param> <init-param> <param-name>config/order</param-name> <param-value>/WEB-INF/struts-config-order.xml</param-value> </init-param>
要配置多个模块,你需要在原有的一个<init-param>(在Struts 1.1中将其对应的模块称为缺省模块)的基础之上,增加模块对应的<init-param>。其中<param-name>表示为config/XXX的形式,其中XXX为对应的模块名,<param-value>中还是指定模块对应的配置文件。上面这个例子说明该应用有三个模块,分别是缺省模块、customer和order,它们分别对应不同的配置文件。
3、准备各个模块所需的ActionForm、Action和JSP等资源
但是要注意的是,模块的出现也同时带来了一个问题,即如何在不同模块间进行转发?有两种方法可以实现模块间的转发,一种就是在<forward>(全局或者本地)中定义,另外一种就是利用org.apache.struts.actions.SwitchAction。
下面就是一个全局的例子:
... <struts-config> ... <global-forwards> <forward name="toModuleB" contextRelative="true" path="/moduleB/index.do" redirect="true"/> ... </global-forwards> ... </struts-config>
可以看出,只需要在原有的path属性前加上模块名,同时将contextRelative属性置为true即可。此外,你也可以在<action>中定义一个类似的本地<forward>。
<action-mappings> <!-- Action mapping for profile form --> <action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin" validate="true"> <forward name="success" contextRelative="true" path="/moduleA/login.do"/> </action> </action-mappings>
如果你已经处在其他模块,需要转回到缺省模块,那应该类似下面这样定义,即模块名为空。
<forward name="success" contextRelative="true" path="/login.do"/>
此外,你也可以使用org.apache.struts.actions.SwitchAction,例如:
... <action-mappings> <action path="/toModule" type="org.apache.struts.actions.SwitchAction"/> ... </action-mappings> ...
4、ActionServlet
我们首先来了解MVC中的控制器。在Struts 1.1中缺省采用ActionServlet类来充当控制器。当然如果ActionServlet不能满足你的需求,你也可以通过继承它来实现自己的类。这可以在/WEB-INF/web.xml中来具体指定。
要掌握ActionServlet,就必须了解它所扮演的角色。首先,ActionServlet表示MVC结构中的控制器部分,它需要完成控制器所需的前端控制及转发请求等职责。其次,ActionServlet被实现为一个专门处理HTTP请求的Servlet,它同时具有servlet的特点。在Struts 1.1中它主要完成以下功能:
□ 接收客户端请求 □ 根据客户端的URI将请求映射到一个相应的Action类 □ 从请求中获取数据填充Form Bean(如果需要) □ 调用Action类的execute()方法获取数据或者执行业务逻辑 □ 选择正确的视图响应客户
此外,ActionServlet还负责初始化和清除应用配置信息的任务。ActionServlet的初始化工作在init方法中完成,它可以分为两个部分:初始化ActionServlet自身的一些信息以及每个模块的配置信息。前者主要通过initInternal、initOther和initServlet三个方法来完成。
我们可以在/WEB-INF/web.xml中指定具体的控制器以及初始参数,由于版本的变化以及Struts 1.1中模块概念的引进,一些初始参数被废弃或者移入到/WEB-INF/struts-config.xml中定义。下面列出所有被废弃的参数,相应地在web.xml文件中也不鼓励再使用。
□ application □ bufferSize □ content □ debug □ factory □ formBean □ forward □ locale □ mapping □ maxFileSize □ multipartClass □ nocache □ null □ tempDir
ActionServlet根据不同的模块来初始化ModuleConfig类,并在其中以XXXconfig集合的方式保存该模块的各种配置信息,比如ActionConfig,FormBeanConfig等。
初始化工作完成之后,ActionServlet准备接收客户请求。针对每个请求,方法process(HttpServletRequest request, HttpServletResponse response)将被调用。该方法指定具体的模块,然后调用该模块的RequestProcessor的process方法。
protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
RequestUtils.selectModule(request, getServletContext()); getRequestProcessor(getModuleConfig(request)).process(request, response); }
RequestProcessor包含了Struts控制器的所有处理逻辑,它调用不同的processXXX方法来完成不同的处理。下表列出其中几个主要的方法:
方法 |
功能 |
processPath |
获取客户端的请求路径 |
processMapping |
利用路径来获得相应的ActionMapping |
processActionForm |
初始化ActionForm(如果需要)并存入正确的scope中 |
processActionCreate |
初始化Action |
processActionPerform |
调用Action的execute方法 |
processForwardConfig |
处理Action返回的ActionForward |
5、ActionForm
对于ActionForm你可以从以下几个方面来理解它:
1. ActionForm表示HTTP窗体中的数据,可以将其看作是模型和视图的中介,它负责保存视图中的数据供模型或者视图使用。Struts 1.1文档中把它比作HTTP和Action之间的防火墙,这体现了ActionForm具有的过滤保护的作用,只有通过ActionForm验证的数据才能够发送到Action处理。 2. ActionForm是与一个或多个ActionConfig关联的JavaBean,在相应的action的execute方法被调用之前,ActionForm会自动利用请求参数来填充自己(初始化属性)。 3. ActionForm是一个抽象类,你必须通过继承来实现自己的类。
ActionForm首先利用属性的getter和setter方法来实现初始化,初始化完毕后,ActionForm的validate方法被调用,你可以在其中来检查请求参数的正确性和有效性,并且可以将错误信息以ActionErrors的形式返回到输入窗体。否则,ActionForm将被作为参数传给action的execute方法以供使用。
ActionForm bean的生命周期可以设置为session(缺省)和request,当设置为session时,记得在reset方法中将所有的属性重新设置为初始值。
由于ActionForm对应于HTTP窗体,所以随着页面的增多,你的ActionForm将会急速增加。而且可能同一类型页面字段将会在不同的ActionForm中出现,并且在每个ActionForm中都存在相同的验证代码。为了解决这个问题,你可以为整个应用实现一个ActionForm或者至少一个模块对应于一个ActionForm。
但是,聚合的代价就是复用性很差,而且难维护。针对这个问题,在Struts 1.1中提出了DynaActionForm的概念。
DynaActionForm类
DynaActionForm的目的就是减少ActionForm的数目,利用它你不必创建一个个具体的ActionForm类,而是在配置文件中配置出所需的虚拟ActionForm。例如,在下表中通过指定<form-bean>的type为"org.apache.struts.action.DynaActionForm"来创建一个动态的ActionForm--loginForm。
<form-beans> <form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="actionClass" type="java.lang.String"/> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> </form-beans>
动态的ActionForm的使用方法跟普通的ActionForm相同,但是要注意一点。普通的ActionForm对象需要为每个属性提供getter和setter方法,以上面的例子而言,我们需要提供getUsername() 和 setUsername()方法取得和设置username属性,同样地有一对方法用于取得和设置password属性和actionClass属性。
如果使用DynaActionForm,它将属性保存在一个HashMap类对象中,同时提供相应的get(name) 和 set(name)方法,其中参数name是要访问的属性名。例如要访问DynaActionForm中username的值,可以采用类似的代码:
String username = (String)form.get("username");
由于值存放于一个HashMap对象,所以要记得对get()方法返回的Object对象做强制性类型转换。正是由于这点区别,如果你在Action中非常频繁地使用ActionForm对象,建议还是使用普通的ActionForm对象。
在Struts 1.1中,除了DynaActionForm以外,还提供了表单输入自动验证的功能,在包org.apache.struts.validator中提供了许多有用的类,其中最常见的就是DynaValidatorForm类。
DynaValidatorForm类
DynaValidatorForm是DynaActionForm的子类,它能够提供动态ActionForm和自动表单输入验证的功能。和使用DynaActionForm类似,你必须首先在配置文件中进行配置:
<form-beans> <form-bean name="loginForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="actionClass" type="java.lang.String"/> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> </form-beans>
同时要定义验证的插件:
<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/> </plug-in>
其中的validator.xml和validator-rules.xml分别表示验证定义和验证规则的内容(可以合并在一起),比如针对上例中的DynaValidatorForm,我们有如下验证定义(validator.xml):
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN" "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd"> <!-- Validation Rules $Id: validation.xml-->
<form-validation> <!-- ========== Default Language Form Definitions ===================== --> <formset> <form name="loginForm"> <field property="username" depends="required, minlength,maxlength"> <arg0 key="prompt.username"/> <arg1 key="${var:minlength}" name="minlength" resource="false"/> <arg2 key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> <var> <var-name>minlength</var-name> <var-value>3</var-value> </var> </field> <field property="password" depends="required, minlength,maxlength" bundle="alternate"> <arg0 key="prompt.password"/> <arg1 key="${var:minlength}" name="minlength" resource="false"/> <arg2 key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> <var> <var-name>minlength</var-name> <var-value>3</var-value> </var> </field> </form> </formset> </form-validation>
从上述定义中,我们可以看到对于字段username有三项验证:required, minlength, maxlength,意思是该字段不能为空,而且长度在3和16之间。而validator-rules.xml文件则可以采用Struts提供的缺省文件。注意在<form-bean>中定义的form是如何与validation.xml中的form关联起来的。最后,要启动自动验证功能,还需要将Action配置的validate属性设置为true。
<action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin"validate="true">
此时,Struts将根据xml配置文件中的定义来检验表单输入,并将不符合要求的错误信息输出到页面。但是你可能会想:这个功能虽然好,可是什么检验都跑到服务器端执行,效率方面和用户易用性方面是不是有些问题?你可能会怀念起那简单的JavaScript客户端验证。
不用担心,在Struts 1.1中也支持JavaScript客户端验证。如果你选择了客户端验证,当某个表单被提交以后,Struts 1.1启动客户端验证,如果浏览器不支持JavaScript验证,则服务器端验证被启动,这种双重验证机制能够最大限度地满足各种开发者的需要。JavaScript验证代码也是在validator-rules.xml文件中定义的。要启动客户端验证,你必须在相应的JSP文件中做如下设置:
1. 为<html:form>增加onsubmit属性 2. 设置Javascript支持
下表中列出了一JSP文件的示例代码,红字部分为Javascript验证所需代码。
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <table bgcolor="#9AFF9A" cellspacing="0" cellpadding="10" border="1" width="100%"> <tr> <td> <table cellspacing="0" cellpadding="0" border="0" width="100%"> <tr bgcolor="#696969"> <td align="center"> <font color="#FFFFFF">Panel 3: Profile</font> </td> </tr> <tr> <td><br> <html:errors/> <html:form action="/login.do" focus="username" onsubmit="return validateLoginForm(this);"> <html:hidden property="actionClass"/> <center> <table> <tr> <td>UserName:</td> <td><html:text property="username" size="20"/></td> </tr> <tr> <td>Password:</td> <td><html:password property="password" size="20"/></td> </tr> <tr> <td colspan=2><html:submit property="submitProperty" value="Submit"/></td> </table> </center> </html:form> <html:javascript formName="loginForm" dynamicJavascript="true" staticJavascript="false"/> <script language="Javascript1.1" src="staticJavascript.jsp"></script> </td> </tr> </table> </td> </tr> </table>
其中onsubmit的值为"return validateLoginForm(this);",它的语法为:
return validate + struts-config.xml中定义的form-bean名称 + (this);
staticJavascript.jsp的内容为:
<%@ page language="java" %> <%-- set document type to Javascript (addresses a bug in Netscape according to a web resource --%> <%@ page contentType="application/x-javascript" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:javascript dynamicJavascript="false" staticJavascript="true"/>
如果validator-rules.xml中定义的基本验证功能不能满足你的需求,你可以自己添加所需的验证类型。
6、Action
我们通过继承Action类来实现具体的执行类。具体Action类的功能一般都在execute(以前是perform方法)方法中完成,其中主要涉及到以下几个方面:
1. 辅助ActionForm进行一些表单数据的检查。 2. 执行必要的业务逻辑,比如存取数据库,调用实体bean等。 3. 更新服务器端的bean数据,后续对象中可能会用到这些数据,比如在JSP中利用bean:write来获得这些数据。 4. 根据处理结果决定程序的去处,并以ActionForward对象的形式返回给ActionServlet。
提示:由于在Action和ActionForm中都可以实现验证方法,那么如何来安排它们之间的分工呢?一般来说,我们秉着MVC分离的原则,也就是视图级的验证工作放在ActionForm来完成,比如输入不能为空,email格式是否正确,利用ValidatorForm可以很轻松地完成这些工作。而与具体业务相关的验证则放入Action中,这样就可以获得最大ActionForm重用性的可能。
前面我们提到过,我们主张将业务逻辑执行分离到单独的JavaBean中,而Action只负责错误处理和流程控制。而且考虑到重用性的原因,在执行业务逻辑的JavaBean中不要引用任何与Web应用相关的对象,比如HttpServletRequest,HttpServletResponse等对象,而应该将其转化为普通的Java对象。关于这一点,可以参考Petstore中WAF框架的实现思路。
此外,你可能还注意到execute与perform的一个区别:execute方法简单地掷出Exception异常,而perform方法则掷出ServletException和IOException异常。这不是说Struts 1.1在异常处理功能方面弱化了,而是为了配合Struts 1.1中一个很好的功能--宣称式异常处理机制。
7、宣称式异常处理
和EJB中的宣称式事务处理概念类似,宣称式异常处理其实就是可配置的异常处理,你可以在配置文件中指定由谁来处理Action类中掷出的某种异常。你可以按照以下步骤来完成该功能:
1. 实现org.apache.struts.action.ExceptionHandler的子类,覆盖execute方法,在该方法中处理异常并且返回一个ActionForward对象 2. 在配置文件中配置异常处理对象,你可以配置一个全局的处理类或者单独为每个Action配置处理类
下表就定义了一个全局的处理类CustomizedExceptionHandler,它被用来处理所有的异常。
<global-exceptions> <exception handler="com.yourcorp.CustomizedExceptionHandler" key="global.error.message" path="/error.jsp" scope="request" type="java.lang.Exception"/> </global-exceptions>
其中具体的参数含义,可以参考ExceptionHandler.java源文件。
8、taglib
讲完了模型和控制器,接下来我们要涉及的是视图。视图的角色主要是由JSP来完成,从JSP的规范中可以看出,在视图层可以"折腾"的技术不是很多,主要的就是自定义标记库的应用。Struts 1.1在原有的四个标记库的基础上新增了两个标记库--Tiles和Nested。
其中Tiles除了替代Template的基本模板功能外,还增加了布局定义、虚拟页面定义和动态页面生成等功能。Tiles强大的模板功能能够使页面获得最大的重用性和灵活性,此外可以结合Tiles配置文件中的页面定义和Action的转发逻辑,即你可以将一个Action转发到一个在Tiles配置文件中定义的虚拟页面,从而减少页面的数量。比如,下表中的Action定义了一个转发路径,它的终点是tile.userMain,而后者是你在Tiles配置文件中定义的一个页面。
<!-- ========== Action Mapping Definitions ============================== --> <action-mappings> <!-- Action mapping for profile form --> <action path="/login" type="com.ncu.test.LoginAction" name="loginForm" scope="request" input="tile.userLogin" validate="true"> <forward name="success" path="tile.userMain"/> </action> </action-mappings>
Tiles配置文件:tiles-defs.xml
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration//EN" "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"> <tiles-definitions> <!-- ======================================================= --> <!-- Master definitions --> <!-- ======================================================= --> <!-- Page layout used as root for all pages. -->
<definition name="rootLayout" path="/tiles-layouts/rootLayout.jsp"> <put name="titleString" value="CHANGE-ME"/> <put name="topMenu" value="/tiles-components/topMenu.jsp"/> <put name="leftMenu" value="/tiles-components/panel1.jsp"/> <put name="body" value="CHANGE-ME"/> <put name="footer" value="/tiles-components/footer.jsp"/> </definition>
<!-- ======================================================= --> <!-- Page definitions --> <!-- ======================================================= -->
<!-- User Login page --> <definition name="tile.userLogin" extends="rootLayout"> <put name="titleString" value="User Login"/> <put name="body" value="/src/userLogin.jsp"/> </definition> <!-- User Main page --> <definition name="tile.userMain" extends="rootLayout"> <put name="titleString" value="User Main"/> <put name="body" value="/src/userMain.jsp"/> </definition> </tiles-definitions>
而Nested标记库的作用是让以上这些基本标记库能够嵌套使用,发挥更大的作用。
9、Commons Logging 接口
所谓的Commons Logging接口,是指将日志功能的使用与日志具体实现分开,通过配置文件来指定具体使用的日志实现。这样你就可以在Struts 1.1中通过统一的接口来使用日志功能,而不去管具体是利用的哪种日志实现,有点于类似JDBC的功能。Struts 1.1中支持的日志实现包括:Log4J,JDK Logging API, LogKit,NoOpLog和SimpleLog。
你可以按照如下的方式来使用Commons Logging接口(可以参照Struts源文中的许多类实现):
package com.foo; // ... import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; //... public class Foo { // ... private static Log log = LogFactory.getLog(Foo.class); // ... public void setBar(Bar bar) { if (log.isTraceEnabled()) { log.trace("Setting bar to " + bar); } this.bar = bar; } // ... }
而开启日志功能最简单的办法就是在WEB-INF/classes目录下添加以下两个文件:
commns-logging.properties文件:
# Note: The Tiles framework now uses the commons-logging package to output different information or debug statements. Please refer to this package documentation to enable it. The simplest way to enable logging is to create two files in WEB-INF/classes: # commons-logging.properties # org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog # simplelog.properties # # Logging detail level, # # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). #org.apache.commons.logging.simplelog.defaultlog=trace org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
simplelog.properties文件:
# Logging detail level, # Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). org.apache.commons.logging.simplelog.defaultlog=fatal
这里我们采用的日志实现是SimpleLog,你可以在simplelog.properties文件指定日志明细的级别:trace,debug,info,warn,error和fatal,从trace到fatal错误级别越来越高,同时输出的日志信息也越来越少。而这些级别是和org.apache.commons.logging.log接口中的方法一一对应的。这些级别是向后包含的,也就是前面的级别包含后面级别的信息。
问题1 我声明了什么!
String s = "Hello world!";
许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容 是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答,一半 的人大概会回答错误。 这个语句声明的是一个指向对象的引用,名为“s”,可以指向类型为String的任何对象, 目前指向"Hello world!"这个String类型的对象。这就是真正发生的事情。我们并没有声 明一个String对象,我们只是声明了一个只能指向String对象的引用变量。所以,如果在 刚才那句语句后面,如果再运行一句:
String string = s;
我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生 ,string还是指向原来那个对象,也就是,和s指向同一个对象。 String s1 = "This is cool!"; // 生成一个字符串对象[OBJ_1],将其引用赋值给s1 String s2 = "This is cool!"; // 将同样的对象[OBJ_1]的引用赋值给s2 String s3 = new String("This is cool!"); // 新建一个字符串对象[OBJ_2],其引用赋 值给s3; s1 = s2; // 将s2保存的[OBJ_1]的引用赋值给s1,没有实际效果 s3 = "This is cool!"; // 将[OBJ_1]的引用赋值给s3,s3原先引用的[OBJ_2]不再被引用
这样的代码共有产生2个String实例,有1对象可以被GC。
问题2,"=="和equals方法究竟有什么区别?
==操作符专门用来比较变量的值是否相等。比较好理解的一点是: int a=10; int b=10; 则a==b将是true。 但不好理解的地方是: String a=new String("foo"); String b=new String("foo"); 则a==b将返回false。
根据前一帖说过,对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不 是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内容为"foo"的字符串 ,既然是“两个”,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址 的值,所以使用"=="操作符,结果会是false。诚然,a和b所指的对象,它们的内容都是" foo",应该是“相等”,但是==操作符并不涉及到对象内容的比较。 对象内容的比较,正是equals方法做的事。
看一下Object对象的equals方法是如何实现的: boolean equals(Object o){
return this==o;
} Object对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法,那你的类使 用equals和使用==会得到同样的结果。同样也可以看出,Object的equals方法没有达到eq uals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定 ,所以Object把这个任务留给了类的创建者。
看一下一个极端的类: Class Monster{ private String content; ... boolean equals(Object another){ return true;}
} 我覆盖了equals方法。这个实现会导致无论Monster实例内容如何,它们之间的比较永远返 回true。
所以当你是用equals方法判断对象的内容是否相等,请不要想当然。因为可能你认为相等 ,而这个类的作者不这样认为,而类的equals方法的实现是由他掌握的。如果你需要使用 equals方法,或者使用任何基于散列码的集合(HashSet,HashMap,HashTable),请察看一 下 java doc以确认这个类的equals逻辑是如何实现的。
问题3 String到底变了没有
没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。请 看下列代码:
String s = "Hello"; s = s + " world!";
s所指向的对象是否改变了呢?从本系列第一篇的结论很容易导出这个结论。我们来看看发 生了什么事情。在这段代码中,s原先指向一个String对象,内容是"Hello",然后我们对 s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向 原来那个对象了,而指向了另一个String对象,内容为"Hello world!",原来那个对象还 存在于内存之中,只是s这个引用变量不再指向它了。 通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改, 或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因 为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对 象来表示。这时,应该考虑使用StringBuffer 类,它允许修改,而不是每个不同的字符串 都要生成一个新的对象。并且,这两种类的对象转换十分容易。 同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如 我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这 样做: public class Demo { private String s; ... public Demo { s = "Initial Value"; } ... } 而非 s = new String("Initial Value"); 后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为St ring对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。 也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象 。 上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同 一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相 同。 至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Ja va标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变 类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为 它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每 个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供 了一个可变版本,即 StringBuffer。
问题4,final关键字到底修饰了什么? final使得被修饰的变量"不变",但是由于对象型变量的本质是“引用”,使得“不变”也 有了两种含义:引用本身的不变,和引用指向的对象不变。
引用本身的不变: final StringBuffer a=new StringBuffer("immutable"); final StringBuffer b=new StringBuffer("not immutable"); a=b;//编译期错误
引用指向的对象不变: final StringBuffer a=new StringBuffer("immutable"); a.append(" broken!"); //编译通过
可见,final只对引用的“值”(也即它所指向的那个对象的内存地址)有效,它迫使引用只 能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变 化,final是不负责的。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个 地址所指向的对象内容是否相等, ==操作符是不管的。
理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向 固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享 或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错 误的解决方法就是在此对象新建的时候把它声明为final,意图使得它“永远不变”。其实 那是徒劳的。
问题5 到底要怎么样初始化!
本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。 1. 类的属性,或者叫值域 2. 方法里的局部变量 3. 方法的参数
对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始 值。如果没有给出,则把它初始化为该类型变量的默认初始值。
int类型变量默认初始值为0 float类型变量默认初始值为0.0f double类型变量默认初始值为0.0 boolean类型变量默认初始值为false char类型变量默认初始值为0(ASCII码) long类型变量默认初始值为0 byte类型变量默认初始值为0 short类型变量默认初始值为0 所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象, 所以没有初始化的数组引用在自动初始化后其值也是null。
对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance 属性在创建实例的时候初始化, static属性在类加载,也就是第一次用到这个类的时候初 始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细 讨论。
对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器 会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一次使用前一定能够得 到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为 执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违 反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过 编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的 事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果 在catch或finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初 始化了。所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成 什么值好,就用上面的默认值吧!
其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯 定是被初始化过的,传入的值就是初始值,所以不需要初始化。
问题6 instanceof是什么东东?
instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的 ,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例, 返回boolean类型的数据。举个例子:
String s = "I AM an Object!"; boolean isObject = s instanceof Object;
我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向 的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的 值为True。 instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:
public class Bill {//省略细节} public class PhoneBill extends Bill {//省略细节} public class GasBill extends Bill {//省略细节}
在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法 不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:
public double calculate(Bill bill) { if (bill instanceof PhoneBill) { //计算电话账单 } if (bill instanceof GasBill) { //计算燃气账单 } ... } 这样就可以用一个方法处理两种子类。
然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用 方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要 提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:
public double calculate(PhoneBill bill) { //计算电话账单 }
public double calculate(GasBill bill) { //计算燃气账单 }
所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。
Java基础方面:
1、作用域public,private,protected,以及不写时的区别 答:区别如下: 作用域 当前类 同一package 子孙类 其他package public √ √ √ √ protected √ √ √ × friendly √ √ × × private √ × × × 不写时默认为friendly
2、ArrayList和Vector的区别,HashMap和Hashtable的区别 答:就ArrayList与Vector主要从二方面来说. 一.同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的 二.数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半 就HashMap与HashTable主要从三方面来说。 一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现 二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 三.值:只有HashMap可以让你将空值作为一个表的条目的key或value
3、char型变量中能不能存贮一个中文汉字?为什么? 答:是能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的
4、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口 同步的实现方面有两种,分别是synchronized,wait与notify
5、继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么? 答:父类: package test; public class FatherClass { public FatherClass() { System.out.println("FatherClass Create"); } } 子类: package test; import test.FatherClass; public class ChildClass extends FatherClass { public ChildClass() { System.out.println("ChildClass Create"); } public static void main(String[] args) { FatherClass fc = new FatherClass(); ChildClass cc = new ChildClass(); } } 输出结果: C:\>java test.ChildClass FatherClass Create FatherClass Create ChildClass Create
6、内部类的实现方式? 答:示例代码如下: package test; public class OuterClass { private class InterClass { public InterClass() { System.out.println("InterClass Create"); } } public OuterClass() { InterClass ic = new InterClass(); System.out.println("OuterClass Create"); } public static void main(String[] args) { OuterClass oc = new OuterClass(); } } 输出结果: C:\>java test/OuterClass InterClass Create OuterClass Create 再一个例题: public class OuterClass { private double d1 = 1.0; //insert code here } You need to insert an inner class declaration at line 3. Which two inner class declarations are
valid?(Choose two.) A. class InnerOne{ public static double methoda() {return d1;} } B. public class InnerOne{ static double methoda() {return d1;} } C. private class InnerOne{ double methoda() {return d1;} } D. static class InnerOne{ protected double methoda() {return d1;} } E. abstract class InnerOne{ public abstract double methoda(); } 说明如下: 一.静态内部类可以有静态成员,而非静态内部类则不能有静态成员。 故 A、B 错 二.静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;return d1 出错。
故 D 错 三.非静态内部类的非静态成员可以访问外部类的非静态变量。 故 C 正确 四.答案为C、E
7、垃圾回收机制,如何优化程序? 希望大家补上,谢谢
8、float型float f=3.4是否正确? 答:不正确。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4
9、介绍JAVA中的Collection FrameWork(包括如何写自己的数据结构)? 答:Collection FrameWork如下: Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set Map ├Hashtable ├HashMap └WeakHashMap Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements) Map提供key到value的映射
10、Java中异常处理机制,事件机制?
11、JAVA中的多形与继承? 希望大家补上,谢谢
12、抽象类与接口? 答:抽象类与接口都用于抽象,但是抽象类(JAVA中)可以有自己的部分实现,而接口则完全是一个标识(同时有多重继承的功能)。
13、Java 的通信编程,编程题(或问答),用JAVA SOCKET编程,读服务器几个字符,再写入本地显示? 答:Server端程序: package test; import java.net.*; import java.io.*;
public class Server { private ServerSocket ss; private Socket socket; private BufferedReader in; private PrintWriter out; public Server() { try { ss=new ServerSocket(10000); while(true) { socket = ss.accept(); String RemoteIP = socket.getInetAddress().getHostAddress(); String RemotePort = ":"+socket.getLocalPort(); System.out.println("A client come in!IP:"+RemoteIP+RemotePort); in = new BufferedReader(new
InputStreamReader(socket.getInputStream())); String line = in.readLine(); System.out.println("Cleint send is :" + line); out = new PrintWriter(socket.getOutputStream(),true); out.println("Your Message Received!"); out.close(); in.close(); socket.close(); } }catch (IOException e) { out.println("wrong"); } } public static void main(String[] args) { new Server(); } }; Client端程序: package test; import java.io.*; import java.net.*;
public class Client { Socket socket; BufferedReader in; PrintWriter out; public Client() { try { System.out.println("Try to Connect to 127.0.0.1:10000"); socket = new Socket("127.0.0.1",10000); System.out.println("The Server Connected!"); System.out.println("Please enter some Character:"); BufferedReader line = new BufferedReader(new
InputStreamReader(System.in)); out = new PrintWriter(socket.getOutputStream(),true); out.println(line.readLine()); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); System.out.println(in.readLine()); out.close(); in.close(); socket.close(); }catch(IOException e) { out.println("Wrong"); } } public static void main(String[] args) { new Client(); } };
14、用JAVA实现一种排序,JAVA类实现序列化的方法(二种)? 如在COLLECTION框架中,实现比较要实现什么样的接口? 答:用插入法进行排序代码如下 package test; import java.util.*; class InsertSort { ArrayList al; public InsertSort(int num,int mod) { al = new ArrayList(num); Random rand = new Random(); System.out.println("The ArrayList Sort Before:"); for (int i=0;i<num ;i++ ) { al.add(new Integer(Math.abs(rand.nextInt()) % mod + 1)); System.out.println("al["+i+"]="+al.get(i)); } } public void SortIt() { Integer tempInt; int MaxSize=1; for(int i=1;i<al.size();i++) { tempInt = (Integer)al.remove(i); if(tempInt.intValue()>=((Integer)al.get(MaxSize-1)).intValue()) { al.add(MaxSize,tempInt); MaxSize++; System.out.println(al.toString()); } else { for (int j=0;j<MaxSize ;j++ ) { if
(((Integer)al.get(j)).intValue()>=tempInt.intValue()) { al.add(j,tempInt); MaxSize++; System.out.println(al.toString()); break; } } } } System.out.println("The ArrayList Sort After:"); for(int i=0;i<al.size();i++) { System.out.println("al["+i+"]="+al.get(i)); } } public static void main(String[] args) { InsertSort is = new InsertSort(10,100); is.SortIt(); } } JAVA类实现序例化的方法是实现java.io.Serializable接口 Collection框架中实现比较要实现Comparable 接口和 Comparator 接口
15、编程:编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。 答:代码如下: package test;
class SplitString { String SplitStr; int SplitByte; public SplitString(String str,int bytes) { SplitStr=str; SplitByte=bytes; System.out.println("The String is:'"+SplitStr+"';SplitBytes="+SplitByte); } public void SplitIt() { int loopCount;
loopCount=(SplitStr.length()%SplitByte==0)?(SplitStr.length()/SplitByte):(SplitStr.length()/Split
Byte+1); System.out.println("Will Split into "+loopCount); for (int i=1;i<=loopCount ;i++ ) { if (i==loopCount){
System.out.println(SplitStr.substring((i-1)*SplitByte,SplitStr.length())); } else {
System.out.println(SplitStr.substring((i-1)*SplitByte,(i*SplitByte))); } } } public static void main(String[] args) { SplitString ss = new SplitString("test中dd文dsaf中男大3443n中国43中国人
0ewldfls=103",4); ss.SplitIt(); } }
16、JAVA多线程编程。 用JAVA写一个多线程程序,如写四个线程,二个加1,二个对一个变量减一,输出。 希望大家补上,谢谢
17、STRING与STRINGBUFFER的区别。 答:STRING的长度是不可变的,STRINGBUFFER的长度是可变的。如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法
Jsp方面
1、jsp有哪些内置对象?作用分别是什么? 答:JSP共有以下9种基本内置组件(可与ASP的6种内部组件相对应): request 用户端请求,此请求会包含来自GET/POST请求的参数 response 网页传回用户端的回应 pageContext 网页的属性是在这里管理 session 与请求有关的会话期 application servlet 正在执行的内容 out 用来传送回应的输出 config servlet的构架部件 page JSP网页本身 exception 针对错误网页,未捕捉的例外
2、jsp有哪些动作?作用分别是什么? 答:JSP共有以下6种基本动作 jsp:include:在页面被请求的时候引入一个文件。 jsp:useBean:寻找或者实例化一个JavaBean。 jsp:setProperty:设置JavaBean的属性。 jsp:getProperty:输出某个JavaBean的属性。 jsp:forward:把请求转到一个新的页面。 jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记
3、JSP中动态INCLUDE与静态INCLUDE的区别? 答:动态INCLUDE用jsp:include动作实现 <jsp:include page="included.jsp" flush="true" />它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数 静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面 <%@ include file="included.htm" %>
4、两种跳转方式分别是什么?有什么区别? 答:有两种,分别为: <jsp:include page="included.jsp" flush="true"> <jsp:forward page= "nextpage.jsp"/> 前者页面不会转向include所指的页面,只是显示该页的结果,主页面还是原来的页面。执行完后还会回来,相当于函数调用。并且可以带参数.后者完全转向新页面,不会再回来。相当于go to 语句。
Servlet方面
1、说一说Servlet的生命周期? 答:servlet有良好的生存期的定义,包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。
2、Servlet版本间(忘了问的是哪两个版本了)的不同? 希望大家补上,谢谢
3、JAVA SERVLET API中forward() 与redirect()的区别? 答:前者仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;后者则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。所以,前者更加高效,在前者可以满足需要时,尽量使用forward()方法,并且,这样也有助于隐藏实际的链接。在有些情况下,比如,需要跳转到一个其它服务器上的资源,则必须使用sendRedirect()方法。
4、Servlet的基本架构 public class ServletName extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
Jdbc、Jdo方面
1、可能会让你写一段Jdbc连Oracle的程序,并实现数据查询. 答:程序如下: package hello.ant; import java.sql.*; public class jdbc { String dbUrl="jdbc:oracle:thin:@127.0.0.1:1521:orcl"; String theUser="admin"; String thePw="manager"; Connection c=null; Statement conn; ResultSet rs=null; public jdbc() { try{ Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); c = DriverManager.getConnection(dbUrl,theUser,thePw); conn=c.createStatement(); }catch(Exception e){ e.printStackTrace(); } } public boolean executeUpdate(String sql) { try { conn.executeUpdate(sql); return true; } catch (SQLException e) { e.printStackTrace(); return false; } } public ResultSet executeQuery(String sql) { rs=null; try { rs=conn.executeQuery(sql); } catch (SQLException e) { e.printStackTrace(); } return rs; } public void close() { try { conn.close(); c.close(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ResultSet rs; jdbc conn = new jdbc(); rs=conn.executeQuery("select * from test"); try{ while (rs.next()) { System.out.println(rs.getString("id")); System.out.println(rs.getString("name")); } }catch(Exception e) { e.printStackTrace(); } } }
2、Class.forName的作用?为什么要用? 答:调用该访问返回一个以字符串指定类名的类的对象。
3、Jdo是什么? 答:JDO是Java对象持久化的新的规范,为java data object的简称,也是一个用于存取某种数据仓库中的对象的标准化API。JDO提供了透明的对象存储,因此对开发人员来说,存储数据对象完全不需要额外的代码(如JDBC API的使用)。这些繁琐的例行工作已经转移到JDO产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,JDO很灵活,因为它可以在任何数据底层上运行。JDBC只是面向关系数据库(RDBMS)JDO更通用,提供到任何数据底层的存储功能,比如关系数据库、文件、XML以及对象数据库(ODBMS)等等,使得应用可移植性更强。
4、在ORACLE大数据量下的分页解决方法。一般用截取ID方法,还有是三层嵌套方法。 答:一种分页方法 <% int i=1; int numPages=14; String pages = request.getParameter("page") ; int currentPage = 1; currentPage=(pages==null)?(1):{Integer.parseInt(pages)} sql = "select count(*) from tables"; ResultSet rs = DBLink.executeQuery(sql) ; while(rs.next()) i = rs.getInt(1) ; int intPageCount=1; intPageCount=(i%numPages==0)?(i/numPages):(i/numPages+1); int nextPage ; int upPage; nextPage = currentPage+1; if (nextPage>=intPageCount) nextPage=intPageCount; upPage = currentPage-1; if (upPage<=1) upPage=1; rs.close(); sql="select * from tables"; rs=DBLink.executeQuery(sql); i=0; while((i<numPages*(currentPage-1))&&rs.next()){i++;} %> //输出内容 //输出翻页连接 合计:<%=currentPage%>/<%=intPageCount%><a href="List.jsp?page=1">第一页</a><a
href="List.jsp?page=<%=upPage%>">上一页</a> <% for(int j=1;j<=intPageCount;j++){ if(currentPage!=j){ %> <a href="list.jsp?page=<%=j%>">[<%=j%>]</a> <% }else{ out.println(j); } } %> <a href="List.jsp?page=<%=nextPage%>">下一页</a><a href="List.jsp?page=<%=intPageCount%>">最后页
</a>
Xml方面
1、xml有哪些解析技术?区别是什么? 答:有DOM,SAX,STAX等 DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问 STAX:Streaming API for XML (StAX)
2、你在项目中用到了xml技术的哪些方面?如何实现的? 答:用到了数据存贮,信息配置两方面。在做数据交换平台时,将不能数据源的数据组装成XML文件,然后将XML文件压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再同XML文件中还原相关信息进行处理。在做软件配置时,利用XML可以很方便的进行,软件的各种配置参数都存贮在XML文件中。
3、用jdom解析xml文件时如何解决中文问题?如何解析? 答:看如下代码,用编码方式加以解决 package test; import java.io.*; public class DOMTest { private String inFile = "c:\\people.xml"; private String outFile = "c:\\people.xml"; public static void main(String args[]) { new DOMTest(); } public DOMTest() { try { javax.xml.parsers.DocumentBuilder builder =
javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder(); org.w3c.dom.Document doc = builder.newDocument(); org.w3c.dom.Element root = doc.createElement("老师"); org.w3c.dom.Element wang = doc.createElement("王"); org.w3c.dom.Element liu = doc.createElement("刘"); wang.appendChild(doc.createTextNode("我是王老师")); root.appendChild(wang); doc.appendChild(root); javax.xml.transform.Transformer transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "gb2312"); transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
transformer.transform(new javax.xml.transform.dom.DOMSource(doc), new
javax.xml.transform.stream.StreamResult(outFile)); } catch (Exception e) { System.out.println (e.getMessage()); } } }
4、编程用JAVA解析XML的方式. 答:用SAX方式解析XML,XML文件如下: <?xml version="1.0" encoding="gb2312"?> <person> <name>王小明</name> <college>信息学院</college> <telephone>6258113</telephone> <notes>男,1955年生,博士,95年调入海南大学</notes> </person> 事件回调类SAXHandler.java import java.io.*; import java.util.Hashtable; import org.xml.sax.*; public class SAXHandler extends HandlerBase { private Hashtable table = new Hashtable(); private String currentElement = null; private String currentValue = null; public void setTable(Hashtable table) { this.table = table; } public Hashtable getTable() { return table; } public void startElement(String tag, AttributeList attrs) throws SAXException { currentElement = tag; } public void characters(char[] ch, int start, int length) throws SAXException { currentValue = new String(ch, start, length); } public void endElement(String name) throws SAXException { if (currentElement.equals(name)) table.put(currentElement, currentValue); } } JSP内容显示源码,SaxXml.jsp: <HTML> <HEAD> <TITLE>剖析XML文件people.xml</TITLE> </HEAD> <BODY> <%@ page errorPage="ErrPage.jsp" contentType="text/html;charset=GB2312" %> <%@ page import="java.io.*" %> <%@ page import="java.util.Hashtable" %> <%@ page import="org.w3c.dom.*" %> <%@ page import="org.xml.sax.*" %> <%@ page import="javax.xml.parsers.SAXParserFactory" %> <%@ page import="javax.xml.parsers.SAXParser" %> <%@ page import="SAXHandler" %> <% File file = new File("c:\\people.xml"); FileReader reader = new FileReader(file); Parser parser; SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); SAXHandler handler = new SAXHandler(); sp.parse(new InputSource(reader), handler); Hashtable hashTable = handler.getTable(); out.println("<TABLE BORDER=2><CAPTION>教师信息表</CAPTION>"); out.println("<TR><TD>姓名</TD>" + "<TD>" + (String)hashTable.get(new String("name")) + "</TD></TR>"); out.println("<TR><TD>学院</TD>" + "<TD>" + (String)hashTable.get(new String("college"))+"</TD></TR>"); out.println("<TR><TD>电话</TD>" + "<TD>" + (String)hashTable.get(new String("telephone")) + "</TD></TR>"); out.println("<TR><TD>备注</TD>" + "<TD>" + (String)hashTable.get(new String("notes")) + "</TD></TR>"); out.println("</TABLE>"); %> </BODY> </HTML>
EJB方面
1、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别? 答:规范内容包括Bean提供者,应用程序装配者,EJB容器,EJB配置工具,EJB服务提供者,系统管理员。这里面,EJB容器是EJB之所以能够运行的核心。EJB容器管理着EJB的创建,撤消,激活,去活,与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC,JMS.....
2、EJB与JAVA BEAN的区别? 答:Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。
3、EJB的基本架构 答:一个EJB包括三个部分: Remote Interface 接口的代码 package Beans; import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Add extends EJBObject { //some method declare } Home Interface 接口的代码 package Beans; import java.rmi.RemoteException; import jaax.ejb.CreateException; import javax.ejb.EJBHome; public interface AddHome extends EJBHome { //some method declare } EJB类的代码 package Beans; import java.rmi.RemoteException; import javax.ejb.SessionBean; import javx.ejb.SessionContext; public class AddBean Implements SessionBean { //some method declare }
J2EE,MVC方面
1、MVC的各个部分都有那些技术来实现?如何实现? 答:MVC是Model-View-Controller的简写。"Model" 代表的是应用的业务逻辑(通过JavaBean,EJB组件实现), "View" 是应用的表示面(由JSP页面产生),"Controller" 是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。
2、应用服务器与WEB SERVER的区别? 希望大家补上,谢谢
3、J2EE是什么? 答:Je22是Sun公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企业级应用模型(enterpriese application model).在这样的一个应用系统中,可按照功能划分为不同的组件,这些组件又可在不同计算机上,并且处于相应的层次(tier)中。所属层次包括客户层(clietn tier)组件,web层和组件,Business层和组件,企业信息系统(EIS)层。
4、WEB SERVICE名词解释。JSWDL开发包的介绍。JAXP、JAXM的解释。SOAP、UDDI,WSDL解释。 答:Web Service描述语言WSDL SOAP即简单对象访问协议(Simple Object Access Protocol),它是用于交换XML编码信息的轻量级协议。 UDDI 的目的是为电子商务建立标准;UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web Service注册,以使别的企业能够发现的访问协议的实现标准。
5、BS与CS的联系与区别。 希望大家补上,谢谢
6、STRUTS的应用(如STRUTS架构) 答:Struts是采用Java Servlet/JavaServer Pages技术,开发Web应用程序的开放源码的framework。 采用Struts能开发出基于MVC(Model-View-Controller)设计模式的应用构架。 Struts有如下的主要功能: 一.包含一个controller servlet,能将用户的请求发送到相应的Action对象。 二.JSP自由tag库,并且在controller servlet中提供关联支持,帮助开发员创建交互式表单应用。 三.提供了一系列实用对象:XML处理、通过Java reflection APIs自动处理JavaBeans属性、国际化的提示和消息。
设计模式方面
1、开发中都用到了那些设计模式?用在什么场合? 答:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。
2、UML方面 答:标准建模语言UML。用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图,合作图),实现图,
JavaScript方面
1、如何校验数字型? var re=/^\d{1,8}$|\.\d{1,2}$/; var str=document.form1.all(i).value; var r=str.match(re); if (r==null) { sign=-4; break; } else{ document.form1.all(i).value=parseFloat(str); }
CORBA方面
1、CORBA是什么?用途是什么? 答:CORBA 标准是公共对象请求代理结构(Common Object Request Broker Architecture),由对象管理组织 (Object Management Group,缩写为 OMG)标准化。它的组成是接口定义语言(IDL), 语言绑定(binding:也译为联编)和允许应用程序间互操作的协议。 其目的为: 用不同的程序设计语言书写 在不同的进程中运行 为不同的操作系统开发
LINUX方面
1、LINUX下线程,GDI类的解释。 答:LINUX实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。 GDI类为图像设备编程接口类库。
145、编程用JAVA解析XML的方式. 答:用SAX方式解析XML,XML文件如下: <?xml version="1.0" encoding="gb2312"?> <person> <name>王小明</name> <college>信息学院</college> <telephone>6258113</telephone> <notes>男,1955年生,博士,95年调入海南大学</notes> </person> 事件回调类SAXHandler.java import java.io.*; import java.util.Hashtable; import org.xml.sax.*; public class SAXHandler extends HandlerBase { private Hashtable table = new Hashtable(); private String currentElement = null; private String currentValue = null; public void setTable(Hashtable table) { this.table = table; } public Hashtable getTable() { return table; } public void startElement(String tag, AttributeList attrs) throws SAXException { currentElement = tag; } public void characters(char[] ch, int start, int length) throws SAXException { currentValue = new String(ch, start, length); } public void endElement(String name) throws SAXException { if (currentElement.equals(name)) table.put(currentElement, currentValue); } } JSP内容显示源码,SaxXml.jsp: <HTML> <HEAD> <TITLE>剖析XML文件people.xml</TITLE> </HEAD> <BODY> <%@ page errorPage="ErrPage.jsp" contentType="text/html;charset=GB2312" %> <%@ page import="java.io.*" %> <%@ page import="java.util.Hashtable" %> <%@ page import="org.w3c.dom.*" %> <%@ page import="org.xml.sax.*" %> <%@ page import="javax.xml.parsers.SAXParserFactory" %> <%@ page import="javax.xml.parsers.SAXParser" %> <%@ page import="SAXHandler" %> <% File file = new File("c:\\people.xml"); FileReader reader = new FileReader(file); Parser parser; SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); SAXHandler handler = new SAXHandler(); sp.parse(new InputSource(reader), handler); Hashtable hashTable = handler.getTable(); out.println("<TABLE BORDER=2><CAPTION>教师信息表</CAPTION>"); out.println("<TR><TD>姓名</TD>" + "<TD>" + (String)hashTable.get(new String("name")) + "</TD></TR>"); out.println("<TR><TD>学院</TD>" + "<TD>" + (String)hashTable.get(new String("college"))+"</TD></TR>"); out.println("<TR><TD>电话</TD>" + "<TD>" + (String)hashTable.get(new String("telephone")) + "</TD></TR>"); out.println("<TR><TD>备注</TD>" + "<TD>" + (String)hashTable.get(new String("notes")) + "</TD></TR>"); out.println("</TABLE>"); %> </BODY> </HTML>
146、EJB的基本架构 答:一个EJB包括三个部分: Remote Interface 接口的代码 package Beans; import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Add extends EJBObject { //some method declare } Home Interface 接口的代码 package Beans; import java.rmi.RemoteException; import jaax.ejb.CreateException; import javax.ejb.EJBHome; public interface AddHome extends EJBHome { //some method declare } EJB类的代码 package Beans; import java.rmi.RemoteException; import javax.ejb.SessionBean; import javx.ejb.SessionContext; public class AddBean Implements SessionBean { //some method declare }
147、如何校验数字型? var re=/^\d{1,8}$|\.\d{1,2}$/; var str=document.form1.all(i).value; var r=str.match(re); if (r==null) { sign=-4; break; } else{ document.form1.all(i).value=parseFloat(str); }
148、将一个键盘输入的数字转化成中文输出 (例如:输入:1234567 输出:一百二拾三万四千五百六拾七) 用java语言实现,,请编一段程序实现! public class Reader { private String strNum; private String strNumChFormat; private String strNumTemp; private int intNumLen; private String strBegin; public Reader(String strNum) { this.strNum = strNum; } public boolean check(String strNum) { boolean valid = false; if (strNum.substring(0,1).equals("0")){ this.strNum = strNum.substring(1); } try { new Double(strNum); valid = true; } catch (NumberFormatException ex) { System.out.println("Bad number format!"); } return valid; } public void init() { strNumChFormat = ""; intNumLen = strNum.length(); strNumTemp = strNum; strNumTemp = strNumTemp.replace('1', '一'); strNumTemp = strNumTemp.replace('2', '二'); strNumTemp = strNumTemp.replace('3', '三'); strNumTemp = strNumTemp.replace('4', '四'); strNumTemp = strNumTemp.replace('5', '五'); strNumTemp = strNumTemp.replace('6', '六'); strNumTemp = strNumTemp.replace('7', '七'); strNumTemp = strNumTemp.replace('8', '八'); strNumTemp = strNumTemp.replace('9', '九'); strNumTemp = strNumTemp.replace('0', '零'); strNumTemp = strNumTemp.replace('.', '点'); strBegin = strNumTemp.substring(0, 1); } public String readNum() { if (check(strNum)) { init(); try { for (int i = 1, j = 1, k = 1; i < intNumLen; i++) { if (strNumTemp.charAt(intNumLen - 1) == '零' && i == 1) { strNumChFormat = "位"; } else if (strNumTemp.charAt(intNumLen - i) == '零' && j == 1) { strNumChFormat = "位" + strNumChFormat; } else if (strNumTemp.charAt(intNumLen - i) == '点') { j = 1; k = 1; strNumChFormat = strNumTemp.charAt(intNumLen - i) + strNumChFormat; continue; } else { strNumChFormat = strNumTemp.charAt(intNumLen - i) + strNumChFormat; } if (strNumTemp.charAt(intNumLen - i - 1) != '位' && strNumTemp.charAt(intNumLen - i - 1) != '零') { if (j == 1 && i < intNumLen) { strNumChFormat = '拾' + strNumChFormat; } else if (j == 2 && i < intNumLen) { strNumChFormat = '百' + strNumChFormat; } else if (j == 3 && i < intNumLen) { strNumChFormat = '千' + strNumChFormat; } } if (j == 4 && i < intNumLen) { j = 0; } if (k == 4 && i < intNumLen) { strNumChFormat = '万' + strNumChFormat; } else if (k == 8 && i < intNumLen) { k = 0; strNumChFormat = '亿' + strNumChFormat; } j++; k++; } while (strNumChFormat.indexOf("位") != -1) { strNumChFormat = strNumChFormat.replaceAll("位", " "); } if (strNumChFormat.substring(0, 2) == "一拾") { strNumChFormat = strNumChFormat.substring(1, strNumChFormat.length()); } if (strNumChFormat.indexOf("点") >= 0) { String rebegin = strNumChFormat.substring(0, strNumChFormat.indexOf("点")); String relast = strNumChFormat.substring(strNumChFormat.indexOf("点"), strNumChFormat.length()); for (int i = 1; i <= relast.length(); i++) { relast = relast.replaceAll("拾", ""); relast = relast.replaceAll("百", ""); relast = relast.replaceAll("千", ""); relast = relast.replaceAll("万", ""); relast = relast.replaceAll("亿", ""); } strNumChFormat = rebegin + relast; } } catch (ArrayIndexOutOfBoundsException ex) { ex.printStackTrace(); } catch (Exception ex) { ex.printStackTrace(); } int off = strNumChFormat.indexOf("点"); strNumChFormat = strBegin + strNumChFormat.substring(0); } else { strNumChFormat = ""; } return strNumChFormat; } public static void main(String args[]) { try { String number = args[0].toString(); System.out.println("The number is: " + number); Reader reader = new Reader(number); System.out.println("Output String: " + reader.readNum()); } catch (Exception ex) { System.out.println("Please input like that: javac Reader <number>"); } } }
149、JAVA代码查错
1. abstract class Name { private String name; public abstract boolean isStupidName(String name) {} } 大侠们,这有何错误? 答案: 错。abstract method必须以分号结尾,且不带花括号。
2. public class Something { void doSomething () { private String s = ""; int l = s.length(); } } 有错吗? 答案: 错。局部变量前不能放置任何访问修饰符 (private,public,和protected)。final可以用来修饰局部变量 (final如同abstract和strictfp,都是非访问修饰符,strictfp只能修饰class和method而非variable)。
3. abstract class Something { private abstract String doSomething (); } 这好像没什么错吧? 答案: 错。abstract的methods不能以private修饰。abstract的methods就是让子类implement(实现)具体细节的,怎么可以用private把abstract method封锁起来呢? (同理,abstract method前不能加final)。
4. public class Something { public int addOne(final int x) { return ++x; } } 这个比较明显。 答案: 错。int x被修饰成final,意味着x不能在addOne method中被修改。
5. public class Something { public static void main(String[] args) { Other o = new Other(); new Something().addOne(o); } public void addOne(final Other o) { o.i++; } } class Other { public int i; } 和上面的很相似,都是关于final的问题,这有错吗? 答案: 正确。在addOne method中,参数o被修饰成final。如果在addOne method里我们修改了o的reference (比如: o = new Other();),那么如同上例这题也是错的。但这里修改的是o的member vairable (成员变量),而o的reference并没有改变。
6. class Something { int i; public void doSomething() { System.out.println("i = " + i); } } 有什么错呢? 看不出来啊。 答案: 正确。输出的是"i = 0"。int i属於instant variable (实例变量,或叫成员变量)。instant variable有default value。int的default value是0。
7. class Something { final int i; public void doSomething() { System.out.println("i = " + i); } } 和上面一题只有一个地方不同,就是多了一个final。这难道就错了吗? 答案: 错。final int i是个final的instant variable (实例变量,或叫成员变量)。final的instant variable没有default value,必须在constructor (构造器)结束之前被赋予一个明确的值。可以修改为"final int i = 0;"。
8. public class Something { public static void main(String[] args) { Something s = new Something(); System.out.println("s.doSomething() returns " + doSomething()); } public String doSomething() { return "Do something ..."; } } 看上去很完美。 答案: 错。看上去在main里call doSomething没有什么问题,毕竟两个methods都在同一个class里。但仔细看,main是static的。static method不能直接call non-static methods。可改成"System.out.println("s.doSomething() returns " + s.doSomething());"。同理,static method不能访问non-static instant variable。
9. 此处,Something类的文件名叫OtherThing.java class Something { private static void main(String[] something_to_do) { System.out.println("Do something ..."); } } 这个好像很明显。 答案: 正确。从来没有人说过Java的Class名字必须和其文件名相同。但public class的名字必须和文件名相同。
10. interface A{ int x = 0; } class B{ int x =1; } class C extends B implements A { public void pX(){ System.out.println(x); } public static void main(String[] args) { new C().pX(); } } 答案:错误。在编译时会发生错误(错误描述不同的JVM有不同的信息,意思就是未明确的x调用,两个x都匹配(就象在同时import java.util和java.sql两个包时直接声明Date一样)。
94、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别? 答:规范内容包括Bean提供者,应用程序装配者,EJB容器,EJB配置工具,EJB服务提供者,系统管理员。这里面,EJB容器是EJB之所以能够运行的核心。EJB容器管理着EJB的创建,撤消,激活,去活,与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC,JMS.....
95、EJB与JAVA BEAN的区别? 答:Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。
96、EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。 答:EJB包括Session Bean、Entity Bean、Message Driven Bean,基于JNDI、RMI、JAT等技术实现。 SessionBean在J2EE应用程序中被用来完成一些服务器端的业务操作,例如访问数据库、调用其他EJB组件。EntityBean被用来代表应用系统中用到的数据。 对于客户机,SessionBean是一种非持久性对象,它实现某些在服务器上运行的业务逻辑。 对于客户机,EntityBean是一种持久性对象,它代表一个存储在持久性存储器中的实体的对象视图,或是一个由现有企业应用程序实现的实体。 Session Bean 还可以再细分为 Stateful Session Bean 与 Stateless Session Bean ,这两种的 Session Bean都可以将系统逻辑放在 method之中执行,不同的是 Stateful Session Bean 可以记录呼叫者的状态,因此通常来说,一个使用者会有一个相对应的 Stateful Session Bean 的实体。Stateless Session Bean 虽然也是逻辑组件,但是他却不负责记录使用者状态,也就是说当使用者呼叫 Stateless Session Bean 的时候,EJB Container 并不会找寻特定的 Stateless Session Bean 的实体来执行这个 method。换言之,很可能数个使用者在执行某个 Stateless Session Bean 的 methods 时,会是同一个 Bean 的 Instance 在执行。从内存方面来看, Stateful Session Bean 与 Stateless Session Bean 比较, Stateful Session Bean 会消耗 J2EE Server 较多的内存,然而 Stateful Session Bean 的优势却在于他可以维持使用者的状态。
97、EJB与JAVA BEAN的区别? 答:Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。 EJB包括(SessionBean,EntityBean)说出他们的生命周期,及如何管理事务的? SessionBean:Stateless Session Bean 的生命周期是由容器决定的,当客户机发出请求要建立一个Bean的实例时,EJB容器不一定要创建一个新的Bean的实例供客户机调用,而是随便找一个现有的实例提供给客户机。当客户机第一次调用一个Stateful Session Bean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful Session Bean 的方法时容器会把调用分派到与此客户机相关联的Bean实例。 EntityBean:Entity Beans能存活相对较长的时间,并且状态是持续的。只要数据库中的数据存在,Entity beans就一直存活。而不是按照应用程序或者服务进程来说的。即使EJB容器崩溃了,Entity beans也是存活的。Entity Beans生命周期能够被容器或者 Beans自己管理。 EJB通过以下技术管理实务:对象管理组织(OMG)的对象实务服务(OTS),Sun Microsystems的Transaction Service(JTS)、Java Transaction API(JTA),开发组(X/Open)的XA接口。
98、EJB的角色和三个对象 答:一个完整的基于EJB的分布式计算结构由六个角色组成,这六个角色可以由不同的开发商提供,每个角色所作的工作必须遵循Sun公司提供的EJB规范,以保证彼此之间的兼容性。这六个角色分别是EJB组件开发者(Enterprise Bean Provider) 、应用组合者(Application Assembler)、部署者(Deployer)、EJB 服务器提供者(EJB Server Provider)、EJB 容器提供者(EJB Container Provider)、系统管理员(System Administrator) 三个对象是Remote(Local)接口、Home(LocalHome)接口,Bean类
99、EJB容器提供的服务 答:主要提供声明周期管理、代码产生、持续性管理、安全、事务管理、锁和并发行管理等服务。
100、EJB规范规定EJB中禁止的操作有哪些? 答:1.不能操作线程和线程API(线程API指非线程对象的方法如notify,wait等),2.不能操作awt,3.不能实现服务器功能,4.不能对静态属生存取,5.不能使用IO操作直接存取文件系统,6.不能加载本地库.,7.不能将this作为变量和返回,8.不能循环调用。
101、remote接口和home接口主要作用 答:remote接口定义了业务方法,用于EJB客户端调用业务方法。 home接口是EJB工厂用于创建和移除查找EJB实例
102、bean 实例的生命周期 答:对于Stateless Session Bean、Entity Bean、Message Driven Bean一般存在缓冲池管理,而对于Entity Bean和Statefull Session Bean存在Cache管理,通常包含创建实例,设置上下文、创建EJB Object(create)、业务方法调用、remove等过程,对于存在缓冲池管理的Bean,在create之后实例并不从内存清除,而是采用缓冲池调度机制不断重用实例,而对于存在Cache管理的Bean则通过激活和去激活机制保持Bean的状态并限制内存中实例数量。
103、EJB的激活机制 答:以Stateful Session Bean 为例:其Cache大小决定了内存中可以同时存在的Bean实例的数量,根据MRU或NRU算法,实例在激活和去激活状态之间迁移,激活机制是当客户端调用某个EJB实例业务方法时,如果对应EJB Object发现自己没有绑定对应的Bean实例则从其去激活Bean存储中(通过序列化机制存储实例)回复(激活)此实例。状态变迁前会调用对应的ejbActive和ejbPassivate方法。
104、EJB的几种类型 答:会话(Session)Bean ,实体(Entity)Bean 消息驱动的(Message Driven)Bean 会话Bean又可分为有状态(Stateful)和无状态(Stateless)两种 实体Bean可分为Bean管理的持续性(BMP)和容器管理的持续性(CMP)两种
105、客服端调用EJB对象的几个基本步骤 答:设置JNDI服务工厂以及JNDI服务地址系统属性,查找Home接口,从Home接口调用Create方法创建Remote接口,通过Remote接口调用其业务方法。
应用服务器方面
106、如何给weblogic指定大小的内存? 答:在启动Weblogic的脚本中(位于所在Domian对应服务器目录下的startServerName),增加set MEM_ARGS=-Xms32m -Xmx200m,可以调整最小内存为32M,最大200M EJB需直接实现它的业务接口或Home接口吗,请简述理由。 远程接口和Home接口不需要直接实现,他们的实现代码是由服务器产生的,程序运行中对应实现类会作为对应接口类型的实例被使用。
107、应用服务器有那些? 答:BEA WebLogic Server,IBM WebSphere Application Server,Oracle9i Application Server,jBoss,Tomcat
108、如何设定的weblogic的热启动模式(开发模式)与产品发布模式? 答:可以在管理控制台中修改对应服务器的启动模式为开发或产品模式之一。或者修改服务的启动文件或者commenv文件,增加set PRODUCTION_MODE=true。
109、如何启动时不需输入用户名与密码? 答:修改服务启动文件,增加 WLS_USER和WLS_PW项。也可以在boot.properties文件中增加加密过的用户名和密码
60、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用? 答:有两种实现方法,分别是继承Thread类与实现Runnable接口 用synchronized关键字修饰同步方法 反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。
61、sleep() 和 wait() 有什么区别? 答:sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
62、同步和异步有何异同,在什么情况下分别使用他们?举例说明。 答:如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
63、启动一个线程是用run()还是start()? 答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。
64、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 答:不能,一个对象的一个synchronized方法只能由一个线程访问。
65、请说出你所知道的线程同步的方法。 答:wait():使一个线程处于等待状态,并且释放所持有的对象的lock。 sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。 notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。 Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
66、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口 同步的实现方面有两种,分别是synchronized,wait与notify
67、线程的基本概念、线程的基本状态以及状态之间的关系 答:线程指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至少都有一个线程,也就是程序本身。 Java中的线程有四种状态分别是:运行、就绪、挂起、结束
68、简述synchronized和java.util.concurrent.locks.Lock的异同 ? 答:主要相同点:Lock能完成synchronized所实现的所有功能 主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。
Jsp方面
69、forward 和redirect的区别 答:forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。 redirect就是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,一般来说浏览器会用刚才请求的所有参数重新请求,所以session,request参数都可以获取。
70、jsp有哪些内置对象?作用分别是什么? 答:JSP共有以下9种基本内置组件(可与ASP的6种内部组件相对应): request 用户端请求,此请求会包含来自GET/POST请求的参数 response 网页传回用户端的回应 pageContext 网页的属性是在这里管理 session 与请求有关的会话期 application servlet 正在执行的内容 out 用来传送回应的输出 config servlet的构架部件 page JSP网页本身 exception 针对错误网页,未捕捉的例外
71、jsp有哪些动作?作用分别是什么? 答:JSP共有以下6种基本动作 jsp:include:在页面被请求的时候引入一个文件。 jsp:useBean:寻找或者实例化一个JavaBean。 jsp:setProperty:设置JavaBean的属性。 jsp:getProperty:输出某个JavaBean的属性。 jsp:forward:把请求转到一个新的页面。 jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记
72、JSP中动态INCLUDE与静态INCLUDE的区别? 答:动态INCLUDE用jsp:include动作实现 <jsp:include page="included.jsp" flush="true" />它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数 静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面 <%@ include file="included.htm" %>
73、两种跳转方式分别是什么?有什么区别? 答:有两种,分别为: <jsp:include page="included.jsp" flush="true"> <jsp:forward page= "nextpage.jsp"/> 前者页面不会转向include所指的页面,只是显示该页的结果,主页面还是原来的页面。执行完后还会回来,相当于函数调用。并且可以带参数.后者完全转向新页面,不会再回来。相当于go to 语句。
74、JSP的内置对象及方法。 答:request表示HttpServletRequest对象。它包含了有关浏览器请求的信息,并且提供了几个用于获取cookie, header, 和session数据的有用的方法。 response表示HttpServletResponse对象,并提供了几个用于设置送回 浏览器的响应的方法(如cookies,头信息等) out对象是javax.jsp.JspWriter的一个实例,并提供了几个方法使你能用于向浏览器回送输出结果。 pageContext表示一个javax.servlet.jsp.PageContext对象。它是用于方便存取各种范围的名字空间、servlet相关的对象的API,并且包装了通用的servlet相关功能的方法。 session表示一个请求的javax.servlet.http.HttpSession对象。Session可以存贮用户的状态信息 applicaton 表示一个javax.servle.ServletContext对象。这有助于查找有关servlet引擎和servlet环境的信息 config表示一个javax.servlet.ServletConfig对象。该对象用于存取servlet实例的初始化参数。 page表示从该页面产生的一个servlet实例
Servlet方面
75、说一说Servlet的生命周期? 答:servlet有良好的生存期的定义,包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。 Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。 与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。
76、JAVA SERVLET API中forward() 与redirect()的区别? 答:前者仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;后者则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。所以,前者更加高效,在前者可以满足需要时,尽量使用forward()方法,并且,这样也有助于隐藏实际的链接。在有些情况下,比如,需要跳转到一个其它服务器上的资源,则必须使用sendRedirect()方法。
77、Servlet的基本架构 答: public class ServletName extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
78、什么情况下调用doGet()和doPost()? 答:Jsp页面中的form标签里的method属性为get时调用doGet(),为post时调用doPost()。
79、servlet的生命周期 答:web容器加载servlet,生命周期开始。通过调用servlet的init()方法进行servlet的初始化。通过调用service()方法实现,根据请求的不同调用不同的do***()方法。结束服务,web容器调用servlet的destroy()方法。
80、如何现实servlet的单线程模式 答:<%@ page isThreadSafe="false"%>
81、页面间对象传递的方法 答:request,session,application,cookie等
82、JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么? 答:JSP是Servlet技术的扩展,本质上是Servlet的简易方式,更强调应用的外表表达。JSP编译后是"类servlet"。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑。
83、四种会话跟踪技术 答:会话作用域ServletsJSP 页面描述 page否是代表与一个页面相关的对象和属性。一个页面由一个编译好的 Java servlet 类(可以带有任何的 include 指令,但是没有 include 动作)表示。这既包括 servlet 又包括被编译成 servlet 的 JSP 页面 request是是代表与 Web 客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件(由于 forward 指令和 include 动作的关系) session是是代表与用于某个 Web 客户机的一个用户体验相关的对象和属性。一个 Web 会话可以也经常会跨越多个客户机请求 application是是代表与整个 Web 应用程序相关的对象和属性。这实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域 84、Request对象的主要方法 答: setAttribute(String name,Object):设置名字为name的request的参数值 getAttribute(String name):返回由name指定的属性值 getAttributeNames():返回request对象所有属性的名字集合,结果是一个枚举的实例 getCookies():返回客户端的所有Cookie对象,结果是一个Cookie数组 getCharacterEncoding():返回请求中的字符编码方式 getContentLength():返回请求的Body的长度 getHeader(String name):获得HTTP协议定义的文件头信息 getHeaders(String name):返回指定名字的request Header的所有值,结果是一个枚举的实例 getHeaderNames():返回所以request Header的名字,结果是一个枚举的实例 getInputStream():返回请求的输入流,用于获得请求中的数据 getMethod():获得客户端向服务器端传送数据的方法 getParameter(String name):获得客户端传送给服务器端的有name指定的参数值 getParameterNames():获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例 getParameterValues(String name):获得有name指定的参数的所有值 getProtocol():获取客户端向服务器端传送数据所依据的协议名称 getQueryString():获得查询字符串 getRequestURI():获取发出请求字符串的客户端地址 getRemoteAddr()
|