2、访问修饰符public,private,protected,以及不写(默认)时的区别?-1-
3、String是最基本的数据类型吗?-1-
4、floatf=3.4;是否正确?-1-
5、shorts1=1;s1=s1+1;有错吗shorts1=1;s1+=1;有错吗?-1-
6、Java有没有goto?-1-
7、int和Integer有什么区别?-2-
8、&和&&的区别?-4-
9、解释内存中的栈(stack)、堆(heap)和方法区(methodarea)的用法。-4-
10、Math.round(11.5)等于多少?Math.round(-11.5)等于多少?-4-
11、switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?-4-
12、用最有效率的方法计算2乘以8?-5-
13、数组有没有length()方法?String有没有length()方法?-6-
14、在Java中,如何跳出当前的多重嵌套循环?-6-
15、构造器(constructor)是否可被重写(override)?-6-
16、两个对象值相同(x.equals(y)==true),但却可有不同的hashcode,这句话对不对?-6-
17、是否可以继承String类?-6-
18、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?-6-
19、String和StringBuilder、StringBuffer的区别?-7-
20、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?-8-
21、描述一下JVM加载class文件的原理机制?-8-
22、char型变量中能不能存贮一个中文汉字,为什么?-8-
23、抽象类(abstractclass)和接口(interface)有什么异同?-9-
24、静态嵌套类(StaticNestedClass)和内部类(InnerClass)的不同?-9-
25、Java中会存在内存泄漏吗,请简单描述。-11-
26、抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?-12-
27、阐述静态变量和实例变量的区别。-13-
28、是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?-13-
29、如何实现对象克隆?-13-
30、GC是什么?为什么要有GC?-16-
31、Strings=newString("xyz");创建了几个字符串对象?-17-
32、接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concreteclass)?-17-
33、一个".java"源文件中是否可以包含多个类(不是内部类)?有什么限制?-17-
34、AnonymousInnerClass(匿名内部类)是否可以继承其它类?是否可以实现接口?-17-
35、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?-17-
36、Java中的final关键字有哪些用法?-17-
37、指出下面程序的运行结果。-17-
38、数据类型之间的转换:-18-
39、如何实现字符串的反转及替换?-18-
40、怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?-18-
42、打印昨天的当前时刻。-20-
43、比较一下Java和JavaSciprt。-20-
44、什么时候用断言(assert)?-21-
45、Error和Exception有什么区别?-21-
46、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后-22-
47、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?-23-
48、运行时异常与受检异常有何异同?-23-
50、阐述final、finally、finalize的区别。-23-
51、类ExampleA继承Exception,类ExampleB继承ExampleA。-24-
52、List、Set、Map是否继承自Collection接口?-25-
53、阐述ArrayList、Vector、LinkedList的存储性能和特性。-25-
54、Collection和Collections的区别?-25-
55、List、Map、Set三个接口存取元素时,各有什么特点?-25-
56、TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?-25-
57、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别-28-
58、线程的sleep()方法和yield()方法有什么区别?-28-
59、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?-29-
61、编写多线程程序有几种实现方式?-33-
62、synchronized关键字的用法?-34-
63、举例说明同步和异步。-34-
64、启动一个线程是调用run()还是start()方法?-35-
65、什么是线程池(threadpool)?-35-
66、线程的基本状态以及状态之间的关系?-35-
67、简述synchronized和java.util.concurrent.locks.Lock的异同?-36-
68、Java中如何实现序列化,有什么意义?-36-
69、Java中有几种类型的流?-36-
70、写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。-37-
71、如何用Java代码列出一个目录下所有的文件?-38-
72、用Java的套接字编程实现一个多线程的回显(echo)服务器。-40-
73、XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?-44-
74、你在项目中哪些地方用到了XML?-44-
75、阐述JDBC操作数据库的步骤。-45-
76、Statement和PreparedStatement有什么区别?哪个性能更好?-45-
77、使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?-46-
78、在进行数据库编程时,连接池有什么作用?-46-
79、什么是DAO模式?-46-
80、事务的ACID是指什么?-46-
81、JDBC中如何进行事务处理?-49-
82、JDBC能否处理Blob和Clob?-49-
83、简述正则表达式及其用途。-50-
84、Java中是如何支持正则表达式操作的?-51-
85、获得一个类的类对象有哪些方式?-51-
86、如何通过反射创建对象?-51-
87、如何通过反射获取和设置对象私有字段的值?-51-
88、如何通过反射调用对象的方法?-53-
89、简述一下面向对象的"六原则一法则"。-54-
90、简述一下你了解的设计模式。-55-
91、用Java写一个单例类。-56-
92、什么是UML?-56-
93、UML中有哪些常用的图?-56-
94、用Java写一个冒泡排序。-58-
95、用Java写一个折半查找。-59-
JavaWeb+WebService-61-
96、阐述Servlet和CGI的区别-61-
97、Servlet接口中有哪些方法?-61-
98、转发(forward)和重定向(redirect)的区别?-61-
99、JSP有哪些内置对象?作用分别是什么?-61-
100、get和post请求的区别?-65-
101、常用的Web服务器有哪些?-65-
102、JSP和Servlet是什么关系?-66-
103、讲解JSP中的四种作用域。-66-
104、如何实现JSP或Servlet的单线程模式?-66-
105、实现会话跟踪的技术有哪些?-67-
106、过滤器有哪些作用和用法?-67-
107、监听器有哪些作用和用法?-70-
108、web.xml文件中可以配置哪些内容?-72-
109、你的项目中使用过哪些JSTL标签?-74-
110、使用标签库有什么好处?如何自定义JSP标签?-74-
111、说一下表达式语言(EL)的隐式对象及其作用。-76-
112、表达式语言(EL)支持哪些运算符?-77-
113、JavaWeb开发的Model1和Model2分别指的是什么?-77-
114、Servlet3中的异步处理指的是什么?-77-
115、如何在基于Java的Web项目中实现文件上传和下载?-78-
116、服务器收到用户提交的表单数据,到底是调用Servlet的doGet()还是doPost()方法?-80-
117、JSP中的静态包含和动态包含有什么区别?-80-
118、Servlet中如何获取用户提交的查询参数或表单数据?-80-
119、Servlet中如何获取用户配置的初始化参数以及服务器上下文参数?-80-
120、如何设置请求的编码以及响应内容的类型?-80-
121、解释一下网络应用的模式及其特点。-81-
122、什么是WebService(Web服务)?-81-
123、概念解释:SOAP、WSDL、UDDI。-81-
125、介绍一下你了解的Java领域的WebService框架。-82-
126、什么是ORM?-82-
127、持久层设计要考虑的问题有哪些?你用过的持久层框架有哪些?-82-
128、Hibernate中SessionFactory是线程安全的吗?Session是线程安全的吗(两个线程能够共享同一个Session吗)?-83-
129、Hibernate中Session的load和get方法的区别是什么?-83-
131、阐述Session加载实体对象的过程。-84-
132、Query接口的list方法和iterate方法有什么区别?-84-
133、Hibernate如何实现分页查询?-84-
134、锁机制有什么用?简述Hibernate的悲观锁和乐观锁机制。-84-
135、阐述实体对象的三种状态以及转换关系。-85-
136、如何理解Hibernate的延迟加载机制?在实际应用中,延迟加载与Session关闭的矛盾是如何处理的?-85-
137、举一个多对多关联的例子,并说明如何实现多对多关联映射。-86-
138、谈一下你对继承映射的理解。-86-
139、简述Hibernate常见优化策略。-86-
140、谈一谈Hibernate的一级缓存、二级缓存和查询缓存。-86-
141、Hibernate中DetachedCriteria类是做什么的?-86-
142、@OneToMany注解的mappedBy属性有什么作用?-87-
143、MyBatis中使用#和$书写占位符有什么区别?-87-
144、解释一下MyBatis中命名空间(namespace)的作用。-87-
145、MyBatis中的动态SQL是什么意思?-87-
146、什么是IoC和DI?DI是如何实现的?-88-
147、Spring中Bean的作用域有哪些?-89-
148、解释一下什么叫AOP(面向切面编程)?-90-
150、你如何理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念?-90-
151、Spring中自动装配的方式有哪些?-93-
153、Spring支持的事务管理类型有哪些?你在项目中使用哪种方式?-94-
154、如何在Web项目中配置Spring的IoC容器?-97-
155、如何在Web项目中配置SpringMVC?-97-
156、SpringMVC的工作原理是怎样的?-98-
157、如何在SpringIoC容器中配置数据源?-98-
158、如何配置配置事务增强?-99-
159、选择使用Spring框架的原因(Spring框架为企业级开发带来的好处有哪些)?-100-
160、SpringIoC容器配置Bean的方式?-101-
161、阐述Spring框架中Bean的生命周期?-103-
162、依赖注入时如何注入集合属性?-103-
163、Spring中的自动装配有哪些限制?-103-
164、在Web项目中如何获得Spring的IoC容器?-103-
165.大型网站在架构上应当考虑哪些问题?-103-
166、你用过的网站前端优化的技术有哪些?-104-
167、你使用过的应用服务器优化技术有哪些?-104-
168、什么是XSS攻击?什么是SQL注入攻击?什么是CSRF攻击?-105-
169.什么是领域模型(domainmodel)?贫血模型(anaemicdomainmodel)和充血模型(richdomainmodel)有什么区别?-106-
170.谈一谈测试驱动开发(TDD)的好处以及你的理解。-107-
1、面向对象的特征有哪些方面?
答:面向对象的特征主要有以下几个方面:
-继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
-封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。
-多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来说都是透明的(就像电动剃须刀是A系统,它的供电系统是B系统,B系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A系统只会通过B类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1).方法重写(子类继承父类并重写父类中已有的或抽象的方法);2).对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
2、访问修饰符public,private,protected,以及不写(默认)时的区别?
答:
类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。
3、String是最基本的数据类型吗?
答:不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitivetype),剩下的都是引用类型(referencetype),Java5以后引入的枚举类型也算是一种比较特殊的引用类型。
4、floatf=3.4;是否正确?
答:不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换floatf=(float)3.4;或者写成floatf=3.4F;。
5、shorts1=1;s1=s1+1;有错吗shorts1=1;s1+=1;有错吗?
答:对于shorts1=1;s1=s1+1;由于1是int类型,因此s1+1运算结果也是int型,需要强制转换类型才能赋值给short型。而shorts1=1;s1+=1;可以正确编译,因为s1+=1;相当于s1=(short)(s1+1);其中有隐含的强制类型转换。
6、Java有没有goto?
答:goto是Java中的保留字,在目前版本的Java中没有使用。(根据JamesGosling(Java之父)编写的《TheJavaProgrammingLanguage》一书的附录中给出了一个Java关键字列表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意义,因为熟悉C语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合都被视为保留字)
7、int和Integer有什么区别?
答:Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapperclass),int的包装类就是Integer,从Java5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java为每个原始类型提供了包装类型:
-原始类型:boolean,char,byte,short,int,long,float,double
-包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
classAutoUnboxingTest{
publicstaticvoidmain(String[]args){
Integera=newInteger(3);
Integerb=3;//将3自动装箱成Integer类型
intc=3;
System.out.println(a==b);//false两个引用没有引用同一对象
System.out.println(a==c);//truea自动拆箱成int类型再和c比较
}
最近还遇到一个面试题,也是和自动装箱和拆箱有点关系的,代码如下所示:
publicclassTest03{
Integerf1=100,f2=100,f3=150,f4=150;
System.out.println(f1==f2);
System.out.println(f3==f4);
如果不明就里很容易认为两个输出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四个变量都是Integer对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf,如果看看valueOf的源代码就知道发生了什么。
publicstaticIntegervalueOf(inti){
if(i>=IntegerCache.low&&i<=IntegerCache.high)
returnIntegerCache.cache[i+(-IntegerCache.low)];
returnnewInteger(i);
IntegerCache是Integer的内部类,其代码如下所示:
/**
*Cachetosupporttheobjectidentitysemanticsofautoboxingforvaluesbetween
*-128and127(inclusive)asrequiredbyJLS.
*
*Thecacheisinitializedonfirstusage.Thesizeofthecache
*maybecontrolledbythe{@code-XX:AutoBoxCacheMax=
*DuringVMinitialization,java.lang.Integer.IntegerCache.highproperty
*maybesetandsavedintheprivatesystempropertiesinthe
*sun.misc.VMclass.
*/
privatestaticclassIntegerCache{
staticfinalintlow=-128;
staticfinalinthigh;
staticfinalIntegercache[];
static{
//highvaluemaybeconfiguredbyproperty
inth=127;
StringintegerCacheHighPropValue=
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if(integerCacheHighPropValue!=null){
try{
inti=parseInt(integerCacheHighPropValue);
i=Math.max(i,127);
//MaximumarraysizeisInteger.MAX_VALUE
h=Math.min(i,Integer.MAX_VALUE-(-low)-1);
}catch(NumberFormatExceptionnfe){
//Ifthepropertycannotbeparsedintoanint,ignoreit.
high=h;
cache=newInteger[(high-low)+1];
intj=low;
for(intk=0;k cache[k]=newInteger(j++); //range[-128,127]mustbeinterned(JLS75.1.7) assertIntegerCache.high>=127; privateIntegerCache(){} 简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1==f2的结果是true,而f3==f4的结果是false。 提醒:越是貌似简单的面试题其中的玄机就越多,需要面试者有相当深厚的功力。 8、&和&&的区别? 补充:如果你熟悉JavaScript,那你可能更能感受到短路运算的强大,想成为JavaScript的高手就先从玩转短路运算开始吧。 9、解释内存中的栈(stack)、堆(heap)和方法区(methodarea)的用法。 答:通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为FromSurvivor和ToSurvivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、"hello"和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。 Stringstr=newString("hello"); 上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。 补充1:较新版本的Java(从Java6的某个更新开始)中,由于JIT编译器的发展和"逃逸分析"技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一定分配在堆上这件事情已经变得不那么绝对了。 补充2:运行时常量池相当于Class文件常量池具有动态性,java语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。 看看下面代码的执行结果是什么并且比较一下Java7以前和以后的运行结果是否一致。 Strings1=newStringBuilder("go").append("od").toString(); System.out.println(s1.intern()==s1); Strings2=newStringBuilder("ja").append("va").toString(); System.out.println(s2.intern()==s2); 10、Math.round(11.5)等于多少?Math.round(-11.5)等于多少? 答:Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行下取整。 11、switch是否能作用在byte上,是否能作用在long上,是否能作用在String上? 答:在Java5以前,switch(expr)中,expr只能是byte、short、char、int。从Java5开始,Java中引入了枚举类型,expr也可以是enum类型,从Java7开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。 12、用最有效率的方法计算2乘以8? 答:2<<3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。 补充:我们为编写的类重写hashCode方法时,可能会看到如下所示的代码,其实我们不太理解为什么要使用这样的乘法运算来产生哈希码(散列码),而且为什么这个数是个素数,为什么通常选择31这个数?前两个问题的答案你可以自己百度一下,选择31是因为可以用移位和减法运算来代替乘法,从而得到更好的性能。说到这里你可能已经想到了:31*num等价于(num<<5)-num,左移5位相当于乘以2的5次方再减去自身就相当于乘以31,现在的VM都能自动完成这个优化。 publicclassPhoneNumber{ privateintareaCode; privateStringprefix; privateStringlineNumber; @Override publicinthashCode(){ finalintprime=31; intresult=1; result=prime*result+areaCode; result=prime*result +((lineNumber==null)0:lineNumber.hashCode()); result=prime*result+((prefix==null)0:prefix.hashCode()); returnresult; publicbooleanequals(Objectobj){ if(this==obj) returntrue; if(obj==null) returnfalse; if(getClass()!=obj.getClass()) PhoneNumberother=(PhoneNumber)obj; if(areaCode!=other.areaCode) if(lineNumber==null){ if(other.lineNumber!=null) }elseif(!lineNumber.equals(other.lineNumber)) if(prefix==null){ if(other.prefix!=null) }elseif(!prefix.equals(other.prefix)) 13、数组有没有length()方法?String有没有length()方法? 答:数组没有length()方法,有length的属性。String有length()方法。JavaScript中,获得字符串的长度是通过length属性得到的,这一点容易和Java混淆。 14、在Java中,如何跳出当前的多重嵌套循环? 答:在最外层循环前加一个标记如A,然后用breakA;可以跳出多重循环。(Java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像要避免使用goto一样,应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法其实不知道更好) 15、构造器(constructor)是否可被重写(override)? 答:构造器不能被继承,因此不能被重写,但可以被重载。 16、两个对象值相同(x.equals(y)==true),但却可有不同的hashcode,这句话对不对? 答:不对,如果两个对象x和y满足x.equals(y)==true,它们的哈希码(hashcode)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。 补充:关于equals和hashCode方法,很多Java程序都知道,但很多人也就是仅仅知道而已,在JoshuaBloch的大作《EffectiveJava》(很多软件公司,《EffectiveJava》、《Java编程思想》以及《重构:改善既有代码质量》是Java程序员必看书籍,如果你还没看过,那就赶紧去亚马逊买一本吧)中是这样介绍equals方法的:首先equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true时,y.equals(x)也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false。实现高质量的equals方法的诀窍包括:1.使用==操作符检查"参数是否为这个对象的引用";2.使用instanceof操作符检查"参数是否为正确的类型";3.对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;4.编写完equals方法后,问自己它是否满足对称性、传递性、一致性;5.重写equals时总是要重写hashCode;6.不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。 17、是否可以继承String类? 答:String类是final类,不可以被继承。 补充:继承String本身就是一个错误的行为,对String类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。 18、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 答:是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。C++和C#中可以通过传引用或传输出参数来改变传入的参数的值。在C#中可以编写如下所示的代码,但是在Java中却做不到。 usingSystem; namespaceCS01{ classProgram{ publicstaticvoidswap(refintx,refinty){ inttemp=x; x=y; y=temp; publicstaticvoidMain(string[]args){ inta=5,b=10; swap(refa,refb); //a=10,b=5; Console.WriteLine("a={0},b={1}",a,b); 说明:Java中没有传引用实在是非常的不方便,这一点在Java8中仍然没有得到改进,正是如此在Java编写的代码中才会出现大量的Wrapper类(将需要通过方法调用修改的引用置于一个Wrapper类中,再将Wrapper对象传入方法),这样的做法只会让代码变得臃肿,尤其是让从C和C++转型为Java程序员的开发者无法容忍。 19、String和StringBuilder、StringBuffer的区别? 答:Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。 面试题1-什么情况下用+运算符进行字符串连接比调用StringBuffer/StringBuilder对象的append方法连接字符串性能更好? 面试题2-请说出下面程序的输出。 classStringEqualTest{ Strings1="Programming"; Strings2=newString("Programming"); Strings3="Program"; Strings4="ming"; Strings5="Program"+"ming"; Strings6=s3+s4; System.out.println(s1==s2);false System.out.println(s1==s5);true System.out.println(s1==s6);true System.out.println(s1==s6.intern());true System.out.println(s2==s2.intern()); 补充:解答上面的面试题需要清除两点:1.String对象的intern方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String对象的equals结果是true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;2.字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,这一点可以用javap-cStringEqualTest.class命令获得class文件对应的JVM字节码指令就可以看出来。 20、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分? 面试题:华为的面试题中曾经问过这样一个问题-"为什么不能根据返回类型来区分重载",快说出你的答案吧! 21、描述一下JVM加载class文件的原理机制? 答:JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。 由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java2(JDK1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明: Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar); Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap; System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。 22、char型变量中能不能存贮一个中文汉字,为什么? 答:char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。 补充:使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于C程序员来说,要完成这样的编码转换恐怕要依赖于union(联合体/共用体)共享内存的特征来实现了。 23、抽象类(abstractclass)和接口(interface)有什么异同? 24、静态嵌套类(StaticNestedClass)和内部类(InnerClass)的不同? *扑克类(一副扑克) *@author骆昊 publicclassPoker{ privatestaticString[]suites={"黑桃","红桃","草花","方块"}; privatestaticint[]faces={1,2,3,4,5,6,7,8,9,10,11,12,13}; privateCard[]cards; *构造器 publicPoker(){ cards=newCard[52]; for(inti=0;i for(intj=0;j cards[i*13+j]=newCard(suites[i],faces[j]); *洗牌(随机乱序) publicvoidshuffle(){ for(inti=0,len=cards.length;i intindex=(int)(Math.random()*len); Cardtemp=cards[index]; cards[index]=cards[i]; cards[i]=temp; *发牌 *@paramindex发牌的位置 publicCarddeal(intindex){ returncards[index]; *卡片类(一张扑克) *[内部类] publicclassCard{ privateStringsuite;//花色 privateintface;//点数 publicCard(Stringsuite,intface){ this.suite=suite; this.face=face; publicStringtoString(){ StringfaceStr=""; switch(face){ case1:faceStr="A";break; case11:faceStr="J";break; case12:faceStr="Q";break; case13:faceStr="K";break; default:faceStr=String.valueOf(face); returnsuite+faceStr; 测试代码: classPokerTest{ Pokerpoker=newPoker(); poker.shuffle();//洗牌 Poker.Cardc1=poker.deal(0);//发第一张牌 //对于非静态内部类Card //只有通过其外部类Poker对象才能创建Card对象 Poker.Cardc2=poker.newCard("红心",1);//自己创建一张牌 System.out.println(c1);//洗牌后的第一张 System.out.println(c2);//打印:红心A 面试题-下面的代码哪些地方会产生编译错误? classOuter{ classInner{} publicstaticvoidfoo(){newInner();} publicvoidbar(){newInner();} newInner(); 注意:Java中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中foo和main方法都是静态方法,静态方法中没有this,也就是说没有所谓的外部类对象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样做: newOuter().newInner(); 25、Java中会存在内存泄漏吗,请简单描述。 答:理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。例如hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。下面例子中的代码也会导致内存泄露。 importjava.util.Arrays; importjava.util.EmptyStackException; publicclassMyStack privateT[]elements; privateintsize=0; privatestaticfinalintINIT_CAPACITY=16; publicMyStack(){ elements=(T[])newObject[INIT_CAPACITY]; publicvoidpush(Telem){ ensureCapacity(); elements[size++]=elem; publicTpop(){ if(size==0) thrownewEmptyStackException(); returnelements[--size]; privatevoidensureCapacity(){ if(elements.length==size){ elements=Arrays.copyOf(elements,2*size+1); 上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试。然而其中的pop方法却存在内存泄露的问题,当我们用pop方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用(obsoletereference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发DiskPaging(物理内存与硬盘的虚拟内存交换数据),甚至造成OutOfMemoryError。 26、抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰? 答:都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。 27、阐述静态变量和实例变量的区别。 答:静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。 补充:在Java开发中,上下文类和工具类中通常会有大量的静态成员。 28、是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用? 答:不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化。 29、如何实现对象克隆? 答:有两种方式: 1).实现Cloneable接口并重写Object类中的clone()方法; 2).实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。 importjava.io.ByteArrayInputStream; importjava.io.ByteArrayOutputStream; importjava.io.ObjectInputStream; importjava.io.ObjectOutputStream; importjava.io.Serializable; publicclassMyUtil{ privateMyUtil(){ thrownewAssertionError(); @SuppressWarnings("unchecked") publicstatic ByteArrayOutputStreambout=newByteArrayOutputStream(); ObjectOutputStreamoos=newObjectOutputStream(bout); oos.writeObject(obj); ByteArrayInputStreambin=newByteArrayInputStream(bout.toByteArray()); ObjectInputStreamois=newObjectInputStream(bin); return(T)ois.readObject(); //说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义 //这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放 下面是测试代码: *人类 classPersonimplementsSerializable{ privatestaticfinallongserialVersionUID=-9102017020286042305L; privateStringname;//姓名 privateintage;//年龄 privateCarcar;//座驾 publicPerson(Stringname,intage,Carcar){ this.name=name; this.age=age; this.car=car; publicStringgetName(){ returnname; publicvoidsetName(Stringname){ publicintgetAge(){ returnage; publicvoidsetAge(intage){ publicCargetCar(){ returncar; publicvoidsetCar(Carcar){ return"Person[name="+name+",age="+age+",car="+car+"]"; *小汽车类 classCarimplementsSerializable{ privatestaticfinallongserialVersionUID=-5713945027627603702L; privateStringbrand;//品牌 privateintmaxSpeed;//最高时速 publicCar(Stringbrand,intmaxSpeed){ this.brand=brand; this.maxSpeed=maxSpeed; publicStringgetBrand(){ returnbrand; publicvoidsetBrand(Stringbrand){ publicintgetMaxSpeed(){ returnmaxSpeed; publicvoidsetMaxSpeed(intmaxSpeed){ return"Car[brand="+brand+",maxSpeed="+maxSpeed+"]"; classCloneTest{ Personp1=newPerson("HaoLUO",33,newCar("Benz",300)); Personp2=MyUtil.clone(p1);//深度克隆 p2.getCar().setBrand("BYD"); //修改克隆的Person对象p2关联的汽车对象的品牌属性 //原来的Person对象p1关联的汽车不会受到任何影响 //因为在克隆Person对象时其关联的汽车对象也被克隆了 System.out.println(p1); }catch(Exceptione){ e.printStackTrace(); 注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。 30、GC是什么?为什么要有GC? 答:GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc()或Runtime.getRuntime().gc(),但JVM可以屏蔽掉显示的垃圾回收调用。 补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域: -伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。 -幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。 -终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。 -Xms/-Xmx—堆的初始大小/堆的最大大小 -Xmn—堆中年轻代的大小 -XX:-DisableExplicitGC—让System.gc()不产生任何作用 -XX:+PrintGCDetails—打印GC的细节 -XX:NewSize/XX:MaxNewSize—设置新生代大小/新生代最大大小 -XX:NewRatio—可以设置老生代和新生代的比例 -XX:PrintTenuringDistribution—设置每次新生代GC后输出幸存者乐园中对象年龄的分布 -XX:InitialTenuringThreshold/-XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值 -XX:TargetSurvivorRatio:设置幸存区的目标使用率 31、Strings=newString("xyz");创建了几个字符串对象? 答:两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。 32、接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concreteclass)? 答:接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。 33、一个".java"源文件中是否可以包含多个类(不是内部类)?有什么限制? 答:可以,但一个源文件中最多只能有一个公开类(publicclass)而且文件名必须和公开类的类名完全保持一致。 34、AnonymousInnerClass(匿名内部类)是否可以继承其它类?是否可以实现接口? 答:可以继承其他类或实现其他接口,在Swing编程和Android开发中常用此方式来实现事件监听和回调。 35、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制? 答:一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。 36、Java中的final关键字有哪些用法? 答:(1)修饰类:表示该类不能被继承;(2)修饰方法:表示方法不能被重写;(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。 37、指出下面程序的运行结果。 classA{ System.out.print("1"); publicA(){ System.out.print("2"); classBextendsA{ System.out.print("a"); publicB(){ System.out.print("b"); publicclassHello{ Aab=newB(); ab=newB(); 答:执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。 提示:如果不能给出此题的正确答案,说明之前第21题Java类加载机制还没有完全理解,赶紧再看看吧。 38、数据类型之间的转换: -如何将字符串转换为基本数据类型? -如何将基本数据类型转换为字符串? -调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型; -一种方法是将基本数据类型与空字符串("")连接(+)即可获得其所对应的字符串;另一种方法是调用String类中的valueOf()方法返回相应字符串 39、如何实现字符串的反转及替换? 答:方法很多,可以自己写实现也可以使用String或StringBuffer/StringBuilder中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示: publicstaticStringreverse(StringoriginStr){ if(originStr==null||originStr.length()<=1) returnoriginStr; returnreverse(originStr.substring(1))+originStr.charAt(0); 40、怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串? 答:代码如下所示: Strings1="你好"; Strings2=newString(s1.getBytes("GB2312"),"ISO-8859-1"); -如何取得年月日、小时分钟秒? -如何取得从1970年1月1日0时0分0秒到现在的毫秒数? -如何取得某月的最后一天? -如何格式化日期? 问题1:创建java.util.Calendar实例,调用其get()方法传入不同的参数即可获得参数所对应的值。Java8中可以使用java.time.LocalDateTimel来获取,代码如下所示。 publicclassDateTimeTest{ Calendarcal=Calendar.getInstance(); System.out.println(cal.get(Calendar.YEAR)); System.out.println(cal.get(Calendar.MONTH));//0-11 System.out.println(cal.get(Calendar.DATE)); System.out.println(cal.get(Calendar.HOUR_OF_DAY)); System.out.println(cal.get(Calendar.MINUTE)); System.out.println(cal.get(Calendar.SECOND)); //Java8 LocalDateTimedt=LocalDateTime.now(); System.out.println(dt.getYear()); System.out.println(dt.getMonthValue());//1-12 System.out.println(dt.getDayOfMonth()); System.out.println(dt.getHour()); System.out.println(dt.getMinute()); System.out.println(dt.getSecond()); 问题2:以下方法均可获得该毫秒数。 Calendar.getInstance().getTimeInMillis(); System.currentTimeMillis(); Clock.systemDefaultZone().millis();//Java8 问题3:代码如下所示。 Calendartime=Calendar.getInstance(); time.getActualMaximum(Calendar.DAY_OF_MONTH); importjava.text.SimpleDateFormat; importjava.time.LocalDate; importjava.time.format.DateTimeFormatter; importjava.util.Date; classDateFormatTest{ SimpleDateFormatoldFormatter=newSimpleDateFormat("yyyy/MM/dd"); Datedate1=newDate(); System.out.println(oldFormatter.format(date1)); DateTimeFormatternewFormatter=DateTimeFormatter.ofPattern("yyyy/MM/dd"); LocalDatedate2=LocalDate.now(); System.out.println(date2.format(newFormatter)); 42、打印昨天的当前时刻。 importjava.util.Calendar; classYesterdayCurrent{ cal.add(Calendar.DATE,-1); System.out.println(cal.getTime()); 在Java8中,可以用下面的代码实现相同的功能。 importjava.time.LocalDateTime; LocalDateTimetoday=LocalDateTime.now(); LocalDateTimeyesterday=today.minusDays(1); System.out.println(yesterday); 43、比较一下Java和JavaSciprt。 答:JavaScript与Java是两个公司开发的不同的两个产品。Java是原SunMicrosystems公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。JavaScript的前身是LiveScript;而Java的前身是Oak语言。 下面对两种语言间的异同作如下比较: -基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。 -解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率) -代码格式不一样。 补充:上面列出的四点是网上流传的所谓的标准答案。其实Java和JavaScript最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势是函数式语言和动态语言。在Java中类(class)是一等公民,而JavaScript中函数(function)是一等公民,因此JavaScript支持函数式编程,可以使用Lambda函数和闭包(closure),当然Java8也开始支持函数式编程,提供了对Lambda表达式以及函数式接口的支持。对于这类问题,在面试的时候最好还是用自己的语言回答会更加靠谱,不要背网上所谓的标准答案。 44、什么时候用断言(assert)? 答:断言在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为false,那么系统会报告一个AssertionError。断言的使用如下面的代码所示: assert(a>0);//throwsanAssertionErrorifa<=0 断言可以有两种形式: assertExpression1; assertExpression1:Expression2; Expression1应该总是产生一个布尔值。 Expression2可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。 要在运行时启用断言,可以在启动JVM时使用-enableassertions或者-ea标记。要在运行时选择禁用断言,可以在启动JVM时使用-da或者-disableassertions标记。要在系统类中启用或禁用断言,可使用-esa或-dsa标记。还可以在包的基础上启用或者禁用断言。 注意:断言不应该以任何方式改变程序的状态。简单的说,如果希望在不满足某些条件时阻止代码的执行,就可以考虑用断言来阻止它。 45、Error和Exception有什么区别? 答:Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。 面试题:2005年摩托罗拉的面试中曾经问过这么一个问题“Ifaprocessreportsastackoverflowrun-timeerror,what’sthemostpossiblecause”,给了四个选项a.lackofmemory; b.writeonaninvalidmemoryspace; c.recursivefunctioncalling; d.arrayindexoutofboundary. Java程序在运行时也可能会遭遇StackOverflowError,这是一个无法恢复的错误,只能重新修改代码了,这个面试题的答案是c。如果写了不能迅速收敛的递归,则很有可能引发栈溢出的错误,如下所示: classStackOverflowErrorTest{ main(null); 提示:用递归编写程序时一定要牢记两点:1.递归公式;2.收敛条件(什么时候就不再继续递归)。 46、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后 答:会执行,在方法返回调用者前执行。 注意:在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。显然,在finally中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java中也可以通过提升编译器的语法检查级别来产生警告或错误,Eclipse中可以在如图所示的地方进行设置,强烈建议将此项设置为编译错误。 47、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用? 48、运行时异常与受检异常有何异同? -不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常) -对可以恢复的情况使用受检异常,对编程错误使用运行时异常 -避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生) -优先使用标准的异常 -每个方法抛出的异常都要有文档 -保持异常的原子性 -不要在catch中忽略掉捕获到的异常 49、列出一些你常见的运行时异常? -ArithmeticException(算术异常) -ClassCastException(类转换异常) -IllegalArgumentException(非法参数异常) -IndexOutOfBoundsException(下标越界异常) -NullPointerException(空指针异常) -SecurityException(安全异常) 50、阐述final、finally、finalize的区别。 -finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。 -finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。 51、类ExampleA继承Exception,类ExampleB继承ExampleA。 有如下代码片断: thrownewExampleB("b") }catch(ExampleAe){ System.out.println("ExampleA"); }catch(Exceptione){ System.out.println("Exception"); 请问执行此段代码的输出是什么? 答:输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取ExampleA类型异常的catch块能够抓住try块中抛出的ExampleB类型的异常) 面试题-说出下面代码的运行结果。(此题的出处是《Java编程思想》一书) classAnnoyanceextendsException{} classSneezeextendsAnnoyance{} classHuman{ publicstaticvoidmain(String[]args) throwsException{ thrownewSneeze(); catch(Annoyancea){ System.out.println("CaughtAnnoyance"); throwa; catch(Sneezes){ System.out.println("CaughtSneeze"); return; finally{ System.out.println("HelloWorld!"); 52、List、Set、Map是否继承自Collection接口? 答:List、Set是,Map不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。 53、阐述ArrayList、Vector、LinkedList的存储性能和特性。 答:ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。Vector属于遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。 补充:遗留容器中的Properties类和Stack类在设计上有严重的问题,Properties是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个Hashtable并将其两个泛型参数设置为String类型,但是JavaAPI中的Properties直接继承了Hashtable,这很明显是对继承的滥用。这里复用代码的方式应该是Has-A关系而不是Is-A关系,另一方面容器都属于工具类,继承工具类本身就是一个错误的做法,使用工具类最好的方式是Has-A关系(关联)或Use-A关系(依赖)。同理,Stack类继承Vector也是不正确的。Sun公司的工程师们也会犯这种低级错误,让人唏嘘不已。 54、Collection和Collections的区别? 答:Collection是一个接口,它是Set、List等容器的父接口;Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。 55、List、Map、Set三个接口存取元素时,各有什么特点? 56、TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素? 答:TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。 例子1: publicclassStudentimplementsComparable publicStudent(Stringname,intage){ return"Student[name="+name+",age="+age+"]"; publicintcompareTo(Studento){ returnthis.age-o.age;//比较年龄(年龄的升序) importjava.util.Set; importjava.util.TreeSet; classTest01{ Set set.add(newStudent("HaoLUO",33)); set.add(newStudent("XJWANG",32)); set.add(newStudent("BruceLEE",60)); set.add(newStudent("BobYANG",22)); for(Studentstu:set){ System.out.println(stu); //输出结果: //Student[name=BobYANG,age=22] //Student[name=XJWANG,age=32] //Student[name=HaoLUO,age=33] //Student[name=BruceLEE,age=60] 例子2: publicclassStudent{ *获取学生姓名 *获取学生年龄 importjava.util.ArrayList; importjava.util.Collections; importjava.util.Comparator; importjava.util.List; classTest02{ List list.add(newStudent("HaoLUO",33)); list.add(newStudent("XJWANG",32)); list.add(newStudent("BruceLEE",60)); list.add(newStudent("BobYANG",22)); //通过sort方法的第二个参数传入一个Comparator接口对象 //相当于是传入一个比较对象大小的算法到sort方法中 //由于Java中没有函数指针、仿函数、委托这样的概念 //因此要将一个算法传入一个方法中唯一的选择就是通过接口回调 Collections.sort(list,newComparator publicintcompare(Studento1,Studento2){ returno1.getName().compareTo(o2.getName());//比较学生姓名 }); for(Studentstu:list){ 57、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别 58、线程的sleep()方法和yield()方法有什么区别? ①sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会; ②线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态; 59、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B? 答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。 -wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; -sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常; -notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; -notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态; 补充:Java5通过Lock接口提供了显式的锁机制(explicitlock),增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;此外,Java5还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。 下面的例子演示了100个线程同时向一个银行账户中存入1元钱,在没有使用同步机制和使用同步机制情况下的执行情况。 银行账户类: *银行账户 publicclassAccount{ privatedoublebalance;//账户余额 *存款 *@parammoney存入金额 publicvoiddeposit(doublemoney){ doublenewBalance=balance+money; catch(InterruptedExceptionex){ ex.printStackTrace(); balance=newBalance; *获得账户余额 publicdoublegetBalance(){ returnbalance; 存钱线程类: *存钱线程 publicclassAddMoneyThreadimplementsRunnable{ privateAccountaccount;//存入账户 privatedoublemoney;//存入金额 publicAddMoneyThread(Accountaccount,doublemoney){ this.account=account; this.money=money; publicvoidrun(){ account.deposit(money); 测试类: importjava.util.concurrent.ExecutorService; importjava.util.concurrent.Executors; publicclassTest01{ Accountaccount=newAccount(); ExecutorServiceservice=Executors.newFixedThreadPool(100); for(inti=1;i<=100;i++){ service.execute(newAddMoneyThread(account,1)); service.shutdown(); while(!service.isTerminated()){} System.out.println("账户余额:"+account.getBalance()); 在没有同步的情况下,执行结果通常是显示账户余额在10元以下,出现这种状况的原因是,当一个线程A试图存入1元的时候,另外一个线程B也能够进入存款的方法中,线程B读取到的账户余额仍然是线程A存入1元钱之前的账户余额,因此也是在原来的余额0上面做了加1元的操作,同理线程C也会做类似的事情,所以最后100个线程执行结束时,本来期望账户余额为100元,但实际得到的通常在10元以下(很可能是1元哦)。解决这个问题的办法就是同步,当一个线程对银行账户存钱时,需要将此账户锁定,待其操作完成后才允许其他的线程进行操作,代码有如下几种调整方案: 在银行账户的存款(deposit)方法上同步(synchronized)关键字 publicsynchronizedvoiddeposit(doublemoney){ 在线程调用存款方法时对银行账户进行同步 synchronized(account){ 通过Java5显示的锁机制,为每个银行账户创建一个锁对象,在存款操作进行加锁和解锁的操作 importjava.util.concurrent.locks.Lock; importjava.util.concurrent.locks.ReentrantLock; privateLockaccountLock=newReentrantLock(); *@parammoney *存入金额 accountLock.lock(); accountLock.unlock(); 61、编写多线程程序有几种实现方式? 答:Java5以前实现多线程有两种实现方法:一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。 补充:Java5以后创建线程还有第三种方式:实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值,代码如下所示: importjava.util.concurrent.Callable; importjava.util.concurrent.Future; classMyTaskimplementsCallable privateintupperBounds; publicMyTask(intupperBounds){ this.upperBounds=upperBounds; publicIntegercall()throwsException{ intsum=0; for(inti=1;i<=upperBounds;i++){ sum+=i; returnsum; classTest{ publicstaticvoidmain(String[]args)throwsException{ List ExecutorServiceservice=Executors.newFixedThreadPool(10); for(inti=0;i<10;i++){ list.add(service.submit(newMyTask((int)(Math.random()*100)))); for(Future //while(!future.isDone()); sum+=future.get(); System.out.println(sum); 62、synchronized关键字的用法? 63、举例说明同步和异步。 64、启动一个线程是调用run()还是start()方法? 答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。 65、什么是线程池(threadpool)? Java5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示: -newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 -newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 -newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 -newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 -newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。 第60题的例子中演示了通过Executors工具类创建线程池并使用线程池执行线程的代码。如果希望在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来创建线程池,这样能获得更好的性能。 66、线程的基本状态以及状态之间的关系? 说明:其中Running表示运行状态,Runnable表示就绪状态(万事俱备,只欠CPU),Blocked表示阻塞状态,阻塞状态又有多种情况,可能是因为调用wait()方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用了sleep()方法或join()方法等待休眠或其他线程结束,或是因为发生了I/O中断。 67、简述synchronized和java.util.concurrent.locks.Lock的异同? 答:Lock是Java5以后引入的新的API,和关键字synchronized相比主要相同点:Lock能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally块中释放(这是释放外部资源的最好的地方)。 68、Java中如何实现序列化,有什么意义? 答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。 要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆(可以参考第29题)。 69、Java中有几种类型的流? 答:字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。 面试题-编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案) importjava.io.FileInputStream; importjava.io.FileOutputStream; importjava.io.IOException; importjava.io.InputStream; importjava.io.OutputStream; importjava.nio.ByteBuffer; importjava.nio.channels.FileChannel; publicfinalclassMyUtil{ publicstaticvoidfileCopy(Stringsource,Stringtarget)throwsIOException{ try(InputStreamin=newFileInputStream(source)){ try(OutputStreamout=newFileOutputStream(target)){ byte[]buffer=newbyte[4096]; intbytesToRead; while((bytesToRead=in.read(buffer))!=-1){ out.write(buffer,0,bytesToRead); publicstaticvoidfileCopyNIO(Stringsource,Stringtarget)throwsIOException{ try(FileInputStreamin=newFileInputStream(source)){ try(FileOutputStreamout=newFileOutputStream(target)){ FileChannelinChannel=in.getChannel(); FileChanneloutChannel=out.getChannel(); ByteBufferbuffer=ByteBuffer.allocate(4096); while(inChannel.read(buffer)!=-1){ buffer.flip(); outChannel.write(buffer); buffer.clear(); 注意:上面用到Java7的TWR,使用TWR后可以不用在finally中释放外部资源,从而让代码更加优雅。 70、写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。 答:代码如下: importjava.io.BufferedReader; importjava.io.FileReader; //工具类中的方法都是静态方式访问的因此将构造器私有不允许创建对象(绝对好习惯) *统计给定文件中给定字符串的出现次数 *@paramfilename文件名 *@paramword字符串 *@return字符串在文件中出现的次数 publicstaticintcountWordInFile(Stringfilename,Stringword){ intcounter=0; try(FileReaderfr=newFileReader(filename)){ try(BufferedReaderbr=newBufferedReader(fr)){ Stringline=null; while((line=br.readLine())!=null){ intindex=-1; while(line.length()>=word.length()&&(index=line.indexOf(word))>=0){ counter++; line=line.substring(index+word.length()); }catch(Exceptionex){ returncounter; 71、如何用Java代码列出一个目录下所有的文件? 如果只要求列出当前文件夹下的文件,代码如下所示: importjava.io.File; classTest12{ Filef=newFile("/Users/Hao/Downloads"); for(Filetemp:f.listFiles()){ if(temp.isFile()){ System.out.println(temp.getName()); 如果需要对文件夹继续展开,代码如下所示: showDirectory(newFile("/Users/Hao/Downloads")); publicstaticvoidshowDirectory(Filef){ _walkDirectory(f,0); privatestaticvoid_walkDirectory(Filef,intlevel){ if(f.isDirectory()){ _walkDirectory(temp,level+1); else{ for(inti=0;i System.out.print("\t"); System.out.println(f.getName()); 在Java7中可以使用NIO.2的API来做同样的事情,代码如下所示: classShowFileTest{ publicstaticvoidmain(String[]args)throwsIOException{ PathinitPath=Paths.get("/Users/Hao/Downloads"); Files.walkFileTree(initPath,newSimpleFileVisitor publicFileVisitResultvisitFile(Pathfile,BasicFileAttributesattrs) throwsIOException{ System.out.println(file.getFileName().toString()); returnFileVisitResult.CONTINUE; 72、用Java的套接字编程实现一个多线程的回显(echo)服务器。 importjava.io.InputStreamReader; importjava.io.PrintWriter; importjava.net.ServerSocket; importjava.net.Socket; publicclassEchoServer{ privatestaticfinalintECHO_SERVER_PORT=6789; try(ServerSocketserver=newServerSocket(ECHO_SERVER_PORT)){ System.out.println("服务器已经启动..."); while(true){ Socketclient=server.accept(); newThread(newClientHandler(client)).start(); }catch(IOExceptione){ privatestaticclassClientHandlerimplementsRunnable{ privateSocketclient; publicClientHandler(Socketclient){ this.client=client; try(BufferedReaderbr=newBufferedReader(newInputStreamReader(client.getInputStream())); PrintWriterpw=newPrintWriter(client.getOutputStream())){ Stringmsg=br.readLine(); System.out.println("收到"+client.getInetAddress()+"发送的:"+msg); pw.println(msg); pw.flush(); }finally{ client.close(); 注意:上面的代码使用了Java7的TWR语法,由于很多外部资源类都间接的实现了AutoCloseable接口(单方法回调接口),因此可以利用TWR语法在try结束的时候通过回调的方式自动调用外部资源类的close()方法,避免书写冗长的finally代码块。此外,上面的代码用一个静态内部类实现线程的功能,使用多线程可以避免一个用户I/O操作所产生的中断影响其他用户对服务器的访问,简单的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可忽视的。 下面是一段回显客户端测试代码: importjava.util.Scanner; publicclassEchoClient{ Socketclient=newSocket("localhost",6789); Scannersc=newScanner(System.in); System.out.print("请输入内容:"); Stringmsg=sc.nextLine(); sc.close(); PrintWriterpw=newPrintWriter(client.getOutputStream()); BufferedReaderbr=newBufferedReader(newInputStreamReader(client.getInputStream())); System.out.println(br.readLine()); 如果希望用NIO的多路复用套接字实现服务器,代码如下所示。NIO的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解。 importjava.net.InetSocketAddress; importjava.nio.CharBuffer; importjava.nio.channels.SelectionKey; importjava.nio.channels.Selector; importjava.nio.channels.ServerSocketChannel; importjava.nio.channels.SocketChannel; importjava.util.Iterator; publicclassEchoServerNIO{ privatestaticfinalintECHO_SERVER_TIMEOUT=5000; privatestaticfinalintBUFFER_SIZE=1024; privatestaticServerSocketChannelserverChannel=null; privatestaticSelectorselector=null;//多路复用选择器 privatestaticByteBufferbuffer=null;//缓冲区 init(); listen(); privatestaticvoidinit(){ serverChannel=ServerSocketChannel.open(); buffer=ByteBuffer.allocate(BUFFER_SIZE); serverChannel.socket().bind(newInetSocketAddress(ECHO_SERVER_PORT)); serverChannel.configureBlocking(false); selector=Selector.open(); serverChannel.register(selector,SelectionKey.OP_ACCEPT); thrownewRuntimeException(e); privatestaticvoidlisten(){ if(selector.select(ECHO_SERVER_TIMEOUT)!=0){ Iterator while(it.hasNext()){ SelectionKeykey=it.next(); it.remove(); handleKey(key); privatestaticvoidhandleKey(SelectionKeykey)throwsIOException{ SocketChannelchannel=null; if(key.isAcceptable()){ ServerSocketChannelserverChannel=(ServerSocketChannel)key.channel(); channel=serverChannel.accept(); channel.configureBlocking(false); channel.register(selector,SelectionKey.OP_READ); }elseif(key.isReadable()){ channel=(SocketChannel)key.channel(); if(channel.read(buffer)>0){ CharBuffercharBuffer=CharsetHelper.decode(buffer); Stringmsg=charBuffer.toString(); System.out.println("收到"+channel.getRemoteAddress()+"的消息:"+msg); channel.write(CharsetHelper.encode(CharBuffer.wrap(msg))); }else{ channel.close(); if(channel!=null){ importjava.nio.charset.CharacterCodingException; importjava.nio.charset.Charset; importjava.nio.charset.CharsetDecoder; importjava.nio.charset.CharsetEncoder; publicfinalclassCharsetHelper{ privatestaticfinalStringUTF_8="UTF-8"; privatestaticCharsetEncoderencoder=Charset.forName(UTF_8).newEncoder(); privatestaticCharsetDecoderdecoder=Charset.forName(UTF_8).newDecoder(); privateCharsetHelper(){ publicstaticByteBufferencode(CharBufferin)throwsCharacterCodingException{ returnencoder.encode(in); publicstaticCharBufferdecode(ByteBufferin)throwsCharacterCodingException{ returndecoder.decode(in); 73、XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式? 74、你在项目中哪些地方用到了XML? 补充:现在有很多时髦的软件(如Sublime)已经开始将配置文件书写成JSON格式,我们已经强烈的感受到XML的另一项功能也将逐渐被业界抛弃。 75、阐述JDBC操作数据库的步骤。 答:下面的代码以连接本机的Oracle数据库为例,演示JDBC操作数据库的步骤。 加载驱动 Class.forName("oracle.jdbc.driver.OracleDriver"); 创建连接 Connectioncon=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","scott","tiger"); 创建语句 PreparedStatementps=con.prepareStatement("select*fromempwheresalbetweenand"); ps.setInt(1,1000); ps.setInt(2,3000); 执行语句 ResultSetrs=ps.executeQuery(); 处理结果 while(rs.next()){ System.out.println(rs.getInt("empno")+"-"+rs.getString("ename"));} 关闭资源 if(con!=null){ con.close(); }catch(SQLExceptione){ 提示:关闭外部资源的顺序应该和打开的顺序相反,也就是说先关闭ResultSet、再关闭Statement、在关闭Connection。上面的代码只关闭了Connection(连接),虽然通常情况下在关闭连接时,连接上创建的语句和打开的游标也会关闭,但不能保证总是如此,因此应该按照刚才说的顺序分别关闭。此外,第一步加载驱动在JDBC4.0中是可以省略的(自动从类路径中加载驱动),但是我们建议保留。 76、Statement和PreparedStatement有什么区别?哪个性能更好? 答:与Statement相比,①PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性);②PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;③当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。 补充:为了提供对存储过程的调用,JDBCAPI中还提供了CallableStatement接口。存储过程(StoredProcedure)是数据库中一组为了完成特定功能的SQL语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。 77、使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能? 78、在进行数据库编程时,连接池有什么作用? 79、什么是DAO模式? 答:DAO(DataAccessObject)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中。用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。DAO模式实际上包含了两个模式,一是DataAccessor(数据访问器),二是DataObject(数据对象),前者要解决如何访问数据的问题,而后者要解决的是如何用对象封装数据。 80、事务的ACID是指什么? -原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败; -一致性(Consistent):事务结束后系统状态是一致的; -隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态; -持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。 补充:关于事务,在面试中被问到的概率是很高的,可以问的问题也是很多的。首先需要知道的是,只有存在并发数据访问时才需要事务。当多个事务访问同一数据时,可能会存在5类问题,包括3类数据读取问题(脏读、不可重复读和幻读)和2类数据更新问题(第1类丢失更新和第2类丢失更新)。 脏读(DirtyRead):A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。 不可重复读(UnrepeatableRead):事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。 幻读(PhantomRead):事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。 第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。 第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。 数据并发访问所产生的问题,在有些场景下可能是允许的,但是有些场景下可能就是致命的,数据库通常会通过锁机制来解决数据并发访问问题,按锁定对象不同可以分为表级锁和行级锁;按并发事务锁定关系可以分为共享锁和独占锁,具体的内容大家可以自行查阅资料进行了解。 直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别,数据库就会通过分析SQL语句然后为事务访问的资源加上合适的锁,此外,数据库还会维护这些锁通过各种手段提高系统的性能,这些对用户来说都是透明的(就是说你不用理解,事实上我确实也不知道)。ANSI/ISOSQL92标准定义了4个等级的事务隔离级别,如下表所示: 需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。 81、JDBC中如何进行事务处理? 答:Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。除此之外,从JDBC3.0中还引入了Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点。 82、JDBC能否处理Blob和Clob? 答:Blob是指二进制大对象(BinaryLargeObject),而Clob是指大字符对象(CharacterLargeObjec),因此其中Blob是为存储大的二进制数据而设计的,而Clob是为存储大的文本数据而设计的。JDBC的PreparedStatement和ResultSet都提供了相应的方法来支持Blob和Clob操作。下面的代码展示了如何使用JDBC操作LOB: 下面以MySQL数据库为例,创建一个张有三个字段的用户表,包括编号(id)、姓名(name)和照片(photo),建表语句如下: createtabletb_user ( idintprimarykeyauto_increment, namevarchar(20)uniquenotnull, photolongblob ); 下面的Java代码向数据库中插入一条记录: importjava.sql.Connection; importjava.sql.DriverManager; importjava.sql.PreparedStatement; importjava.sql.SQLException; classJdbcLobTest{ Connectioncon=null; //1.加载驱动(Java6以上版本可以省略) Class.forName("com.mysql.jdbc.Driver"); //2.建立连接 con=DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456"); //3.创建语句对象 PreparedStatementps=con.prepareStatement("insertintotb_uservalues(default,,)"); ps.setString(1,"骆昊");//将SQL语句中第一个占位符换成字符串 try(InputStreamin=newFileInputStream("test.jpg")){//Java7的TWR ps.setBinaryStream(2,in);//将SQL语句中第二个占位符换成二进制流 //4.发出SQL语句获得受影响行数 System.out.println(ps.executeUpdate()==1"插入成功":"插入失败"); System.out.println("读取照片失败!"); }catch(ClassNotFoundException|SQLExceptione){//Java7的多异常捕获 }finally{//释放外部资源的代码都应当放在finally中保证其能够得到执行 if(con!=null&&!con.isClosed()){ con.close();//5.释放数据库连接 con=null;//指示垃圾回收器可以回收该对象 83、简述正则表达式及其用途。 答:在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。 说明:计算机诞生初期处理的信息几乎都是数值,但是时过境迁,今天我们使用计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符串匹配和处理的时候最为强大的工具,绝大多数语言都提供了对正则表达式的支持。 84、Java中是如何支持正则表达式操作的? 答:Java中的String类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java中可以用Pattern类表示正则表达式对象,它提供了丰富的API进行各种正则表达式操作,请参考下面面试题的代码。 面试题:-如果要从字符串中截取第一个英文左括号之前的字符串,例如:北京市(朝阳区)(西城区)(海淀区),截取结果为:北京市,那么正则表达式怎么写? importjava.util.regex.Matcher; importjava.util.regex.Pattern; classRegExpTest{ Stringstr="北京市(朝阳区)(西城区)(海淀区)"; Patternp=Pattern.compile(".*(=\\()"); Matcherm=p.matcher(str); if(m.find()){ System.out.println(m.group()); 说明:上面的正则表达式中使用了懒惰匹配和前瞻,如果不清楚这些内容,推荐读一下网上很有名的《正则表达式30分钟入门教程》。 85、获得一个类的类对象有哪些方式? -方法1:类型.class,例如:String.class -方法2:对象.getClass(),例如:"hello".getClass() -方法3:Class.forName(),例如:Class.forName("java.lang.String") 86、如何通过反射创建对象? -方法1:通过类对象调用newInstance()方法,例如:String.class.newInstance() -方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance("Hello"); 87、如何通过反射获取和设置对象私有字段的值? 答:可以通过类对象的getDeclaredField()方法字段(Field)对象,然后再通过字段对象的setAccessible(true)将其设置为可以访问,接下来就可以通过get/set方法来获取/设置字段的值了。下面的代码实现了一个反射的工具类,其中的两个静态方法分别用于获取和设置私有字段的值,字段可以是基本类型也可以是对象类型且支持多级对象操作,例如ReflectionUtil.get(dog,"owner.car.engine.id");可以获得dog对象的主人的汽车的引擎的ID号。 importjava.lang.reflect.Constructor; importjava.lang.reflect.Field; importjava.lang.reflect.Modifier; *反射工具类 publicclassReflectionUtil{ privateReflectionUtil(){ *通过反射取对象指定字段(属性)的值 *@paramtarget目标对象 *@paramfieldName字段的名字 *@throws如果取不到对象指定字段的值则抛出异常 *@return字段的值 publicstaticObjectgetValue(Objecttarget,StringfieldName){ Class<>clazz=target.getClass(); String[]fs=fieldName.split("\\."); for(inti=0;i Fieldf=clazz.getDeclaredField(fs[i]); f.setAccessible(true); target=f.get(target); clazz=target.getClass(); Fieldf=clazz.getDeclaredField(fs[fs.length-1]); returnf.get(target); catch(Exceptione){ *通过反射给对象的指定字段赋值 *@paramfieldName字段的名称 *@paramvalue值 publicstaticvoidsetValue(Objecttarget,StringfieldName,Objectvalue){ Objectval=f.get(target); if(val==null){ Constructor<>c=f.getType().getDeclaredConstructor(); c.setAccessible(true); val=c.newInstance(); f.set(target,val); target=val; f.set(target,value); 88、如何通过反射调用对象的方法? 答:请看下面的代码: importjava.lang.reflect.Method; classMethodInvokeTest{ Stringstr="hello"; Methodm=str.getClass().getMethod("toUpperCase"); System.out.println(m.invoke(str));//HELLO 89、简述一下面向对象的"六原则一法则"。 -单一职责原则:一个类只做它该做的事情。(单一职责原则想表达的就是"高内聚",写代码最终极的原则只有六个字"高内聚、低耦合",就如同葵花宝典或辟邪剑谱的中心思想就八个字"欲练此功必先自宫",所谓的高内聚就是一个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。我们都知道一句话叫"因为专注,所以专业",一个对象如果承担太多的职责,那么注定它什么都做不好。这个世界上任何好的东西都有两个特征,一个是功能单一,好的相机绝对不是电视购物里面卖的那种一个机器有一百多种功能的,它基本上只能照相;另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定是底板和胶皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模块也应该是可以轻易的拿到其他系统中使用的,这样才能实现软件复用的目标。) -开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱,如果不清楚如何封装可变性,可以参考《设计模式精解》一书中对桥梁模式的讲解的章节。) 里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的描述,BarbaraLiskov女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。) -接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被复用的可能性是很高的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。) -合成聚合复用原则:优先使用聚合或合成关系复用代码。(通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码,原因嘛可以自己从百度上找到一万个理由,需要说明的是,即使在Java的API中也有不少滥用继承的例子,例如Properties类继承了Hashtable类,Stack类继承了Vector类,这些继承明显就是错误的,更好的做法是在Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符串来存储数据,而Stack类的设计也应该是在Stack类中放一个Vector对象来存储数据。记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。) 90、简述一下你了解的设计模式。 答:所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。 在GoF的《DesignPatterns:ElementsofReusableObject-OrientedSoftware》中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如何将类或对象结合在一起形成更大的结构]、行为型[对在不同的对象之间划分责任和算法的抽象化])共23种设计模式,包括:AbstractFactory(抽象工厂模式),Builder(建造者模式),FactoryMethod(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),TemplateMethod(模板方法模式),ChainOfResponsibility(责任链模式)。 面试被问到关于设计模式的知识时,可以拣最常用的作答,例如: -工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。 -代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理。 -适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。 除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式(Collections工具类和I/O系统中都使用装潢模式)等,反正基本原则就是拣自己最熟悉的、用得最多的作答,以免言多必失。 91、用Java写一个单例类。 -饿汉式单例 publicclassSingleton{ privateSingleton(){} privatestaticSingletoninstance=newSingleton(); publicstaticSingletongetInstance(){ returninstance; -懒汉式单例 privatestaticSingletoninstance=null; publicstaticsynchronizedSingletongetInstance(){ if(instance==null)instance=newSingleton(); 注意:实现一个单例有两点注意事项,①将构造器私有,不允许外界通过构造器创建对象;②通过公开的静态方法向外界返回类的唯一实例。这里有一个问题可以思考:Spring的IoC容器可以为普通的类创建单例,它是怎么做到的呢? 92、什么是UML? 答:UML是统一建模语言(UnifiedModelingLanguage)的缩写,它发表于1997年,综合了当时已经存在的面向对象的建模语言、方法和过程,是一个支持模型化和软件系统开发的图形化语言,为软件开发的所有阶段提供模型化和可视化支持。使用UML可以帮助沟通与交流,辅助应用设计和文档的生成,还能够阐释系统的结构和行为。 93、UML中有哪些常用的图? 答:UML定义了多种图形化的符号来描述软件系统部分或全部的静态结构和动态结构,包括:用例图(usecasediagram)、类图(classdiagram)、时序图(sequencediagram)、协作图(collaborationdiagram)、状态图(statechartdiagram)、活动图(activitydiagram)、构件图(componentdiagram)、部署图(deploymentdiagram)等。在这些图形化符号中,有三种图最为重要,分别是:用例图(用来捕获需求,描述系统的功能,通过该图可以迅速的了解系统的功能模块及其关系)、类图(描述类以及类与类之间的关系,通过该图可以快速了解系统)、时序图(描述执行特定任务时对象之间的交互关系以及执行顺序,通过该图可以了解对象能接收的消息也就是说对象能够向外界提供的服务)。 用例图: 类图: 时序图: 94、用Java写一个冒泡排序。 答:冒泡排序几乎是个程序员都写得出来,但是面试的时候如何写一个逼格高的冒泡排序却不是每个人都能做到,下面提供一个参考代码: *排序器接口(策略模式:将算法封装到具有共同接口的独立的类中使得它们可以相互替换) publicinterfaceSorter{ *排序 *@paramlist待排序的数组 public *@paramcomp比较两个对象的比较器 public *冒泡排序 publicclassBubbleSorterimplementsSorter{ public booleanswapped=true; for(inti=1,len=list.length;i swapped=false; for(intj=0;j if(list[j].compareTo(list[j+1])>0){ Ttemp=list[j]; list[j]=list[j+1]; list[j+1]=temp; swapped=true; public if(comp.compare(list[j],list[j+1])>0){ 95、用Java写一个折半查找。 publicstatic returnbinarySearch(x,0,x.length-1,key); //使用循环实现的二分查找 publicstatic intlow=0; inthigh=x.length-1; while(low<=high){ intmid=(low+high)>>>1; intcmp=comp.compare(x[mid],key); if(cmp<0){ low=mid+1; elseif(cmp>0){ high=mid-1; returnmid; return-1; //使用递归实现的二分查找 privatestatic if(low<=high){ intmid=low+((high-low)>>1); if(key.compareTo(x[mid])==0){ elseif(key.compareTo(x[mid])<0){ returnbinarySearch(x,low,mid-1,key); returnbinarySearch(x,mid+1,high,key); 说明:上面的代码中给出了折半查找的两个版本,一个用递归实现,一个用循环实现。需要注意的是计算中间位置时不应该使用(high+low)/2的方式,因为加法运算可能导致整数越界,这里应该使用以下三种方式之一:low+(high-low)/2或low+(high–low)>>1或(low+high)>>>1(>>>是逻辑右移,是不带符号位的右移) JavaWeb+WebService 96、阐述Servlet和CGI的区别 答:Servlet与CGI的区别在于Servlet处于服务器进程中,它通过多线程方式运行其service()方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于Servlet。 补充:SunMicrosystems公司在1996年发布Servlet技术就是为了和CGI进行竞争,Servlet是一个特殊的Java程序,一个基于Java的Web应用通常包含一个或多个Servlet类。Servlet不能够自行创建并执行,它是在Servlet容器中运行的,容器将用户的请求传递给Servlet程序,并将Servlet的响应回传给用户。通常一个Servlet会关联一个或多个JSP页面。以前CGI经常因为性能开销上的问题被诟病,然而FastCGI早就已经解决了CGI效率上的问题,所以面试的时候大可不必信口开河的诟病CGI,事实上有很多你熟悉的网站都使用了CGI技术。 97、Servlet接口中有哪些方法? -voidinit(ServletConfigconfig)throwsServletException -voidservice(ServletRequestreq,ServletResponseresp)throwsServletException,java.io.IOException -voiddestory() -java.lang.StringgetServletInfo() -ServletConfiggetServletConfig() Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法。 98、转发(forward)和重定向(redirect)的区别? 答:forward是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。redirect就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址,很明显redirect无法访问到服务器保护起来资源,但是可以从一个网站redirect到其他网站。forward更加高效,所以在满足需要时尽量使用forward(通过调用RequestDispatcher对象的forward()方法,该对象可以通过ServletRequest对象的getRequestDispatcher()方法获得),并且这样也有助于隐藏实际的链接;在有些情况下,比如需要访问一个其它服务器上的资源,则必须使用重定向(通过HttpServletResponse对象调用其sendRedirect()方法实现)。 99、JSP有哪些内置对象?作用分别是什么? 答:JSP有9个内置对象: -request:封装客户端的请求,其中包含来自GET或POST请求的参数; -response:封装服务器对客户端的响应; -pageContext:通过该对象可以获取其他对象; -session:封装用户会话的对象; -application:封装服务器运行环境的对象; -out:输出服务器响应的输出流对象; -config:Web应用的配置对象; -page:JSP页面本身(相当于Java程序中的this); -exception:封装页面抛出异常的对象。 JSP页面: <%@pagepageEncoding="UTF-8"%> <% Stringpath=request.getContextPath(); StringbasePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> *{font-family:"Arial";} 对应的Java代码: /* *GeneratedbytheJaspercomponentofApacheTomcat *Version:ApacheTomcat/7.0.52 *Generatedat:2014-10-1313:28:38UTC *Note:Thelastmodifiedtimeofthisfilewassetto *thelastmodifiedtimeofthesourcefileafter *generationtoassistwithmodificationtracking. packageorg.apache.jsp; importjavax.servlet.*; importjavax.servlet.jsp.*; publicfinalclassindex_jspextendsorg.apache.jasper.runtime.HttpJspBase implementsorg.apache.jasper.runtime.JspSourceDependent{ privatestaticfinaljavax.servlet.jsp.JspFactory_jspxFactory=javax.servlet.jsp.JspFactory .getDefaultFactory(); privatestaticjava.util.Map privatejavax.el.ExpressionFactory_el_expressionfactory; privateorg.apache.tomcat.InstanceManager_jsp_instancemanager; publicjava.util.Map return_jspx_dependants; publicvoid_jspInit(){ _el_expressionfactory=_jspxFactory.getJspApplicationContext( getServletConfig().getServletContext()).getExpressionFactory(); _jsp_instancemanager=org.apache.jasper.runtime.InstanceManagerFactory .getInstanceManager(getServletConfig()); publicvoid_jspDestroy(){ publicvoid_jspService( throwsjava.io.IOException,javax.servlet.ServletException{ //内置对象就是在这里定义的 finaljavax.servlet.jsp.PageContextpageContext; finaljavax.servlet.ServletContextapplication; finaljavax.servlet.ServletConfigconfig; javax.servlet.jsp.JspWriterout=null; finaljava.lang.Objectpage=this; javax.servlet.jsp.JspWriter_jspx_out=null; javax.servlet.jsp.PageContext_jspx_page_context=null; response.setContentType("text/html;charset=UTF-8"); pageContext=_jspxFactory.getPageContext(this,request,response, null,true,8192,true); _jspx_page_context=pageContext; application=pageContext.getServletContext(); config=pageContext.getServletConfig(); session=pageContext.getSession(); out=pageContext.getOut(); _jspx_out=out; out.write('\r'); out.write('\n'); StringbasePath=request.getScheme()+"://" +request.getServerName()+":"+request.getServerPort() +path+"/"; //以下代码通过输出流将HTML标签输出到浏览器中 out.write("\r\n"); out.write("\r\n"); out.write("\r\n"); out.write(" out.print(basePath); out.write("\">\r\n"); out.write("Hello,World!
Currenttimeis:<%=newjava.util.Date().toString()%>
out.write("
out.write("\t*{font-family:\"Arial\";}\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("
\r\n");out.write("
Hello,World!
\r\n");out.write("
\r\n");
out.write("
Currenttimeis:");
out.print(newjava.util.Date().toString());
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
}catch(java.lang.Throwablet){
if(!(tinstanceofjavax.servlet.jsp.SkipPageException)){
out=_jspx_out;
if(out!=null&&out.getBufferSize()!=0)
out.clearBuffer();
}catch(java.io.IOExceptione){
if(_jspx_page_context!=null)
_jspx_page_context.handlePageException(t);
else
thrownewServletException(t);
_jspxFactory.releasePageContext(_jspx_page_context);
100、get和post请求的区别?
①get请求用来从服务器上获得资源,而post是用来向服务器提交数据;
②get将表单中数据按照name=value的形式,添加到action所指向的URL后面,并且两者使用""连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL;
③get传输的数据要受到URL长度限制(1024字节);而post可以传输大量的数据,上传文件通常要使用post方式;
④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post;
⑤get使用MIME类型application/x-www-form-urlencoded的URL编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20"。
101、常用的Web服务器有哪些?
答:Unix和Linux平台下使用最广泛的免费HTTP服务器是Apache服务器,而Windows平台的服务器通常使用IIS作为Web服务器。选择Web服务器应考虑的因素有:性能、安全性、日志和统计、虚拟主机、代理服务器、缓冲服务和集成应用程序等。下面是对常见服务器的简介:
-IIS:Microsoft的Web服务器产品,全称是InternetInformationServices。IIS是允许在公共Intranet或Internet上发布信息的Web服务器。IIS是目前最流行的Web服务器产品之一,很多著名的网站都是建立在IIS的平台上。IIS提供了一个图形界面的管理工具,称为Internet服务管理器,可用于监视配置和控制Internet服务。IIS是一种Web服务组件,其中包括Web服务器、FTP服务器、NNTP服务器和SMTP服务器,分别用于网页浏览、文件传输、新闻服务和邮件发送等方面,它使得在网络(包括互联网和局域网)上发布信息成了一件很容易的事。它提供ISAPI(IntranetServerAPI)作为扩展Web服务器功能的编程接口;同时,它还提供一个Internet数据库连接器,可以实现对数据库的查询和更新。
-Kangle:KangleWeb服务器是一款跨平台、功能强大、安全稳定、易操作的高性能Web服务器和反向代理服务器软件。此外,Kangle也是一款专为做虚拟主机研发的Web服务器。实现虚拟主机独立进程、独立身份运行。用户之间安全隔离,一个用户出问题不影响其他用户。支持PHP、ASP、ASP.NET、Java、Ruby等多种动态开发语言。
-WebSphere:WebSphereApplicationServer是功能完善、开放的Web应用程序服务器,是IBM电子商务计划的核心部分,它是基于Java的应用环境,用于建立、部署和管理Internet和IntranetWeb应用程序,适应各种Web应用程序服务器的需要。
-WebLogic:WebLogicServer是一款多功能、基于标准的Web应用服务器,为企业构建企业应用提供了坚实的基础。针对各种应用开发、关键性任务的部署,各种系统和数据库的集成、跨Internet协作等Weblogic都提供了相应的支持。由于它具有全面的功能、对开放标准的遵从性、多层架构、支持基于组件的开发等优势,很多公司的企业级应用都选择它来作为开发和部署的环境。WebLogicServer在使应用服务器成为企业应用架构的基础方面一直处于领先地位,为构建集成化的企业级应用提供了稳固的基础。
-Tomcat:Tomcat是一个开放源代码、运行Servlet和JSP的容器。Tomcat实现了Servlet和JSP规范。此外,Tomcat还实现了Apache-Jakarta规范而且比绝大多数商业应用软件服务器要好,因此目前也有不少的Web服务器都选择了Tomcat。
-Nginx:读作"enginex",是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器。Nginx是由IgorSysoev为俄罗斯访问量第二的Rambler站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。在2014年下半年,Nginx的市场份额达到了14%。
102、JSP和Servlet是什么关系?
答:其实这个问题在上面已经阐述过了,Servlet是一个特殊的Java程序,它运行于服务器的JVM中,能够依靠服务器的支持向浏览器提供显示内容。JSP本质上是Servlet的一种简易形式,JSP会被服务器处理成一个类似于Servlet的Java程序,可以简化页面内容的生成。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。有人说,Servlet就是在Java中写HTML,而JSP就是在HTML中写Java代码,当然这个说法是很片面且不够准确的。JSP侧重于视图,Servlet更侧重于控制逻辑,在MVC架构模式中,JSP适合充当视图(view)而Servlet适合充当控制器(controller)。
103、讲解JSP中的四种作用域。
答:JSP中的四种作用域包括page、request、session和application,具体来说:
104、如何实现JSP或Servlet的单线程模式?
对于JSP页面,可以通过page指令进行设置。
<%@pageisThreadSafe=”false”%>
对于Servlet,可以让自定义的Servlet实现SingleThreadModel标识接口。
说明:如果将JSP或Servlet设置成单线程工作模式,会导致每个请求创建一个Servlet实例,这种实践将导致严重的性能问题(服务器的内存压力很大,还会导致频繁的垃圾回收),所以通常情况下并不会这么做。
105、实现会话跟踪的技术有哪些?
答:由于HTTP协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的ID,下一次用户在请求中包含此ID,服务器据此判断到底是哪一个用户。
①URL重写:在URL中添加用户会话的信息作为请求的参数,或者将唯一的会话ID添加到URL结尾以标识一个会话。
④HttpSession:在所有会话跟踪技术中,HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建HttpSession,每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方法获得HttpSession,通过HttpSession的setAttribute方法可以将一个值放在HttpSession中,通过调用HttpSession对象的getAttribute方法,同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的是,HttpSession放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的Servlet容器可以在内存将满时将HttpSession中的对象移到其他存储设备中,但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象,这个对象最好实现了Serializable接口,这样Servlet容器在必要的时候可以将其序列化到文件中,否则在序列化时就会出现异常。
**补充:**HTML5中可以使用WebStorage技术通过JavaScript来保存数据,例如可以使用localStorage和sessionStorage来保存用户会话的信息,也能够实现会话跟踪。
106、过滤器有哪些作用和用法?
常见的过滤器用途主要包括:对用户请求进行统一认证、对用户的访问请求进行记录和审核、对用户发送的数据进行过滤或替换、转换图象格式、对响应内容进行压缩以减少传输量、对请求或响应进行加解密处理、触发资源访问事件、对XML的输出应用XSLT等。
编码过滤器的例子:
importjavax.servlet.Filter;
importjavax.servlet.FilterChain;
importjavax.servlet.FilterConfig;
importjavax.servlet.ServletException;
importjavax.servlet.ServletRequest;
importjavax.servlet.ServletResponse;
importjavax.servlet.annotation.WebFilter;
importjavax.servlet.annotation.WebInitParam;
@WebFilter(urlPatterns={"*"},
initParams={@WebInitParam(name="encoding",value="utf-8")})
publicclassCodingFilterimplementsFilter{
privateStringdefaultEncoding="utf-8";
publicvoiddestroy(){
publicvoiddoFilter(ServletRequestreq,ServletResponseresp,
FilterChainchain)throwsIOException,ServletException{
req.setCharacterEncoding(defaultEncoding);
resp.setCharacterEncoding(defaultEncoding);
chain.doFilter(req,resp);
publicvoidinit(FilterConfigconfig)throwsServletException{
Stringencoding=config.getInitParameter("encoding");
if(encoding!=null){
defaultEncoding=encoding;
下载计数过滤器的例子:
importjava.io.FileWriter;
importjava.util.Properties;
@WebFilter(urlPatterns={"/*"})
publicclassDownloadCounterFilterimplementsFilter{
privateExecutorServiceexecutorService=Executors.newSingleThreadExecutor();
privatePropertiesdownloadLog;
privateFilelogFile;
executorService.shutdown();
HttpServletRequestrequest=(HttpServletRequest)req;
finalStringuri=request.getRequestURI();
executorService.execute(newRunnable(){
Stringvalue=downloadLog.getProperty(uri);
if(value==null){
downloadLog.setProperty(uri,"1");
intcount=Integer.parseInt(value);
downloadLog.setProperty(uri,String.valueOf(++count));
downloadLog.store(newFileWriter(logFile),"");
catch(IOExceptione){
StringappPath=config.getServletContext().getRealPath("/");
logFile=newFile(appPath,"downloadLog.txt");
if(!logFile.exists()){
logFile.createNewFile();
downloadLog=newProperties();
downloadLog.load(newFileReader(logFile));
说明:这里使用了Servlet3规范中的注解来部署过滤器,当然也可以在web.xml中使用
107、监听器有哪些作用和用法?
答:JavaWeb开发中的监听器(listener)就是application、session、request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件,如下所示:
①ServletContextListener:对Servlet上下文的创建和销毁进行监听。
②ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和替换。
③HttpSessionListener:对Session的创建和销毁进行监听。
④HttpSessionAttributeListener:对Session对象中属性的添加、删除和替换进行监听。
⑤ServletRequestListener:对请求对象的初始化和销毁进行监听。
⑥ServletRequestAttributeListener:对请求对象属性的添加、删除和替换进行监听。
下面是一个统计网站最多在线人数监听器的例子。
importjavax.servlet.ServletContextEvent;
importjavax.servlet.ServletContextListener;
importjavax.servlet.annotation.WebListener;
上下文监听器,在服务器启动时初始化onLineCount和maxOnLineCount两个变量
并将其置于服务器上下文(ServletContext)中,其初始值都是0
@WebListener
publicclassInitListenerimplementsServletContextListener{
publicvoidcontextDestroyed(ServletContextEventevt){
publicvoidcontextInitialized(ServletContextEventevt){
evt.getServletContext().setAttribute("onLineCount",0);
evt.getServletContext().setAttribute("maxOnLineCount",0);
importjava.text.DateFormat;
importjavax.servlet.ServletContext;
会话监听器,在用户会话创建和销毁的时候根据情况
修改onLineCount和maxOnLineCount的值
publicclassMaxCountListenerimplementsHttpSessionListener{
publicvoidsessionCreated(HttpSessionEventevent){
ServletContextctx=event.getSession().getServletContext();
intcount=Integer.parseInt(ctx.getAttribute("onLineCount").toString());
count++;
ctx.setAttribute("onLineCount",count);
intmaxOnLineCount=Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());
if(count>maxOnLineCount){
ctx.setAttribute("maxOnLineCount",count);
DateFormatdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");
ctx.setAttribute("date",df.format(newDate()));
publicvoidsessionDestroyed(HttpSessionEventevent){
ServletContextapp=event.getSession().getServletContext();
intcount=Integer.parseInt(app.getAttribute("onLineCount").toString());
count--;
app.setAttribute("onLineCount",count);
说明:这里使用了Servlet3规范中的@WebListener注解配置监听器,当然你可以在web.xml文件中用
108、web.xml文件中可以配置哪些内容?
①配置spring上下文加载监听器加载Spring配置文件并创建IoC容器:
org.springframework.web.context.ContextLoaderListener
②配置Spring的OpenSessionInView过滤器来解决延迟加载和hibernate会话关闭的矛盾:
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
④配置404和Exception的错误页面:
⑤配置安全认证方式:
说明:对Servlet(小服务)、Listener(监听器)和Filter(过滤器)等Web组件的配置,Servlet3规范提供了基于注解的配置方式,可以分别使用@WebServlet、@WebListener、@WebFilter注解进行配置。
补充:如果Web提供了有价值的商业信息或者是敏感数据,那么站点的安全性就是必须考虑的问题。安全认证是实现安全性的重要手段,认证就是要解决“Areyouwhoyousayyouare”的问题。认证的方式非常多,简单说来可以分为三类:
A.Whatyouknow—口令
B.Whatyouhave—数字证书(U盾、密保卡)
C.Whoyouare—指纹识别、虹膜识别
在Tomcat中可以通过建立安全套接字层(SecureSocketLayer,SSL)以及通过基本验证或表单验证来实现对安全性的支持。
109、你的项目中使用过哪些JSTL标签?
答:项目中主要使用了JSTL的核心标签库,包括
说明:虽然JSTL标签库提供了core、sql、fmt、xml等标签库,但是实际开发中建议只使用核心标签库(core),而且最好只使用分支和循环标签并辅以表达式语言(EL),这样才能真正做到数据显示和业务逻辑的分离,这才是最佳实践。
110、使用标签库有什么好处?如何自定义JSP标签?
答:使用标签库的好处包括以下几个方面:
-分离JSP页面的内容和逻辑,简化了Web开发;
-开发者可以创建自定义标签来封装业务逻辑和显示逻辑;
-标签具有很好的可移植性、可维护性和可重用性;
-避免了对Scriptlet(小脚本)的使用(很多公司的项目开发都不允许在JSP中书写小脚本)
自定义JSP标签包括以下几个步骤:
-编写一个Java类实现实现Tag/BodyTag/IterationTag接口(开发中通常不直接实现这些接口而是继承TagSupport/BodyTagSupport/SimpleTagSupport类,这是对缺省适配模式的应用),重写doStartTag()、doEndTag()等方法,定义标签要完成的功能
-编写扩展名为tld的标签描述文件对自定义标签进行部署,tld文件通常放在WEB-INF文件夹下或其子目录中
-在JSP页面中使用taglib指令引用该标签库
下面是一个自定义标签库的例子。
步骤1-标签类源代码TimeTag.java:
packagecom.jackfrued.tags;
importjavax.servlet.jsp.JspException;
importjavax.servlet.jsp.JspWriter;
importjavax.servlet.jsp.tagext.TagSupport;
publicclassTimeTagextendsTagSupport{
privatestaticfinallongserialVersionUID=1L;
privateStringformat="yyyy-MM-ddhh:mm:ss";
privateStringforeColor="black";
privateStringbackColor="white";
publicintdoStartTag()throwsJspException{
SimpleDateFormatsdf=newSimpleDateFormat(format);
JspWriterwriter=pageContext.getOut();
StringBuildersb=newStringBuilder();
sb.append(String.format("
foreColor,backColor,sdf.format(newDate())));
writer.print(sb.toString());
returnSKIP_BODY;
publicvoidsetFormat(Stringformat){
this.format=format;
publicvoidsetForeColor(StringforeColor){
this.foreColor=foreColor;
publicvoidsetBackColor(StringbackColor){
this.backColor=backColor;
步骤2-编写标签库描述文件my.tld:
version="2.0">
步骤3-在JSP页面中使用自定义标签:
<%@taglibprefix="my"uri="/WEB-INF/tld/my.tld"%>
*{font-family:"Arial";font-size:72px;}
提示:如果要将自定义的标签库发布成JAR文件,需要将标签库描述文件(tld文件)放在JAR文件的META-INF目录下,可以JDK中的jar工具完成JAR文件的生成,如果不清楚如何操作,可以请教谷老师和百老师。
111、说一下表达式语言(EL)的隐式对象及其作用。
答:EL的隐式对象包括:pageContext、initParam(访问上下文参数)、param(访问请求参数)、paramValues、header(访问请求头)、headerValues、cookie(访问cookie)、applicationScope(访问application作用域)、sessionScope(访问session作用域)、requestScope(访问request作用域)、pageScope(访问page作用域)。
用法如下所示:
${pageContext.request.method}
${pageContext["request"]["method"]}
${pageContext.request["method"]}
${pageContext["request"].method}
${initParam.defaultEncoding}
${header["accept-language"]}
${headerValues["accept-language"][0]}
${cookie.jsessionid.value}
${sessionScope.loginUser.username}
补充:表达式语言的.和[]运算作用是一致的,唯一的差别在于如果访问的属性名不符合Java标识符命名规则,例如上面的accept-language就不是一个有效的Java标识符,那么这时候就只能用[]运算符而不能使用.运算符获取它的值
112、表达式语言(EL)支持哪些运算符?
答:除了.和[]运算符,EL还提供了:
-算术运算符:+、-、*、/或div、%或mod
-关系运算符:==或eq、!=或ne、>或gt、>=或ge、<或lt、<=或le
-逻辑运算符:&&或and、||或or、!或not
-条件运算符:${statementA:B}(跟Java的条件运算符类似)
-empty运算符:检查一个值是否为null或者空(数组长度为0或集合中没有元素也返回true)
113、JavaWeb开发的Model1和Model2分别指的是什么?
答:Model1是以页面为中心的JavaWeb开发,使用JSP+JavaBean技术将页面显示逻辑和业务逻辑处理分开,JSP实现页面显示,JavaBean对象用来保存数据和实现业务逻辑。Model2是基于MVC(模型-视图-控制器,Model-View-Controller)架构模式的开发模型,实现了模型和视图的彻底分离,利于团队开发和代码复用,如下图所示。
114、Servlet3中的异步处理指的是什么?
补充:多线程在Java诞生初期无疑是一个亮点,而Servlet单实例多线程的工作方式也曾为其赢得美名,然而技术的发展往往会颠覆我们很多的认知,就如同当年爱因斯坦的相对论颠覆了牛顿的经典力学一般。事实上,异步处理绝不是Serlvet3首创,如果你了解Node.js的话,对Servlet3的这个重要改进就不以为奇了。
下面是一个支持异步处理请求的Servlet的例子。
importjavax.servlet.AsyncContext;
importjavax.servlet.annotation.WebServlet;
@WebServlet(urlPatterns={"/async"},asyncSupported=true)
publicclassAsyncServletextendsHttpServlet{
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)
throwsServletException,IOException{
//开启Tomcat异步Servlet支持
req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED",true);
finalAsyncContextctx=req.startAsync();//启动异步处理的上下文
//ctx.setTimeout(30000);
ctx.start(newRunnable(){
//在此处添加异步处理的代码
ctx.complete();
115、如何在基于Java的Web项目中实现文件上传和下载?
答:在Sevlet3以前,ServletAPI中没有支持上传功能的API,因此要实现上传功能需要引入第三方工具从POST请求中获得上传的附件或者通过自行处理输入流来获得上传的文件,我们推荐使用Apache的commons-fileupload。
从Servlet3开始,文件上传变得无比简单,相信看看下面的例子一切都清楚了。
上传页面index.jsp:
<%@pagepageEncoding="utf-8"%>
Selectyourphotoandupload
Photofile:
支持上传的Servlet:
packagecom.jackfrued.servlet;
importjavax.servlet.annotation.MultipartConfig;
@WebServlet("/UploadServlet")
@MultipartConfig
publicclassUploadServletextendsHttpServlet{
protectedvoiddoPost(HttpServletRequestrequest,
HttpServletResponseresponse)throwsServletException,IOException{
//可以用request.getPart()方法获得名为photo的上传附件
//也可以用request.getParts()获得所有上传附件(多文件上传)
//然后通过循环分别处理每一个上传的文件
Partpart=request.getPart("photo");
if(part!=null&&part.getSubmittedFileName().length()>0){
//用ServletContext对象的getRealPath()方法获得上传文件夹的绝对路径
StringsavePath=request.getServletContext().getRealPath("/upload");
//Servlet3.1规范中可以用Part对象的getSubmittedFileName()方法获得上传的文件名
//更好的做法是为上传的文件进行重命名(避免同名文件的相互覆盖)
part.write(savePath+"/"+part.getSubmittedFileName());
request.setAttribute("hint","UploadSuccessfully!");
request.setAttribute("hint","Uploadfailed!");
//跳转回到上传页面
request.getRequestDispatcher("index.jsp").forward(request,response);
116、服务器收到用户提交的表单数据,到底是调用Servlet的doGet()还是doPost()方法?
答:HTML的