1、面向对象的特征有哪些方面【基础】
答:面向对象的特征主要有以下几个方面:
1)抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
2)继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
3)封装:封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
4)多态性:多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
2、作用域public,private,protected,以及不写时的区别?【基础】
答:区别如下:
作用域当前类同包子孙类其他
public√√√√
protected√√√×
default√√××
private√×××
不写时默认为default。
3、String是最基本的数据类型吗【基础】
答:不是。
4、float型floatf=3.4是否正确【基础】
答:不正确;精度不准确,应该用强制类型转换,如下所示:floatf=(float)3.4。
5、语句floatf=1.3;编译能否通过?【基础】
答:不能;应该用强制类型转换,如下所示:floatf=(float)1.3;。
6、shorts1=1;s1=s1+1;有什么错
shorts1=1;s1+=1;有什么错【基础】
答:shorts1=1;s1=s1+1;s1+1运算结果是int型,需要强制转换类型;shorts1=1;s1+=1;可以正确编译,自动类型提升。
7、Java有没有goto【基础】
答:goto是java中的保留字,现在没有在java中使用。
8、int和Integer有什么区别【基础】
答:Java提供两种不同的类型:引用类型和原始类型(或内置类型);
int是java的原始数据类型,Integer是java为int提供的封装类。
Java为每个原始类型提供了封装类:
原始类型:boolean,char,byte,short,int,long,float,double
封装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为null,而原始类型实例变量的缺省值与它们的类型有关。
9、&和&&的区别?【基础】
答:&是位运算符,表示按位与运算,&&是逻辑运算符,表示逻辑与(and)。
10、简述逻辑操作(&,|,^)与条件操作(&&,||)的区别?【基础】
答:区别主要有两点:a.条件操作只能操作布尔型的,而逻辑操作不仅可以操作布尔型,而且可以操作数值型b.逻辑操作不会产生短路。
11、heap和stack有什么区别?【基础】
答:栈是一种线形集合,其添加和删除元素的操作应在同一段完成,栈按照后进先出的方式进行处理;堆是栈的一个组成元素。
12、Math.round(11.5)等于多少Math.round(-11.5)等于多少【基础】
答:Math.round(11.5)==12Math.round(-11.5)==-11round方法返回与参数最接近的长整数,参数加1/2后求其floor。
13、swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上【基础】
答:switch(expr1)中,expr1是一个整数表达式。因此传递给switch和case语句的参数应该是int、short、char或者byte。long,string都不能作用于swtich。
14、编程题:用最有效率的方法算出2乘以8等於几【基础】
答:2<<3。
15、有没有length()这个方法String有没有length()这个方法?【基础】
答:数组没有length()这个方法,有length的属性。String有length()这个方法。
16、在JAVA中,如何跳出当前的多重嵌套循环?【基础】
答:在最外层循环前加label标识,然后用break:label方法即可跳出多重循环。
17、构造器Constructor是否可被override【基础】
答:构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。
18、两个对象值相同(x.equals(y)==true),但却可有不同的hashcode,这句话对不对【基础】
答:不对,有相同的hashcode。
19、是否可以继承String类【基础】
答:String类是final类,故不可以继承。
20、以下二条语句返回值为true的有:
A:“beijing”==“beijing”;
B:“beijing”.equalsIgnoreCase(newString(“beijing”));【基础】
答:A和B。
21、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递【基础】
答:是值传递。Java编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。
22、我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串?【基础】
答:publicStringtranslate(Stringstr){
StringtempStr="";
try{
tempStr=newString(str.getBytes("ISO-8859-1"),"GBK");
tempStr=tempStr.trim();
}catch(Exceptione){
System.err.println(e.getMessage());
}
returntempStr;
23、String和StringBuffer的区别【基础】
答:JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。
24、String,StringBufferStringBuilder的区别。【基础】
答:String的长度是不可变的;StringBuffer的长度是可变的,如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法;线程安全;StringBuilder是从JDK5开始,为StringBuffer该类补充了一个单个线程使用的等价类;通常应该优先使用StringBuilder类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。
25、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型【基础】
答:方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写(Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。
26、定义类A和类B如下:【基础】
classA{
inta=1;
doubled=2.0;
voidshow(){
System.out.println("ClassA:a="+a+"\td="+d);
classBextendsA{
floata=3.0f;
Stringd="Javaprogram.";
super.show();
System.out.println("ClassB:a="+a+"\td="+d);
(1)若在应用程序的main方法中有以下语句:
Aa=newA();
a.show();
则输出的结果如何?
(2)若在应用程序的main方法中定义类B的对象b:
Ab=newB();
b.show();
答:输出结果为:
1)ClassA:a=1d=2.0;
2)ClassA:a=1d=2.0
ClassB:a=3.0d=Javaprogram。
27、描述一下JVM加载class文件的原理机制【基础】
答:JVM中类的装载是由ClassLoader和它的子类来实现的,JavaClassLoader是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
28、char型变量中能不能存贮一个中文汉字为什么【基础】
答:能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的。
29、abstractclass和interface有什么区别【基础】
将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof运算符可以用来决定某对象的类是否实现了接口。
30、StaticNestedClass和InnerClass的不同?【基础】
31、java中会存在内存泄漏吗,请简单描述。【基础】
答:会;存在无用但可达的对象,这些对象不能被GC回收,导致耗费内存资源。
32、abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized【基础】
答:都不能。
33、静态变量和实例变量的区别?【基础】
答:静态变量也称为类变量,归全类共有,它不依赖于某个对象,可通过类名直接访问;而实例变量必须依存于某一实例,只能通过对象才能访问到它。
34、是否可以从一个static方法内部发出对非static方法的调用?【基础】
答:不可以,如果其中包含对象的method(),不能保证对象初始化。
35、写clone()方法时,通常都有一行代码,是什么?【基础】
答:Clone有缺省行为:super.clone(),他负责产生正确大小的空间,并逐位复制。
36、GC是什么为什么要有GC【基础】
答:GC是垃圾收集的意思(GabageCollection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc()或Runtime.getRuntime().gc()。
37、垃圾回收的优点和原理。并考虑2种回收机制。【基础】
38、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?【基础】
答:对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
39、Strings=newString(“xyz”);创建了几个StringObject【基础】
答:两个对象,一个是"xyx",一个是指向"xyx"的引用对象s。
40、接口是否可继承接口抽象类是否可实现(implements)接口抽象类是否可继承实体类(concreteclass)【基础】
答:接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承实体类,但前提是实体类必须有明确的构造函数。
41、Java的接口和C++的虚类的相同和不同处。【基础】
答:由于Java不支持多继承,而有可能某个类或对象要使用分别在几个类或对象里面的方法或属性,现有的单继承机制就不能满足要求。与继承相比,接口有更高的灵活性,因为接口中没有任何实现代码。当一个类实现了接口以后,该类要实现接口里面所有的方法和属性,并且接口里面的属性在默认状态下面都是publicstatic,所有方法默认情况下是public.一个类可以实现多个接口。
42、一个“.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?【基础】
答:可以;必须只有一个类名与文件名相同。
43、说出一些常用的类,包,接口,请各举5个。【基础】
答:常用的类:BufferedReaderBufferedWriterFileReaderFileWirterStringInteger;
常用的包:java.langjava.awtjava.iojava.utiljava.sql;
常用的接口:RemoteListMapDocumentNodeList
44、AnonymousInnerClass(匿名内部类)是否可以extends(继承)其它类?是否可以implements(实现)interface(接口)【基础】
答:可以继承其他类或实现其他接口,在swing编程中常用此方式。
45、内部类可以引用他包含类的成员吗?有没有什么限制?【基础】
答:一个内部类对象可以访问创建它的外部类对象的内容。
46、java中实现多态的机制是什么?【基础】
答:方法的覆盖Overriding和重载Overloading是java多态性的不同表现;覆盖Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。
答:表示该类不能被继承,是顶级类。
48、下面哪些类可以被继承【基础】
1)java.lang.Thread(T)
2)java.lang.Number(T)
3)java.lang.Double(F)
4)java.lang.Math(F)
5)java.lang.Void(F)
6)java.lang.Class(F)
7)java.lang.ClassLoader(T)
答:1、2、7可以被继承。
49、指出下面程序的运行结果:【基础】
static{
System.out.print("1");
publicA(){
System.out.print("2");
System.out.print("a");
publicB(){
System.out.print("b");
publicclassHello{
publicstaticvoidmain(String[]ars){
Aab=newB();//执行到此处,结果:1a2b
ab=newB();//执行到此处,结果:1a2b2b
答:输出结果为1a2b2b;类的static代码段,可以看作是类首次加载(虚拟机加载)执行的代码,而对于类加载,首先要执行其基类的构造,再执行其本身的构造。
50、继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么【基础】
父类:
packagetest;
publicclassFatherClass{
publicFatherClass(){
System.out.println("FatherClassCreate");
子类:
importtest.FatherClass;
publicclassChildClassextendsFatherClass{
publicChildClass(){
System.out.println("ChildClassCreate");
publicstaticvoidmain(String[]args){
FatherClassfc=newFatherClass();
ChildClasscc=newChildClass();
FatherClassCreate
ChildClassCreate
51、内部类的实现方式【基础】
答:示例代码如下:
publicclassOuterClass{
privateclassInterClass{
publicInterClass(){
System.out.println("InterClassCreate");
publicOuterClass(){
InterClassic=newInterClass();
System.out.println("OuterClassCreate");
OuterClassoc=newOuterClass();
输出结果为:
InterClassCreate
OuterClassCreate
52、关于内部类:【基础】
privatedoubled1=1.0;
//insertcodehere
Youneedtoinsertaninnerclassdeclarationatline3,Whichtwo
innerclassdeclarationsarevalid(Choosetwo.)
A.classInnerOne{
publicstaticdoublemethoda(){returnd1;}
B.publicclassInnerOne{
staticdoublemethoda(){returnd1;}
C.privateclassInnerOne{
doublemethoda(){returnd1;}
D.staticclassInnerOne{
protecteddoublemethoda(){returnd1;}
E.abstractclassInnerOne{
publicabstractdoublemethoda();
答:答案为C、E;说明如下:
1)静态内部类可以有静态成员,而非静态内部类则不能有静态成员;故A、B错;
2)静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;故D错;
3)非静态内部类的非静态成员可以访问外部类的非静态变量;故C正确。
53、数据类型之间的转换:
1)如何将数值型字符转换为数字?
2)如何将数字转换为字符?
3)如何取小数点前两位并四舍五入【基础】
答:1)调用数值类型相应包装类中的方法parse***(String)或valueOf(String)即可返回相应基本类型或包装类型数值;
2)将数字与空字符串相加即可获得其所对应的字符串;另外对于基本类型数字还可调用String类中的valueOf(…)方法返回相应字符串,而对于包装类型数字则可调用其toString()方法获得相应字符串;
3)可用该数字构造一java.math.BigDecimal对象,再利用其round()方法进行四舍五入到保留小数点后两位,再将其转换为字符串截取最后两位。
54、字符串操作:如何实现字符串的反转及替换?【基础】
答:可用字符串构造一StringBuffer对象,然后调用StringBuffer中的reverse方法即可实现字符串的反转,调用replace方法即可实现字符串的替换。
55、编码转换:怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?【基础】
答:示例代码如下:
Strings1="你好";
Strings2=newString(s1.getBytes("GB2312"),"ISO-8859-1");
56、写一个函数,要求输入一个字符串和一个字符长度,对该字符串进行分隔。【基础】
答:函数代码如下:
publicString[]split(Stringstr,intchars){
intn=(str.length()+chars-1)/chars;
Stringret[]=newString[n];
for(inti=0;i if(i ret[i]=str.substring(i*chars,(i+1)*chars); }else{ ret[i]=str.substring(i*chars); returnret; 57、写一个函数,2个参数,1个字符串,1个字节数,返回截取的字符串,要求字符串中的中文不能出现乱码:如(“我ABC”,4)应该截为“我AB”,输入(“我ABC汉DEF”,6)应该输出为“我ABC”而不是“我ABC+汉的半个”。【基础】 答:代码如下: publicStringsubString(Stringstr,intsubBytes){ intbytes=0;//用来存储字符串的总字节数 for(inti=0;i if(bytes==subBytes){ returnstr.substring(0,i); charc=str.charAt(i); if(c<256){ bytes+=1;//英文字符的字节数看作1 bytes+=2;//中文字符的字节数看作2 if(bytes-subBytes==1){ returnstr; 1)如何取得年月日、小时分秒? 2)如何取得从1970年到现在的毫秒数? 3)如何取得某个日期是当月的最后一天? 4)如何格式化日期?【基础】 答:1)创建java.util.Calendar实例(Calendar.getInstance()),调用其get()方法传入不同的参数即可获得参数所对应的值,如:calendar.get(Calendar.YEAR);//获得年 2)以下方法均可获得该毫秒数:Calendar.getInstance().getTimeInMillis();System.currentTimeMillis(); 3)示例代码如下: Calendartime=Calendar.getInstance(); time.set(Calendar.DAY_OF_MONTH, time.getActualMaximum(Calendar.DAY_OF_MONTH)); 4)利用java.text.DataFormat类中的format()方法可将日期格式化。 59、Java编程,打印昨天的当前时刻。【基础】 答:publicclassYesterdayCurrent{ Calendarcal=Calendar.getInstance(); cal.add(Calendar.DATE,-1); System.out.println(cal.getTime()); 60、java和javasciprt的区别。【基础】 答:JavaScript与Java是两个公司开发的不同的两个产品。Java是SUN公司推出的新一代面向对象的程序设计语言,特别适合于Internet应用程序开发;而JavaScript是Netscape公司的产品,其目的是为了扩展NetscapeNavigator功能,而开发的一种可以嵌入Web页面中的基于对象和事件驱动的解释性语言,它的前身是LiveScript;而Java的前身是Oak语言。下面对两种语言间的异同作如下比较: 1)基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(ObjectBased)和事件驱动(EventDriver)的编程语言。因而它本身提供了非常丰富的内部对象供设计人员使用; 2)解释和编译:Java的源代码在执行之前,必须经过编译;JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行; 4)代码格式不一样。 61、什么时候用assert?【中等难度】 答:assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后,assertion检查通常是关闭的。在实现中,断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式计算为false,那么系统会报告一个Assertionerror。 断言用于调试目的: assert(a>0);//throwsanAssertionerrorifa<=0 断言可以有两种形式: assertExpression1; assertExpression1:Expression2; Expression1应该总是产生一个布尔值。 Expression2可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的String消息。 断言在默认情况下是禁用的,要在编译时启用断言,需使用source1.4标记: javac-source1.4Test.java 要在运行时启用断言,可使用-enableassertions或者-ea标记。 要在运行时选择禁用断言,可使用-da或者-disableassertions标记。 要在系统类中启用断言,可使用-esa或者-dsa标记。还可以在包的基础上启用或者禁用断言。可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。 62、Java中的异常处理机制的简单原理和应用?【基础】 答:当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。所有的异常都是java.lang.Thowable的子类。 63、error和exception有什么区别【基础】 答:error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。 64、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后【基础】 答:会执行,在return前执行。 65、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?【基础】 66、运行时异常与一般异常有何异同?【基础】 67、给我一个你最常见到的runtimeexception?【基础】 答:ArithmeticException,ArrayStoreException,BufferOverflowException, BufferUnderflowException,CannotRedoException,CannotUndoException, ClassCastException,CMMException,ConcurrentModificationException, DOMException,EmptyStackException,IllegalArgumentException, IllegalMonitorStateException,IllegalPathStateException, IllegalStateException,ImagingOpException,IndexOutOfBoundsException, MissingResourceException,NegativeArraySizeException, NoSuchElementException,NullPointerException,ProfileDataException, ProviderException,RasterFormatException,SecurityException, SystemException,UndeclaredThrowableException, UnmodifiableSetException,UnsupportedOperationException 68、final,finally,finalize的区别【基础】 69、类ExampleA继承Exception,类ExampleB继承ExampleA;【基础】 有如下代码片断: thrownewExampleB(“b”); }catch(ExampleAe){ System.out.printfln(“ExampleA”); }catch(Exceptione){ System.out.printfln(“Exception”); 输出的内容应该是: A:ExampleAB:ExceptionC:bD:无 答:输出为A。 70、介绍JAVA中的CollectionFrameWork(及如何写自己的数据结构)【基础】 答:CollectionFrameWork如下: Collection ├List │├LinkedList │├ArrayList │└Vector │└Stack └Set Map ├Hashtable ├HashMap └WeakHashMap Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements);Map提供key到value的映射。 71、List,Set,Map是否继承自Collection接口?【基础】 答:List,Set是;Map不是。 72、你所知道的集合类都有哪些?主要方法?【基础】 答:最常用的集合类是List和Map。List的具体实现包括ArrayList和Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。List适用于按数值索引访问元素的情形。Map提供了一个更通用的元素存储方法。Map集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。 73、说出ArrayList,Vector,LinkedList的存储性能和特性?【基础】 答:ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。 74、Collection和Collections的区别?【基础】 答:Collection是java.util下的接口,它是各种集合的父接口,继承于它的接口主要有Set和List;Collections是个java.util下的类,是针对集合的帮助类,提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。 75、HashMap和Hashtable的区别【基础】 答:二者都实现了Map接口,是将惟一键映射到特定的值上;主要区别在于: 1)HashMap没有排序,允许一个null键和多个null值,而Hashtable不允许; 2)HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey,因为contains方法容易让人引起误解; 3)Hashtable继承自Dictionary类,HashMap是Java1.2引进的Map接口的实现; 4)Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap就必须为之提供外同步。Hashtable和HashMap采用的hash/rehash算法大致一样,所以性能不会有很大的差异。 76、Arraylist与Vector区别?【基础】 答:就ArrayList与Vector主要从二方面来说: 1)同步性:Vector是线程安全的(同步),而ArrayList是线程序不安全的; 2)数据增长:当需要增长时,Vector默认增长一倍,而ArrayList却是一半。 77、List、Map、Set三个接口,存取元素时,各有什么特点?【基础】 答:List以特定次序来持有元素,可有重复元素。Set无法拥有重复元素,内部排序。Map保存key-value值,value可多值。 78、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢是用==还是equals()它们有何区别【基础】 答:Set里的元素是不能重复的,用equals()方法来区分重复与否。覆盖equals()方法用来判断对象的内容是否相同,而”==”判断地址是否相等,用来决定引用值是否指向同一对象。 79、用程序给出随便大小的10个数,序号为1-10,按从小到大顺序输出,并输出相应的序号。【基础】 importjava.util.ArrayList; importjava.util.Collections; importjava.util.Iterator; importjava.util.List; importjava.util.Random; publicclassRandomSort{ publicstaticvoidprintRandomBySort(){ Randomrandom=newRandom();//创建随机数生成器 Listlist=newArrayList(); //生成10个随机数,并放在集合list中 for(inti=0;i<10;i++){ list.add(random.nextInt(1000)); Collections.sort(list);//对集合中的元素进行排序 Iteratorit=list.iterator(); intcount=0; while(it.hasNext()){//顺序输出排序后集合中的元素 System.out.println(++count+":"+it.next()); printRandomBySort(); 80、用JAVA实现一种排序,JAVA类实现序列化的方法?在COLLECTION框架中,实现比较要实现什么样的接口?【基础】 答:用插入法进行排序代码如下: importjava.util.*; classInsertSort{ ArrayListal; publicInsertSort(intnum,intmod){ al=newArrayList(num); Randomrand=newRandom(); System.out.println("TheArrayListSortBefore:"); for(inti=0;i al.add(newInteger(Math.abs(rand.nextInt())%mod+ 1)); System.out.println("al["+i+"]="+al.get(i)); publicvoidSortIt(){ tempInt; intMaxSize=1; for(inti=1;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()); for(intj=0;j if(((Integer)al.get(j)).intValue() >=tempInt.intValue()){ al.add(j,tempInt); break; System.out.println("TheArrayListSortAfter:"); for(inti=0;i InsertSortis=newInsertSort(10,100); is.SortIt(); JAVA类实现序例化的方法是实现java.io.Serializable接口;Collection框架中实现比较要实现Comparable接口和Comparator接口。 81、sleep()和wait()有什么区别【基础】 82、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法【基础】 答:其它线程只能访问该对象的其它非同步方法,同步方法则不能进入。 83、请说出你所知道的线程同步的方法。【基础】 答:wait():使一个线程处于等待状态,并且释放所持有的对象的lock;sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常;notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级; notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。 84、多线程有几种实现方法,都是什么同步有几种实现方法,都是什么【基础】 答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口,同步的实现方面有两种,分别是synchronized,wait与notify。 85、同步和异步有何异同,在什么情况下分别使用他们?举例说明。【基础】 86、启动一个线程是用run()还是start()【基础】 答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。 87、线程的基本概念、线程的基本状态以及状态之间的关系?【基础】 答:线程指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至少都有一个线程,也就是程序本身;Java中的线程有四种状态分别是:运行、就绪、挂起、结束。 88、简述synchronized和java.util.concurrent.locks.Lock的异同?【中等难度】 答:主要相同点:Lock能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。 89、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法stop()和suspend()方法为何不推荐使用?【中等难度】 答:有两种实现方法,分别是继承Thread类与实现Runnable接口;用synchronized关键字修饰同步方法;反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在;suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被“挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。故不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。 90、设计4个线程,其中两个线程每次对j增加1,另两个线程对j每次减少1;写出程序。【中等难度】 答:以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题: publicclassTestThread{ privateintj; publicTestThread(intj){this.j=j;} privatesynchronizedvoidinc(){ j++; System.out.println(j+"--Inc--"+ Thread.currentThread().getName()); privatesynchronizedvoiddec(){ j--; System.out.println(j+"--Dec--"+ publicvoidrun(){ (newDec()).start(); newThread(newInc()).start(); classDecextendsThread{ for(inti=0;i<100;i++){ dec(); classIncimplementsRunnable{ inc(); (newTestThread(5)).run(); 91、什么是java序列化,如何实现java序列化?【基础】 答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题;序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需实现的方法,implementsSerializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Objectobj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。 92、java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?【基础】 答:字节流,字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。 93、文件和目录(IO)操作: 1)如何列出某个目录下的所有文件? 2)如何列出某个目录下的所有子目录? 3)如何判断一个文件或目录是否存在? 4)如何读写文件?【基础】 答:1)示例代码如下: Filefile=newFile("e:\\总结"); File[]files=file.listFiles(); for(inti=0;i if(files[i].isFile())System.out.println(files[i]); 2)示例代码如下: if(files[i].isDirectory())System.out.println(files[i]); 3)创建File对象,调用其exsit()方法即可返回是否存在,如: System.out.println(newFile("d:\\t.txt").exists()); 4)示例代码如下: //读文件: FileInputStreamfin=newFileInputStream("e:\\tt.txt"); byte[]bs=newbyte[100]; while(true){ intlen=fin.read(bs); if(len<=0)break; System.out.print(newString(bs,0,len)); fin.close(); //写文件: FileWriterfw=newFileWriter("e:\\test.txt"); fw.write("helloworld!"+System.getProperty("line.separator")); fw.write("你好!北京!"); fw.close(); 94、写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。【基础】 publicintcountWords(Stringfile,Stringfind)throwsException { Readerin=newFileReader(file); intc; while((c=in.read())!=-1){ while(c==find.charAt(0)){ for(inti=1;i c=in.read(); if(c!=find.charAt(i))break; if(i==find.length()-1)count++; returncount; 95、Java的通信编程,编程题(或问答),用JAVASOCKET编程,读服务器几个字符,再写入本地显示?【基础】 答:Server端程序: importjava.net.*; importjava.io.*; publicclassServer{ privateServerSocketss; privateSocketsocket; privateBufferedReaderin; privatePrintWriterout; publicServer(){ ss=newServerSocket(10000); socket=ss.accept(); StringRemoteIP= socket.getInetAddress().getHostAddress(); StringRemotePort=":"+socket.getLocalPort(); System.out.println("Aclientcomein!IP:" +RemoteIP+RemotePort); in=newBufferedReader(new InputStreamReader(socket.getInputStream())); Stringline=in.readLine(); System.out.println("Cleintsendis:"+line); out= newPrintWriter(socket.getOutputStream(),true); out.println("YourMessageReceived!"); out.close(); in.close(); socket.close(); }catch(IOExceptione){ out.println("wrong"); newServer(); Client端程序: publicclassClient{ Socketsocket; BufferedReaderin; PrintWriterout; publicClient(){ System.out.println("TrytoConnectto 127.0.0.1:10000"); socket=newSocket("127.0.0.1",10000); System.out.println("TheServerConnected!"); System.out.println("PleaseentersomeCharacter:"); BufferedReaderline=newBufferedReader(new InputStreamReader(System.in)); out=newPrintWriter(socket.getOutputStream(),true); out.println(line.readLine()); in=newBufferedReader( newInputStreamReader(socket.getInputStream())); System.out.println(in.readLine()); out.println("Wrong"); newClient(); 96、UML是什么?常用的几种图?【基础】 答:UML是标准建模语言;常用图包括:用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图,合作图),实现图。 97、编程题:写一个Singleton出来。【基础】 答:Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。举例:定义一个类,它的构造函数为private的,它有一个static的private的该类变量,在类初始化时实例话,通过一个public的getInstance 方法获取对它的引用,继而调用其中的方法。 第一种形式: publicclassSingleton{ privateSingleton(){} privatestaticSingletoninstance=newSingleton(); publicstaticSingletongetInstance(){ returninstance; 第二种形式: privatestaticSingletoninstance=null; publicstaticsynchronizedSingletongetInstance(){ if(instance==null) instance=newSingleton(); 其他形式:定义一个类,它的构造函数为private的,所有方法为static的。一般认为第一种形式要更加安全些。 98、说说你所熟悉或听说过的j2ee中的几种常用模式及对设计模式的一些看法。【中等难度】 答:SessionFacadePattern:使用SessionBean访问EntityBean; MessageFacadePattern:实现异步调用; EJBCommandPattern:使用CommandJavaBeans取代SessionBean,实现轻量级访问; DataTransferObjectFactory:通过DTOFactory简化EntityBean数据提供特性; GenericAttributeAccess:通过AttibuteAccess接口简化EntityBean数据提供特性; BusinessInterface:通过远程(本地)接口和Bean类实现相同接口规范业务逻辑一致性; EJB架构的设计好坏将直接影响系统的性能、可扩展性、可维护性、组件可 重用性及开发效率。项目越复杂,项目队伍越庞大则越能体现良好设计的重要性。 99、Java中常用的设计模式?说明工厂模式?【中等难度】 答:Java中的23种设计模式:Factory(工厂模式),Builder(建造模式),FactoryMethod(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式),Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式),Command(命令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),TemplateMethod(模板方法模式),ChainOfResponsibleity(责任链模式)。 工厂模式:工厂模式是一种经常被使用到的模式,根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。 100、开发中都用到了那些设计模式用在什么场合【中等难度】 101、你对软件开发中迭代的含义的理解;【中等难度】 答:软件开发中,各个开发阶段不是顺序执行的,应该是并行执行,也就是迭代的意思。这样对于开发中的需求变化,及人员变动都能得到更好的适应。 102、XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?【基础】 答:1)两种形式:dtd以及schema; 2)本质区别:schema本身是xml的,可以被XML解析器解析(这也是从DTD上发展schema的根本目的); 3)解析方式:有DOM,SAX,STAX等: DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问;SAX:不同于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问; STAX:StreamingAPIforXML(StAX)。 103、你在项目中用到了xml技术的哪些方面如何实现的【中等难度】 104、用jdom解析xml文件时如何解决中文问题如何解析【较难】 答:看如下代码,用编码方式加以解决 publicclassDOMTest{ privateStringinFile="c:\people.xml"; privateStringoutFile="c:\people.xml"; publicstaticvoidmain(Stringargs[]){ newDOMTest(); publicDOMTest(){ javax.xml.parsers.DocumentBuilderbuilder= javax.xml.parsers.DocumentBuilderFactory. newInstance().newDocumentBuilder(); org.w3c.dom.Documentdoc=builder.newDocument(); org.w3c.dom.Elementroot=doc.createElement("老师"); org.w3c.dom.Elementwang=doc.createElement("王"); org.w3c.dom.Elementliu=doc.createElement("刘"); wang.appendChild(doc.createTextNode("我是王老师")); root.appendChild(wang); doc.appendChild(root); javax.xml.transform.Transformertransformer= javax.xml.transform.TransformerFactory. newInstance().newTransformer(); transformer.setOutputProperty( javax.xml.transform.OutputKeys.ENCODING,"gb2312"); javax.xml.transform.OutputKeys.INDENT,"yes"); transformer.transform(new javax.xml.transform.dom.DOMSource(doc), newjavax.xml.transform.stream.StreamResult(outFile)); System.out.println(e.getMessage()); 105、编程用JAVA解析XML的方式。【较难】 答:用SAX方式解析XML,XML文件如下: 事件回调类SAXHandler.java: importjava.util.Hashtable; importorg.xml.sax.*; publicclassSAXHandlerextendsHandlerBase{ privateHashtabletable=newHashtable(); privateStringcurrentElement=null; privateStringcurrentValue=null; publicvoidsetTable(Hashtabletable){ this.table=table; publicHashtablegetTable(){ returntable; publicvoidstartElement(Stringtag,AttributeListattrs) throwsSAXException{ currentElement=tag; publicvoidcharacters(char[]ch,intstart,intlength) currentValue=newString(ch,start,length); publicvoidendElement(Stringname)throwsSAXException{ if(currentElement.equals(name)) table.put(currentElement,currentValue); JSP内容显示源码,SaxXml.jsp: <%@pageerrorPage="ErrPage.jsp" contentType="text/html;charset=GB2312"%> <%@pageimport="java.io.*"%> <%@pageimport="java.util.Hashtable"%> <%@pageimport="org.w3c.dom.*"%> <%@pageimport="org.xml.sax.*"%> <%@pageimport="javax.xml.parsers.SAXParserFactory"%> <%@pageimport="javax.xml.parsers.SAXParser"%> <%@pageimport="SAXHandler"%> <% Filefile=newFile("c:\people.xml"); FileReaderreader=newFileReader(file); Parserparser; SAXParserFactoryspf=SAXParserFactory.newInstance(); SAXParsersp=spf.newSAXParser(); SAXHandlerhandler=newSAXHandler(); sp.parse(newInputSource(reader),handler); HashtablehashTable=handler.getTable(); out.println(" "教师信息表"); out.println(" (String)hashTable.get(newString("name"))+ " out.println(" (String)hashTable.get(newString("college")) +" (String)hashTable.get(newString("telephone")) out.println(" (String)hashTable.get(newString("notes")) out.println(""); %> 106、有3个表(15分钟):【基础】 Student学生表(学号,姓名,性别,年龄,组织部门) Course课程表(编号,课程名称) Sc选课表(学号,课程编号,成绩) 表结构如下: 1)写一个SQL语句,查询选修了’计算机原理’的学生学号和姓名(3分钟) 2)写一个SQL语句,查询’周星驰’同学选修了的课程名字(3分钟) 3)写一个SQL语句,查询选修了5门课程的学生学号和姓名(9分钟) 答:1)SQL语句如下: selectstu.sno,stu.snamefromStudentstu where(selectcount(*)fromscwheresno=stu.snoandcno= (selectcnofromCoursewherecname='计算机原理'))!=0; 2)SQL语句如下: selectcnamefromCourse wherecnoin(selectcnofromscwheresno= (selectsnofromStudentwheresname='周星驰')); 3)SQL语句如下: selectstu.sno,stu.snamefromstudentstu where(selectcount(*)fromscwheresno=stu.sno)=5; 107、有三张表,学生表S,课程C,学生课程表SC,学生可以选修多门课程,一门课程可以被多个学生选修,通过SC表关联。【基础】 1)写出建表语句; 2)写出SQL语句,查询选修了所有选修课程的学生; 3)写出SQL语句,查询选修了至少5门以上的课程的学生。 答:1)建表语句如下(mysql数据库): createtables(idintegerprimarykey,namevarchar(20)); createtablec(idintegerprimarykey,namevarchar(20)); createtablesc( sidintegerreferencess(id), cidintegerreferencesc(id), primarykey(sid,cid) ); selectstu.id,stu.namefromsstu where(selectcount(*)fromscwheresid=stu.id) =(selectcount(*)fromc); where(selectcount(*)fromscwheresid=stu.id)>=5; 108、数据库表(Test)结构如下:【基础】 IDNAMEAGEMANAGER(所属主管人ID) 106A30104 109B19104 104C20111 107D35109 112E25120 119F45NULL 要求:列出所有年龄比所属主管年龄大的人的ID和名字 答:SQL语句如下: selectemployee.namefromtestemployee whereemployee.age>(selectmanager.agefromtestmanager wheremanager.id=employee.manager); 109、有如下两张表:【中等难度】 表city:表state: CityNoCityNameStateNo BJ北京(Null) SH上海(Null) GZ广州GD DL大连LN 欲得 到如下结果:City NoCityNameStateNoStateNameBJ 北京(Null)(Null)DL 大连LN辽宁GZ 广州GD广东SH 上海(Null)(Null)写相 应的SQL语句。 答:SQL语句为: SELECTC.CITYNO,C.CITYNAME,C.STATENO,S.STATENAME FROMCITYC,STATES WHEREC.STATENO=S.STATENO(+) ORDERBY(C.CITYNO); 存储过程是用户定义的一系列sql语句的集合,涉及特定表或其它对象的任务,用户可以调用存储过程,而函数通常是数据库已定义的方法,它接收参数并返回某种类型的值并且不涉及特定用户表。 事务是作为一个逻辑单元执行的一系列操作,一个逻辑工作单元必须有四个属性,称为ACID(原子性、一致性、隔离性和持久性)属性,只有这样才能成为一个事务: 原子性事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。 隔离性由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。 持久性事务完成之后,它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。 游标用于定位结果集的行,通过判断全局变量@@FETCH_STATUS可以判断是否到了最后,通常此变量不等于0表示出错或到了最后。 触发器分为事前触发和事后触发,这两种触发有和区别。语句级触发和行级触发有何区别。 事前触发器运行于触发事件发生之前,而事后触发器运行于触发事件发生之后。通常事前触发器可以获取事件之前和新的字段值。 语句级触发器可以在语句执行前或后执行,而行级触发在触发器所影响的每一行触发一次。 由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个很著名的设计模式:资源池(ResourcePool)。该模式正是为了解决资源的频繁分配释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量使用情况,为系统开发测试及性能调整提供依据 JDBC的API中没有提供连接池的方法。一些大型的WEB应用服务器如BEA的WebLogic和IBM的WebSphere等提供了连接池的机制,但是必须有其第三方的专用类方法支持连接池的用法。 为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为Java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如: publicsynchronizedConnectiongetConnection() 对于大型的企业级应用,常常需要同时连接不同的数据库(如连接Oracle和Sybase)。如何连接不同的数据库呢?我们采用的策略是:设计一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的url地址()用户名()密码()等信息。如tx.url=172.21.15.123:5000/tx_it,tx.user=yang,tx.password=yang321。根据资源文件提供的信息,创建多个连接池类的实例,每一个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连接池。对于同一个数据库有多个用户使用不同的名称和密码访问的情况,也可以通过资源文件处理,即在资源文件中设置多个具有相同url地址,但具有不同用户名和密码的数据库连接信息 我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。在Java语言中,Connection类本身提供了对事务的支持,可以通过设置Connection的AutoCommit属性为false,然后显式的调用commit或rollback方法来实现。但要高效的进行Connection复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。 本文讨论的连接池包括一个连接池类(DBConnectionPool)和一个连接池管理类(DBConnetionPoolManager)。连接池类是对某一数据库所有连接的“缓冲池”,主要实现以下功能:①从连接池获取或创建可用连接;②使用完毕之后,把连接返还给连接池;③在系统关闭前,断开所有连接并释放连接占用的系统资源;④还能够处理无效连接(原来登记为可用的连接,由于某种原因不再可用,如超时,通讯问题),并能够限制连接池中的连接总数不低于某个预定值和不超过某个预定值。连接池管理类是连接池类的外覆类(wrapper),符合单例模式,即系统中只能有一个连接池管理类的实例。其主要用于对多个连接池对象的管理,具有以下功能:①装载并注册特定数据库的JDBC驱动程序;②根据属性文件给定的信息,创建连接池对象;③为方便管理多个连接池对象,为每一个连接池对象取一个名字,实现连接池名字与其实例之间的映射;④跟踪客户使用连接情况,以便需要是关闭连接释放资源。连接池管理类的引入主要是为了方便对多个连接池的使用和管理,如系统需要连接不同的数据库,或连接相同的数据库但由于安全性问题,需要不同的用户使用不同的名称和密码。 下面给出连接池类和连接池管理类的主要属性及所要实现的基本接口: 代码: 上面所实现的连接池在程序开发时如何应用到系统中呢?下面以Servlet为例说明连接池的使用。Servlet的生命周期是:在开始建立servlet时,调用其初始化(init)方法。之后每个用户请求都导致一个调用前面建立的实例的service方法的线程。最后,当服务器决定卸载一个servlet时,它首先调用该servlet的destroy方法。根据servlet的特点,我们可以在初始化函数中生成连接池管理类的唯一实例(其中包括创建一个或多个连接池)。如: publicvoidinit()throwsServletException{connMgr=DBConnectionManager.getInstance();} 然后就可以在service方法中通过连接池名称使用连接池,执行数据库操作。最后在destroy方法中释放占用的系统资源,如: publicvoiddestroy(){connMgr.release();super.destroy(); 110、数据库,比如100用户同时来访,要采取什么技术解决?【基础】 答:可采用连接池。 111、什么是ORM?【基础】 答:对象关系映射(Object—RelationalMapping,简称ORM)是一种为了解决面向对象与面向关系数据库存在的互不匹配的现象的技术;简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中;本质上就是将数据从一种形式转换到另外一种形式。 112、Hibernate有哪5个核心接口?【基础】 答:Configuration接口:配置Hibernate,根据其启动hibernate,创建SessionFactory对象;SessionFactory接口:初始化Hibernate,充当数据存储源的代理,创建session对象,sessionFactory是线程安全的,意味着它的同一个实例可以被应用的多个线程共享,是重量级、二级缓存;Session接口:负责保存、更新、删除、加载和查询对象,是线程不安全的,避免多个线程共享同一个session,是轻量级、一级缓存;Transaction接口:管理事务;Query和Criteria接口:执行数据库的查询。 113、关于hibernate:【基础】 1)在hibernate中,在配置文件呈标题一对多,多对多的标签是什么;2)Hibernate的二级缓存是什么;3)Hibernate是如何处理事务的; 答:1)一对多的标签为 2)sessionFactory的缓存为hibernate的二级缓存; 3)Hibernate的事务实际上是底层的JDBCTransaction的封装或者是JTA Transaction的封装;默认情况下使用JDBCTransaction。 State No Name GD广东 LN辽宁 SD山东 NMG内蒙古 114、Hibernate的应用(Hibernate的结构)?【基础】 答://首先获得SessionFactory的对象 SessionFactorysessionFactory=newConfiguration().configure(). buildSessionFactory(); //然后获得session的对象 Sessionsession=sessionFactory.openSession(); //其次获得Transaction的对象 Transactiontx=session.beginTransaction(); session.save(user);//增加,user是User类的对象 session.delete(user);//删除 session.update(user);//更新 Queryquery=session.createQuery(“fromUser”);//查询 Listlist=query.list(); //提交事务 tx.commit(); //如果有异常,我们还要作事务的回滚,恢复到操作之前 tx.rollback(); //最后还要关闭session,释放资源 session.close(); 115、什么是重量级?什么是轻量级?【基础】 答:轻量级是指它的创建和销毁不需要消耗太多的资源,意味着可以在程序中经常创建和销毁session的对象;重量级意味不能随意的创建和销毁它的实例,会占用很多的资源。 116、数据库的连接字符串?【基础】 答:MSSQLServer //第二种连接方式 Class.forName(“com.microsoft.jdbc.sqlserver.SQLServerDriver”). newInstance(); conn=DriverManager.getConnection(“jdbc:Microsoft:sqlserver ://localhost:1433;DatabaseName=pubs”,”sa”,””); //Oracle Class.forName(“oracle.jdbc.driver.OracleDriver”).newInstance(); conn=DriverManager.getConnection(“jdbc:oracle:thin: @localhost:1521:sid”,uid,pwd); //Mysql Class.forName(“org.git.mm.mysql.Driver”).newInstance(); conn=DriverManager.getConnection(“jdbc:mysql ://localhost:3306/pubs”,”root”,””); 处理中文的问题: jdbc:mysql://localhost:3306/pubsuseUnicode=true &characterEncoding=GB2312 117、事务处理?【基础】 答:Connection类中提供了3个事务处理方法:setAutoCommit(BooleanautoCommit):设置是否自动提交事务,默认为自动提交事务,即为true,通过设置false禁止自动提交事务;commit():提交事务;rollback():回滚事务。 118、Java中访问数据库的步骤?Statement和PreparedStatement之间的区别? 【基础】 答:Java中访问数据库的步骤如下: 1)注册驱动; 2)建立连接; 3)创建Statement; 4)执行sql语句; 5)处理结果集(若sql语句为查询语句); 6)关闭连接。 PreparedStatement被创建时即指定了SQL语句,通常用于执行多次结构相同的SQL语句。 119、用你熟悉的语言写一个连接ORACLE数据库的程序,能够完成修改和查询工作。【基础】 答:JDBC示例程序如下: publicvoidtestJdbc(){ Connectioncon=null; PreparedStatementps=null; ResultSetrs=null; //step1:注册驱动; Class.forName("oracle.jdbc.driver.OracleDriver"); //step2:获取数据库连接; con=DriverManager.getConnection( "jdbc:oracle:thin:@192.168.0.39:1521:TARENADB", "sd0605","sd0605"); /************************查询************************/ //step3:创建Statement; Stringsql="SELECTid,fname,lname,age,FROM Person_Tbl"; ps=con.prepareStatement(sql); //step4:执行查询语句,获取结果集; rs=ps.executeQuery(); //step5:处理结果集—输出结果集中保存的查询结果; while(rs.next()){ System.out.print("id="+rs.getLong("id")); System.out.print(",fname="+ 第35页共59页 rs.getString("fname")); System.out.print(",lname="+ rs.getString("lname")); System.out.print(",age="+rs.getInt("age")); /************************JDBC修改*********************/ sql="UPDATEPerson_TblSETage=23WHEREid="; ps.setLong(1,88); introws=ps.executeUpdate(); System.out.println(rows+"rowsaffected."); e.printStackTrace(); }finally{ con.close();//关闭数据库连接,以释放资源。 }catch(Exceptione1){ 120、JDBC,Hibernate分页怎样实现?【中等难度】 答:方法分别为: 1)Hibernate的分页: Queryquery=session.createQuery("fromStudent"); query.setFirstResult(firstResult);//设置每页开始的记录号 query.setMaxResults(resultNumber);//设置每页显示的记录数 Collectionstudents=query.list(); 2)JDBC的分页:根据不同的数据库采用不同的sql分页语句 例如:Oracle中的sql语句为:"SELECT*FROM(SELECTa.*,rownumrFROM TB_STUDENT)WHERErbetween2and10"查询从记录号2到记录号10之间的所有记录 121、在ORACLE大数据量下的分页解决方法。一般用截取ID方法,还有是三层嵌套方法。【中等难度】 答:一种分页方法 inti=1; intnumPages=14; Stringpages=request.getParameter("page"); intcurrentPage=1; currentPage=(pages==null)(1):{Integer.parseInt(pages)} sql="selectcount(*)fromtables"; ResultSetrs=DBLink.executeQuery(sql); while(rs.next())i=rs.getInt(1); intintPageCount=1; intPageCount=(i%numPages==0)(i/numPages):(i/numPages+1); intnextPage; intupPage; nextPage=currentPage+1; if(nextPage>=intPageCount)nextPage=intPageCount; upPage=currentPage-1; if(upPage<=1)upPage=1; rs.close(); sql="select*fromtables"; rs=DBLink.executeQuery(sql); i=0; while((i //输出内容 //输出翻页连接 合计:<%=currentPage%>/<%=intPageCount%>页 for(intj=1;j<=intPageCount;j++){ if(currentPage!=j){ out.println(j); 180、一个byte几个单位?【基础】 答:8bit。 181、常用UNIX命令(Linux的常用命令)(至少10个)【基础】 答:lspwdmkdirrmcpmvcdpsftptelnetpingenvmoreecho 182、后序遍历下列二叉树,访问结点的顺序是?【基础】 A /\ BC /\\ DEF //\ GNI JK 答:顺序为:DJGEBKNIFCA。 183、排序都有哪几种方法?请列举。用JAVA实现一个快速排序。【基础】 答:排序的方法有:插入排序(直接插入排序、希尔排序),交换排序(冒泡排序、快速排序),选择排序(直接选择排序、堆排序),归并排序,分配排序(箱排序、基数排序);快速排序的伪代码: //使用快速排序方法对a[0:n-1]排序 从a[0:n-1]中选择一个元素作为middle,该元素为支点; 把余下的元素分割为两段left和right,使得left中的元素都小于等于支点, 而right中的元素都大于等于支点; 递归地使用快速排序方法对left进行排序; 递归地使用快速排序方法对right进行排序; 所得结果为left+middle+right。 184、写一种常见排序。【基础】 答:C++中冒泡排序: voidswap(int&a,int&b){ intc=a;a=b;b=c; voidbubble(int*p,intlen){ boolbSwapped; do{ bSwapped=false; for(inti=1;i if(p[i-1]>p[i]){ swap(p[i-1],p[i]); bSwapped=true; }while(bSwapped); 185、写一个一小段程序检查数字是否为质数;以上的程序你采用的哪种语言写的?采用该种语言的理由是什么?【基础】 #include boolprime(intn){ if(n<=0)exit(0); for(inti=2;i<=n;i++) for(intj=2;j<=sqrt(i);j++) if((n%j==0)&&(j!=n)) returnfalse; returntrue; 采用C++,因为其运行效率高。 186、编程题:设有n个人依围成一圈,从第1个人开始报数,数到第m个人出列,然后从出列的下一个人开始报数,数到第m个人又出列,…,如此反复到所有的人全部出列为止。设n个人的编号分别为1,2,…,n,打印出出列的顺序;要求用java实现。【中等难度】 publicclassCountGame{ privatestaticbooleansame(int[]p,intl,intn){ for(inti=0;i if(p[i]==n){ publicstaticvoidplay(intplayerNum,intstep){ int[]p=newint[playerNum]; intcounter=1; if(counter>playerNum*step){ for(inti=1;i if(same(p,playerNum,i)==false)break; elsei=i+1; if(i>playerNum)break; if(counter%step==0){ System.out.print(i+""); p[counter/step-1]=i; counter+=1; System.out.println(); play(10,7); 187、写一个方法1000的阶乘。【较难】 答:C++的代码实现如下: #include #include #include usingnamespacestd; classlongint{ private: vector public: longint(void){iv.push_back(1);} longint&multiply(constint&); friendostream&operator<<(ostream&,constlongint&); }; ostream&operator<<(ostream&os,constlongint&v){ vector os<<*iv_iter++; for(;iv_iter os< returnos; longint&longint::multiply(constint&rv){ vector intoverflow=0,product=0; for(;iv_iter product=(*iv_iter)*rv; product+=overflow; overflow=0; if(product>10000){ overflow=product/10000; product-=overflow*10000; iv_iter=product; if(0!=overflow){ iv.push_back(overflow); return*this; intmain(intargc,char**argv){ longintresult; intl=0; if(argc==1){ cout<<"like:multiply1000"< exit(0); sscanf(argv[1],"%d",&l); for(inti=2;i<=l;++i){ result.multiply(i); cout< return0; 编程语言书籍中经常解释值类型被创建在栈上,引用类型被创建在堆上,但是并没有本质上解释这堆和栈是什么。我仅有高级语言编程经验,没有看过对此更清晰的解释。我的意思是我理解什么是栈,但是它们到底是什么,在哪儿呢(站在实际的计算机物理内存的角度上看)? 答案一 栈是为执行线程留出的内存空间。当函数被调用的时候,栈顶为局部变量和一些bookkeeping数据预留块。当函数执行完毕,块就没有用了,可能在下次的函数调用的时候再被使用。栈通常用后进先出(LIFO)的方式预留空间;因此最近的保留块(reservedblock)通常最先被释放。这么做可以使跟踪堆栈变的简单;从栈中释放块(freeblock)只不过是指针的偏移而已。 堆(heap)是为动态分配预留的内存空间。和栈不一样,从堆上分配和重新分配块没有固定模式;你可以在任何时候分配和释放它。这样使得跟踪哪部分堆已经被分配和被释放变的异常复杂;有许多定制的堆分配策略用来为不同的使用模式下调整堆的性能。 每一个线程都有一个栈,但是每一个应用程序通常都只有一个堆(尽管为不同类型分配内存使用多个堆的情况也是有的)。 直接回答你的问题:1.当线程创建的时候,操作系统(OS)为每一个系统级(system-level)的线程分配栈。通常情况下,操作系统通过调用语言的运行时(runtime)去为应用程序分配堆。2.栈附属于线程,因此当线程结束时栈被回收。堆通常通过运行时在应用程序启动时被分配,当应用程序(进程)退出时被回收。3.当线程被创建的时候,设置栈的大小。在应用程序启动的时候,设置堆的大小,但是可以在需要的时候扩展(分配器向操作系统申请更多的内存)。4.栈比堆要快,因为它存取模式使它可以轻松的分配和重新分配内存(指针/整型只是进行简单的递增或者递减运算),然而堆在分配和释放的时候有更多的复杂的bookkeeping参与。另外,在栈上的每个字节频繁的被复用也就意味着它可能映射到处理器缓存中,所以很快(译者注:局部性原理)。 答案二 Stack: Heap: 举例: intfoo(){char*pBuffer;//<–nothingallocatedyet(excludingthepointeritself,whichisallocatedhereonthestack).boolb=true;//Allocatedonthestack.if(b){//Create500bytesonthestackcharbuffer[500]; //Create500bytesontheheappBuffer=newchar[500]; }//<–bufferisdeallocatedhere,pBufferisnot}//<—oopsthere’samemoryleak,Ishouldhavecalleddelete[]pBuffer; 答案三 堆和栈是两种内存分配的两个统称。可能有很多种不同的实现方式,但是实现要符合几个基本的概念: 1.对栈而言,栈中的新加数据项放在其他数据的顶部,移除时你也只能移除最顶部的数据(不能越位获取)。 2.对堆而言,数据项位置没有固定的顺序。你可以以任何顺序插入和删除,因为他们没有“顶部”数据这一概念。 上面上个图片很好的描述了堆和栈分配内存的方式。 在通常情况下由操作系统(OS)和语言的运行时(runtime)控制吗? 堆是在任何内存中动态和随机分配的(内存的)统称;也就是无序的。内存通常由操作系统分配,通过应用程序调用API接口去实现分配。在管理动态分配内存上会有一些额外的开销,不过这由操作系统来处理。 它们的作用范围是什么? 调用栈是一个低层次的概念,就程序而言,它和“作用范围”没什么关系。如果你反汇编一些代码,你就会看到指针引用堆栈部分。就高级语言而言,语言有它自己的范围规则。一旦函数返回,函数中的局部变量会直接直接释放。你的编程语言就是依据这个工作的。 在堆中,也很难去定义。作用范围是由操作系统限定的,但是你的编程语言可能增加它自己的一些规则,去限定堆在应用程序中的范围。体系架构和操作系统是使用虚拟地址的,然后由处理器翻译到实际的物理地址中,还有页面错误等等。它们记录那个页面属于那个应用程序。不过你不用关心这些,因为你仅仅在你的编程语言中分配和释放内存,和一些错误检查(出现分配失败和释放失败的原因)。 它们的大小由什么决定? 依旧,依赖于语言,编译器,操作系统和架构。栈通常提前分配好了,因为栈必须是连续的内存块。语言的编译器或者操作系统决定它的大小。不要在栈上存储大块数据,这样可以保证有足够的空间不会溢出,除非出现了无限递归的情况(额,栈溢出了)或者其它不常见了编程决议。 堆是任何可以动态分配的内存的统称。这要看你怎么看待它了,它的大小是变动的。在现代处理器中和操作系统的工作方式是高度抽象的,因此你在正常情况下不需要担心它实际的大小,除非你必须要使用你还没有分配的内存或者已经释放了的内存。 哪个更快一些? 栈更快因为所有的空闲内存都是连续的,因此不需要对空闲内存块通过列表来维护。只是一个简单的指向当前栈顶的指针。编译器通常用一个专门的、快速的寄存器来实现。更重要的一点事是,随后的栈上操作通常集中在一个内存块的附近,这样的话有利于处理器的高速访问(译者注:局部性原理)。 答案四 你问题的答案是依赖于实现的,根据不同的编译器和处理器架构而不同。下面简单的解释一下: 堆: 栈: *函数的分配可以用堆来代替栈吗? 不可以的,函数的活动记录(即局部或者自动变量)被分配在栈上,这样做不但存储了这些变量,而且可以用来嵌套函数的追踪。 堆的管理依赖于运行时环境,C使用malloc,C++使用new,但是很多语言有垃圾回收机制。 栈是更低层次的特性与处理器架构紧密的结合到一起。当堆不够时可以扩展空间,这不难做到,因为可以有库函数可以调用。但是,扩展栈通常来说是不可能的,因为在栈溢出的时候,执行线程就被操作系统关闭了,这已经太晚了。 关于堆栈的这个帖子,对我来说,收获非常多。我之前看过一些资料,自己写代码的时候也常常思考。就这方面,也和祥子(我的大学舍友,现在北京邮电读研,技术牛人)探讨过多次了。但是终究是一个一个的知识点,这个帖子看完之后,豁然开朗,把知识点终于连接成了一个网。这种感觉,经历过的一定懂得,期间的兴奋不言而喻。 兴奋之余,有几点还是要强调的,翻译没有逐字逐词翻译,大部分是通过我个人的知识积累和对回帖者的意图揣测而来的。请大家不要咬文嚼字,逐个推敲,我们的目的在于技术交流,不是么?达到这一目的就够了。 下面是一些不确定点: 以上,送给大家,本文结束。 Java使用的是共享内存的并发模型,在线程之间共享变量。Java语言定义了线程模型规范,通过内存模型控制线程与变量的交互,从而实现Java线程之间的通信。在JDK5之前,Java一直使用的是旧内存模型。如图1所示。变量保存在由所有线程共享的主内存中,主内存中的变量称为mastingcopy。每个线程都有一个工作内存,它保存变量的workingcopy。旧的内存模型定义了若干规则,通过这些规则来保证线程何时将主内存中的mastingcopy传送到线程的工作内存中;以及线程何时将工作内存中的workingcopy传送回主内存。旧内存模型使用8个操作来定义线程可以执行的动作。 图1Java旧内存模型 这里的关键是,由于read操作是由主内存执行,而对应的load是由线程执行,read操作和load操作之间是松散耦合的。也就是说,主内存和线程工作内存之间的变量传递是松散耦合的。同样,由于store操作是由线程执行,而对应的write是由主内存执行,store操作和write操作之间是松散耦合的。也就是说,线程工作内存和主内存之间的变量传递是松散耦合的。旧Java内存模型对Java实现如何执行变量的读/写,加锁/解锁,以及volatile变量的读/写,定义了非常严格的规则。这些规则非常复杂,具体详情请参考《JVM规范》,这里就不赘述了。旧Java内存模型通过这些复杂的规则,来保证多线程程序的线程之间,可以可靠地传递共享变量,从而保证多线程程序的正确性。 从JDK5开始,Java使用新的内存模型,新内存模型完全抛弃了旧内存模型的主内存和工作内存的概念,也抛弃了旧内存模型的8个内存操作。也就是说,新内存模型完全是重新设计的。 新内存模型引入了一个新的概念,叫happens-before。happens-before的概念最初由LeslieLamport在其一篇影响深远的论文(《Time,ClocksandtheOrderingofEventsinaDistributedSystem》)中提出。LeslieLamport使用happens-before来定义分布式系统中,事件之间的一个偏序关系(partialordering)。LeslieLamport在这篇论文中给出了一个分布式算法,该算法可以将该偏序关系扩展为某种全序关系。 JSR-133使用happens-before的概念来指定两个操作(这里的操作是指程序中对变量的读/写,对锁的加锁和解锁)之间的执行顺序。新内存模型定义了如下的happens-before规则。 由于两个操作可以在一个线程之内,也可以是在不同线程之间。因此JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM 图2新内存模型的设计示意图 向程序员保证a操作将对b操作可见)。在新内存模型向程序员提供happens-before规则,程序员只需要与happens-before打交道即可,因此Java程序员的学习负担大大降低。同时,新内存模型允许不会改变程序结果的重排序,这可以最大限度地放松对编译器和处理器的束缚,新内存模型的执行性能比旧内存模型要好。 如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。 调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化。4、不要重复初始化变量 默认情况下,调用类的构造函数时,Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。 Java编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,即使关闭以释放资源。 因为对这些大对象的操作会造成系统大的开销,稍有不慎,会导致严重的后果。 由于JVM的有其自身的GC机制,不需要程序开发者的过多考虑,从一定程度上减轻了开发者负担,但同时也遗漏了隐患,过分的创建对象会消耗系统的大量内存,严重时会导致内存泄露,因此,保证过期对象的及时回收具有重要意义。 JVM回收垃圾的条件是:对象不在被引用;然而,JVM的GC并非十分的机智,即使对象满足了垃圾回收的条件也不一定会被立即回收。所以,建议我们在对象使用完毕,应手动置成null。 异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。 用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。在使用设计模式(DesignPattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:publicstaticCreditgetNewCredit(){returnnewCredit();}改进后的代码使用clone()方法,如下所示:privatestaticCreditBaseCredit=newCredit();publicstaticCreditgetNewCredit(){return(Credit)BaseCredit.clone();}上面的思路对于数组处理同样很有用。 一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用<%@pagesession=”false”%>关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSessionsession=HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。对于那些无需跟踪会话状态的页面,关闭自动创建的会话可以节省一些资源。使用如下page指令:<%@pagesession=”false”%> 如果应用程序需要访问一个规模很大的数据集,则应当考虑使用块提取方式。默认情况下,JDBC每次提取32行数据。举例来说,假设我们要遍历一个5000行的记录集,JDBC必须调用数据库157次才能提取到全部数据。如果把块大小改成512,则调用数据库的次数将减少到10次。 一些应用服务器加入了面向JSP的缓冲标记功能。例如,BEA的WebLogicServer从6.0版本开始支持这个功能,OpenSymphony工程也同样支持这个功能。JSP缓冲标记既能够缓冲页面片断,也能够缓冲整个页面。当JSP页面执行时,如果目标片断已经在缓冲之中,则生成该片断的代码就不用再执行。页面级缓冲捕获对指定URL的请求,并缓冲整个结果页面。对于购物篮、目录以及门户网站的主页来说,这个功能极其有用。对于这类应用,页面级缓冲能够保存页面执行的结果,供后继请求使用。 在典型的JSP应用系统中,页头、页脚部分往往被抽取出来,然后根据需要引入页头、页脚。当前,在JSP页面中引入外部资源的方法主要有两种:include指令,以及include动作。include指令:例如<%@includefile=”copyright.html”%>。该指令在编译时引入指定的资源。在编译之前,带有include指令的页面和指定的资源被合并成一个文件。被引用的外部资源在编译时就确定,比运行时才确定资源更高效。include动作:例如 array([]):最高效;但是其容量固定且无法动态改变;ArrayList:容量可动态增长;但牺牲效率;基于效率和类型检验,应尽可能使用array,无法确定数组大小时才使用ArrayList!ArrayList是Array的复杂版本ArrayList内部封装了一个Object类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于ArrayList的许多方法,如Index、IndexOf、Contains、Sort等都是在内部数组的基础上直接调用Array的对应方法。ArrayList存入对象时,抛弃类型信息,所有对象屏蔽为Object,编译时不检查类型,但是运行时会报错。注:jdk5中加入了对泛型的支持,已经可以在使用ArrayList时进行类型检查。从这一点上看来,ArrayList与数组的区别主要就是由于动态增容的效率问题了 高性能应用构成了现代网络的支柱。LinkedIn有许多内部高吞吐量服务来满足每秒数千次的用户请求。要优化用户体验,低延迟地响应这些请求非常重要。 图1LinkedIn动态信息 这些高吞吐低延迟的Java应用转变为产品,开发人员必须确保应用开发周期的每个阶段一致的性能。确定优化垃圾回收(GarbageCollection,GC)的设置对达到这些指标非常关键。 GC运行随着代码级的优化和工作负载而发生变化。因此在一个已实施性能优化的接近完成的代码库上调整GC非常重要。但是在端到端的基本原型上进行初步分析也很有必要,该原型系统使用存根代码并模拟了可代表产品环境的工作负载。这样可以捕捉该架构延迟和吞吐量的真实边界,进而决定是否纵向或横向扩展。 下面是为满足高吞吐,低延迟需求优化GC的总体步骤。也包括在动态信息数据平台原型实施的具体细节。可以看到在ParNew/CMS有最好的性能,但我们也实验了G1垃圾回收器。 使用初始的GC配置,每三秒发生一次80ms的新生代GC停顿,超过百分之99.9的应用延迟100ms。这样的GC很可能适合于SLA不太严格要求延迟的许多应用。然而,我们的目标是尽可能降低百分之99.9应用的延迟,为此GC优化是必不可少的。 优化之前要先衡量。了解GC日志的详细细节(使用这些选项:-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintGCDateStamps-XX:+PrintTenuringDistribution-XX:+PrintGCApplicationStoppedTime)可以对该应用的GC特征有总体的把握。 在这个阶段,需要确定GC频率和停顿时长是否影响应用满足延迟性需求的能力。 在分代GC算法中,降低回收频率可以通过:(1)降低对象分配/提升率;(2)增加代空间的大小。 由于我们的应用在堆中维持了长期存活对象的较大缓存,将老年代GC触发阈值设置为-XX:CMSInitiatingOccupancyFraction=92-XX:+UseCMSInitiatingOccupancyOnly。我们也试图增加新生代大小来减少新生代回收频率,但是并没有采用,因为这增加了应用延迟。 6.优化GC工作线程的任务分配 也有其他选项将任务映射到GC线程,如果OS允许的话,-XX:+BindGCTaskThreadsToCPUs选项绑定GC线程到个别的CPU核。-XX:+UseGCTaskAffinity使用affinity参数将任务分配给GC工作线程。然而,我们的应用并没有从这些选项发现任何益处。实际上,一些调查显示这些选项在Linux系统不起作用[1,2]。 7.了解GC的CPU和内存开销 并发GC通常会增加CPU的使用。我们观察了运行良好的CMS默认设置,并发GC和G1垃圾回收器共同工作引起的CPU使用增加显著降低了应用的吞吐量和延迟。与CMS相比,G1可能占用了应用更多的内存开销。对于低吞吐量的非计算密集型应用,GC的高CPU使用率可能不需要担心。 图2ParNew/CMS和G1的CPU使用百分数%:相对来说CPU使用率变化明显的节点使用G1选项-XX:G1RSetUpdatingPauseTimePercent=20 图3ParNew/CMS和G1每秒服务的请求数:吞吐量较低的节点使用G1选项-XX:G1RSetUpdatingPauseTimePercent=20 8.为GC优化系统内存和I/O管理 为避免运行时性能损失,启动应用时使用JVM选项-XX:+AlwaysPreTouch访问和清零页面。设置vm.swappiness为零,除非在绝对必要时,OS不会交换页面。 对于该平台原型系统,我们使用HotspotJVM的两个算法优化垃圾回收: 使用ParNew/CMS,应用每三秒40-60ms的新生代停顿和每小时一个CMS周期。JVM选项如下: 1 2 3 4 5 6 7 8 //JVMsizingoptions -server-Xms40g-Xmx40g-XX:MaxDirectMemorySize=4096m-XX:PermSize=256m-XX:MaxPermSize=256m //Younggenerationoptions -XX:NewSize=6g-XX:MaxNewSize=6g-XX:+UseParNewGC-XX:MaxTenuringThreshold=2-XX:SurvivorRatio=8-XX:+UnlockDiagnosticVMOptions-XX:ParGCCardsPerStrideChunk=32768 //Oldgenerationoptions -XX:+UseConcMarkSweepGC-XX:CMSParallelRemarkEnabled-XX:+ParallelRefProcEnabled-XX:+CMSClassUnloadingEnabled-XX:CMSInitiatingOccupancyFraction=80-XX:+UseCMSInitiatingOccupancyOnly //Otheroptions -XX:+AlwaysPreTouch-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintGCDateStamps-XX:+PrintTenuringDistribution-XX:+PrintGCApplicationStoppedTime-XX:-OmitStackTraceInFastThrow 使用这些选项,对于几千次读请求的吞吐量,应用百分之99.9的延迟降低到60ms。 十三、提高Java代码性能的各种技巧 Java6,7,8中的String.intern–字符串池 字符串池 字符串池(有名字符串标准化)是通过使用唯一的共享String对象来使用相同的值不同的地址表示字符串的过程。你可以使用自己定义的Map Java6中的String.intern() 在美好的过去所有共享的String对象都存储在PermGen中—堆中固定大小的部分主要用于存储加载的类对象和字符串池。除了明确的共享字符串,PermGen字符串池还包含所有程序中使用过的字符串(这里要注意是使用过的字符串,如果类或者方法从未加载或者被条用,在其中定义的任何常量都不会被加载) Java6中字符串池的最大问题是它的位置—PermGen。PermGen的大小是固定的并且在运行时是无法扩展的。你可以使用-XX:MaxPermSize=N配置来调整它的大小。据我了解,对于不同的平台默认的PermGen大小在32M到96M之间。你可以扩展它的大小,不过大小使用都是固定的。这个限制需要你在使用String.intern时需要非常小心—你最好不要使用这个方法intern任何无法控制的用户输入。这是为什么在JAVA6中大部分使用手动管理Map来实现字符串池 Java7中的String.intern() Java7中Oracle的工程师对字符串池的逻辑做了很大的改变—字符串池的位置被调整到heap中了。这意味着你再也不会被固定的内存空间限制了。所有的字符串都保存在堆(heap)中同其他普通对象一样,这使得你在调优应用时仅需要调整堆大小。这个改动使得我们有足够的理由让我们重新考虑在Java7中使用String.intern()。 字符串池中的数据会被垃圾收集 没错,在JVM字符串池中的所有字符串会被垃圾收集,如果这些值在应用中没有任何引用。这是用于所有版本的Java,这意味着如果interned的字符串在作用域外并且没有任何引用—它将会从JVM的字符串池中被垃圾收集掉。 因为被重新定位到堆中以及会被垃圾收集,JVM的字符串池看上去是存放字符串的合适位置,是吗?理论上是—违背使用的字符串会从池中收集掉,当外部输入一个字符传且池中存在时可以节省内存。看起来是一个完美的节省内存的策略?在你回答这个之前,可以肯定的是你需要知道字符串池是如何实现的。 在Java6,7,8中JVM字符串池的实现 默认的池大小是1009(出现在上面提及的bug报告的源码中,在Java7u40中增加了)。在JAVA6早期版本中是一个常量,在随后的java6u30至java6u41中调整为可配置的。而在java7中一开始就是可以配置的(至少在java7u02中是可以配置的)。你需要指定参数-XX:StringTableSize=N,N是字符串池Map的大小。确保它是为性能调优而预先准备的大小。 在Java6中这个参数没有太多帮助,因为你仍任被限制在固定的PermGen内存大小中。后续的讨论将直接忽略Java6 Java7(直至Java7u40) 在Java7中,换句话说,你被限制在一个更大的堆内存中。这意味着你可以预先设置好String池的大小(这个值取决于你的应用程序需求)。通常说来,一旦程序开始内存消耗,内存都是成百兆的增长,在这种情况下,给一个拥有100万字符串对象的字符串池分配8-16M的内存看起来是比较适合的(不要使用1,000,000作为-XX:StringTaleSize的值–它不是质数;使用1,000,003代替) 你可能期待关于String在Map中的分配—可以阅读我之前关于HashCode方法调优的经验。 你必须设置一个更大的-XX:StringTalbeSize值(相比较默认的1009),如果你希望更多的使用String.intern()—否则这个方法将很快递减到0(池大小)。 我没有注意到在intern小于100字符的字符串时的依赖情况(我认为在一个包含50个重复字符的字符串与现实数据并不相似,因此100个字符看上去是一个很好的测试限制) 0;time=0.0sec 50000;time=0.03sec 100000;time=0.073sec 150000;time=0.13sec 200000;time=0.196sec 250000;time=0.279sec 300000;time=0.376sec 350000;time=0.471sec 400000;time=0.574sec 450000;time=0.666sec 500000;time=0.755sec 550000;time=0.854sec 600000;time=0.916sec 650000;time=1.006sec 700000;time=1.095sec 750000;time=1.273sec 800000;time=1.248sec 850000;time=1.446sec 900000;time=1.585sec 950000;time=1.635sec 1000000;time=1.913sec 测试是在Corei5-3317U@1.7GhzCPU设备上进行的。你可以看到,它成线性增长,并且在JVM字符串池包含一百万个字符串时,我仍然可以近似每秒intern5000个字符串,这对于在内存中处理大量数据的应用程序来说太慢了。 现在,调整-XX:StringTableSize=100003参数来重新运行测试: 50000;time=0.017sec 100000;time=0.009sec 150000;time=0.01sec 200000;time=0.009sec 250000;time=0.007sec 300000;time=0.008sec 350000;time=0.009sec 400000;time=0.009sec 450000;time=0.01sec 500000;time=0.013sec 550000;time=0.011sec 600000;time=0.012sec 650000;time=0.015sec 700000;time=0.015sec 750000;time=0.01sec 800000;time=0.01sec 850000;time=0.011sec 900000;time=0.011sec 950000;time=0.012sec 1000000;time=0.012sec 2000000;time=0.024sec 3000000;time=0.028sec 4000000;time=0.053sec 5000000;time=0.051sec 6000000;time=0.034sec 7000000;time=0.041sec 8000000;time=0.089sec 9000000;time=0.111sec 10000000;time=0.123sec 现在让我们将吃的大小增加到100万(精确的说是1,000,003) 1000000;time=0.005sec 2000000;time=0.005sec 3000000;time=0.005sec 4000000;time=0.004sec 5000000;time=0.004sec 6000000;time=0.009sec 7000000;time=0.01sec 8000000;time=0.009sec 9000000;time=0.009sec 10000000;time=0.009sec 我们还需要手工管理字符串池吗? 现在我们需要对比JVM字符串池和WeakHashMap privatestaticfinalWeakHashMap newWeakHashMap privatestaticStringmanualIntern(finalStringstr) finalWeakReference if(cached!=null) finalStringvalue=cached.get(); if(value!=null) returnvalue; s_manualCache.put(str,newWeakReference 下面针对手工池的相同测试: 0;manualtime=0.001sec 50000;manualtime=0.03sec 100000;manualtime=0.034sec 150000;manualtime=0.008sec 200000;manualtime=0.019sec 250000;manualtime=0.011sec 300000;manualtime=0.011sec 350000;manualtime=0.008sec 400000;manualtime=0.027sec 450000;manualtime=0.008sec 500000;manualtime=0.009sec 550000;manualtime=0.008sec 600000;manualtime=0.008sec 650000;manualtime=0.008sec 700000;manualtime=0.008sec 750000;manualtime=0.011sec 800000;manualtime=0.007sec 850000;manualtime=0.008sec 900000;manualtime=0.008sec 950000;manualtime=0.008sec 1000000;manualtime=0.008sec 当JVM有足够内存时,手工编写的池提供了良好的性能。不过不幸的是,我的测试(保留String.valueOf(0 在Java7u40+以及Java8中的String.intern() Java7u40版本扩展了字符串池的大小(这是组要的性能更新)到60013.这个值允许你在池中包含大约30000个独立的字符串。通常来说,这对于需要保存的数据来说已经足够了,你可以通过-XX:+PrintFlagsFinalJVM参数获得这个值。 我尝试在原始发布的Java8中运行相同的测试,Java8仍然支持-XX:StringTableSize参数来兼容Java7特性。主要的区别在于Java8中默认的池大小增加到60013: 50000;time=0.019sec 150000;time=0.009sec 250000;time=0.009sec 300000;time=0.009sec 350000;time=0.011sec 400000;time=0.012sec 550000;time=0.013sec 600000;time=0.014sec 650000;time=0.018sec 750000;time=0.029sec 800000;time=0.018sec 850000;time=0.02sec 900000;time=0.017sec 950000;time=0.018sec 1000000;time=0.021sec 测试代码 这里有两个测试:testStringPoolGarbageCollection将显示JVM字符串池被垃圾收集—检查垃圾收集日志消息。在Java6的默认PermGen大小配置上,这个测试会失败,因此最好增加这个值,或者更新测试方法,或者使用Java7. 第二个测试显示内存中保留了多少字符串。在Java6中执行需要两个不同的内存配置比如:-Xmx128M以及-Xmx1280M(10倍以上)。你可能发现这个值不会影响放入池中字符串的数量。另一方面,在Java7中你能够在堆中填满你的字符串。 /** -TestingString.intern. * -Runthisclassatleastwith-verbose:gcJVMparameter. */ publicclassInternTest{ testStringPoolGarbageCollection(); testLongLoop(); -Usethismethodtoseewhereinternedstringsarestored -andhowmanyofthemcanyoufitforthegivenheapsize. privatestaticvoidtestLongLoop() test(1000*1000*1000); //uncommentthefollowinglinetoseethehand-writtencacheperformance //testManual(1000*1000*1000); -Usethismethodtocheckthatnotusedinternedstringsaregarbagecollected. privatestaticvoidtestStringPoolGarbageCollection() //firstmethodcall-useitasareference test(1000*1000); //wearegoingtocleanthecachehere. System.gc(); //checkthememoryconsumptionandhowlongdoesittaketointernstrings //inthesecondmethodcall. privatestaticvoidtest(finalintcnt) finalList longstart=System.currentTimeMillis(); for(inti=0;i finalStringstr="Verylongteststring,whichtellsyouaboutsomething"+ "very-veryimportant,definitelydeservingtobeinterned#"+i; //uncommentthefollowinglinetotestdependencyfromstringlength //finalStringstr=Integer.toString(i); lst.add(str.intern()); if(i%10000==0) System.out.println(i+";time="+(System.currentTimeMillis()-start)/1000.0+"sec"); start=System.currentTimeMillis(); System.out.println("Totallength="+lst.size()); privatestaticvoidtestManual(finalintcnt) lst.add(manualIntern(str)); System.out.println(i+";manualtime="+(System.currentTimeMillis()-start)/1000.0+"sec"); 总结 本文首先介绍一下Java虚拟机的生存周期,然后大致介绍JVM的体系结构,最后对体系结构中的各个部分进行详细介绍。 (首先这里澄清两个概念:JVM实例和JVM执行引擎实例,JVM实例对应了一个独立运行的java程序,而JVM执行引擎实例则对应了属于用户运行程序的线程;也就是JVM实例是进程级别,而执行引擎是线程级别的。) 当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有publicstaticvoidmain(String[]args)函数的class都可以作为JVM实例运行的起点,既然如此,那么JVM如何知道是运行classA的main而不是运行classB的main呢?这就需要显式的告诉JVM类名,也就是我们平时运行java程序命令的由来,如javaclassAhelloworld,这里java是告诉os运行Sunjava2SDK的java虚拟机,而classA则指出了运行JVM所需要的类名。 JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。 JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。 粗略分来,JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。 下面将先介绍类装载器,然后是执行引擎,最后是运行时数据区 顾名思义,就是用来装载.class文件的。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。(下面所述情况是针对SunJDK1.2) 动类装载器:只在系统类(javaAPI的类文件)的安装路径查找要装入的类 用户自定义类装载器: 系统类装载器:在JVM启动时创建,用来在CLASSPATH目录下查找要装入的类 其他用户自定义类装载器:这里有必要先说一下ClassLoader类的几个方法,了解它们对于了解自定义类装载器如何装载.class文件至关重要。 protectedfinalClassdefineClass(Stringname,bytedata[],intoffset,intlength) protectedfinalClassdefineClass(Stringname,bytedata[],intoffset,intlength,ProtectionDomainprotectionDomain); protectedfinalClassfindSystemClass(Stringname) protectedfinalvoidresolveClass(Classc) defineClass用来将二进制class文件(新类型)导入到方法区,也就是这里指的类是用户自定义的类(也就是负责装载类) findSystemClass通过类型的全限定名,先通过系统类装载器或者启动类装载器来装载,并返回Class对象。 ResolveClass:让类装载器进行连接动作(包括验证,分配内存初始化,将类型中的符号引用解析为直接引用),这里涉及到java命名空间的问题,JVM保证被一个类装载器装载的类所引用的所有类都被这个类装载器装载,同一个类装载器装载的类之间可以相互访问,但是不同类装载器装载的类看不见对方,从而实现了有效的屏蔽。 要说执行引擎,就不得不的指令集,每一条指令包含一个单字节的操作码,后面跟0个或者多个操作数。 (一)指令集以栈为设计中心,而非以寄存器为中心 这种指令集设计如何满足Java体系的要求: 平台无关性:以栈为中心使得在只有很少register的机器上实现java更便利 compiler一般采用stack向连接优化器传递编译的中间结果,若指令集以stack为基础,则有利于运行时进行的优化工作与执行即时编译或者自适应优化的执行引擎结合,通俗的说就是使编译和运行用的数据结构统一,更有利于优化的开展。 网络移动性:class文件的紧凑性。 安全性:指令集中绝大部分操作码都指明了操作的类型。(在装载的时候使用数据流分析期进行一次性验证,而非在执行每条指令的时候进行验证,有利于提高执行速度)。 (二)执行技术 主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行 其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。 3,运行时数据区:主要包括:方法区,堆,java栈,PC寄存器,本地方法栈 (1)方法区和堆由所有线程共享堆:存放所有程序在运行时创建的对象方法区:当JVM的类装载器加载.class文件,并进行解析,把解析的类型信息放入方法区。 (3)本地方法栈:存储本地方法调用的状态 上边总体介绍了运行时数据区的主要内容,下边进行详细介绍,要介绍数据区,就不得不说明JVM中的数据类型。 JVM中的数据类型:JVM中基本的数据单元是word,而word的长度由JVM具体的实现者来决定 数据类型包括基本类型和引用类型, (1)基本类型 包括:数值类型(包括除boolean外的所有的java基本数据类型),boolean(在JVM中使用int来表示,0表示false,其他int值均表示true)和returnAddress(JVM的内部类型,用来实现finally子句)。 (2)引用类型包括:数组类型,类类型,接口类型 前边讲述了JVM中数据的表示,下面让我们输入到JVM的数据区 首先来看方法区: 上边已经提到,方法区主要用来存储JVM从class文件中提取的类型信息,那么类型信息是如何存储的呢?众所周知,Java使用的是大端序(big—endian:即低字节的数据存储在高位内存上,如对于1234,12是高位数据,34为低位数据,则java中的存储格式应该为12存在内存的低地址,34存在内存的高地址,x86中的存储格式与之相反)来存储数据,这实际上是在class文件中数据的存储格式,但是当数据倒入到方法区中时,JVM可以以任何方式来存储它。 类型信息:包括class的全限定名,class的直接父类,类类型还是接口类型,类的修饰符(public,等),所有直接父接口的列表,Class对象提供了访问这些信息的窗口(可通过Class.forName(“”)或instance.getClass()获得),下面是Class的方法,相信大家看了会恍然大悟,(原来如此J) getName(),getSuperClass(),isInterface(),getInterfaces(),getClassLoader(); static变量作为类型信息的一部分保存 指向ClassLoader类的引用:在动态连接时装载该类中引用的其他类 指向Class类的引用:必然的,上边已述 该类型的常量池:包括直接常量(String,integer和floatpoint常量)以及对其他类型、字段和方法的符号引用(注意:这里的常量池并不是普通意义上的存储常量的地方,这些符号引用可能是我们在编程中所接触到的变量),由于这些符号引用,使得常量池成为java程序动态连接中至关重要的部分 方法信息:类型中各个方法的信息 class将所有的常量复制至其常量池或者其字节码流中。 方法表:一个数组,包括所有它的实例可能调用的实例方法的直接引用(包括从父类中继承来的) 除此之外,若某个类不是抽象和本地的,还要保存方法的字节码,操作数栈和该方法的栈帧,异常表。 classLava{privateintspeed=5;voidflow(){}}classVolcano{publicstaticvoidmain(String[]args){Lavalava=newLava();lava.flow(); }运行命令javaVolcano;(1)JVM找到Volcano.class倒入,并提取相应的类型信息到方法区。通过执行方法区中的字节码,JVM执行main()方法,(执行时会一直保存指向Vocano类的常量池的指针) (2)Main()中第一条指令告诉JVM需为列在常量池第一项的类分配内存(此处再次说明了常量池并非只存储常量信息),然后JVM找到常量池的第一项,发现是对Lava类的符号引用,则检查方法区,看Lava类是否装载,结果是还未装载,则查找“Lava.class”,将类型信息写入方法区,并将方法区Lava类信息的指针来替换Volcano原常量池中的符号引用,即用直接引用来替换符号引用。 (3)JVM看到new关键字,准备为Lava分配内存,根据Volcano的常量池的第一项找到Lava在方法区的位置,并分析需要多少对空间,确定后,在堆上分配空间,并将speed变量初始为0,并将lava对象的引用压到栈中 (4调用lava的flow()方法 好了,大致了解了方法区的内容后,让我们来看看堆 java对象的堆实现: 那么为什么java对象中要有指向类数据的指针呢?我们从几个方面来考虑 首先:当程序中将一个对象引用转为另一个类型时,如何检查转换是否允许?需用到类数据 其次:动态绑定时,并不是需要引用类型,而是需要运行时类型 这里的迷惑是:为什么类数据中保存的是实际类型,而非引用类型?这个问题先留下来,我想在后续的读书笔记中应该能明白 指向方法表的指针:这里和C++的VTBL是类似的,有利于提高方法调用的效率 对象锁:用来实现多个线程对共享数据的互斥访问 等待集合:用来让多个线程为完成共同目标而协调功过。(注意Object类中的wait(),notify(),notifyAll()方法)。 至此,堆已大致介绍完毕,下面来介绍程序计数器和java栈 程序计数器:为每个线程独有,在线程启动时创建, 若thread执行java方法,则PC保存下一条执行指令的地址。 若thread执行native方法,则Pc的值为undefined Java栈:java栈以帧为单位保存线程的运行状态,java栈只有两种操作,帧的压栈和出栈。 每个帧代表一个方法,java方法有两种返回方式,return和抛出异常,两种方式都会导致该方法对应的帧出栈和释放内存。 本地方法栈:依赖于本地方法的实现,如某个JVM实现的本地方法借口使用C连接模型,则本地方法栈就是C栈,可以说某线程在调用本地方法时,就进入了一个不受JVM限制的领域,也就是JVM可以利用本地方法来动态扩展本身。 运行时类型识别(Run-timeTypeIdentification,RTTI)主要有两种方式,一种是我们在编译时和运行时已经知道了所有的类型,另外一种是功能强大的“反射”机制。 要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由“Class对象”完成的,它包含了与类有关的信息。类是程序的重要组成部分,每个类都有一个Class对象,每当编写并编译了一个新类就会产生一个Class对象,它被保存在一个同名的.class文件中。在运行时,当我们想生成这个类的对象时,运行这个程序的Java虚拟机(JVM)会确认这个类的Class对象是否已经加载,如果尚未加载,JVM就会根据类名查找.class文件,并将其载入,一旦这个类的Class对象被载入内存,它就被用来创建这个类的所有对象。一般的RTTI形式包括三种: 1.传统的类型转换。如“(Apple)Fruit”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。 2.通过Class对象来获取对象的类型。如 Classc=Class.forName(“Apple”); Objecto=c.newInstance(); 3.通过关键字instanceof或Class.isInstance()方法来确定对象是否属于某个特定类型的实例,准确的说,应该是instanceof/Class.isInstance()可以用来确定对象是否属于某个特定类及其所有基类的实例,这和equals()/==不一样,它们用来比较两个对象是否属于同一个类的实例,没有考虑继承关系。 如果不知道某个对象的类型,可以通过RTTI来获取,但前提是这个类型在编译时必须已知,这样才能使用RTTI来识别。即在编译时,编译器必须知道所有通过RTTI来处理的类。 第二种情况,在运行时获取类的信息的另外一个动机,就是希望能够提供在跨网络的远程平台上创建和运行对象的能力。这被成为远程调用(RMI),它允许一个Java程序将对象分步在多台机器上,这种分步能力将帮助开发人员执行一些需要进行大量计算的任务,充分利用计算机资源,提高运行速度。 Class支持反射,java.lang.reflect中包含了Field/Method/Constructor类,每个类都实现了Member接口。这些类型的对象都是由JVM在运行时创建的,用来表示未知类里对应的成员。如可以用Constructor类创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。同时,还可以调用getFields()、getMethods()、getConstructors()等方法来返回表示字段、方法以及构造器的对象数组。这样,未知的对象的类信息在运行时就能被完全确定下来,而在编译时不需要知道任何信息。 另外,RTTI有时能解决效率问题。当程序中使用多态给程序的运行带来负担的时候,可以使用RTTI编写一段代码来提高效率。 Reflection是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过ReflectionAPIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public,static等等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。本文借由实例,大面积示范ReflectionAPIs。 Introspection(内省、内观) Reflection(反射) 有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。我们朗朗上口动态绑定(dynamicbinding)、动态链接(dynamiclinking)、动态加载(dynamicloading)等。然而“动态”一词其实没有绝对而普遍适用的严格定义,有时候甚至像对象导向当初被导入编程领域一样,一人一把号,各吹各的调。 一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。 Java如何能够做出上述的动态特性呢?这是一个深远话题,本文对此只简单介绍一些概念。整个篇幅最主要还是介绍ReflectionAPIs,也就是让读者知道如何探索class的结构、如何对某个“运行时才获知名称的class”生成一份实体、为其fields设值、调用其methods。本文将谈到java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等等classes。 Classclass十分特殊。它和一般classes一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitiveJavatypes(boolean,byte,char,short,int,long,float,double)以及关键词void。当一个class被加载,或当加载器(classloader)的defineClass()被JVM调用,JVM便自动产生一个Classobject。如果您想借由“修改Java标准库源码”来观察Classobject的实际生成时机(例如在Class的constructor内添加一个println()),不能够!因为Class并没有publicconstructor(见图1)。本文最后我会拨一小块篇幅顺带谈谈Java标准库源码的改动办法。 Class是Reflection故事起源。针对任何您想探勘的class,唯有先为它产生一个Classobject,接下来才能经由后者唤起为数十多个的ReflectionAPIs。这些APIs将在稍后的探险活动中一一亮相。 #001publicfinal #002classClass #003java.lang.reflect.GenericDeclaration, #004java.lang.reflect.Type, #005java.lang.reflect.AnnotatedElement{ #006privateClass(){} #007publicStringtoString(){ #008return(isInterface()"interface": #009(isPrimitive()"":"class")) #010+getName(); #011} ... 图1:Classclass片段。注意它的privateemptyctor,意指不允许任何人经由编程方式产生Classobject。是的,其object只能由JVM产生。 “Class”object的取得途径 Java允许我们从多种管道为一个class生成对应的Classobject。图2是一份整理。 Classobject诞生管道 示例 运用getClass() 注:每个class都有此函数 Stringstr="abc"; Classc1=str.getClass(); 运用 Class.getSuperclass()2 Buttonb=newButton(); Classc1=b.getClass(); Classc2=c1.getSuperclass(); 运用staticmethod Class.forName() (最常被使用) Classc1=Class.forName("java.lang.String"); Classc2=Class.forName("java.awt.Button"); Classc3=Class.forName("java.util.LinkedList$Entry"); Classc4=Class.forName("I"); Classc5=Class.forName("[I"); .class语法 Classc1=String.class; Classc2=java.awt.Button.class; Classc3=Main.InnerClass.class; Classc4=int.class; Classc5=int[].class; primitivewrapperclasses 的TYPE语法 Classc1=Boolean.TYPE; Classc2=Byte.TYPE; Classc3=Character.TYPE; Classc4=Short.TYPE; Classc5=Integer.TYPE; Classc6=Long.TYPE; Classc7=Float.TYPE; Classc8=Double.TYPE; Classc9=Void.TYPE; 图2:Java允许多种管道生成Classobject。 首先容我以图3的java.util.LinkedList为例,将Javaclass的定义大卸八块,每一块分别对应图4所示的ReflectionAPI。图5则是“获得class各区块信息”的程序示例及执行结果,它们都取自本文示例程序的对应片段。 packagejava.util;//(1) importjava.lang.*;//(2) publicclassLinkedList extendsAbstractSequentialList implementsList Cloneable,java.io.Serializable//(7) privatestaticclassEntry publicLinkedList(){…}//(9) publicLinkedList(Collection publicEgetFirst(){…}//(10) publicEgetLast(){…} privatetransientEntry privatetransientintsize=0; 图3:将一个Javaclass大卸八块,每块相应于一个或一组ReflectionAPIs(图4)。 图3的各个Javaclass成份,分别对应于图4的ReflectionAPI,其中出现的Package、Method、Constructor、Field等等classes,都定义于java.lang.reflect。 Javaclass内部模块(参见图3) Javaclass内部模块说明 相应之ReflectionAPI,多半为Classmethods。 返回值类型(returntype) (1)package class隶属哪个package getPackage() Package (2)import class导入哪些classes 无直接对应之API。 解决办法见图5-2。 (3)modifier class(或methods,fields)的属性 intgetModifiers() Modifier.toString(int) Modifier.isInterface(int) int String bool (4)classnameorinterfacename class/interface 名称getName() (5)typeparameters 参数化类型的名称 getTypeParameters() TypeVariable (6)baseclass baseclass(只可能一个) getSuperClass() Class (7)implementedinterfaces 实现有哪些interfaces getInterfaces() Class[] (8)innerclasses 内部classes getDeclaredClasses() (8')outerclass 如果我们观察的class本身是innerclasses,那么相对它就会有个outerclass。 getDeclaringClass() (9)constructors 构造函数getDeclaredConstructors() 不论public或private或其它accesslevel,皆可获得。另有功能近似之取得函数。 Constructor[] (10)methods 操作函数getDeclaredMethods() Method[] (11)fields 字段(成员变量) getDeclaredFields()不论public或private或其它accesslevel,皆可获得。另有功能近似之取得函数。 Field[] 图4:Javaclass大卸八块后(如图3),每一块所对应的ReflectionAPI。本表并非 ReflectionAPIs的全部。 图5示范图4提过的每一个ReflectionAPI,及其执行结果。程序中出现的tName()是个辅助函数,可将其第一自变量所代表的“Javaclass完整路径字符串”剥除路径部分,留下class名称,储存到第二自变量所代表的一个hashtable去并返回(如果第二自变量为null,就不储存而只是返回)。 #001Classc=null; #002c=Class.forName(args[0]); #003 #004Packagep; #005p=c.getPackage(); #006 #007if(p!=null) #008System.out.println("package"+p.getName()+";"); 执行结果(例): packagejava.util; 图5-1:找出class隶属的package。其中的c将继续沿用于以下各程序片段。 #001ff=c.getDeclaredFields(); #002for(inti=0;i #003x=tName(ff[i].getType().getName(),classRef); #004 #005cn=c.getDeclaredConstructors(); #006for(inti=0;i #007Classcx[]=cn[i].getParameterTypes(); #008for(intj=0;j #009x=tName(cx[j].getName(),classRef); #010} #011 #012mm=c.getDeclaredMethods(); #013for(inti=0;i #014x=tName(mm[i].getReturnType().getName(),classRef); #015Classcx[]=mm[i].getParameterTypes(); #016for(intj=0;j #017x=tName(cx[j].getName(),classRef); #018} #019classRef.remove(c.getName());//不必记录自己(不需import自己) importjava.util.ListIterator; importjava.lang.Object; importjava.util.LinkedList$Entry; importjava.util.Collection; importjava.io.ObjectOutputStream; importjava.io.ObjectInputStream; 图5-2:找出导入的classes,动作细节详见内文说明。 #001intmod=c.getModifiers(); #002System.out.print(Modifier.toString(mod));//整个modifier #004if(Modifier.isInterface(mod)) #005System.out.print("");//关键词"interface"已含于modifier #006else #007System.out.print("class");//关键词"class" #008System.out.print(tName(c.getName(),null));//class名称 publicclassLinkedList 图5-3:找出class或interface的名称,及其属性(modifiers)。 #001TypeVariable #002tv=c.getTypeParameters();//warning:uncheckedconversion #003for(inti=0;i #004x=tName(tv[i].getName(),null);//例如E,K,V... #005if(i==0)//第一个 #006System.out.print("<"+x); #007else//非第一个 #008System.out.print(","+x); #009if(i==tv.length-1)//最后一个 #010System.out.println(">"); publicabstractinterfaceMap 或publicclassLinkedList 图5-4:找出parameterizedtypes的名称 #001ClasssupClass; #002supClass=c.getSuperclass(); #003if(supClass!=null)//如果有superclass #004System.out.print("extends"+ #005tName(supClass.getName(),classRef)); publicclassLinkedList extendsAbstractSequentialList, 图5-5:找出baseclass。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。 #001Classcc[]; #002Classctmp; #003//找出所有被实现的interfaces #004cc=c.getInterfaces(); #005if(cc.length!=0) #006System.out.print(",\r\n"+"implements");//关键词 #007for(Classcite:cc)//JDK1.5新式循环写法 #008System.out.print(tName(cite.getName(),null)+","); implementsList,Queue,Cloneable,Serializable, 图5-6:找出implementedinterfaces。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。 #001cc=c.getDeclaredClasses();//找出innerclasses #002for(Classcite:cc) #003System.out.println(tName(cite.getName(),null)); #005ctmp=c.getDeclaringClass();//找出outerclasses #006if(ctmp!=null) #007System.out.println(ctmp.getName()); LinkedList$Entry LinkedList$ListItr 图5-7:找出innerclasses和outerclass #001Constructorcn[]; #002cn=c.getDeclaredConstructors(); #003for(inti=0;i #004intmd=cn[i].getModifiers(); #005System.out.print(""+Modifier.toString(md)+""+ #006cn[i].getName()); #008System.out.print("("); #009for(intj=0;j #010System.out.print(tName(cx[j].getName(),null)); #011if(j<(cx.length-1))System.out.print(","); #012} #013System.out.print(")"); #014} publicjava.util.LinkedList(Collection) publicjava.util.LinkedList() 图5-8a:找出所有constructors #004System.out.println(cn[i].toGenericString()); publicjava.util.LinkedList(java.util.Collection 图5-8b:找出所有constructors。本例在for循环内使用toGenericString(),省事。 #001Methodmm[]; #002mm=c.getDeclaredMethods(); #003for(inti=0;i #004intmd=mm[i].getModifiers(); #006tName(mm[i].getReturnType().getName(),null)+""+ #007mm[i].getName()); #008Classcx[]=mm[i].getParameterTypes(); #009System.out.print("("); #010for(intj=0;j #011System.out.print(tName(cx[j].getName(),null)); #012if(j<(cx.length-1))System.out.print(","); #013} #014System.out.print(")"); #015} publicObjectget(int) publicintsize() 图5-9a:找出所有methods #004System.out.println(mm[i].toGenericString()); publicEjava.util.LinkedList.get(int) publicintjava.util.LinkedList.size() 图5-9b:找出所有methods。本例在for循环内使用toGenericString(),省事。 #001Fieldff[]; #002ff=c.getDeclaredFields(); #003for(inti=0;i #004intmd=ff[i].getModifiers(); #005System.out.println(""+Modifier.toString(md)+""+ #006tName(ff[i].getType().getName(),null)+""+ #007ff[i].getName()+";"); #008} privatetransientLinkedList$Entryheader; privatetransientintsize; 图5-10a:找出所有fields #004System.out.println("G:"+ff[i].toGenericString()); privatetransientjava.util.LinkedList.java.util.LinkedList$Entry java.util.LinkedList.header privatetransientintjava.util.LinkedList.size 图5-10b:找出所有fields。本例在for循环内使用toGenericString(),省事。 没有直接可用的ReflectionAPI可以为我们找出某个class参用的所有其它classes。要获得这项信息,必须做苦工,一步一脚印逐一记录。我们必须观察所有fields的类型、所有methods(包括constructors)的参数类型和回返类型,剔除重复,留下唯一。这正是为什么图5-2程序代码要为tName()指定一个hashtable(而非一个null)做为第二自变量的缘故:hashtable可为我们储存元素(本例为字符串),又保证不重复。 本文讨论至此,几乎可以还原一个class的原貌(唯有methods和ctors的定义无法取得)。接下来讨论Reflection的另三个动态性质:(1)运行时生成instances,(2)执 行期唤起methods,(3)运行时改动fields。 欲生成对象实体,在Reflection动态机制中有两种作法,一个针对“无自变量ctor”, 一个针对“带参数ctor”。图6是面对“无自变量ctor”的例子。如果欲调用的是“带参数ctor“就比较麻烦些,图7是个例子,其中不再调用Class的newInstance(),而是调用Constructor的newInstance()。图7首先准备一个Class[]做为ctor的参数类型(本例指定为一个double和一个int),然后以此为自变量调用getConstructor(),获得一个专属ctor。接下来再准备一个Object[]做为ctor实参值(本例指定3.14159和125),调用上述专属ctor的newInstance()。 #001Classc=Class.forName("DynTest"); #002Objectobj=null; #003obj=c.newInstance();//不带自变量 #004System.out.println(obj); 图6:动态生成“Classobject所对应之class”的对象实体;无自变量。 #002Class[]pTypes=newClass[]{double.class,int.class}; #003Constructorctor=c.getConstructor(pTypes); #004//指定parameterlist,便可获得特定之ctor #005 #006Objectobj=null; #007Object[]arg=newObject[]{3.14159,125};//自变量 #008obj=ctor.newInstance(arg); #009System.out.println(obj); 图7:动态生成“Classobject对应之class”的对象实体;自变量以Object[]表示。 这个动作和上述调用“带参数之ctor”相当类似。首先准备一个Class[]做为ctor的参数类型(本例指定其中一个是String,另一个是Hashtable),然后以此为自变量调用getMethod(),获得特定的Methodobject。接下来准备一个Object[]放置自变量,然后调用上述所得之特定Methodobject的invoke(),如图8。知道为什么索取Methodobject时不需指定回返类型吗?因为methodoverloading机制要求signature(署名式)必须唯一,而回返类型并非signature的一个成份。换句话说,只要指定了method名称和参数列,就一定指出了一个独一无二的method。 #001publicStringfunc(Strings,Hashtableht) #002{ #003…System.out.println("funcinvoked");returns; #004} #005publicstaticvoidmain(Stringargs[]) #006{ #007Classc=Class.forName("Test"); #008Classptypes[]=newClass[2]; #009ptypes[0]=Class.forName("java.lang.String"); #010ptypes[1]=Class.forName("java.util.Hashtable"); #011Methodm=c.getMethod("func",ptypes); #012Testobj=newTest(); #013Objectargs[]=newObject[2]; #014arg[0]=newString("Hello,world"); #015arg[1]=null; #016Objectr=m.invoke(obj,arg); #017Integerrval=(String)r; #018System.out.println(rval); #019} 图8:动态唤起method 与先前两个动作相比,“变更field内容”轻松多了,因为它不需要参数和自变量。首先调用Class的getField()并指定field名称。获得特定的Fieldobject之后便可直接调用Field的get()和set(),如图9。 #001publicclassTest{ #002publicdoubled; #004publicstaticvoidmain(Stringargs[]) #005{ #006Classc=Class.forName("Test"); #007Fieldf=c.getField("d");//指定field名称 #008Testobj=newTest(); #009System.out.println("d="+(Double)f.get(obj)); #010f.set(obj,12.34); #011System.out.println("d="+obj.d); 图9:动态变更field内容 先前我曾提到,原本想借由“改动Java标准库源码”来测知Classobject的生成,但由于其ctor原始设计为private,也就是说不可能透过这个管道生成Classobject(而是由classloader负责生成),因此“在ctor中打印出某种信息”的企图也就失去了意义。 这里我要谈点题外话:如何修改Java标准库源码并让它反应到我们的应用程序来。假设我想修改java.lang.Class,让它在某些情况下打印某种信息。首先必须找出标准源码!当你下载JDK套件并安装妥当,你会发现jdk150\src\java\lang目录(见图10)之中有Class.java,这就是我们此次行动的标准源码。备份后加以修改,编译获得Class.class。接下来准备将.class搬移到jdk150\jre\lib\endorsed(见图10)。 这是一个十分特别的目录,classloader将优先从该处读取内含classes的.jar文件——成功的条件是.jar内的classes压缩路径必须和Java标准库的路径完全相同。为此,我们可以将刚才做出的Class.class先搬到一个为此目的而刻意做出来的\java\lang目录中,压缩为foo.zip(任意命名,唯需夹带路径java\lang),再将这个foo.zip搬到jdk150\jre\lib\endorsed并改名为foo.jar。此后你的应用程序便会优先用上这里的java.lang.Class。整个过程可写成一个批处理文件(batchfile),如图11,在DOSBox中使用。 图10:JDK1.5安装后的目录组织。其中的endorsed是我新建。 dele:\java\lang\*.class//清理干净 delc:\jdk150\jre\lib\endorsed\foo.jar//清理干净 c: cdc:\jdk150\src\java\lang javac-Xlint:uncheckedClass.java//编译源码 javac-Xlint:uncheckedClassLoader.java//编译另一个源码(如有必要) move*.classe:\java\lang//搬移至刻意制造的目录中 e: cde:\java\lang//以下压缩至适当目录 pkzipc-add-path=rootc:\jdk150\jre\lib\endorsed\foo.jar*.class cde:\test//进入测试目录 javac-Xlint:uncheckedTest.java//编译测试程序 javaTest//执行测试程序 图11:一个可在DOSBox中使用的批处理文件(batchfile),用以自动化java.lang.Class 的修改动作。Pkzipc(.exe)是个命令列压缩工具,add和path都是其命令。 注1用过诸如MFC这类所谓ApplicationFramework的程序员也许知道,MFC有所谓的dynamiccreation。但它并不等同于Java的动态加载或动态辨识;所有能够在MFC程序中起作用的classes,都必须先在编译期被编译器“看见”。 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。 在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。理解了应用程序的工作负荷和jvm支持的垃圾收集算法,便可以进行优化配置垃圾收集器。 垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。gc首先要判断该对象是否是时候可以收集。两种常用的方法是引用计数和对象引用遍历。 引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,jvm必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。 早期的jvm使用引用计数,现在大多数jvm采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。 下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。 这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。 有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。 这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。 增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。 并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高java应用程序的可扩展性。 SunHotSpot1.4.1使用分代收集器,它把堆分为三个主要的域:新域、旧域以及永久域。Jvm生成的所有新对象放在新域中。一旦对象经历了一定数量的垃圾收集循环后,便获得使用期并进入旧域。在永久域中jvm则存储class和method对象。就配置而言,永久域是一个独立域并且不认为是堆的一部分。 下面介绍如何控制这些域的大小。可使用-Xms和-Xmx控制整个堆的原始大小或最大值。 下面的命令是把初始大小设置为128M: java–Xms128m –Xmx256m为控制新域的大小,可使用-XX:NewRatio设置新域在堆中所占的比例。 下面的命令把整个堆设置成128m,新域比率设置成3,即新域与旧域比例为1:3,新域为堆的1/4或32M: java–Xms128m–Xmx128m –XX:NewRatio=3可使用-XX:NewSize和-XX:MaxNewsize设置新域的初始值和最大值。 下面的命令把新域的初始值和最大值设置成64m: java–Xms256m–Xmx256m–Xmn64m 永久域默认大小为4m。运行程序时,jvm会调整永久域的大小以满足需要。每次调整时,jvm会对堆进行一次完全的垃圾收集。 使用-XX:MaxPerSize标志来增加永久域搭大小。在WebLogicServer应用程序加载较多类时,经常需要增加永久域的最大值。当jvm加载类时,永久域中的对象急剧增加,从而使jvm不断调整永久域大小。为了避免调整,可使用-XX:PerSize标志设置初始值。 下面把永久域初始值设置成32m,最大值设置成64m。 java-Xms512m-Xmx512m-Xmn128m-XX:PermSize=32m-XX:MaxPermSize=64m 默认状态下,HotSpot在新域中使用复制收集器。该域一般分为三个部分。第一部分为Eden,用于生成新的对象。另两部分称为救助空间,当Eden充满时,收集器停止应用程序,把所有可到达对象复制到当前的from救助空间,一旦当前的from救助空间充满,收集器则把可到达对象复制到当前的to救助空间。From和to救助空间互换角色。维持活动的对象将在救助空间不断复制,直到它们获得使用期并转入旧域。使用-XX:SurvivorRatio可控制新域子空间的大小。 同NewRation一样,SurvivorRation规定某救助域与Eden空间的比值。比如,以下命令把新域设置成64m,Eden占32m,每个救助域各占16m: java-Xms256m-Xmx256m-Xmn64m-XX:SurvivorRation=2 如前所述,默认状态下HotSpot对新域使用复制收集器,对旧域使用标记-清除-压缩收集器。在新域中使用复制收集器有很多意义,因为应用程序生成的大部分对象是短寿命的。理想状态下,所有过渡对象在移出Eden空间时将被收集。如果能够这样的话,并且移出Eden空间的对象是长寿命的,那么理论上可以立即把它们移进旧域,避免在救助空间反复复制。但是,应用程序不能适合这种理想状态,因为它们有一小部分中长寿命的对象。最好是保持这些中长寿命的对象并放在新域中,因为复制小部分的对象总比压缩旧域廉价。为控制新域中对象的复制,可用-XX:TargetSurvivorRatio控制救助空间的比例(该值是设置救助空间的使用比例。如救助空间位1M,该值50表示可用500K)。该值是一个百分比,默认值是50。当较大的堆栈使用较低的sruvivorratio时,应增加该值到80至90,以更好利用救助空间。用-XX:maxtenuringthreshold可控制上限。 为放置所有的复制全部发生以及希望对象从eden扩展到旧域,可以把MaxTenuringThreshold设置成0。设置完成后,实际上就不再使用救助空间了,因此应把SurvivorRatio设成最大值以最大化Eden空间,设置如下: java…-XX:MaxTenuringThreshold=0–XX:SurvivorRatio=50000… BeaWebLogic8.1使用的新的JVM用于Intel平台。在Bea安装完毕的目录下可以看到有一个类似于jrockit81sp1_141_03的文件夹。这就是Bea新JVM所在目录。不同于HotSpot把Java字节码编译成本地码,它预先编译成类。JRockit还提供了更细致的功能用以观察JVM的运行状态,主要是独立的GUI控制台(只能适用于使用Jrockit才能使用jrockit81sp1_141_03自带的console监控一些cpu及memory参数)或者WebLogicServer控制台。 BeaJRockitJVM支持4种垃圾收集器: 它与默认的分代收集器工作策略类似。对象在新域中分配,即JRockit文档中的nursery。这种收集器最适合单cpu机上小型堆操作。 分代并发收集器这种收集器在护理域使用排它复制收集器,在旧域中则使用并发收集器。由于它比单空间共同发生收集器中断频繁,因此它需要较少的内存,应用程序的运行效率也较高,注意,过小的护理域可以导致大量的临时对象被扩展到旧域中。这会造成收集器超负荷运作,甚至采用排它性工作方式完成收集。 默认状态下,JRockit使用分代并发收集器。要改变收集器,可使用-Xgc:,对应四个收集器分别为gencopy,singlecon,gencon以及parallel。可使用-Xms和-Xmx设置堆的初始大小和最大值。要设置护理域,则使用-Xns:java–jrockit–Xms512m–Xmx512m–Xgc:gencon–Xns128m…尽管JRockit支持-verbose:gc开关,但它输出的信息会因收集器的不同而异。JRockit还支持memory、load和codegen的输出。 注意:如果使用JRockitJVM的话还可以使用WLS自带的console(C:\bea\jrockit81sp1_141_03\bin下)来监控一些数据,如cpu,memery等。要想能构监控必须在启动服务时startWeblogic.cmd中加入-Xmanagement参数。 可通过如下参数进行调整:-server启用服务器模式(如果CPU多,服务器机建议使用此项) -Xms,-Xmx一般设为同样大小。800m -Xmn是将NewSize与MaxNewSize设为一致。320m -XX:PerSize64m -XX:NewSize320m此值设大可调大新对象区,减少FullGC次数 -XX:MaxNewSize320m -XX:NewRatoNewSize设了可不设。 -XX:SurvivorRatio -XX:userParNewGC可用来设置并行收集 -XX:ParallelGCThreads可用来增加并行度 -XXUseParallelGC设置后可以使用并行清除收集器 -XX:UseAdaptiveSizePolicy与上面一个联合使用效果更好,利用它可以自动优化新域大小以及救助空间比值 JNLP中参数:initial-heap-size和max-heap-size 这可以在framework的RequestManager中生成JNLP文件时加入上述参数,但是这些值是要求根据客户机的硬件状态变化的(如客户机的内存大小等)。建议这两个参数值设为客户机可用内存的60%(有待测试)。为了在动态生成JNLP时以上两个参数值能够随客户机不同而不同,可靠虑获得客户机系统信息并将这些嵌到首页index.jsp中作为连接请求的参数。 在设置了上述参数后可以通过Visualgc来观察垃圾回收的一些参数状态,再做相应的调整来改善性能。一般的标准是减少fullgc的次数,最好硬件支持使用并行垃圾回收(要求多CPU)。 对象是使用new创建的,但是并没有与之相对应的delete操作来回收对象占用的内存.当我们完成对某个对象的使用时,只需停止该对象的引用: ->将引用改变为指向其他对象 ->将引用指向null ->从方法中返回,使得该方法的局部变量不复存在 要点: ->当我们从任何可执行代码都无法到达某个对象时,它所占用的空间就可以被回收. ->垃圾回收意味着我们永远不用担心出现虚悬引用(danglingreference).虚悬引用,指得是引用已经被删除的内存空间.在那些程序员可以直接控制何时删除对象的系统中,会存在这样的问题. ->垃圾回收器模型:引用计数器法(不能解决循环引用),标记-清除(mark-and-sweep) finalize方法 ->在垃圾回收器确定该对象是不可达的且该对象的空间将被回收之后,垃圾回收器就会调用这个方法. ->这个方法可以清除该对象所使用的所有非内存资源,对每一个对象最多只能调用一次,即使在这个方法的执行使得该对象重新变为可达之后又马上会再次变为不可达的情况下,该方法也只能调用一次. 覆写finalize方法 ->当一个对象变成垃圾时,它所引用的其他对象也很有可能会变成垃圾.这些垃圾可能在调用我们编写的finalize方法之前就已经被终结了,因此它们可能处于不可预知的状态. 类:Runtime.getRuntime(),System 方法:gc(),runFinalization(),freeMemory(),totalMemory(),maxMemory() System类支持静态的gc()和runFinalization()方法,它们将调用当前Runtime对象上的相应方法. 对象只有在没有任何引用指定它的时候才可以被当作垃圾回收,但有时我们可能希望在仍旧有选定引用指向对象时,将该对象作为垃圾回收掉. 引用对象的唯一用途就是维护对另一个被称为指称物(referent)的对象的引用.通常我们通过字段或者局部变量来维护对对象的引用,但是现在我们可以维护对引用对象的直接引用,而该引用对象包装了我们实际需要的对象.垃圾回收器可能判断出对某个对象的残留引用是否都是经由引用对象面引用到该对象的,因此它可以决定是否要回收该对象.引用对象的强度将决定垃圾回收器的行为,普通的引用都是强度最大的引用. Reference类 ->包:java.lang.ref ->典型方法:get(),clear(),enqueue(),isEnqueued() 引用和可达性强度 ->对象是强可达的(stronglyreachable):普通的引用 ->对象是软可达的(softlyreachable):SoftReference ->对象是弱可达的(weaklyreachable):WeakReference ->对象是虚可达的(phantomreachable):PhantomReference ->对象是不可达的:没有引用链接 一旦对象变为弱可达的(或者列弱),它就可以被终结.如果在终结之后该对象是不可达的,那么它就可以被回收了. ->软可达对象可能会任凭垃圾回收器去回收.我们可确定的是所有对软可达对象的SoftReference都会在抛出outofMemoryError错误这前被清除. ->弱可达对象将会被垃圾回收器回收. ->虚可达对象并不是真正意义上的可达,因为无法通过PhantomReference访问其指称对象,其get方法总是返回null.但是虚引用的存在可以防止对象在显式清除虚引用之前被回收.虚引用使我们可以处理那些finalize方法已经被调用过的对象,从而可以安全地认为它们是"死"的. 1、什么是Spring框架?Spring框架有哪些主要模块? Spring框架至今已集成了20多个模块。这些模块主要被分如下图所示的核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。 2、使用Spring框架能带来哪些好处? 下面列举了一些使用Spring框架带来的主要好处: 3、什么是控制反转(IOC)?什么是依赖注入? 控制反转是应用于软件工程领域中的,在运行时被装配器对象来绑定耦合对象的一种编程技巧,对象之间耦合关系在编译时通常是未知的。在传统的编程方式中,业务逻辑的流程是由应用程序中的早已被设定好关联关系的对象来决定的。在使用控制反转的情况下,业务逻辑的流程是由对象关系图来决定的,该对象关系图由装配器负责实例化,这种实现方式还可以将对象之间的关联关系的定义抽象化。而绑定的过程是通过“依赖注入”实现的。 控制反转是一种以给予应用程序中目标组件更多控制为目的设计范式,并在我们的实际工作中起到了有效的作用。 依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象实例化的模式。这就需要一种机制用来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件? 在Java中依然注入有以下三种实现方式: 4、请解释下Spring框架中的IoC? Spring中的org.springframework.beans包和org.springframework.context包构成了Spring框架IoC容器的基础。 BeanFactory接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。ApplicationContex接口对BeanFactory(是一个子接口)进行了扩展,在BeanFactory的基础上添加了其他功能,比如与Spring的AOP更容易集成,也提供了处理messageresource的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对Web应用的WebApplicationContext。 org.springframework.beans.factory.BeanFactory是SpringIoC容器的具体实现,用来包装和管理前面提到的各种bean。BeanFactory接口是SpringIoC容器的核心接口。 5、BeanFactory和ApplicationContext有什么区别? BeanFactory可以理解为含有bean集合的工厂类。BeanFactory包含了种bean的定义,以便在接收到客户端请求时将对应的bean实例化。 BeanFactory还能在实例化对象的时生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initializationmethods)和销毁方法(destructionmethods)。 从表面上看,applicationcontext如同beanfactory一样具有bean定义、bean关联关系的设置,根据请求分发bean的功能。但applicationcontext在此基础上还提供了其他的功能。 以下是三种较常见的ApplicationContext实现方式: 1、ClassPathXmlApplicationContext:从classpath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。 ApplicationContextcontext=newClassPathXmlApplicationContext(“bean.xml”); 2、FileSystemXmlApplicationContext:由文件系统中的XML配置文件读取上下文。 ApplicationContextcontext=newFileSystemXmlApplicationContext(“bean.xml”); 3、XmlWebApplicationContext:由Web应用的XML文件读取上下文。 6、Spring有几种配置方式? 将Spring配置到应用开发中有以下三种方式: 7、如何用基于XML配置的方式配置Spring? 在Spring框架中,依赖和服务需要在专门的配置文件来实现,我常用的XML格式的配置文件。这些配置文件的格式通常用 Spring的XML配置方式是使用被Spring命名空间的所支持的一系列的XML标签来实现的。Spring有以下主要的命名空间:context、beans、jdbc、tx、aop、mvc和aso。 下面这个web.xml仅仅配置了DispatcherServlet,这件最简单的配置便能满足应用程序配置运行时组件的需求。 org.springframework.web.servlet.DispatcherServlet 8、如何用基于Java配置的方式配置Spring? @Configuration publicclassAppConfig @Bean publicMyServicemyService(){ returnnewMyServiceImpl(); 对于上面的@Beans配置文件相同的XML配置文件如下: 上述配置方式的实例化方式如下:利用AnnotationConfigApplicationContext类进行实例化 ApplicationContextctx=newAnnotationConfigApplicationContext(AppConfig.class); MyServicemyService=ctx.getBean(MyService.class); myService.doStuff(); 要使用组件组建扫描,仅需用@Configuration进行注解即可: @ComponentScan(basePackages="com.howtodoinjava") publicclassAppConfig{ 如果你要在你的web应用开发中选用上述的配置的方式的话,需要用AnnotationConfigWebApplicationContext类来读取配置文件,可以用来配置Spring的Servlet监听器ContrextLoaderListener或者SpringMVC的DispatcherServlet。 org.springframework.web.context.support.AnnotationConfigWebApplicationContext 9、怎样用注解的方式配置Spring? 注解装配在Spring中是默认关闭的。所以需要在Spring文件中配置一下才能使用基于注解的装配模式。如果你想要在你的应用程序中使用关于注解的方法的话,请参考如下的配置。 在 下面是几种比较重要的注解类型: 10、请解释SpringBean的生命周期? Springbeanfactory负责管理在spring容器中被创建的bean的生命周期。Bean的生命周期由两组回调(callback)方法组成。 Spring框架提供了以下四种方式来管理bean的生命周期事件: 使用customInit()和customDestroy()方法管理bean生命周期的代码样例如下: init-method="customInit"destroy-method="customDestroy"> 更多内容请参考:Spring生命周期SpringBeanLifeCycle。 11、SpringBean的作用域之间有什么区别? Spring容器中的bean可以分为5个范围。所有范围的名称都是自说明的,但是为了避免混淆,还是让我们来解释一下: 全局作用域与Servlet中的session作用域效果相同。 12、什么是Springinnerbeans? 比如,在我们的应用程序中,一个Customer类引用了一个Person类,我们的要做的是创建一个Person的实例,然后在Customer内部使用。 publicclassCustomer privatePersonperson; //SettersandGetters publicclassPerson privateStringname; privateStringaddress; privateintage; 13、Spring框架中的单例Beans是线程安全的么? Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Springbean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如ViewModel对象),就需要自行保证线程安全。 最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。 14、请举例说明如何在Spring中注入一个JavaCollection? Spring提供了以下四种集合类的配置元素: 下面看一下具体的例子: 15、如何向SpringBean中注入一个Java.util.Properties? 第一种方法是使用如下面代码所示的 也可用”util:”命名空间来从properties文件中创建出一个propertiesbean,然后利用setter方法注入bean的引用。 16、请解释SpringBean的自动装配? 在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring容器还可以自动装配合作关系bean之间的关联关系。这意味着Spring可以通过向BeanFactory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。 下面的XML配置文件表明了如何根据名称将一个bean设置为自动装配: 除了bean配置文件中提供的自动装配模式,还可以使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在按照如下的配置方式在Spring配置文件进行配置才可以使用。 也可以通过在配置文件中配置AutowiredAnnotationBeanPostProcessor达到相同的效果。 配置好以后就可以使用@Autowired来标注了。 @Autowired publicEmployeeDAOImpl(EmployeeManagermanager){ this.manager=manager; 17、请解释自动装配模式的区别? 在Spring框架中共有5种自动装配,让我们逐一分析。 18、如何开启基于注解的自动装配? 要使用@Autowired,需要注册AutowiredAnnotationBeanPostProcessor,可以有以下两种方式来实现: 1、引入配置文件中的 2、在bean配置文件中直接引入AutowiredAnnotationBeanPostProcessor 19、请举例解释@Requiredannotation? 需要用如下的方式使用来标明bean的设值方法。 publicclassEmployeeFactoryBeanextendsAbstractFactoryBean privateStringdesignation; publicStringgetDesignation(){ returndesignation; @Required publicvoidsetDesignation(Stringdesignation){ this.designation=designation; //morecodehere RequiredAnnotationBeanPostProcessor是Spring中的后置处理用来验证被@Required注解的bean属性是否被正确的设置了。在使用RequiredAnnotationBeanPostProcesso来验证bean属性之前,首先要在IoC容器中对其进行注册: 但是如果没有属性被用@Required注解过的话,后置处理器会抛出一个BeanInitializationException异常。 20、请举例解释@Autowired注解? @Autowired注解对自动装配何时何处被实现提供了更多细粒度的控制。@Autowired注解可以像@Required注解、构造器一样被用于在bean的设值方法上自动装配bean的属性,一个参数或者带有任意名称或带有多个参数的方法。 比如,可以在设值方法上使用@Autowired注解来替代配置文件中的 当然我们也可以在构造方法上使用@Autowired注解。带有@Autowired注解的构造方法意味着在创建一个bean时将会被自动装配,即便在配置文件中使用 publicclassTextEditor{ privateSpellCheckerspellChecker; publicTextEditor(SpellCheckerspellChecker){ System.out.println("InsideTextEditorconstructor."); this.spellChecker=spellChecker; publicvoidspellCheck(){ spellChecker.checkSpelling(); 下面是没有构造参数的配置方式: 21、请举例说明@Qualifier注解? @Qualifier注解意味着可以在被标注bean的字段上可以自动装配。Qualifier注解可以用来取消Spring不能取消的bean应用。 下面的示例将会在Customer的person属性中自动装配person的值。 下面我们要在配置文件中来配置Person类。 Spring会知道要自动装配哪个personbean么?不会的,但是运行上面的示例时,会抛出下面的异常: Causedby:org.springframework.beans.factory.NoSuchBeanDefinitionException: Nouniquebeanoftype[com.howtodoinjava.common.Person]isdefined: expectedsinglematchingbeanbutfound2:[personA,personB] 要解决上面的问题,需要使用@Quanlifier注解来告诉Spring容器要装配哪个bean: @Qualifier("personA") 22、构造方法注入和设值注入有什么区别? 请注意以下明显的区别: 23、Spring框架中有哪些不同类型的事件? Spring的ApplicationContext提供了支持事件和代码中监听器的功能。 我们可以创建bean用来监听在ApplicationContext中发布的事件。ApplicationEvent类和在ApplicationContext接口中处理的事件,如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent被发布以后,bean会自动被通知。 publicclassAllApplicationEventListenerimplementsApplicationListener @Override publicvoidonApplicationEvent(ApplicationEventapplicationEvent) //processevent Spring提供了以下5中标准的事件: 除了上面介绍的事件以外,还可以通过扩展ApplicationEvent类来开发自定义的事件。 publicclassCustomApplicationEventextendsApplicationEvent publicCustomApplicationEvent(Objectsource,finalStringmsg) super(source); System.out.println("CreatedaCustomevent"); 为了监听这个事件,还需要创建一个监听器: publicclassCustomEventListenerimplementsApplicationListener publicvoidonApplicationEvent(CustomApplicationEventapplicationEvent){ //handleevent 之后通过applicationContext接口的publishEvent()方法来发布自定义事件。 CustomApplicationEventcustomEvent=newCustomApplicationEvent(applicationContext,"Testmessage"); applicationContext.publishEvent(customEvent); 24、FileSystemResource和ClassPathResource有何区别? 在FileSystemResource中需要给出spring-config.xml文件在你项目中的相对路径或者绝对路径。在ClassPathResource中spring会在ClassPath中自动搜寻配置文件,所以要把ClassPathResource文件放在ClassPath下。 如果将spring-config.xml保存在了src文件夹下的话,只需给出配置文件的名称即可,因为src文件夹是默认。 简而言之,ClassPathResource在环境变量中读取配置文件,FileSystemResource在配置文件中读取配置文件。 Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的: IoC就是InversionofControl,控制反转。在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制。这称为控制反转。 下面我们以几个例子来说明什么是IoC。假设我们要设计一个Girl和一个Boy类,其中Girl有kiss方法,即Girl想要Kiss一个Boy。那么,我们的问题是,Girl如何能够认识这个Boy? 在我们中国,常见的MM与GG的认识方式有以下几种:1青梅竹马;2亲友介绍;3父母包办。那么哪一种才是最好呢?青梅竹马:Girl从小就知道自己的Boy。 publicclassGirl...{voidkiss()...{Boyboy=newBoy();}} 然而从开始就创建的Boy缺点就是无法在更换。并且要负责Boy的整个生命周期。如果我们的Girl想要换一个怎么办?(笔者严重不支持Girl经常更换Boy)亲友介绍:由中间人负责提供Boy来见面 publicclassGirl...{voidkiss()...{Boyboy=BoyFactory.createBoy();}} 亲友介绍,固然是好。如果不满意,尽管另外换一个好了。但是,亲友BoyFactory经常是以Singleton的形式出现,不然就是,存在于Globals,无处不在,无处不能。实在是太繁琐了一点,不够灵活。我为什么一定要这个亲友掺和进来呢?为什么一定要付给她介绍费呢?万一最好的朋友爱上了我的男朋友呢?父母包办:一切交给父母,自己不用费吹灰之力,只需要等着Kiss就好了。 publicclassGirl...{voidkiss(Boyboy)...{//kissboyboy.kiss();}} Well,这是对Girl最好的方法,只要想办法贿赂了Girl的父母,并把Boy交给他。那么我们就可以轻松的和Girl来Kiss了。看来几千年传统的父母之命还真是有用哦。至少Boy和Girl不用自己瞎忙乎了。这就是IOC,将对象的创建和获取提取到外部。由外部容器提供需要的组件。我们知道好莱坞原则:“Donotcallus,wewillcallyou.”意思就是,You,girlie,donotcalltheboy.Wewillfeedyouaboy。我们还应该知道依赖倒转原则即DependenceInversionPrincinple,DIP。EricGamma说,要面向抽象编程。面向接口编程是面向对象的核心。 IoC的Type指的是Girl得到Boy的几种不同方式。我们逐一来说明。 IOCtype0:不用IOC publicclassGirlimplementsServicable...{privateKissablekissable;publicGirl()...{kissable=newBoy();}publicvoidkissYourKissable()...{kissable.kiss();}} Girl自己建立自己的Boy,很难更换,很难共享给别人,只能单独使用,并负责完全的生命周期。IOCtype1,先看代码: publicclassGirlimplementsServicable...{Kissablekissable;publicvoidservice(ServiceManagermgr)...{kissable=(Kissable)mgr.lookup(“kissable”);}publicvoidkissYourKissable()...{kissable.kiss();}} 这种情况出现于AvalonFramework。一个组件实现了Servicable接口,就必须实现service方法,并传入一个ServiceManager。其中会含有需要的其它组件。只需要在service方法中初始化需要的Boy。另外,J2EE中从Context取得对象也属于type1,它依赖于配置文件:…IOCtype2:IOCtype3 publicclassGirl...{privateKissablekissable;publicvoidsetKissable(Kissablekissable)...{this.kissable=kissable;}publicvoidkissYourKissable()...{kissable.kiss();}} Type2出现于SpringFramework,是通过JavaBean的set方法来将需要的Boy传递给Girl。它必须依赖于配置文件。 publicclassGirl...{privateKissablekissable;publicGirl(Kissablekissable)...{this.kissable=kissable;}publicvoidkissYourKissable()...{kissable.kiss();}} 这就是PicoContainer的组件。通过构造函数传递Boy给Girl。 PicoContainercontainer=newDefaultPicoContainer();container.registerComponentImplementation(Boy.class);container.registerComponentImplementation(Girl.class);Girlgirl=(Girl)container.getComponentInstance(Girl.class);girl.kissYourKissable(); 介绍完IoC之后,我们来介绍另外一个重要的概念:AOP(AspectOrientedProgramming),也就是面向方面编程的技术。AOP基于IoC基础,是对OOP的有益补充。 AOP将应用系统分为两部分,核心业务逻辑(Corebusinessconcerns)及横向的通用逻辑,也就是所谓的方面Crosscuttingenterpriseconcerns,例如,所有大中型应用都要涉及到的持久化管理(Persistent)、事务管理(TransactionManagement)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。 AOP正在成为软件开发的下一个光环。使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect。AOP可以防止代码混乱。 Springframework是很有前途的AOP技术。作为一种非侵略性的、轻型的AOPframework,你无需使用预编译器或其他的元标签,便可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOPframework,其他人还是像往常一样编程。 让我们从定义一些重要的AOP概念开始。 —连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。 —通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。 —切入点(Pointcut):指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。 —引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。 —目标对象(TargetObject):包含连接点的对象,也被称作被通知或被代理对象。 —AOP代理(AOPProxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。 —编织(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯JavaAOP框架一样,在运行时完成织入。 各种通知类型包括: —Around通知:包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用前后完成自定义的行为,它们负责选择继续执行连接点或通过返回它们自己的返回值或抛出异常来短路执行。 —Before通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。 —Throws通知:在方法抛出异常时执行的通知。Spring提供强制类型的Throws通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从Throwable或Exception强制类型转换。 —Afterreturning通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。 Around通知是最通用的通知类型。大部分基于拦截的AOP框架(如Nanning和Jboss4)只提供Around通知。 如同AspectJ,Spring提供所有类型的通知,我们推荐你使用最为合适的通知类型来实现需要的行为。例如,如果只是需要用一个方法的返回值来更新缓存,你最好实现一个afterreturning通知,而不是around通知,虽然around通知也能完成同样的事情。使用最合适的通知类型使编程模型变得简单,并能减少潜在错误。例如,你不需要调用在around通知中所需使用的MethodInvocation的proceed()方法,因此就调用失败。 下面让我们实现一个SpringAOP的例子。在这个例子中,我们将实现一个beforeadvice,这意味着advice的代码在被调用的public方法开始前被执行。以下是这个beforeadvice的实现代码。 packagecom.ascenttech.springaop.test; importjava.lang.reflect.Method; importorg.springframework.aop.MethodBeforeAdvice; publicclassTestBeforeAdviceimplementsMethodBeforeAdvice{ publicvoidbefore(Methodm,Object[]args,Objecttarget) throwsThrowable{ System.out.println("Helloworld!(by" +this.getClass().getName() +")"); 接口MethodBeforeAdvice只有一个方法before需要实现,它定义了advice的实现。before方法共用3个参数,它们提供了相当丰富的信息。参数Methodm是advice开始后执行的方法,方法名称可以用作判断是否执行代码的条件。Object[]args是传给被调用的public方法的参数数组。当需要记日志时,参数args和被执行方法的名称都是非常有用的信息。你也可以改变传给m的参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突。Objecttarget是执行方法m对象的引用。 在下面的BeanImpl类中,每个public方法调用前,都会执行advice,代码如下。 publicclassBeanImplimplementsBean{ publicvoidtheMethod(){ System.out.println(this.getClass().getName() +"."+newException().getStackTrace()[0].getMethodName() +"()" +"saysHELLO!"); 类BeanImpl实现了下面的接口Bean,代码如下。 publicinterfaceBean{ publicvoidtheMethod(); 虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践,Spring也鼓励这样做。 pointcut和advice通过配置文件来实现,因此,接下来你只需编写主方法的Java代码,代码如下。 importorg.springframework.context.ApplicationContext; importorg.springframework.context.support.FileSystemXmlApplicationContext; publicclassMain{ //Readtheconfigurationfile ApplicationContextctx =newFileSystemXmlApplicationContext("springconfig.xml"); //Instantiateanobject Beanx=(Bean)ctx.getBean("bean"); //Executethepublicmethodofthebean(thetest) x.theMethod(); 我们从读入和处理配置文件开始,接下来马上要创建它。这个配置文件将作为粘合程序不同部分的“胶水”。读入和处理配置文件后,我们会得到一个创建工厂ctx,任何一个Spring管理的对象都必须通过这个工厂来创建。对象通过工厂创建后便可正常使用。 仅仅用配置文件便可把程序的每一部分组装起来,代码如下。 4个bean定义的次序并不重要。我们现在有了一个advice、一个包含了正则表达式pointcut的advisor、一个主程序类和一个配置好的接口,通过工厂ctx,这个接口返回自己本身实现的一个引用。 BeanImpl和TestBeforeAdvice都是直接配置。我们用一个惟一的ID创建一个bean元素,并指定了一个实现类,这就是全部的工作。 advisor通过Springframework提供的一个RegexMethodPointcutAdvisor类来实现。我们用advisor的第一个属性来指定它所需的advice-bean,第二个属性则用正则表达式定义了pointcut,确保良好的性能和易读性。 最后配置的是bean,它可以通过一个工厂来创建。bean的定义看起来比实际上要复杂。bean是ProxyFactoryBean的一个实现,它是Springframework的一部分。这个bean的行为通过以下的3个属性来定义。 —属性proxyInterface定义了接口类。 —属性target指向本地配置的一个bean,这个bean返回一个接口的实现。 —属性interceptorNames是惟一允许定义一个值列表的属性,这个列表包含所有需要在beanTarget上执行的advisor。注意,advisor列表的次序是非常重要的。 SpringMVCFramework有这样一些特点: 1.mybatis配置2.SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。3.mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。4.通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂5.由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。6.mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。7.MappedStatement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个MappedStatement对象,sql的id即是Mappedstatement的id。8.MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。9.MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程 大型网站,比如门户网站,在面对大量用户访问、高并发请求方面,基本的解决方案集中在这样几个环节:使用高性能的服务器、高性能的数据库、高效率的编程语言、还有高性能的Web容器。这几个解决思路在一定程度上意味着更大的投入。 其实大家都知道,效率最高、消耗最小的就是纯静态化的html页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法。但是对于大量内容并且频繁更新的网站,我们无法全部手动去挨个实现,于是出现了我们常见的信息发布系统CMS,像我们常访问的各个门户站点的新闻频道,甚至他们的其他频道,都是通过信息发布系统来管理和实现的,信息发布系统可以实现最简单的信息录入自动生成静态页面,还能具备频道管理、权限管理、自动抓取等功能,对于一个大型网站来说,拥有一套高效、可管理的CMS是必不可少的。 同时,html静态化也是某些缓存策略使用的手段,对于系统中频繁使用数据库查询但是内容更新很小的应用,可以考虑使用html静态化来实现。比如论坛中论坛的公用设置信息,这些信息目前的主流论坛都可以进行后台管理并且存储在数据库中,这些信息其实大量被前台程序调用,但是更新频率很小,可以考虑将这部分内容进行后台更新的时候进行静态化,这样避免了大量的数据库访问请求。 大家知道,对于Web服务器来说,不管是Apache、IIS还是其他容器,图片是最消耗资源的,于是我们有必要将图片与页面进行分离,这是基本上大型网站都会采用的策略,他们都有独立的、甚至很多台的图片服务器。这样的架构可以降低提供页面访问请求的服务器系统压力,并且可以保证系统不会因为图片问题而崩溃。 在应用服务器和图片服务器上,可以进行不同的配置优化,比如apache在配置ContentType的时候可以尽量少支持、尽可能少的LoadModule,保证更高的系统消耗和执行效率。 大型网站都有复杂的应用,这些应用必须使用数据库,那么在面对大量访问的时候,数据库的瓶颈很快就能显现出来,这时一台数据库将很快无法满足应用,于是我们需要使用数据库集群或者库表散列。 在数据库集群方面,很多数据库都有自己的解决方案,Oracle、Sybase等都有很好的方案,常用的MySQL提供的Master/Slave也是类似的方案,您使用了什么样的DB,就参考相应的解决方案来实施即可。 上面提到的数据库集群由于在架构、成本、扩张性方面都会受到所采用DB类型的限制,于是我们需要从应用程序的角度来考虑改善系统架构,库表散列是常用并且最有效的解决方案。 我们在应用程序中安装业务和应用或者功能模块将数据库进行分离,不同的模块对应不同的数据库或者表,再按照一定的策略对某个页面或者功能进行更小的数据库散列,比如用户表,按照用户ID进行表散列,这样就能够低成本的提升系统的性能并且有很好的扩展性。 sohu的论坛就是采用了这样的架构,将论坛的用户、设置、帖子等信息进行数据库分离,然后对帖子、用户按照板块和ID进行散列数据库和表,最终可以在配置文件中进行简单的配置便能让系统随时增加一台低成本的数据库进来补充系统性能。 缓存一词搞技术的都接触过,很多地方用到缓存。网站架构和网站开发中的缓存也是非常重要。这里先讲述最基本的两种缓存。高级和分布式的缓存在后面讲述。 架构方面的缓存,对Apache比较熟悉的人都能知道Apache提供了自己的缓存模块,也可以使用外加的Squid模块进行缓存,这两种方式均可以有效的提高Apache的访问响应能力。 网站程序开发方面的缓存,Linux上提供的MemoryCache是常用的缓存接口,可以在web开发中使用,比如用Java开发的时候就可以调用MemoryCache对一些数据进行缓存和通讯共享,一些大型社区使用了这样的架构。另外,在使用web语言开发的时候,各种语言基本都有自己的缓存模块和方法,PHP有Pear的Cache模块,Java就更多了,.net不是很熟悉,相信也肯定有。 镜像是大型网站常采用的提高性能和数据安全性的方式,镜像的技术可以解决不同网络接入商和地域带来的用户访问速度差异,比如ChinaNet和EduNet之间的差异就促使了很多网站在教育网内搭建镜像站点,数据进行定时更新或者实时更新。在镜像的细节技术方面,这里不阐述太深,有很多专业的现成的解决架构和产品可选。也有廉价的通过软件实现的思路,比如Linux上的rsync等工具。 负载均衡将是大型网站解决高负荷访问和大量并发请求采用的高端解决办法。负载均衡技术发展了多年,有很多专业的服务提供商和产品可以选择,我个人接触过一些解决方法,其中有两个架构可以给大家做参考。 (1)、硬件四层交换 第四层交换使用第三层和第四层信息包的报头信息,根据应用区间识别业务流,将整个区间段的业务流分配到合适的应用服务器进行处理。 第四层交换功能就像是虚IP,指向物理服务器。它传输的业务服从的协议多种多样,有HTTP、FTP、NFS、Telnet或其他协议。这些业务在物理服务器基础上,需要复杂的载量平衡算法。在IP世界,业务类型由终端TCP或UDP端口地址来决定,在第四层交换中的应用区间则由源端和终端IP地址、TCP和UDP端口共同决定。 在硬件四层交换产品领域,有一些知名的产品可以选择,比如Alteon、F5等,这些产品很昂贵,但是物有所值,能够提供非常优秀的性能和很灵活的管理能力。“Yahoo中国”当初接近2000台服务器,只使用了三、四台Alteon就搞定了。 (2)、软件四层交换 大家知道了硬件四层交换机的原理后,基于OSI模型来实现的软件四层交换也就应运而生,这样的解决方案实现的原理一致,不过性能稍差。但是满足一定量的压力还是游刃有余的,有人说软件实现方式其实更灵活,处理能力完全看你配置的熟悉能力。 软件四层交换我们可以使用Linux上常用的LVS来解决,LVS就是LinuxVirtualServer,他提供了基于心跳线heartbeat的实时灾难应对解决方案,提高系统的强壮性,同时可供了灵活的虚拟VIP配置和管理功能,可以同时满足多种应用需求,这对于分布式的系统来说必不可少。 一个典型的使用负载均衡的策略就是,在软件或者硬件四层交换的基础上搭建squid集群,这种思路在很多大型网站包括搜索引擎上被采用,这样的架构低成本、高性能还有很强的扩张性,随时往架构里面增减节点都非常容易。 对于大型网站来说,前面提到的每个方法可能都会被同时使用到,这里介绍得比较浅显,具体实现过程中很多细节还需要大家慢慢熟悉和体会。有时一个很小的squid参数或者apache参数设置,对于系统性能的影响就会很大。 什么是CDN? CDN的全称是内容分发网络。其目的是通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。 CDN有别于镜像,因为它比镜像更智能,或者可以做这样一个比喻:CDN=更智能的镜像+缓存+流量导流。因而,CDN可以明显提高Internet网络中信息流动的效率。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等问题,提高用户访问网站的响应速度。 CDN的类型特点 CDN的实现分为三类:镜像、高速缓存、专线。 镜像站点(MirrorSite),是最常见的,它让内容直接发布,适用于静态和准动态的数据同步。但是购买和维护新服务器的费用较高,还必须在各个地区设置镜像服务器,配备专业技术人员进行管理与维护。对于大型网站来说,更新所用的带宽成本也大大提高了。 CDN服务一般会在全国范围内的关键节点上放置缓存服务器。 专线,让用户直接访问数据源,可以实现数据的动态同步。 CDN的实例 举个例子来说,当某用户访问网站时,网站会利用全球负载均衡技术,将用户的访问指向到距离用户最近的正常工作的缓存服务器上,直接响应用户的请求。 通过用户定位算法和服务器健康检测算法综合后的数据,可以将用户的请求就近定向到分布在网络“边缘”的缓存服务器上,保证用户的访问能得到更及时可靠的响应。 由于大量的用户访问都由分布在网络边缘的CDN节点缓存服务器直接响应了,这就不仅提高了用户的访问质量,同时有效地降低了源服务器的负载压力。 附:某CDN服务商的服务说明 采用GCDN加速方式 采用了GCDN加速方式以后,系统会在浏览用户和您的服务器之间增加一台GCDN服务器。浏览用户访问您的服务器时,一般静态数据,如图片、多媒体资料等数据将直接从GCDN服务器读取,使得从主服务器上读取静态数据的交换量大大减少。 为VIP型虚拟主机而特加的VPN高速压缩通道,使用高速压缩的电信<==>网通、电信<==>国际(HK)、网通<==>国际(HK)等跨网专线通道,智能多线,自动获取最快路径,极速的动态实时并发响应速度,实现了网站的动态脚本实时同步,对动态网站有一个更加明显的加速效果。 每个网络运营商(电信、网通、铁通、教育网)均有您服务器的GCDN服务器,无论浏览用户是来自何处,GCDN都能让您的服务器展现最快的速度!另外,我们将对您的数据进行实时备份,让您的数据更安全 消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上,队列存储消息直到它们被应用程序读走。通过消息队列,应用程序可独立地执行--它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。 在分布式计算环境中,为了集成分布式应用,开发者需要对异构网络环境下的分布式应用提供有效的通信手段。为了管理需要共享的信息,对应用提供公共的信息交换机制是重要的。 消息队列为构造以同步或异步方式实现的分布式应用提供了松耦合方法。消息队列的API调用被嵌入到新的或现存的应用中,通过消息发送到内存或基于磁盘的队列或从它读出而提供信息交换。消息队列可用在应用中以执行多种功能,比如要求服务、交换信息或异步处理等。 中间件是一种独立的系统软件或服务程序,分布式应用系统借助这种软件在不同的技术之间共享资源,管理计算资源和网络通讯。它在计算机系统中是一个关键软件,它能实现应用的互连和互操作性,能保证系统的安全、可靠、高效的运行。中间件位于用户应用和操作系统及网络软件之间,它为应用提供了公用的通信手段,并且独立于网络和操作系统。中间件为开发者提供了公用于所有环境的应用程序接口,当应用程序中嵌入其函数调用,它便可利用其运行的特定操作系统和网络环境的功能,为应用执行通信功能。 如果没有消息中间件完成信息交换,应用开发者为了传输数据,必须要学会如何用网络和操作系统软件的功能,编写相应的应用程序来发送和接收信息,且交换信息没有标准方法,每个应用必须进行特定的编程从而和多平台、不同环境下的一个或多个应用通信。例如,为了实现网络上不同主机系统间的通信,将要求具备在网络上如何交换信息的知识(比如用TCP/IP的socket程序设计);为了实现同一主机内不同进程之间的通讯,将要求具备操作系统的消息队列或命名管道(Pipes)等知识。 MQ的通讯模式 1)点对点通讯:点对点方式是最为传统和常见的通讯方式,它支持一对一、一对多、多对多、多对一等多种配置方式,支持树状、网状等多种拓扑结构。 2)多点广播:MQ适用于不同类型的应用。其中重要的,也是正在发展中的是"多点广播"应用,即能够将消息发送到多个目标站点(DestinationList)。可以使用一条MQ指令将单一消息发送到多个目标站点,并确保为每一站点可靠地提供信息。MQ不仅提供了多点广播的功能,而且还拥有智能消息分发功能,在将一条消息发送到同一系统上的多个用户时,MQ将消息的一个复制版本和该系统上接收者的名单发送到目标MQ系统。目标MQ系统在本地复制这些消息,并将它们发送到名单上的队列,从而尽可能减少网络的传输量。 3)发布/订阅(Publish/Subscribe)模式:发布/订阅功能使消息的分发可以突破目的队列地理指向的限制,使消息按照特定的主题甚至内容进行分发,用户或应用程序可以根据主题或内容接收到所需要的消息。发布/订阅功能使得发送者和接收者之间的耦合关系变得更为松散,发送者不必关心接收者的目的地址,而接收者也不必关心消息的发送地址,而只是根据消息的主题进行消息的收发。在MQ家族产品中,MQEventBroker是专门用于使用发布/订阅技术进行数据通讯的产品,它支持基于队列和直接基于TCP/IP两种方式的发布和订阅。 4)群集(Cluster):为了简化点对点通讯模式中的系统配置,MQ提供Cluster(群集)的解决方案。群集类似于一个域(Domain),群集内部的队列管理器之间通讯时,不需要两两之间建立消息通道,而是采用群集(Cluster)通道与其它成员通讯,从而大大简化了系统配置。此外,群集中的队列管理器之间能够自动进行负载均衡,当某一队列管理器出现故障时,其它队列管理器可以接管它的工作,从而大大提高系统的高可靠性。 数据库的集群和扩展不像应用程序扩展那样容易,因为从数据库端来说,一旦涉及到了集群,往往会涉及到数据库层面的同步,因此从是否存在数据冗余这个角度来讲,我们可以从大面上把数据库集群分为以下两种形式: Share-Disk架构是通过多个服务器节点共享一个存储来实现数据库集群,两台机器最简单的Share-Disk架构如图1所示。 图1.简单的Share-Disk架构 在此基础之上,Share-Disk架构又分为单活和双活,双活即为集群中的每一个节点都可以同时对外提供服务,而单活为集群中只有一个节点可对外提供服务,集群中的其他服务器作为冗余在“活”的节点出现故障时接替该服务器成为对外提供服务的节点。该类架构最典型的产品就是SQLServerFailoverCluster(SQLServer故障转移集群)、NEC的EXPRESSCLUSTER、ROSE的ROSEHA。这种方式的弊端也是显而易见的,如下: 因此该类方案仅仅可以做到服务器层面的高可用,无法带来性能的提升,也无法解决存储单点故障的问题。因此如果不搭配其他高可用或负载均衡的技术,存在的意义并不是很大。 另一类技术是Share-Disk中的双活的技术,与单活技术不同的是,双活的技术虽然也是共享磁盘,但集群中的所有节点都可以对外提供服务,典型的产品就是Oracle的RAC。RAC的技术性非常的高,因此需要水平比较高的人来运维系统。RAC设计的初衷并不是为了性能,而是为了高可用和可扩展性,如果应用程序不是针对RAC架构设计和开发的,则将应用程序迁移到RAC上由于blockcontention(blockbusywaits)可能会导致性能的急剧下降,并且节点越多性能下降越明显。 Share-Nothing架构又分为两种,首先是分布式架构。将数据库中的数据按照某一标准分布到多台机器中,查询或插入时按照条件查询或插入对应的分区。 另一种是每一个节点完全独立,节点之间通过网络连接,通常是通过光钎等专用网络。如图2所示。 图2.Share-Nothing冗余架构 在Share-Nothing架构中,每一个节点都拥有自己的内存和存储,都保留数据的完整副本。通常来说,又可以分为两种,可以负载均衡和不可以负载均衡。 首先谈谈不可负载均衡的集群,在不可负载均衡的技术中,集群中的节点会被分为主节点和辅助节点,主节点向外提供服务,辅助节点作为热备(二阶段事务提交)或暖备(不需要保证事务同步),同时有可能使得辅助节点提供只读的服务。使用这个架构的技术包括:SQLServerAlwaysOn,SQLServerMirror,OracleDataGuard这种架构带来的好处包括: 当然,弊端也显而易见,因为辅助节点无法对外提供服务或只能提供只读服务,因此该类集群的弊端包括: 图3.Amoeba 图4.HAProxy 图5.Moebius集群 可负载均衡的Share-Nothing架构的好处是每台服务器都能提供服务,能充分利用现有资源,达到更高的吞吐量。其中Amoeba中可能会涉及到数据分片,数据分片的好处是对于海量数据的处理更加高效,但同时也引入了其他问题,比如说需要应用程序端对应数据分片进行调整、跨分片节点查询的处理问题、每一个数据分片节点是否能够承受各自业务负载的高峰问题等。该类架构需要实施的人员水平比较高,且需要应用层面做调整,因此更适合于互联网企业。 另一类不涉及到数据分片的架构,比如一类可以使用组合方案,比如说OracleRAC+F5。另一类是使用单个厂商提供的方案,比如说SQLServer上的Moebius。这类方案集群中的每个节点都会对外提供服务,因此有如下好处: 但相比较于MySQL的数据分片,该类方案的弊端也显而易见,因为每一个节点都需要完整的数据集,因此需要占用更多的存储空间。 本文从一个比较高的层面谈到了数据库集群技术。从数据库应用层面的Share-Disk集群直到集群的最高形式-能够提供负载均衡的集群,并列举了一些主流的商用产品。集群的存在意义是为了保证高可用、数据安全、扩展性以及负载均衡。如果现在的集群产品不能包含这几个特性,而业务场景也需要,也可以将和一些现有的技术结合来实现,但毕竟不是每一个人都是数据库专家,即使给你一堆工具和材料你也做不出来iPhone,因此在系统设计之初就对数据库方面的方案有所考虑会免去很多麻烦。 提高性能最好最快的办法当然是通过缓存来改善,对于任何一个web开发者都应该善用缓存。Asp.net下的缓存机制十分强大,用好缓存机制可以让我们极大的改善web应用的性能。 1.页面缓存 2.部分页面缓存 3.使用DataSource缓存4.Cache对象 1.能采用SQL或直接存储过程一次执行的尽量不要用代码多次执行 2、及时关闭数据库连接 1.对不经常更改并且数据量小的可采用xml或者配置文件设置 1.严格验证上传图片大小 2、严格控制上传Flash动画和视频大小 1.执行定时任务尽量避开访问高峰期 2、对应固定报表2可以采取预定格式,避开高峰自动提取 1.需要多少数据取多少数据 1.尽量采取弹框或切换选项方式展示数据,避免来回刷新列表重新获大量数据 1.查询数据尽量不要SELECT* 1、查询出的数据量过大(可以采用多次查询,其他的方法降低数据量),尽量采取分页查询数据 2、锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷) 3、返回了不必要的行和列 用OR的字句可以分解成多个查询,并且通过UNION链接多个查询。它们的速度只与是否使用索引有关,如果查询需要用到联合索引,用UNIONall执行的效率更高。 4、如果是使用like进行查询的话,简单的使用index是不行的,但是全文索引,耗空间。like'a%'使用索引like'%a'不使用索引用like'%a%'查询时,查询耗时和字段值总长度成正比,所以不能用CHAR类型,而是VARCHAR。对于字段的值很长的建全文索引。 5、尽量将数据的处理工作放在服务器上,减少网络的开销,如使用存储过程。存储过程是编译、优化过,并且被组织到一个执行规划里,且存储在数据库中的SQL语句(存储过程是数据库服务器端的一段程序),是控制流语言的集合,速度当然快。 6、将需要查询的结果预先计算好放在表中,查询的时候再Select。这在SQL7.0以前是最重要的手段。例如计算商品购买小计计算。 7、没有必要时不要用DISTINCT和ORDERBY,这些动作可以改在客户端执行。它们增加了额外的开销。这同UNION和UNIONALL一样的道理。 8、一次更新多条记录比分多次更新每次一条快,就是说批处理好 9、用临时表,尽量用结果集和Table类性的变量来代替它,Table类型的变量比临时表好 10、数据库设计:数据库内所有表结构均添加索引 调整原因: 近日数据库压力很大,经查有些大数据量表的查询速度很慢,导致数据库服务器CPU一直持续90%-100%,将这些表添加索引后,CPU很快变正常。 根据查询条件,建立索引,优化索引、优化访问方式,限制结果集的数据量。注意填充因子要适当(最好是使用默认值0)。索引应该尽量小,使用字节数小的列建索引好(参照索引的创建),不要对有限的几个值的字段建单一索引如性别字段 11、将大数据表做分库、分区处理: 具体操作如下: 1)、将大数据表与主数据库分离,单独新建一个数据库,然后将这些表做分区; 2)、将数据插入到消息队列内,后台利用windows计划任务执行(5分钟执行一次)C#控制台程序将消息队列内的数据批量(消息队列内有50000条记录,一次性插入到数据表内)插入到相应的数据表内; 例如:用户访问日志,每次用户访问一个页面的时候我们之前的操作是直接将数据插入数据库,这样做对数据库的访问及操作太大,严重影响其他数据插入、查询的效率,利用分库、分区、消息队列完成此操作的好处是用户访问页面的时候不直接对数据库操作,而是在消息队列内积累一定数量的数据后批量插入数据库,只执行一次数据库操作,而且因为数据库分离的原因,对其他的查询及插入不会有影响; 模块化结构化开发,实现多资源分站点,数据分库,为后期实现分布式部署做准备,主要分为以下几部分: web站点: 1.web前端站点 2.图片、文件资源站点 3.管理端站点4.数据接口站点 数据库: 1.业务数据库 2.访问信息数据库、日志 前期访问量和数据量较小可采取单台或小数目台数服务器部署,后期大数据量采取多web站点多数据服务器方式进行部署。 CDN的全称是ContentDeliveryNetwork,即内容分发网络。其目的是通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络"边缘",使用户可以就近取得所需的内容,解决Internet网络拥塞状况,提高用户访问网站的响应速度。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等原因,解决用户访问网站的响应速度慢的根本原因。 该项为收费项目。 从模块、表单、数据审核、功能按钮全面数据安全验证及管理。 数据接口访问进行IP校验 a、表单控件js前端校验,特殊字符过滤 b、采用Global.asax的Application_BeginRequest事件过滤敏感字符。 c、request请求过滤 a、前端js代码验证 b、后端程序代码验证 c、数据库约束 a、邮件动态验证码验证 b、短信动态验证码验证 让我们先来说说如何什么是系统性能。这个定义非常关键,如果我们不清楚什么是系统性能,那么我们将无法定位之。我见过很多朋友会觉得这很容易,但是仔细一问,其实他们并没有一个比较系统的方法,所以,在这里我想告诉大家如何系统地来定位性能。总体来说,系统性能就是两个事: 一般来说,一个系统的性能受到这两个条件的约束,缺一不可。比如,我的系统可以顶得住一百万的并发,但是系统的延迟是2分钟以上,那么,这个一百万的负载毫无意义。系统延迟很短,但是吞吐量很低,同样没有意义。所以,一个好的系统的性能测试必然受到这两个条件的同时作用。有经验的朋友一定知道,这两个东西的一些关系: 经过上述的说明,我们知道要测试系统的性能,需要我们收集系统的Throughput和Latency这两个值。 再多说一些, 有了上面的铺垫,我们就可以测试到到系统的性能了,再调优之前,我们先来说说如何找到性能的瓶颈。我见过很多朋友会觉得这很容易,但是仔细一问,其实他们并没有一个比较系统的方法。 1)先看CPU利用率,如果CPU利用率不高,但是系统的Throughput和Latency上不去了,这说明我们的程序并没有忙于计算,而是忙于别的一些事,比如IO。(另外,CPU的利用率还要看内核态的和用户态的,内核态的一上去了,整个系统的性能就下来了。而对于多核CPU来说,CPU0是相当关键的,如果CPU0的负载高,那么会影响其它核的性能,因为CPU各核间是需要有调度的,这靠CPU0完成) 2)然后,我们可以看一下IO大不大,IO和CPU一般是反着来的,CPU利用率高则IO不大,IO大则CPU就小。关于IO,我们要看三个事,一个是磁盘文件IO,一个是驱动程序的IO(如:网卡),一个是内存换页率。这三个事都会影响系统性能。 3)然后,查看一下网络带宽使用情况,在Linux下,你可以使用iftop,iptraf,ntop,tcpdump这些命令来查看。或是用Wireshark来查看。 4)如果CPU不高,IO不高,内存使用不高,网络带宽使用不高。但是系统的性能上不去。这说明你的程序有问题,比如,你的程序被阻塞了。可能是因为等那个锁,可能是因为等某个资源,或者是在切换上下文。 通过了解操作系统的性能,我们才知道性能的问题,比如:带宽不够,内存不够,TCP缓冲区不够,等等,很多时候,不需要调整程序的,只需要调整一下硬件或操作系统的配置就可以了。 使用Profiler有个问题我们需要注意一下,因为Profiler会让你的程序运行的性能变低,像PurifyPlus这样的工具会在你的代码中插入很多代码,会导致你的程序运行效率变低,从而没发测试出在高吞吐量下的系统的性能,对此,一般有两个方法来定位系统瓶颈: 1)在你的代码中自己做统计,使用微秒级的计时器和函数调用计算器,每隔10秒把统计log到文件中。 2)分段注释你的代码块,让一些函数空转,做HardCode的Mock,然后再测试一下系统的Throughput和Latency是否有质的变化,如果有,那么被注释的函数就是性能瓶颈,再在这个函数体内注释代码,直到找到最耗性能的语句。 最后再说一点,对于性能测试,不同的Throughput会出现不同的测试结果,不同的测试数据也会有不同的测试结果。所以,用于性能测试的数据非常重要,性能测试中,我们需要观测试不同Throughput的结果。 一般来说,性能优化也就是下面的几个策略: 总之,根据2:8原则来说,20%的代码耗了你80%的性能,找到那20%的代码,你就可以优化那80%的性能。下面的一些东西都是我的一些经验,我只例举了一些最有价值的性能调优的的方法,供你参考,也欢迎补充。 4.1)算法调优。算法非常重要,好的算法会有更好的性能。举几个我经历过的项目的例子,大家可以感觉一下。 4.2)代码调优。从我的经验上来说,代码上的调优有下面这几点: 4.3)网络调优 A)TCP调优 我们知道TCP链接是有很多开销的,一个是会占用文件描述符,另一个是会开缓存,一般来说一个系统可以支持的TCP链接数是有限的,我们需要清楚地认识到TCP链接对系统的开销是很大的。正是因为TCP是耗资源的,所以,很多攻击都是让你系统上出现大量的TCP链接,把你的系统资源耗尽。比如著名的SYNCFlood攻击。 net.ipv4.tcp_keepalive_probes=5 net.ipv4.tcp_keepalive_intvl=20 net.ipv4.tcp_fin_timeout=30 对于TCP的TIME_WAIT这个状态,主动关闭的一方进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(MaxSegmentLifetime),默认为4分钟,TIME_WAIT状态下的资源不能回收。有大量的TIME_WAIT链接的情况一般是在HTTP服务器上。对此,有两个参数需要注意, net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_tw_recycle=1 前者表示重用TIME_WAIT,后者表示回收TIME_WAIT的资源。 另外,我们想一想,如果网络质量非常好,基本不丢包,而业务上我们不怕偶尔丢几个包,如果是这样的话,那么,我们为什么不用速度更快的UDP呢?你想过这个问题了吗? B)UDP调优 说到UDP的调优,有一些事我想重点说一样,那就是MTU——最大传输单元(其实这对TCP也一样,因为这是链路层上的东西)。所谓最大传输单元,你可以想像成是公路上的公交车,假设一个公交车可以最多坐70人,带宽就像是公路的车道数一样,如果一条路上最多可以容下100辆公交车,那意味着我最多可以运送7000人,但是如果公交车坐不满,比如平均每辆车只有20人,那么我只运送了2000人,于是我公路资源(带宽资源)就被浪费了。所以,我们对于一个UDP的包,我们要尽量地让他大到MTU的最大尺寸再往网络上传,这样可以最大化带宽利用率。对于这个MTU,以太网是1500字节,光纤是4352字节,802.11无线网是7981。但是,当我们用TCP/UDP发包的时候,我们的有效负载Payload要低于这个值,因为IP协议会加上20个字节,UDP会加上8个字节(TCP加的更多),所以,一般来说,你的一个UDP包的最大应该是1500-8-20=1472,这是你的数据的大小。当然,如果你用光纤的话,这个值就可以更大一些。(顺便说一下,对于某些NB的千光以态网网卡来说,在网卡上,网卡硬件如果发现你的包的大小超过了MTU,其会帮你做fragment,到了目标端又会帮你做重组,这就不需要你在程序中处理了) 再多说一下,使用Socket编程的时候,你可以使用setsockopt()设置SO_SNDBUF/SO_RCVBUF的大小,TTL和KeepAlive这些关键的设置,当然,还有很多,具体你可以查看一下Socket的手册。 最后说一点,UDP还有一个最大的好处是multi-cast多播,这个技术对于你需要在内网里通知多台结点时非常方便和高效。而且,多播这种技术对于机会的水平扩展(需要增加机器来侦听多播信息)也很有利。 C)网卡调优 D)其它网络性能 关于多路复用技术,也就是用一个线程来管理所有的TCP链接,有三个系统调用要重点注意:一个是select,这个系统调用只支持上限1024个链接,第二个是poll,其可以突破1024的限制,但是select和poll本质上是使用的轮询机制,轮询机制在链接多的时候性能很差,因主是O(n)的算法,所以,epoll出现了,epoll是操作系统内核支持的,仅当在链接活跃时,操作系统才会callback,这是由操作系统通知触发的,但其只有LinuxKernel2.6以后才支持(准确说是2.5.44中引入的),当然,如果所有的链接都是活跃的,过多的使用epoll_ctl可能会比轮询的方式还影响性能,不过影响的不大。 4.4)系统调优 A)I/O模型 第一种,同步阻塞式I/O,这个不说了。 第二种,同步无阻塞方式。其通过fctnl设置O_NONBLOCK来完成。 第三种,对于select/poll/epoll这三个是I/O不阻塞,但是在事件上阻塞,算是:I/O异步,事件同步的调用。 第四种,AIO方式。这种I/O模型是一种处理与I/O并行的模型。I/O请求会立即返回,说明请求已经成功发起了。在后台完成I/O操作时,向应用程序发起通知,通知有两种方式:一种是产生一个信号,另一种是执行一个基于线程的回调函数来完成这次I/O处理过程。 第四种因为没有任何的阻塞,无论是I/O上,还是事件通知上,所以,其可以让你充分地利用CPU,比起第二种同步无阻塞好处就是,第二种要你一遍一遍地去轮询。Nginx之所所以高效,是其使用了epoll和AIO的方式来进行I/O的。 再说一下Windows下的I/O模型, a)一个是WriteFile系统调用,这个系统调用可以是同步阻塞的,也可以是同步无阻塞的,关于看文件是不是以Overlapped打开的。关于同步无阻塞,需要设置其最后一个参数Overlapped,微软叫OverlappedI/O,你需要WaitForSingleObject才能知道有没有写完成。这个系统调用的性能可想而知。 c)然后是IOCP–IOCompletionPort,IOCP会把I/O的结果放在一个队列中,但是,侦听这个队列的不是主线程,而是专门来干这个事的一个或多个线程去干(老的平台要你自己创建线程,新的平台是你可以创建一个线程池)。IOCP是一个线程池模型。这个和Linux下的AIO模型比较相似,但是实现方式和使用方式完全不一样。 当然,真正提高I/O性能方式是把和外设的I/O的次数降到最低,最好没有,所以,对于读来说,内存cache通常可以从质上提升性能,因为内存比外设快太多了。对于写来说,cache住要写的数据,少写几次,但是cache带来的问题就是实时性的问题,也就是latency会变大,我们需要在写的次数上和相应上做权衡。 B)多核CPU调优 关于CPU的多核技术,我们知道,CPU0是很关键的,如果0号CPU被用得过狠的话,别的CPU性能也会下降,因为CPU0是有调整功能的,所以,我们不能任由操作系统负载均衡,因为我们自己更了解自己的程序,所以,我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤在一起。 numactl--cpubind=0--membind=0,1myprogramarg1arg2 当然,上面这个命令并不好,因为内存跨越了两个node,这非常不好。最好的方式是只让程序访问和自己运行一样的node,如: $numactl--membind1--cpunodebind1--localallocmyapplication C)文件系统调优 关于文件系统,因为文件系统也是有cache的,所以,为了让文件系统有最大的性能。首要的事情就是分配足够大的内存,这个非常关键,在Linux下可以使用free命令来查看free/used/buffers/cached,理想来说,buffers和cached应该有40%左右。然后是一个快速的硬盘控制器,SCSI会好很多。最快的是IntelSSD固态硬盘,速度超快,但是写次数有限。 当然,对于这些来说,ext4的默认设置基本上是最佳优化了。 这里介绍一个Linux下的查看I/O的命令——iotop,可以让你看到各进程的磁盘读写的负载情况。 4.5)数据库调优 数据库调优并不是我的强项,我就仅用我非常有限的知识说上一些吧。注意,下面的这些东西并不一定正确,因为在不同的业务场景,不同的数据库设计下可能会得到完全相反的结论,所以,我仅在这里做一些一般性的说明,具体问题还要具体分析。 A)数据库引擎调优 我对数据库引擎不是熟,但是有几个事情我觉得是一定要去了解的。 B)SQL语句优化 还有一点很重要,数据库的各种操作需要大量的内存,所以服务器的内存要够,优其应对那些多表查询的SQL语句,那是相当的耗内存。 下面我根据我有限的数据库SQL的知识说几个会有性能问题的SQL: 还是那句话,具体要看什么样的数据,什么样的SQL语句,你才知道用哪种方法是最好的。 先写这么多吧,欢迎大家指正补充。 大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。 这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例: //查询缓存不开启 $r=mysql_query("SELECTusernameFROMuserWHEREsignup_date>=CURDATE()"); //开启查询缓存 $today=date("Y-m-d"); $r=mysql_query("SELECTusernameFROMuserWHEREsignup_date>='$today'"); 上面两条SQL语句的差别就是CURDATE(),MySQL的查询缓存对这个函数不起作用。所以,像NOW()和RAND()或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。 EXPLAIN的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的……等等,等等。 挑一个你的SELECT语句(推荐挑选那个最复杂的,有多表联接的),把关键字EXPLAIN加到前面。你可以使用phpmyadmin来做这个事。然后,你会看到一张表格。下面的这个示例中,我们忘记加上了group_id索引,并且有表联接: 当我们为group_id字段加上索引后: 我们可以看到,前一个结果显示搜索了7883行,而后一个只是搜索了两个表的9和16行。查看rows列可以让我们找到潜在的性能问题。 当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。 在这种情况下,加上LIMIT1可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。 下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select*,第二条是Select1) 9 10 11 //没有效率的: $r=mysql_query("SELECT*FROMuserWHEREcountry='China'"); if(mysql_num_rows($r)>0){ //... //有效率的: $r=mysql_query("SELECT1FROMuserWHEREcountry='China'LIMIT1"); 索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么,请为其建立索引吧。 从上图你可以看到那个搜索字串“last_nameLIKE‘a%'”,一个是建了索引,一个是没有索引,性能差了4倍左右。 如果你的应用程序有很多JOIN查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。 而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把DECIMAL字段和一个INT字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样) //在state中查找company $r=mysql_query("SELECTcompany_nameFROMusers LEFTJOINcompaniesON(users.state=companies.state) WHEREusers.id=$user_id"); //两个state字段应该是被建过索引的,而且应该是相当的类型,相同的字符集。 想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你确不了解这样做有多么可怕的性能问题。 下面的示例是随机挑一条记录 //千万不要这样做: $r=mysql_query("SELECTusernameFROMuserORDERBYRAND()LIMIT1"); //这要会更好: $r=mysql_query("SELECTcount(*)FROMuser"); $d=mysql_fetch_row($r); $rand=mt_rand(0,$d[0]-1); $r=mysql_query("SELECTusernameFROMuserLIMIT$rand,1"); 从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。 所以,你应该养成一个需要什么就取什么的好的习惯。 //不推荐 $r=mysql_query("SELECT*FROMuserWHEREuser_id=1"); $d=mysql_fetch_assoc($r); echo"Welcome{$d['username']}"; //推荐 $r=mysql_query("SELECTusernameFROMuserWHEREuser_id=1"); 我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。 就算是你users表有一个主键叫“email”的字段,你也别让它成为主键。使用VARCHAR类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。 而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区…… 在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课程ID叫“外键”其共同组成主键。 如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用ENUM而不是VARCHAR。 例如,如果你创建了一个INT字段作为你的主键,然而并没有太多的数据,那么,PROCEDUREANALYSE()会建议你把这个字段的类型改成MEDIUMINT。或是你使用了一个VARCHAR字段,因为数据不多,你可能会得到一个让你把它改成ENUM的建议。这些建议,都是可能因为数据不够多,所以决策做得就不够准。 在phpmyadmin里,你可以在查看表时,点击“Proposetablestructure”来查看这些建议 一定要注意,这些只是建议,只有当你的表里的数据越来越多时,这些建议才会变得准确。一定要记住,你才是最终做决定的人。 除非你有一个很特别的原因去使用NULL值,你应该总是让你的字段保持NOTNULL。这看起来好像有点争议,请往下看。 首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在Oracle里,NULL和Empty的字符串是一样的!) 不要以为NULL不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。 下面摘自MySQL自己的文档: “NULLcolumnsrequireadditionalspaceintherowtorecordwhethertheirvaluesareNULL.ForMyISAMtables,eachNULLcolumntakesonebitextra,roundeduptothenearestbyte.” PreparedStatements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用preparedstatements获得很多好处,无论是性能问题还是安全问题。 PreparedStatements可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击。当然,你也可以手动地检查你的这些变量,然而,手动的检查容易出问题,而且很经常会被程序员忘了。当我们使用一些framework或是ORM的时候,这样的问题会好一些。 在性能方面,当一个相同的查询被使用多次的时候,这会为你带来可观的性能优势。你可以给这些PreparedStatements定义一些参数,而MySQL只会解析一次。 虽然最新版本的MySQL在传输PreparedStatements是使用二进制形势,所以这会使得网络传输非常有效率。 当然,也有一些情况下,我们需要避免使用PreparedStatements,因为其不支持查询缓存。但据说版本5.1后支持了。 12 13 14 15 16 17 18 19 //创建preparedstatement if($stmt=$mysqli->prepare("SELECTusernameFROMuserWHEREstate=")){ //绑定参数 $stmt->bind_param("s",$state); //执行 $stmt->execute(); //绑定结果 $stmt->bind_result($username); //移动游标 $stmt->fetch(); printf("%sisfrom%s\n",$username,$state); $stmt->close(); 正常的情况下,当你在当你在你的脚本中执行一个SQL语句的时候,你的程序会停在那里直到没这个SQL语句返回,然后你的程序再往下继续执行。你可以使用无缓冲查询来改变这个行为。 “mysql_unbuffered_query()sendstheSQLqueryquerytoMySQLwithoutautomaticallyfetchingandbufferingtheresultrowsasmysql_query()does.ThissavesaconsiderableamountofmemorywithSQLqueriesthatproducelargeresultsets,andyoucanstartworkingontheresultsetimmediatelyafterthefirstrowhasbeenretrievedasyoudon’thavetowaituntilthecompleteSQLqueryhasbeenperformed.” 上面那句话翻译过来是说,mysql_unbuffered_query()发送一个SQL语句到MySQL而并不像mysql_query()一样去自动fethch和缓存结果。这会相当节约很多可观的内存,尤其是那些会产生大量结果的查询语句,并且,你不需要等到所有的结果都返回,只需要第一行数据返回的时候,你就可以开始马上开始工作于查询结果了。 很多程序员都会创建一个VARCHAR(15)字段来存放字符串形式的IP而不是整形的IP。如果你用整形来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IPbetweenip1andip2。 我们必需要使用UNSIGNEDINT,因为IP地址会使用整个32位的无符号整形。 $r="UPDATEusersSETip=INET_ATON('{$_SERVER['REMOTE_ADDR']}')WHEREuser_id=$user_id"; 固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。 并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。 使用“垂直分割”技术(见下一条),你可以分割你的表成为两个一个是定长的,一个则是不定长的。 “垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。(以前,在银行做过项目,见过一张表有100多个字段,很恐怖) 示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。那么,为什么不把他放到另外一张表中呢?这样会让你的表有更好的性能,大家想想是不是,大量的时候,我对于用户表来说,只有用户ID,用户名,口令,用户角色等会被经常使用。小一点的表总是会有好的性能。 另外,你需要注意的是,这些被分出去的字段所形成的表,你不会经常性地去Join他们,不然的话,这样的性能会比不分割时还要差,而且,会是极数级的下降。 如果你需要在一个在线的网站上去执行一个大的DELETE或INSERT查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。 Apache会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。 所以,如果你有一个大的处理,你定你一定把其拆分,使用LIMIT条件是一个好的方法。下面是一个示例: while(1){ //每次只做1000条 mysql_query("DELETEFROMlogsWHERElog_date<='2009-11-01'LIMIT1000"); if(mysql_affected_rows()==0){ //没得可删了,退出! //每次都要休息一会儿 usleep(50000); 对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把你的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。 MyISAM适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM对于SELECTCOUNT(*)这类的计算是超快无比的。 InnoDB的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比MyISAM还慢。他是它支持“行锁”,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。 下面是MySQL的手册 使用ORM(ObjectRelationalMapper),你能够获得可靠的性能增涨。一个ORM可以做的所有事情,也能被手动的编写出来。但是,这需要一个高级专家。 ORM的最重要的是“LazyLoading”,也就是说,只有在需要的去取值的时候才会去真正的去做。但你也需要小心这种机制的副作用,因为这很有可能会因为要去创建很多很多小的查询反而会降低性能。 ORM还可以把你的SQL语句打包成一个事务,这会比单独执行他们快得多得多。 “永久链接”的目的是用来减少重新创建MySQL链接的次数。当一个链接被创建了,它会永远处在连接的状态,就算是数据库操作已经结束了。而且,自从我们的Apache开始重用它的子进程后——也就是说,下一次的HTTP请求会重用Apache的子进程,并重用相同的MySQL链接。 在理论上来说,这听起来非常的不错。但是从个人经验(也是大多数人的)上来说,这个功能制造出来的麻烦事更多。因为,你只有有限的链接数,内存问题,文件句柄数,等等。 而且,Apache运行在极端并行的环境中,会创建很多很多的了进程。这就是为什么这种“永久链接”的机制工作地不好的原因。在你决定要使用“永久链接”之前,你需要好好地考虑一下你的整个系统的架 122、说出Servlet的生命周期,并说出Servlet和CGI的区别【基础】 答:Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init方法进行Servlet的初始化,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。 123、Servlet的基本架构。【基础】 答:publicclassServletNameextendsHttpServlet{ publicvoiddoPost(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException,IOException{ publicvoiddoGet(HttpServletRequestrequest, 124、forward和redirect的区别【基础】 答:forward是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。redirect就是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,一般来说浏览器会用刚才请求的所有参数重新请求,所以session,request参数都可以获取,并且从浏览器的地址栏中可以看到跳转后的链接地址。前者更加高效,在前者可以满足需要时,尽量使用forward()方法,并且,这样也有助于隐藏实际的链接;在有些情况下,比如,需要跳转到一个其它服务器上的资源,则必须使用sendRedirect()方法。 125、JSP中动态INCLUDE与静态INCLUDE的区别?【基础】 答:动态INCLUDE用jsp:include动作实现 126、说出数据连接池的工作机制是什么【基础】 答:J2EE服务器启动时会建立一定数量的池连接,并一直维持不少于此数目的池连接。客户端程序需要连接时,池驱动程序会返回一个未使用的池连接并将其表记为忙。如果当前没有空闲连接,池驱动程序就新建一定数量的连接,新建连接的数量有配置参数决定。当使用的池连接调用完成后,池驱动程序将此连接表记为空闲,其他调用就可以使用这个连接。 127、JSP的内置对象及方法?【基础】 128、JSP的常用指令?【基础】 答:<%@pagelanguage=”java”contenType=”text/html;charset=gb2312” session=”true”buffer=”64kb”autoFlush=”true”isThreadSafe=”true” info=”text”errorPage=”error.jsp”isErrorPage=”true”isELIgnored=” true”pageEncoding=”gb2312”import=”java.sql.*”%> isErrorPage:是否能使用Exception对象;isELIgnored:是否忽略EL表达式; <%@includefile=”filename”%> 129、jsp有哪些动作作用分别是什么【基础】 答:JSP共有以下6种基本动作: jsp:include:在页面被请求的时候引入一个文件; jsp:useBean:寻找或者实例化一个JavaBean。; jsp:setProperty:设置JavaBean的属性。; jsp:getProperty:输出某个JavaBean的属性; jsp:forward:把请求转到一个新的页面; jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记。 130、jsp有哪些内置对象作用分别是什么【基础】 答:JSP共有以下9种基本内置组件(可与ASP的6种内部组件相对应): request:用户端请求,此请求会包含来自GET/POST请求的参数; response:网页传回用户端的回应; pageContext:网页的属性是在这里管理; session:与请求有关的会话期; application:servlet正在执行的内容; out:用来传送回应的输出; config:servlet的构架部件; page:JSP网页本身; exception:针对错误网页,未捕捉的例外。 131、get和post的区别?【基础】 答:Form中的get和post方法,在数据传输过程中分别对应了HTTP协议中的GET和POST方法。二者主要区别如下: 1)Get是用来从服务器上获得数据,而Post是用来向服务器上传递数据; 2)Get将表单中数据按照variable=value的形式,添加到action所指向的URL后面,并且两者使用“”连接,而各个变量之间使用“&”连接;Post是将表单中的数据放在form的数据体中,按照变量和值相对应的方式,传递到action所指向URL; 3)Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的; 4)Get传输的数据量小,这主要是因为受URL长度限制;而Post可以传输大量的数据,所以在上传文件只能使用Post; 5)Get限制Form表单的数据集必须为ASCII字符,而Post支持整个ISO10646字符集; 6)Get是Form的默认方法。 132、什么情况下调用doGet()和doPost()?【基础】 答:Jsp页面中的form标签里的method属性为get时调用doGet(),为post时调用doPost()。 133、如何从form表单中得取checkbox的值;【基础】 答:可在页面把checkbox的name属性取同一个,value属性取每个条目的id,后台用getParamter(“name”)能取到checkbox的一组值。 134、页面中有一个命名为bankNo的下拉列表,写脚本获取当前选项的索引值。【基础】 答:用java或javaScript的处理方式分别如下: Java:request.getParameter(“bankNo”); javaScript: varselectItems=document.getElementsByName(“bankNo”); selectItems[0].value; 135、javascript常用的方面;【基础】 答:常用于数据输入校验和页面特殊效果等。 136、常用的web容器和开发工具;【基础】 答:最常用的容器包括:tomcat、weblogic;开发工具有:eclipse,jbuilder。 137、请画出Servlet2.2以上WebApplication的基本目录结构(2分钟)【基础】 答:目录结构如下图所示: webapps | Applocation __________________ || JSP页面WEB-INF ___________________ ||| classeslibweb.xml 138、JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么?【基础】 答:JSP是Servlet技术的扩展,本质上是Servlet的简易方式,更强调应用的外表表达。JSP编译后是"类servlet"。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑。 139、jsp的四种范围?【基础】 140、Request对象的主要方法【基础】 答:setAttribute(Stringname,Object):设置名字为name的属性值 getAttribute(Stringname):返回由name指定的属性值 getAttributeNames():返回request对象所有属性的名字集合(枚举) getCookies():返回客户端的所有Cookie对象,结果是一个Cookie数组 getCharacterEncoding():返回请求中的字符编码方式 getContentLength():返回请求的Body的长度 getHeader(Stringname):获得HTTP协议定义的文件头信息 getHeaders(Stringname):返回指定名的requestHeader的所有值(枚举) getHeaderNames():返回所有requestHeader的名字(枚举) getInputStream():返回请求的输入流,用于获得请求中的数据 getMethod():获得客户端向服务器端传送数据的方法 getParameter(Stringname):获得客户端请求中传送的name指定的参数值 getParameterNames():获得客户端传送给服务器端的所有参数的名字(枚 举) getParameterValues(Stringname):获得有name指定的参数的所有值 getProtocol():获取客户端向服务器端传送数据所依据的协议名称 getQueryString():获得查询字符串 getRequestURI():获取发出请求字符串的客户端地址 getRemoteAddr():获取客户端的IP地址 getRemoteHost():获取客户端的名字 getServerName():获取服务器的名字 getServletPath():获取客户端所请求的脚本文件的路径 getServerPort():获取服务器的端口号 removeAttribute(Stringname):删除请求中的一个属性 141、如何实现servlet的单线程模式?【基础】 答:<%@pageisThreadSafe=”false”%> 142、页面间对象传递的方法。【基础】 答:request,session,application,cookie等。 143、详细描述MVC。【基础】 答:基于Java的Web应用系统采用MVC架构模式,即model(模型)、view(视图)、control(控制)分离设计;这是目前WEB应用服务系统的主流设计方向。 Model:即处理业务逻辑的模块,每一种处理一个模块; View:负责页面显示,显示MODEL处理结果给用户,主要实现数据到页面转换过程; Control:负责每个请求的分发,把FORM数据传递给MODEL处理,把处理结果的数据传递给VIEW显示。 144、MVC的各个部分都有那些技术来实现如何实现【基础】 答:MVC是Model-View-Controller的简写。"Model"代表的是应用的业务逻辑(通过JavaBean,EJB组件实现),"View"是应用的表示面(由JSP页面产生),"Controller"是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。 145、应用服务器有那些?【基础】 答:BEAWebLogicServer,IBMWebSphereApplicationServer,Oracle9iApplicationServer,JBoss,Tomcat。 146、Servlet执行时一般实现哪几个方法?【基础】 答:publicvoidinit(ServletConfigconfig) publicServletConfiggetServletConfig() publicStringgetServletInfo() publicvoidservice(ServletRequestrequest,ServletResponse response) publicvoiddestroy() 147、struts的入口类?【基础】 答:是ActionServlet,所有的struts请求都经由该类转发处理。 148、STRUTS的应用(如STRUTS架构)?【基础】 答:Struts是采用JavaServlet/JavaServerPages技术开发Web应用程序的开放源码的framework。采用Struts能开发出基于MVC(Model-View-Controller)设计模式的应用构架。Struts有如下的主要功能: 1)包含一个controllerservlet,能将用户的请求发送到相应的Action对象; 2)JSP自由tag库,并且在controllerservlet中提供关联支持,帮助开发人员创建交互式表单应用; 3)提供了一系列实用对象:XML处理、通过JavareflectionAPIs自动处理JavaBeans属性、国际化的提示和消息。 149、几种会话跟踪技术?【基础】 答:cookie、URL重写、设置表单隐藏域。 150、BS与CS的联系与区别?【基础】 答:C/S是Client/Server的缩写,是客户机与服务器结构的应用程序,服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sybase、Informix或SQLServer。客户端需要安装专用的客户端软件。B/S是Brower/Server的缩写,是浏览器和服务器结构的应用程序,即Web应用程序,客户机上只要安装一个浏览器(Browser),如NetscapeNavigator或InternetExplorer,服务器安装Oracle、Sybase、Informix或SQLServer等数据库。在这种结构下,用户界面完全通过WWW浏览器实现,一部分事务逻辑在前端实现,但是主要事务逻辑在服务器端实现。浏览器通过WebServer同数据库进行数据交互。 C/S与B/S区别: 1)硬件环境不同: 2)对安全要求不同: C/S一般面向相对固定的用户群,对信息安全的控制能力很强.一般高度机密的信息系统采用C/S结构适宜.可以通过B/S发布部分可公开信息;B/S建立在广域网之上,对安全的控制能力相对弱,可能面向不可知的用户; 3)对程序架构不同: C/S程序可以更加注重流程,可以对权限多层次校验,对系统运行速度可以较少考虑;B/S对安全以及访问速度的多重的考虑,建立在需要更加优化的基础之上.比C/S有更高的要求B/S结构的程序架构是发展的趋势,从MS的.Net系列的BizTalk2000Exchange2000等,全面支持网络的构件搭建的系统.SUN和IBM推的JavaBean构件技术等,使B/S更加成熟; 4)软件重用不同: C/S程序可以不可避免的整体性考虑,构件的重用性不如在B/S要求下的构件的重用性好;B/S对的多重结构,要求构件相对独立的功能.能够相对较好的重用.就入买来的餐桌可以再利用,而不是做在墙上的石头桌子; 5)系统维护不同: C/S程序由于整体性,必须整体考察,处理出现的问题以及系统升级.升级难.可能是再做一个全新的系统;B/S构件组成,方面构件个别的更换,实现系统的无缝升级.系统维护开销减到最小.用户从网上自己下载安装就可以实现升级; 6)处理问题不同: 7)用户接口不同: C/S多是建立的Window平台上,表现方法有限,对程序员普遍要求较高;B/S建立在浏览器上,有更加丰富和生动的表现方式与用户交流.并且大部分难度减低,减低开发成本; 8)信息流不同: C/S程序一般是典型的中央集权的机械式处理,交互性相对低;B/S信息流向可变化,B-BB-CB-G等信息、流向的变化,更像交易中心。 151、过滤器有哪些作用?【基础】 152、过滤器的用法?(对客户端的请求统一编码和对客户端进行认证)【基础】 答:首先要实现(implements)Filter接口,同时覆盖Filter接口的三个方法: init(FilterConfigconfig)//用于获得FilterConfig对象; doFilter(ServletRequestrequest,ServletResponseresponse, FilterChainchain)//进行过滤处理一些业务; destroy()//销毁Filter。 153、简述HttpSession的作用、使用方法,可用代码说明。(3分钟)【基础】 答:HttpSession中可以跟踪并储存用户信息,把值设置到属性中,有2个方法:setAttribute(),getAttrribute(); 例如:在一个方法中用session.setAttribute(“student”,student);在session中设置一个属性名为student,值为一个名为student的对象。而后可在同一session范围内用getAttribute(“student”)取出该属性,得到student对象。 154、介绍在JSP中如何使用JavaBeans?【基础】 答:在JSP中使用JavaBean常用的动作有: 1) 2) 3) 155、JSP和Servlet中的请求转发分别如何实现?【基础】 答:JSP中的请求转发可利用forward动作实现: getServletContext().getRequestDispatcher(path).forward(req,res)。 156、Web.Xml的作用?【基础】 答:用于配置web应用的信息;如listener、filter及servlet的配置信息等。 157、写出熟悉的JSTL标签。【基础】 答: 158、说出struts中的标签。【基础】 答: 159、JSP标签的作用?如何定义?【中等难度】 答:作用:分离jsp页面的内容和逻辑; 业务逻辑开发者可以创建自定义标签; 封装业务逻辑; 可重用并且易维护; 易于手工修改、易于工具维护; 提供简洁的语法; 定义: 写标签处理器; 写tld文件; 讲标签处理器和tld文件放到同一个包里面; 把jsp页面和标签库配置部署在一起。 160、写一个自定义标签;【中等难度】 importjavax.servlet.jsp.tagext.*; importjavax.servlet.jsp.*; publicclassTimeTagextendsSimpleTagSupport{ privatebooleanisServer=true; publicvoidsetServer(booleanisServer){ this.isServer=isServer; publicvoiddoTag()throwsJspException,IOException{ JspWriterout=getJspContext().getOut(); if(isServer){ out.println(newjava.util.Date()); out.println(" out.println("document.write(newDate());"); out.println(""); 161、javascript的优缺点和内置对象;【中等难度】 2)缺点:不适合开发大型应用程序; 3)Javascript有11种内置对象:Array、String、Date、Math、Boolean、Number、Function、Global、Error、RegExp、Object。 ");姓名 "+""+ ");学院 "+""+ 备注 "+""+