其中收录了一些其他博客上的面试题,欢迎大家补充和讨论
一、java基础
1、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
2、JDK、JRE、JVM关系是什么?
JDK(JavaDevelopmentKit)即为Java开发工具包,包含编写Java程序所必须的编译、运行等开发工具以及JRE。开发工具如:用于编译java程序的javac命令、用于启动JVM运行java程序的java命令、用于生成文档的javadoc命令以及用于打包的jar命令等等。JRE(JavaRuntimeEnvironment)即为Java运行环境,提供了运行Java应用程序所必须的软件环境,包含有Java虚拟机(JVM)和丰富的系统类库。系统类库即为java提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。JVM(JavaVirtualMachines)即为Java虚拟机,提供了字节码文件(.class)的运行环境支持。简单说,就是JDK包含JRE包含JVM。
3、Java支持的数据类型有哪些?什么是自动拆装箱?
基本数据类型:整数值型:byte,short,int,long,字符型:char浮点类型:float,double布尔型:boolean整数默认int型,小数默认是double型。Float和long类型的必须加后缀。
4、面向对象是什么?
面向对象是一种思想,世间万物都可以看做一个对象,这里只讨论面向对象编程(OOP),Java是一个支持并发、基于类和面向对象的计算机编程语言,面向对象软件开发的优点:
代码开发模块化,更易维护和修改;
代码复用性强;
增强代码的可靠性和灵活性;
增加代码的可读性。
抽象:提取现实世界中某事物的关键特性,为该事物构建模型的过程。对同一事物在不同的需求下,需要提取的特性可能不一样。得到的抽象模型中一般包含:属性(数据)和操作(行为)。这个抽象模型我们称之为类。对类进行实例化得到对象。
封装:封装可以使类具有独立性和隔离性;保证类的高内聚。只暴露给类外部或者子类必须的属性和操作。类封装的实现依赖类的修饰符(public、protected和private等)
继承:对现有类的一种复用机制。一个类如果继承现有的类,则这个类将拥有被继承类的所有非私有特性(属性和操作)。这里指的继承包含:类的继承和接口的实现。
多态:多态是在继承的基础上实现的。多态的三个要素:继承、重写和父类引用指向子类对象。父类引用指向不同的子类对象时,调用相同的方法,呈现出不同的行为;就是类多态特性。多态可以分成编译时多态和运行时多态。
允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。主要有以下优点:
可替换性:多态对已存在代码具有可替换性
可扩充性:增加新的子类不影响已经存在的类结构
接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的。
灵活性
简化性
实现多态主要有以下三种方式:1.接口实现2.继承父类重写方法3.同一类中进行方法重载
动态绑定技术(dynamicbinding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法。
抽象、封装、继承和多态是面向对象的基础。在面向对象四大基础特性之上,我们在做面向对象编程设计时还需要遵循有一些基本的设计原则。
SOLID原则(单一职责原则、开放关闭原则、里氏替换原则、接口隔离原则和依赖倒置原则)迪米特法则组合优于继承原则(合成复用原则)。
5、设计模式在遵循这些面向对象设计原则基础上,前辈们总结出一些解决不同问题场景的设计模式,以四人帮的gof23最为知名。
24种设计模式(gof23+1):
创建型模式:1.简单工厂模式(不包含在gof23中)2.工厂模式3.抽象工厂模式4.单例模式5.原型模式创建者模式6.结构型模式:7.组合模式8.装饰者模式9.外观模式10.适配器模式11.代理模式12.享元模式13.桥接模式行为型模式:14.观察者模式15.策略模式16.状态模式17.中介模式18.模板方法19.命令模式20.备忘录模式21.访问者模式22.解释器模式23.迭代器模式24.职责链模式
主要讲一下单例模式,
单例模式:在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例
这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,
“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,
publicclassSingleton{privatestaticclassLazyHolder{privatestaticfinalSingletonINSTANCE=newSingleton();}privateSingleton(){}publicstaticfinalSingletongetInstance(){returnLazyHolder.INSTANCE;}}
这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
//饿汉式单例类.在类初始化时,已经自行实例化publicclassSingleton1{privateSingleton1(){}privatestaticfinalSingleton1single=newSingleton1();//静态工厂方法publicstaticSingleton1getInstance(){returnsingle;}}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
饿汉式和懒汉式区别
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。
工厂模式
这里只是简单描述了定义和特征以及设计模式的关系,具体细节不讨论。
7、&和&&的区别?
8、什么是值传递和引用传递?
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。一般认为,java内的传递都是值传递.java中实例对象的传递是引用传递。
9、是否可以在static环境中访问非static变量?
static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
10、Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?
Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。
11、Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?
当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。Java不支持像C++中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造方法。
12、Java支持多继承么?
Java中类不支持多继承,只支持单继承(即一个类只有一个父类)。但是java中的接口支持多继承,,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。
13、解释内存中的栈(stack)、堆(heap)和方法区(methodarea)的用法。
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为FromSurvivor和ToSurvivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。
14、接口和抽象类的区别是什么?
接口的意义用三个词就可以概括:规范,扩展,回调。
抽象类的意义可以用三句话来概括:
为其他子类提供一个公共的类型
封装子类中重复定义的内容
定义抽象方法,子类虽然有不同的实现,但是定义时一致的
15、String和StringBuilder、StringBuffer的区别?
Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。
16、Java集合框架是什么?说出一些集合框架的优点?
每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。集合框架的部分优点如下:(1)使用核心集合类降低开发成本,而非实现我们自己的集合类。(2)随着使用经过严格测试的集合框架类,代码质量会得到提高。(3)通过使用JDK附带的集合类,可以降低代码维护成本。(4)复用性和可操作性。
17、集合框架中的泛型有什么优点?
Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。
18、Java集合框架的基础接口有哪些?
Collection为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。##标题##Set是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。List是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组。Map是一个将key映射到value的对象.一个Map不能包含重复的key:每个key最多只能映射一个value。一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator。
19、为何Collection不从Cloneable和Serializable接口继承?
20、为何Map接口不继承Collection接口?
尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。
21、什么是迭代器(Iterator)?
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(ObjectObj)删除,可以通过迭代器的remove()方法删除。
22、Iterator和ListIterator的区别是什么?
下面列出了他们的区别:Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
23、快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
快速失败:当你在迭代一个集合的时候,如果有另一个线程正在修改你正在访问的那个集合时,就会抛出一个ConcurrentModification异常。在java.util包下的都是快速失败。安全失败:你在迭代的时候会去底层集合做一个拷贝,所以你在修改上层集合的时候是不会受影响的,不会抛出ConcurrentModification异常。在java.util.concurrent包下的全是安全失败的。
24、Java中的HashMap的工作原理是什么?
我们知道在Java中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap也是如此。实际上HashMap是一个“链表散列”,如下是它数据结构:最左侧是一个数组,数组中的每一个元素都是一个链表,链表的每一个元素都是entry。
HashMap是基于hashing的原理,我们使用put(key,value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
25、当两个对象的hashcode相同会发生什么?
因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。
26、如果两个键的hashcode相同,你如何获取值对象?
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
27、hashCode()和equals()方法有何重要性?
28、HashMap和Hashtable有什么区别?
1、HashMap是非线程安全的,HashTable是线程安全的。2、HashMap的键和值都允许有null值存在,而HashTable则不行。3、因为线程安全的问题,HashMap效率比HashTable的要高。4、Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。一般现在不建议用HashTable,①是HashTable是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。
29、如何决定选用HashMap还是TreeMap?
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
30、ArrayList和Vector有何异同点?
ArrayList和Vector在很多时候都很类似。(1)两者都是基于索引的,内部由一个数组支持。(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。(3)ArrayList和Vector的迭代器实现都是fail-fast的。(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。以下是ArrayList和Vector的不同点。(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。(2)ArrayList比Vector快,它因为有同步,不会过载。(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
31、Array和ArrayList有何区别?什么时候更适合用Array?
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。Array是指定大小的,而ArrayList大小是固定的。Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。(1)如果列表的大小已经指定,大部分情况下是存储和遍历它们。(2)对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。(3)如果你要使用多维数组,使用[][]比List
32、去掉list集合里的重复数据
去掉list重复的数据,目前总结的以下三种方法,分别是采用set集合来做、两层循环不用任何方法来做,以及一层循环采用contains()方法来做,如下:
1.采用set结合来做:
packagetest;
importjava.util.ArrayList;importjava.util.HashSet;importjava.util.List;importjava.util.Set;
publicclassTest1{//采用set来做,因为set是不重复的publicstaticvoidmain(String[]args){List
}
2.采用双重循环来做:
importjava.util.ArrayList;importjava.util.List;
publicclassTest2{//方法二:采用循环来做publicstaticvoidmain(String[]args){List
3.采用contains()方法来做,一层循环即可解决问题:
publicclassTest3{publicstaticvoidmain(String[]args){//测试方法List 4.还是双重循环,也不用方法二来做 /***@authoryanxu*用双重循环去掉重复,不用任何方法,也不用那个:外层循环从0到长度-1,后面那个相反的那个*/publicstaticList poll()和remove()都是从队列中取出一个元素,但是poll()在获取元素失败的时候会返回空,但是remove()失败的时候会抛出异常。 PriorityQueue是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部,但是LinkedHashMap维持的顺序是元素插入的顺序。当遍历一个PriorityQueue时,没有任何顺序保证,但是LinkedHashMap课保证遍历顺序是元素插入的顺序。 WeakHashMap的工作与正常的HashMap类似,但是使用弱引用作为key,意思就是当key对象没有任何引用时,key/value将会被回收。 Array可以容纳基本类型和对象,而ArrayList只能容纳对象。 Array是指定大小的,而ArrayList大小是固定的 在Java7中,ArrayList的默认大小是10个元素,HashMap的默认大小是16个元素(必须是2的幂)。这就是Java7中ArrayList和HashMap类的代码片段。 Comparable接口用于定义对象的自然顺序,而comparator通常用于定义用户定制的顺序。Comparable总是只有一个,但是可以有多个comparator来定义对象的顺序。 你可以使用有序集合,如TreeSet或TreeMap,你也可以使用有顺序的的集合,如list,然后通过Collections.sort()来排序。 你可以使用Arrays.toString()和Arrays.deepToString()方法来打印数组。由于数组没有实现toString()方法,所以如果将数组传递给System.out.println()方法,将无法打印出数组的内容,但是Arrays.toString()可以打印每个元素。 双向循环列表,具体实现自行查阅源码。 采用红黑树实现,具体实现自行查阅源码。 该问题的关键在于面试者使用的是ArrayList的remove()还是Iterator的remove()方法。这有一段示例代码,是使用正确的方式来实现在遍历的过程中移除元素,而不会出现ConcurrentModificationException异常的示例代码。 1.HashMap概述:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。2.HashMap的数据结构:在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上. 需要注意Jdk1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn) Fail-Fast即我们常说的快速失败, Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。Java.util包中的所有集合类都被设计为fail->fast的,而java.util.concurrent中的集合类都为fail-safe的。当检测到正在遍历的集合的结构被改变时,Fail-fast迭代器抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException。 33、string类型的比较方法 ①== java中的==是用来判断对象所使用的内存地址是不是同一个,进而判断是不是同一个对象。例如objA==objB 注意这里是同一个对象才会是true,如果不是同一个对象,哪怕两个对象所有属性都相同也会返回false。 ②equse 而equals则是一个方法,你可以为自己的类编写equals方法来判断是不是相等。这里string类就提供了equals方法来判断两个String对象是不是相同,而不是去判断两个String对象是不是同一个对象,因为我们一般使用中,只关心两个字符串内容相同与否,而不会关心是不是同一个对象。 ③compareTo 只有当string类型的字符串为数值类型的时候,(多用于日期的比较)可以用compareTo来比较,intc=a.compareTo(b) c的值为a-b 不能。重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏。 不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如String、Integer及其它包装类。 静态变量存储在方法区,属于类所有。实例变量存储在堆当中,其引用存在当前线程栈。 当然可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。 采用new 通过反射 采用clone 通过序列化机制 前2者都需要显式地调用构造方法。造成耦合性最高的恰好是第一种,因此你发现无论什么框架,只要涉及到解耦必先减少new的使用。 在idk1.7之前,switch只能支持byte,short,char,int或者其对应的封装类以及Enum类型。从idk1.7之后switch开始支持String。 可以用在byte上,但是不能用在long上。 返回false。在编译过程中,编译器会将s2直接优化为”ab”,会将其放置在常量池当中,s5则是被创建在堆区,相当于s5=newString(“ab”); intern()方法会首先从常量池中查找是否存在该常量值,如果常量池中不存在则现在常量池中创建,如果已经存在则直接返回。比如Strings1=”aa”;Strings2=s1.intern();System.out.print(s1==s2);//返回true equals() clone() getClass() notify(),notifyAll(),wait() toString 强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上: 软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。 弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当JVM进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。 虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。 更多了解参见深入对象引用: 这点在四种引用类型中已经做了解释,这里简单说明一下即可:虽然WeakReference与SoftReference都有利于提高GC和内存的效率,但是WeakReference,一旦失去最后一个强引用,就会被GC回收,而软引用虽然不能阻止被回收,但是可以延迟到JVM内存不足的时候。 不像C语言,我们可以控制内存的申请和释放,在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协。有以下几个使用场景可以充分的说明: 利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题. 通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和HashMap的结合可以构建高速缓存,提供性能。 ==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。换句话说:基本类型比较用==,比较的是他们的值。默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。 hashCode()是Object类的一个方法,返回一个哈希值。如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值。如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的。) hashCode()方法是相应对象整型的hash值。它常用于基于hash的集合类,如Hashtable、HashMap、LinkedHashMap等等。它与equals()方法关系特别紧密。根据Java规范,使用equal()方法来判断两个相等的对象,必须具有相同的hashcode。 将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合。如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入。 有可能,两个不相等的对象可能会有相同的hashcode值,这就是为什么在hashmap中会有冲突。如果两个对象相等,必须有相同的hashcode值,反之不成立。 不行,因为同一对象的hashcode值必须是相同的 如果a和b都是对象,则a==b是比较两个对象的引用,只有当a和b指向的是堆中的同一个对象才会返回true,而a.equals(b)是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String类重写equals()方法,所以可以用于两个不同对象,但是包含的字母相同的比较。 false,因为有些浮点数不能完全精确的表示出来。 +=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。如:bytea=127;byteb=127;b=a+b;//error:cannotconvertfrominttobyteb+=a;//ok(译者注:这个地方应该表述的有误,其实无论a+b的值为多少,编译器都会报错,因为a+b操作会将a、b提升为int类型,所以将int类型赋值给byte就会编译出错) 有错误,short类型在进行运算时会自动提升为int类型,也就是说s1+1的运算结果是int类型。 +=操作符会自动对右边的表达式结果强转匹配左边的数据类型,所以没错。 只能有一个public公共类,但是可以有多个default修饰的类。 使用标号和break; 通过在外层循环中添加标识符 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.创建内部类对象的时刻不依赖于外部类对象的创建。内部类并没有令人疑惑的”is-a”管系,它就像是一个独立的实体。 内部类提供了更好的封装,除了该外围类,其他类都不能访问。 final是一个修饰符,可以修饰变量、方法和类。如果final修饰变量,意味着该变量的值在初始化后不能被改变。finalize方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用finalize没有保证。finally是一个关键字,与try和catch一起用于异常的处理。finally块一定会被执行,无论在try块中是否有发生异常。 java.lang.Cloneable是一个标示性接口,不包含任何方法,clone方法在object类中定义。并且需要知道clone()方法是一个本地方法,这意味着它是由c或c++或其他本地语言实现的。 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。 深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。 几乎所有的人都知道static关键字这两个基本的用法:静态变量和静态方法。也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享。 除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作: 此外static也多用于修饰内部类,此时称之为静态内部类。 最后一种用法就是静态导包,即importstatic.importstatic是在JDK1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名。资源名,可以直接使用资源名,比如: final也是很多面试喜欢问的地方,能回答下以下三点就不错了:1.被final修饰的类不可以被继承2.被final修饰的方法不可以被重写3.被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变。4.被final修饰的方法,JVM会尝试将其内联,以提高运行效率5.被final修饰的常量,在编译阶段会存入常量池中。 回答出编译器对final域要遵守的两个重排序规则更好:1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。 Java中,int类型变量的长度是一个固定值,与平台无关,都是32位。意思就是说,在32位和64位的Java虚拟机中,int类型的长度是相同的。 Integer是int的包装类型,在拆箱和装箱中,二者自动转换。int是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象。 Integer对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是int是一个原始类型的数据,所以占用的空间更少。 如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的double类型。 可以使用String接收byte[]参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。 我们可以做强制转换,但是Java中int是32位的而byte是8位的,所以,如果强制转化int类型的高24位将会被丢弃,byte类型的范围是从-128到128 Serializable接口是一个序列化Java类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是JVM内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。 DOM,SAX,PULL三种解析方式: DOM:消耗内存:先把xml文档都读到内存中,然后再用DOMAPI来访问树形结构,并获取数据。这个写起来很简单,但是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机 SAX:解析效率高,占用内存少,基于事件驱动的:更加简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。 PULL:与SAX类似,也是基于事件驱动,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。 使用有缓冲的IO类,不要单独读取字节或字符 使用NIO和NIO2或者AIO,而非BIO 在finally中关闭流 使用内存映射文件获取更快的IO 优先使用批量操作来插入和更新数据 使用PreparedStatement来避免SQL漏洞 使用数据连接池 通过列名来获取结果集 垃圾回收从理论上非常容易理解,具体的方法有以下几种:1.标记-清除2.标记-复制3.标记-整理4.分代回收更详细的内容参见深入理解垃圾回收算法: 这就是所谓的对象存活性判断,常用的方法有两种:1.引用计数法;2.对象可达性分析。由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法。 简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。 程序运行完毕,jvm会等待非守护线程完成后关闭,但是jvm不会等待守护线程。守护线程最典型的例子就是GC线程。 多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。 通过实现java.lang.Runnable或者通过扩展java.lang.Thread类。相比扩展Thread,实现Runnable接口可能更优.原因有二: Java不支持多继承。因此扩展Thread类就代表这个子类不能扩展其他类。而实现Runnable接口的类还可能扩展另一个类。 类可能只要求可执行即可,因此继承整个Thread类的开销过大。 start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。 Thread类提供了一个holdsLock(Objectobj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着”某条线程”指的是当前线程。 阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一定已经很熟悉了。Java提供了大量方法来支持阻塞,下面让我们逐一分析。 初看起来它们与suspend()和resume()方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。 首先,前面叙述的所有方法都隶属于Thread类,但是这一对却直接隶属于Object类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的wait()方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致从调用该对象的wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。 其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在synchronized方法或块中调用,理由也很简单,只有在synchronized方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常。 关于wait()和notify()方法最后再说明两点:第一:调用notify()方法导致解除阻塞的线程是从因调用该对象的wait()方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。 第二:除了notify(),还有一个方法notifyAll()也可起到类似作用,唯一的区别在于,调用notifyAll()方法将把因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。 谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend()方法和不指定超时期限的wait()方法的调用都可能产生死锁。遗憾的是,Java并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。 以上我们对Java中实现线程阻塞的各种方法作了一番分析,我们重点分析了wait()和notify()方法,因为它们的功能最强大,使用也最灵活,但是这也导致了它们的效率较低,较容易出错。实际使用中我们应该灵活使用各种方法,以便更好地达到我们的目的。 1.互斥条件:一个资源每次只能被一个进程使用。2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁 wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。 关于这两者已经在上面进行详细的说明,这里就做个概括好了: sleep()来自Thread类,和wait()来自Object类。调用sleep()方法的过程中,线程不会释放对象锁。而调用wait方法线程会释放对象锁 sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU 一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。 如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。 这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。 如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。 偏向锁:在JDK1.之后引入的一项锁优化,目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。偏向锁就是偏心的偏,意思是这个锁会偏向第一个获得他的线程,如果接下来的执行过程中,改锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不一定总是对程序运行有利,如果程序中大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑是否使用偏向锁。 轻量级锁:为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在JavaSE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。 通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。 wait()方法应该在循环调用,因为当线程获取到CPU开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用wait和notify方法的代码: 线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如web服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java应用就存在内存泄露的风险。 (1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用。(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约。 可以通过阻塞队列实现,也可以通过wait-notify来实现。 如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。 避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。 CAS,全称为CompareandSwap,即比较-替换。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。 乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。 悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。 ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗? ConcurrentHashMap在jdk1.6和jdk1.8实现原理是不同的。 ConcurrentHashMap是线程安全的,但是与Hashtablea相比,实现线程安全的方式不同。Hashtable是通过对hash表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁。ConcurrentHashMap是采用分离锁的方式,它并没有对整个hash表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其他线程对hash表其他地方的访问。具体实现:ConcurrentHashMap内部有一个Segment. 在jdk8中,ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表->红黑树”的实现。 这两个类非常类似,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于: CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行。 CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务 CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了。 不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。 给线程命名 最小化同步范围 优先使用volatile 尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore 优先使用并发容器而非同步容器. 考虑使用线程池 Java中,可以使用SimpleDateFormat类或者joda-time库来格式日期。DateFormat类允许你使用多种流行的格式来格式化日期。参见答案中的示例代码,代码中演示了将日期格式化成不同的格式,如dd-MM-yyyy或ddMMyyyy。 Datedate=newDate();System.out.println(date);SimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy-MM-ddHH");System.out.println(simpleDateFormat.format(date));System.out.println(simpleDateFormat.parse(simpleDateFormat.format(date))); 七大模块,如下:1.SpringCore:Core封装包是框架的最基础部分,提供IOC和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。 SpringContext:构建于Core封装包基础上的Context封装包,提供了一种框架式的对象访问方法,有些象JNDI注册器。Context封装包的特性得自于Beans封装包,并添加了对国际化(I18N)的支持(例如资源绑定),事件传播,资源装载的方式和Context的透明创建,比如说通过Servlet容器。 SpringAOP:Spring的AOP封装包提供了符合AOPAlliance规范的面向方面的编程实现,让你可以定义,例如方法拦截器(method-interceptors)和切点(pointcuts),从逻辑上讲,从而减弱代码的功能耦合,清晰的被分离开。而且,利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中。 SpringWeb:Spring中的Web包提供了基础的针对Web开发的集成特性,例如多方文件上传,利用Servletlisteners进行IOC容器初始化和针对Web的ApplicationContext。当与WebWork或Struts一起使用Spring时,这个包使Spring可与其他框架结合。 SpringWebMVC:Spring中的MVC封装包提供了Web应用的Model-View-Controller(MVC)实现。Spring的MVC框架并不是仅仅提供一种传统的实现,它提供了一种清晰的分离模型,在领域模型代码和WebForm之间。并且,还可以借助Spring框架的其他特性。 DI(依赖注入),IOC(控制反转),AOP(面向切面编程) IOC:InversionofControl,控制反转模式(也称作依赖性介入)的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器(在Spring框架中是IOC容器)负责将这些联系在一起。 在Java开发中,IOC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制,这称为控制反转,就是被调用类的实例由原先的调用类控制创建、销毁现在转变成由Spring的容器管理。 注入方式:1.接口注入2.属性注入[属性的SET/GET]3.构造注入[构造方法注入]使用构造函数依赖注入时,Spring保证对象其所依赖的所有对象先实例化后,才实例化这个对象。使用set方法依赖注入时,Spring首先实例化对象,然后才实例化所有依赖的对象。*当设值注入与构造注入同时存在时,先执行设置注入,在执行构造注入。 使用DI注入时,Property代表注入类的属性,如果引用其他的bean,则用ref属性来表明被引用bean的名称,如果是引用字符串的话,用value属性。 如:\ 注入DataSource数据源对象,不需要手动关闭数据库连接,JdbcTemplate会帮我们关闭数据库连接 个人理解:如果说汇编语言是对计算机指令的抽象,面向对象语言是对程序要解决的问题的抽象,AOP则是对程序功能本身的抽象。 实现AOP有几种方式:>1.Spring1.2版本中通过ProxyFactoryBean来实现aop,即通过动态代理来实现的,Aspect必须继承MethodBeforeAdvice,MethodAfterAdvice等>2.Spring2.0AOP需要改的是FBI这个类,而且它也不需要再实现某些接口>3.使用标注(@AspectJ)实现AOP SpringMVC是基于过滤器对servlet进行了封装的一个框架,我们使用的时候就在web.xml中配置DispatcherServlet类;SpringMVC工作是主要是通过DispatcherServlet管理接受到的请求并进行处理。*SpringMVC的工作流程描述: upstreamwww.myweb.com{server127.0.0.1:9100weight=1;server127.0.0.1:9200weight=1;} 其中,request、response、session、application、config这五个对象和Servlet的API是一样的。 struts1是基于JSP和servlet的一个开源的Web应用框架,使用的是MVC的设计模式。struts2是基于webwork技术的框架,是sun和webwork公司联手开发的一个功能非常齐全的框架,struts2和struts1没有任何关系,是一个全新的框架 JavaServerFace是基于组件的web开发框架,跟sturts差不多的框架 SessionFactory对应Hibernate的一个数据存储的概念,它是线程安全的,可以被多个线程并发访问。SessionFactory一般只会在启动的时候构建。对于应用程序,最好将SessionFactory通过单例模式进行封装以便于访问。Session是一个轻量级非线程安全的对象(线程间不能共享session),它表示与数据库进行交互的一个工作单元。Session是由SessionFactory创建的,在任务完成之后它会被关闭。Session是持久层服务对外提供的主要接口。Session会延迟获取数据库连接(也就是在需要的时候才会获取)。为了避免创建太多的session,可以使用ThreadLocal将session和当前线程绑定在一起,这样可以让同一个线程获得的总是同一个session。Hibernate3中SessionFactory的getCurrentSession()方法就可以做到。 有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。1.悲观锁,顾名思义悲观的认为在数据处理过程中极有可能存在修改数据的并发事务(包括本系统的其他事务或来自外部系统的事务),于是将处理的数据设置为锁定状态。悲观锁必须依赖数据库本身的锁机制才能真正保证数据访问的排他性2.乐观锁,顾名思义,对并发事务持乐观态度(认为对数据的并发操作不会经常性的发生),通过更加宽松的锁机制来解决由于悲观锁排他性的数据访问对系统性能造成的严重影响。最常见的乐观锁是通过数据版本标识来实现的,读取数据时获得数据的版本号,更新数据时将此版本号加1,然后和数据库表对应记录的当前版本号进行比较,如果提交的数据版本号大于数据库中此记录的当前版本号则更新数据,否则认为是过期数据无法更新。*Hibernate中通过Session的get()和load()方法从数据库中加载对象时可以通过参数指定使用悲观锁;而乐观锁可以通过给实体类加整型的版本字段再通过XML或@Version注解进行配置。 ApplicationContext是spring中较高级的容器。和BeanFactory类似,它可以加载配置文件中定义的bean,将所有的bean集中在一起,当有请求的时候分配bean。另外,它增加了企业所需要的功能,比如,从属性文件从解析文本信息和将事件传递给所指定的监听器。这个容器在org.springframework.context.ApplicationContextinterface接口中定义。 ApplicationContext包含BeanFactory所有的功能,一般情况下,相对于BeanFactory,ApplicationContext会被推荐使用。BeanFactory仍然可以在轻量级应用中使用,比如移动设备或者基于applet的应用程序。 Springbeans是那些形成Spring应用的主干的java对象。它们被SpringIOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 Spring框架定义的beans都是单件beans。在beantag中有个属性”singleton”,如果它被赋为TRUE,bean就是单件,否则就是一个prototypebean。默认是TRUE,所以所有在Spring框架中的beans缺省都是单件。 两者都是装入bean定义信息,装配bean,根据需要分发bean。但是ApplicationContext提供更多功能,它提供了bean工厂所没有的解析信息文本工具,包括对国际化的支持,提供了载入文件资源的通用方法,如载入图片,它可以用注册为监听器的bean发送事件。另外一个很重要的区别是单例bean被载入的方式不一样。bean工厂延迟载入所有的bean,直到getbean方法被调用,才被创建。而ApplicationContext会预装入所有的单例bean,确保需要的时候单例bean都已经准备好了,这样我们的应用就不需要等待这些单例bean被创建。 这里有三种重要的方法给Spring容器提供配置元数据。1.XML配置文件。2.基于注解的配置。3.基于java的配置。 有两个重要的bean生命周期方法,第一个是setup,它是在容器加载bean的时候被调用。第二个方法是teardown它是在容器卸载类的时候被调用。 Spring提供以下几种集合的配置元素:1.\ 有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。1.no:默认的方式是不进行自动装配,通过显式设置ref属性来进行装配。2.byName:通过参数名自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。3.byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。4.constructor:这个方式类似于byType,但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。5.autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。 自动装配的局限性是:1.重写:你仍需用 可以。 基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。 以@Configuration注解为例,它用来标记类可以当做一个bean的定义,被SpringIOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。 在Spring中有两种方式访问Hibernate:1.控制反转HibernateTemplate和Callback。2.继承HibernateDAOSupport提供一个AOP拦截器。 用Spring的SessionFactory调用LocalSessionFactory。集成过程分三步:1.配置theHibernateSessionFactory。2.继承HibernateDaoSupport实现一个DAO。3.在AOP支持的事务中装配。 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。 引入允许我们在已存在的类中增加新的方法和属性。 DispatcherServlet主要用作职责调度工作,本身主要用于控制流程 Springmvc运行原理 1.springmvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求进行真正的处理工作。 2.DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller. 3.DispatcherServlet将请求提交到目标Controller 4.Controller进行业务逻辑处理后,会返回一个ModelAndView 5.DispathcherServlet查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象; 6.视图负责将结果显示到客户端; 视图对象负责渲染返回给客户端。 DispatcherServlet是整个SpringMVC的核心。它负责接收HTTP请求组织协调SpringMVC的各个组成部分。其主要工作有以下三项: 1.截获符合特定格式的URL请求。 2.初始化DispatcherServlet上下文对应的WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。 3.初始化SpringMVC的各个组成组件,并装配到DispatcherServlet中。 ==================================================================================================== DispatcherServlet:前端控制器;(相当于一个转发器,中央处理器,调度) ModelAndView:模型和视图的结合体;(Springmvc的底层对象) HandlerMapping:处理器映射器; 工作原理及为什么要用? 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Sesssion 4.创建事务 Transation 5.持久化操作 6.提交事务 7.关闭Session 8.关闭SesstionFactory 为什么要用: 2.Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作 4.hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。 (三)Struts工作机制?为什么要使用Struts? 工作机制: Struts的工作流程: 在web应用启动时就会加载初始化ActionServlet,ActionServlet从struts-config.xml文件中读取配置信息,把它们存放到各种配置对象,当ActionServlet接收到一个客户请求时,将执行如下流程. (1)检索和用户请求匹配的ActionMapping实例,如果不存在,就返回请求路径无效信息; (2)如果ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中; (3)根据配置信息决定是否需要表单验证.如果需要验证,就调用ActionForm的validate()方法; (4)如果ActionForm的validate()方法返回null或返回一个不包含ActionMessage的ActuibErrors对象,就表示表单验证成功; (5)ActionServlet根据ActionMapping所包含的映射信息决定将请求转发给哪个Action,如果相应的Action实例不存在,就先创建这个实例,然后调用Action的execute()方法; (6)Action的execute()方法返回一个ActionForward对象,ActionServlet再把客户请求转发给ActionForward对象指向的JSP组件; (7)ActionForward对象指向JSP组件生成动态网页,返回给客户; JSP、Servlet,JavaBean技术的出现给我们构建强大的企业应用系统提供了可能。但用这些技术构建的系统非常的繁乱,所以在此之上,我们需要一个规则、一个把这些技术组织起来的规则,这就是框架,Struts便应运而生。 基于Struts开发的应用由3类组件构成:控制器组件、模型组件、视图组件 (四)如何优化Hibernate? 1.使用双向一对多关联,不使用单向一对多 2.灵活使用单向一对多关联 3.不用一对一,用多对一取代 4.配置对象缓存,不使用集合缓存 5.一对多集合使用Bag,多对多集合使用Set 6.继承类使用显式多态 7.表字段要少,表关联不要怕多,有二级缓存撑腰 Spring工作原理 内部最核心的就是IOC了,动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射,反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行时,跟xmlSpring的配置文件来动态的创建对象,和调用对象里的方法的。 Spring还有一个核心就是AOP这个就是面向切面编程,可以为某一类对象进行监督和控制(也就是在调用这类对象的具体方法的前后去调用你指定的模块)从而达到对一个模块扩充的功能。这些都是通过配置类达到的。 Spring目的:就是让对象与对象(模块与模块)之间的关系没有通过代码来关联,都是通过配置类说明管理的(Spring根据这些配置内部通过反射去动态的组装对象) Struts2框架本身大致可以分为3个部分: 核心控制器FilterDispatcher、业务控制器Action和用户实现的企业业务逻辑组件。 核心控制器FilterDispatcher是Struts2框架的基础,包含了框架内部的控制流程和处理机制。 业务控制器Action和业务逻辑组件是需要用户来自己实现的。 Struts2的工作流程相对于Struts1要简单,与WebWork框架基本相同,所以说Struts2是WebWork的升级版本。 基本简要流程如下: 1.客户端初始化一个指向Servlet容器的请求; 2.这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMeshPlugin) 3.接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action; 4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy; 5、ActionProxy通过ConfigurationManager(配置管理器)询问框架的配置文件,找到需要调用的Action类; 6、ActionProxy创建一个ActionInvocation的实例。 8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。 返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。 在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper; 9.响应的返回是通过我们在web.xml中配置的过滤器; 10、如果ActionContextCleanUp是当前使用的,则FilterDispatecher将不会清理sreadlocalActionContext; 如果ActionContextCleanUp不使用,则将会去清理 2、说下Struts的设计模式 MVC模式:web应用程序启动时就会加载并初始化ActionServler。用户提交表单时,一个配置好的ActionForm对象被创建,并被填入表单相应的数据,ActionServler根据Struts-config.xml文件配置好的设置决定是否需要表单验证,如果需要就调用ActionForm的Validate()验证后选择将请求发送到哪个Action,如果Action不存在,ActionServlet会先创建这个对象,然后调用Action的execute()方法。Execute()从ActionForm对象中获取数据,完成业务逻辑,返回一个ActionForward对象,ActionServlet再把客户请求转发给ActionForward对象指定的jsp组件,ActionForward对象指定的jsp生成动态的网页,返回给客户。 3、拦截器和过滤器的区别 1、拦截器是基于java反射机制的,而过滤器是基于函数回调的。 2、拦截器不依赖于servlet容器,过滤器依赖于servlet容器。 3、拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用。 4、拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。 5、在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。 4、struts1于struts2的比较 1、Action类:Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口。 Struts2Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。 Struts2提供一个ActionSupport基类去实现常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。 2、线程模式:Struts1Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。 单例策略限制了Struts1Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。 Struts2Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上, servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题) 3、Servlet依赖: Struts1Action依赖于ServletAPI,因为当一个Action被调用时HttpServletRequest和HttpServletResponse被传递给execute方法。 但是,其他的元素减少或者消除了直接访问HttpServetRequest和HttpServletResponse的必要性。 4、可测性: 测试Struts1Action的一个主要问题是execute方法暴露了servletAPI(这使得测试要依赖于容器)。 一个第三方扩展--StrutsTestCase--提供了一套Struts1的模拟对象(来进行测试) Struts2Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。 5、捕获输入: Struts1使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是, 开发者可能是在重新描述(创建)已经存在的JavaBean(仍然会导致有冗余的javabean)。 Struts2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。 Action属性能够通过web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。 这种ModelDriven特性简化了taglib对POJO输入对象的引用。 6、表达式语言: Struts1整合了JSTL,因此使用JSTLEL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。 Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--"ObjectGraphNotationLanguage"(OGNL). 7、绑定值到页面(view): Struts1使用标准JSP机制把对象绑定到页面中来访问。 Struts2使用"ValueStack"技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。 ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。 8、类型转换: Struts1ActionForm属性通常都是String类型。Struts1使用Commons-Beanutils进行类型转换。每个类一个转换器,对每一个实例来说是不可配置的。 Struts2使用OGNL进行类型转换。提供基本和常用对象的转换器。 9、校验: Struts1支持在ActionForm的validate方法中手动校验,或者通过CommonsValidator的扩展来校验。 同一个类可以有不同的校验内容,但不能校验子对象。 Struts2支持通过validate方法和XWork校验框架来进行校验。XWork校验框架使用为属性类类型定义的校验和内容校验, 来支持chain校验子属性. 10、Action执行的控制: Struts1支持每一个模块有单独的RequestProcessors(生命周期),但是模块中的所有Action必须共享相同的生命周期。 Struts2支持通过拦截器堆栈(InterceptorStacks)为每一个Action创建不同的生命周期。堆栈能够根据需要和不同的Action一起使用。 为什么要使用Struts2 2.使用OGNL进行参数传递。OGNL提供了在Struts2里访问各种作用域中的数据的简单方式,你可以方便的获取Request,Attribute,Application,Session,Parameters中的数据。大大简化了开发人员在获取这些数据时的代码量。 (OGNL是Object-GraphNavigationLanguage的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。) 以完成一些JavaWeb项目中比较通用的功能。在我实现的的一Web项目中,就是使用Struts2的拦截器来完成了系统中的权限验证功能。 4.易于测试:Struts2的Action都是简单的POJO,这样可以方便的对Struts2的Action编写测试用例,大大方便了5JavaWeb项目的测试。 5.易于扩展的插件机制在Struts2添加扩展是一件愉快而轻松的事情,只需要将所需要的Jar包放到WEB-INF/lib文件夹中,在struts.xml中作一些简单的设置就可以实现扩展; 6.模块化管理:Struts2已经把模块化作为了体系架构中的基本思想,可以通过三种方法来将应用程序模块化:将配置信息拆分成多个文件把自包含的应用模块创建为插件创建新的框架特性,即将与特定应用无关的新功能组织成插件,以添加到多个应用中去。 为应用程序添加全局的Result,和在配置文件中对异常进行处理,这样当处理过程中出现指定异常时,可以跳转到特定页面。 他的如此之多的优点,是很多人比较的青睐,与spring,Hibernate进行结合,组成了现在比较流行的ssh框架, struts2有哪些优点 1.在软件设计上Struts2的应用可以不依赖于ServletAPI和strutsAPI。Struts2的这种设计属于无侵入式设计; 2.拦截器,实现如参数拦截注入等功能; 3.类型转换器,可以把特殊的请求参数转换成需要的类型; 4.多种表现层技术,如:JSP、freeMarker、Velocity等; 5.Struts2的输入校验可以对指定某个方法进行校验; 6.提供了全局范围、包范围和Action范围的国际化资源文件管理实现 struts2是如何启动的? struts2框架是通过Filter启动的,即StrutsPrepareAndExecuteFilter,此过滤器为struts2的核心过滤器; StrutsPrepareAndExecuteFilter的init()方法中将会读取类路径下默认的配置文件struts.xml完成初始化操作。 struts2读取到struts.xml的内容后,是将内容封装进javabean对象然后存放在内存中,以后用户的每次请求处理将使用内存中的数据,而不是每次请求都读取struts.xml文件。 struts2框架的核心控制器是什么?它有什么作用? 1)Struts2框架的核心控制器是StrutsPrepareAndExecuteFilter。 2)作用:负责拦截由 默认情况下,如果用户请求的路径不带后缀或者后缀以.action结尾,这时请求将被转入struts2框架处理, 否则struts2框架将略过该请求的处理。可以通过常量"struts.action.extension"修改action的后缀, 如: 如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开。 struts2配置文件的加载顺序: struts.xml——>struts.properties,常量可以在struts.xml或struts.properties中配置,如果在多个文件中配置了同一个常量,则后一个文件中配置的常量值会覆盖前面文件中配置的常量值. struts.xml文件的作用:通知Struts2框架加载对应的Action资源 struts2是如何管理action的?这种管理方式有什么好处? struts2框架中使用包来管理Action,包的作用和java中的类包是非常类似的。 struts2默认能解决get和post提交方式的乱码问题吗? 不能。struts.i18n.encoding=UTF-8属性值只能解析POST提交下的乱码问题 ActionContext、ServletContext、pageContext的区别? 1)ActionContext是当前的Action的上下文环境,通过ActionContext可以获取到request、session、ServletContext等与Action有关的对象的引用; 2)ServletContext是域对象,一个web应用中只有一个ServletContext,生命周期伴随整个web应用; 3)pageContext是JSP中的最重要的一个内置对象,可以通过pageContext获取其他域对象的应用,同时它是一个域对象,作用范围只针对当前页面,当前页面结束时,pageContext销毁,生命周期是JSP四个域对象中最小的。 ========================================================== Spring面试题 1.Spring框架有哪些模块? Spring框架由七个模块组成组成,这7个模块(或组件)均可以单独存在,也可以与其它一个或多个模块联合使用,如下所示: (1)Spring核心容器——IoC容器 (2)SpringAOP (3)SpringORM (4)SpringDAO (5)SpringWEB (6)Spring上下文(Context) (7)SpringMVC 2.为什么要使用Spring框架,它有什么优点? (1)轻量级的框架 (2)非侵入性的 (3)可以整合其它的框架,比如Struts,Hibernate等 (4)可以提供事务管理 4.怎么使用Spring配置事务 (1)使用TransactionProxyFactoryBean为目标Bean生成事务代理的配置。此方式是最传统、配置文件最臃肿、最难以阅读的方式。 (2)采用Bean继承的事务代理配置方式,比较简洁,但依然是增量式配置。 (3)采用BeanNameAutoProxyCreator,根据BeanName自动生成事务代理的方式。这是直接利用Spring的AOP框架配置事务代理的方式,需要对Spring的AOP框架有所理解。但这种方式避免了增量式配置,效果非常不错。 (4)采用DefaultAdvisorAutoProxyCreator,直接利用Spring的AOP框架配置事务代理的方式,效果非常不错,只是这种配置方式的可读性不如第3种方式。 5.请你谈谈SSH整合 SSH整合: (1)Struts(表示层)+Spring(业务层)+Hibernate(持久层) (2)Struts:Struts是一个表示层框架,主要作用是界面展示、接收请求和分发请求。 在MVC框架中,Struts属于VC层次,负责界面表现,负责MVC关系的分发。 (View:沿用JSP,HTTP,Form,Tag,Resourse;Controller:ActionServlet,struts-config.xml,Action) (3)Hibernate: Hibernate是一个持久层框架,它只负责与关系数据库的操作。 (4)Spring:Spring是一个业务层框架,是一个整合的框架,能够很好地黏合表示层与持久层。 9.Spring里如何定义HibernateMapping? 添加hibernatemapping文件到WEB-INF目录下的applicationContext.xml文件中。 10.解释一下DependencyInjection(DI,依赖注入)和IoC(InversionofControl,控制反转) 1.依赖注入DI是一种设计模式,通常也称作控制反转,尽管在技术上来讲,依赖注入是一个IoC的特殊实现,依赖注入是指一个对象应用另外一个对象来提供一种特殊的能力。例如,把一个数据库连接以参数的形式传到一个对象的构造函数里面而不是在那个对象内部自行创建一个连接。 2.控制反转和依赖注入的基本思想就是把类的依赖从类内部转化到外部以减少依赖。 3.应用控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用,传递给它。 也可以说,依赖被注入到对象中。所以,控制反转是,关于一个对象如何获取它依赖的对象的引用,这个责任的反转。 13.Spring中的核心类有那些,各有什么作用 BeanFactory:产生一个新的实例,可以实现单例模式。 BeanWrapper:提供统一的get及set方法。 ApplicationContext:提供Spring框架的实现,包括BeanFactory的所有功能。 14.什么是AOP,AOP的作用是什么 面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。 允许用户实现自定义切面,用AOP来完善OOP的使用,可以把SpringAOP看作是对Spring的一种增强. 15.使用Spring有什么好处 (2)Spring能消除在许多工程上对Singleton的过多使用。 (3)Spring能消除使用各种格式的属性定制文件的需要,在整个工程中,可通过一种一致的方法来进行配置。 (4)Spring能通过接口而不是类促进好的编程习惯,减少编程代价到几乎为零。 (5)Spring被设计为让使用它创建的应用尽可能少的依赖于它的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。 (6)使用Spring构建的应用程序易于单元测试。 (7)Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或localEJBs来实现业务接口,却不会影响调用代码。 (8)Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适于许多web应用。 (9)Spring为数据存取提供了一致的框架,不论是使用JDBC或O/Rmapping产品(如Hibernate)。 16.什么是Spring,它有什么特点 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 (1)轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布,并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。 (2)控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。 (3)面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。 (4)容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。 比较Hibernate的三种检索策略优缺点 1.立即检索: 优点:对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象; 缺点:1.select语句太多;2.可能会加载应用程序不需要访问的对象白白浪费许多内存空间; 2.延迟检索: 优点:由应用程序决定需要加载哪些对象,可以避免可执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并且能节省内存空间; 缺点:应用程序如果希望访问游离状态代理类实例,必须保证他在持久化状态时已经被初始化; 3.迫切左外连接检索 优点: 1.对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便地冲一个对象导航到与它关联的对象。 2.使用了外连接,select语句数目少; 缺点: 1.可能会加载应用程序不需要访问的对象,白白浪费许多内存空间; 2.复杂的数据库表连接也会影响检索性能; hibernate里面的sortedcollection和orderedcollection有什么区别 sortedcollection是在内存中通过java比较器进行排序的 orderedcollection是在数据库中通过orderby进行排序的 ========================================== springhibernatestruts的笔试面试题 2.Hibernate是如何延迟加载 1.Hibernate2延迟加载实现:a)实体对象b)集合(Collection) 2.Hibernate3提供了属性的延迟加载功能,当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的关系(如:一对多、多对多的关系)类与类之间的关系主要体现在表与表之间的关系进行操作,它们都是对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many、 4.说下Hibernate的缓存机制 1.内部缓存存在Hibernate中又叫一级缓存,属于应用事物级缓存; 2.二级缓存: a)应用级缓存; b)分布式缓存; 条件:数据不会被第三方修改、数据大小在可接受范围、数据更新频率低、同一数据被系统频繁使用、非关键数据; c)第三方缓存的实现; 6.如何优化Hibernate? 1.使用双向一对多关联,不使用单向一对多; 2.灵活使用单向一对多关联; 3.不用一对一,用多对一取代; 4.配置对象缓存,不使用集合缓存; 5.一对多集合使用Bag,多对多集合使用Set; 6.继承类使用显式多态; 7.表字段要少,表关联不要怕多,有二级缓存撑腰; 7.Struts工作机制?为什么要使用Struts? 工作机制:Struts的工作流程:在web应用启动时就会加载初始化ActionServlet,ActionServlet从struts-config.xml文件中读取配置信息,把它们存放到各种配置对象当ActionServlet接收到一个客户请求时,将执行如下流程; -(1)检索和用户请求匹配的ActionMapping实例,如果不存在,就返回请求路径无效信息; -(2)如果ActionForm实例不存在,就创建一个ActionForm对象,把客户提交的表单数据保存到ActionForm对象中; -(3)根据配置信息决定是否需要表单验证.如果需要验证,就调用ActionForm的validate()方法; -(4)如果ActionForm的validate()方法返回null或返回一个不包含ActionMessage的ActuibErrors对象,就表示表单验证成功; -(5)ActionServlet根据ActionMapping所包含的映射信息决定将请求转发给哪个Action,如果相应的Action实例不存在,就先创建这个实例,然后调用Action的execute()方法; -(6)Action的execute()方法返回一个ActionForward对象,ActionServlet在把客户请求转发给ActionForward对象指向的JSP组件; -(7)ActionForward对象指向JSP组件生成动态网页,返回给客户; ================================================= hibernate面试题小集 3.说说Hibernate中的update()和saveOrUpdate()的区别,session的load()和get()的区别。 saveOrUpdate()方法可以实现update()的功能,但会多些步骤,具体如下: 1.如果对象在该session中已经被持久化,不进行操作; 2.对象的标识符属性(identifierproperty)在数据库中不存在或者是个暂时的值,调用save()方法保存它; 3.如果session中的另一个对象有相同的标识符抛出一个异常;以上皆不符合则调用update()更新之。 Session.load/get方法均可以根据指定的实体类和id从数据库读取记录,并返回与之对应的实体对象。 session的get()和load()其区别在于: 1.如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException; 2.load方法可返回实体的代理类实例,而get方法永远直接返回实体类; 3.load方法可以充分利用内部缓存和二级缓存中的现有数据,而get方法则仅仅在内部缓存中进行数据查找,如没有发现对应数据,将越过二级缓存,直接调用SQL完成数据读取。 hibernate中对象的三种状态 瞬时态(Transient)、持久态(Persistent)、脱管态(Detached)。 处于持久态的对象也称为PO(PersistenceObject),瞬时对象和脱管对象也称为VO(ValueObject)。 瞬时态: 由new命令开辟内存空间的java对象,eg.Personperson=newPerson(”amigo”,“女”); 如果没有变量对该对象进行引用,它将被java虚拟机回收。 此时该瞬时对象转变成持久化对象。 持久态: 处于该状态的对象在数据库中具有对应的记录,并拥有一个持久化标识。如果是用hibernate的delete()方法,对应的持久对象就变成瞬时对象,因数据库中的对应数据已被删除,该对象不再与数据库的记录关联。 当一个session执行close()或clear()、evict()之后,持久对象变成脱管对象,此时持久对象会变成脱管对象,此时该对象虽然具有数据库识别值,但它已不在HIbernate持久层的管理之下。 持久对象具有如下特点: 1.和session实例关联; 2.在数据库中有与之关联的记录。 脱管态: 当与某持久对象关联的session被关闭后,该持久对象转变为脱管对象。当脱管对象被重新关联到session上时,并再次转变成持久对象。 脱管对象拥有数据库的识别值,可通过update()、saveOrUpdate()等方法,转变成持久对象。 脱管对象具有如下特点: 1.本质上与瞬时对象相同,在没有任何变量引用它时,JVM会在适当的时候将它回收; 2.比瞬时对象多了一个数据库记录标识值。 1.在数据库中条件查询速度很慢的时候,如何优化 1.建索引; 2.减少表之间的关联; 3.优化sql,尽量让sql很快定位数据,不要让sql做全表查询,应该走索引,把数据量大的表排在前面; 4.简化查询字段,没用的字段不要,已经对返回结果的控制,尽量返回少量数据; =============================== spring面试题 1、简述你对IoC(InversionofControl)的理解,描述一下Spring中实现DI(DependencyInjection)的几种方式。 spring的IOC有三种注入方式: 第一是根据属性注入也叫set方法注入; 第二种是根据构造方法进行注入; 第三种是根据注解进行注入,这种方式我认为比较好,方便,要是bean多的话,使用前两种方式会使得配置文件过于臃肿。 3、简单描述SpringFramework与Struts的不同之处,整合Spring与Struts有哪些方法,哪种最好,为什么? 答、Spring是完整的一站式框架,而Struts仅是MVC框架,且着重于MVC中的C。 Spring有三种方式整合 Struts:使用Spring的ActionSupport类整合Struts; 使用Spring的DelegatingRequestProcessor覆盖Struts的RequestProcessor; 将StrutsAction管理委托给Spring框架,动作委托最好。 4、Hibernate中的update()和saveOrUpdate()的区别 答、saveOrUpdate()方法可以实现update()的功能,但会多些步骤,具体如下: struts2的原理 一工作原理: 在Struts2框架中的处理大概分为以下几个步骤: 1.客户端初始化一个指向Servlet容器(例如Tomcat)的请求 2.这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,iteMeshPlugin) 3.接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action 4.如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy(泼若科c) 5.ActionProxy通过ConfigurationManager(康飞科润熊Manager)询问框架的配置文件,找到需要调用的Action类.ActionProxy创建一个ActionInvocation(Action印则k熊)的实例。 7.一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper 二struts2工作流程: 1、客户端浏览器发出HTTP请求. 2、根据web.xml配置,该请求被FilterDispatcher接收 3、根据struts.xml配置,找到需要调用的Action类和方法,并通过IoC方式,将值注入给Aciton 4、Action调用业务逻辑组件处理业务逻辑,这一步包含表单验证。 5、Action执行完毕,根据struts.xml中的配置找到对应的返回结果result,并跳转到相应页面 6、返回HTTP响应到客户端浏览器 JSP的运行原理 WEB容器(Servlet引擎)接收到以.jsp为扩展名的URL的访问请求时,它将把该访问请求交给JSP引擎去处理。Tomcat中的JSP引擎就是一个Servlet程序,它负责解释和执行JSP页面。 每个JSP页面在第一次被访问时,JSP引擎将它翻译成一个Servlet源程序,接着再把这个Servlet源程序编译成Servlet的class类文件,然后再由WEB容器(Servlet引擎)像调用普通Servlet程序一样的方式来装载和解释执行这个由JSP页面翻译成的Servlet程序。 六、其他 1、servlet执行流程 2、springMVC的执行流程 springMVC是由dispatchservlet为核心的分层控制框架。首先客户端发出一个请求web服务器解析请求url并去匹配dispatchservlet的映射url,如果匹配上就将这个请求放入到dispatchservlet,dispatchservlet根据mapping映射配置去寻找相对应的handel,然后把处理权交给找到的handel,handel封装了处理业务逻辑的代码,当handel处理完后会返回一个逻辑视图modelandview给dispatchservlet,此时的modelandview是一个逻辑视图不是一个正式视图,所以dispatchservlet会通过viewresource视图资源去解析modelandview,然后将解析后的参数放到view中返回到客户端并展现。 3、给定一个txt文件,如何得到某字符串出现的次数 Filefile=newFile("E://test.txt"); InputStreamis=newFileInputStream(file); byteb[]=newbyte[1024]; inta=is.read(b); Stringstr[]=newString(b,0,a).split(""); intcount=0; for(inti=0;i if("a".equals(str[i]))count++; System.out.println(count); 4、Java设计模式思想(单列模式,工厂模式,策略模式,共23种设计模式) a)单例模式:单例模式核心只需要new一个实例对象的模式,比如数据库连接,在线人数等,一些网站上看到的在线人数统计就是通过单例模式实现的,把一个计时器存放在数据库或者内存中,当有人登陆的时候取出来加一再放回去,有人退出登陆的时候取出来减一再放回去,但是当有两个人同时登陆的时候,会同时取出计数器,同时加一,同时放回去,这样的话数据就会错误,所以需要一个全局变量的对象给全部人使用,只需要new出一个实例对象,这就是单例模式的应用,并且单例模式节省资源,因为它控制了实例对象的个数,并有利于gc回收。 b)策略模式:就是将几个类中公共的方法提取到一个新的类中,从而使扩展更容易,保证代码的可移植性,可维护性强。比如有个需求是写鸭子对象,鸭子有叫,飞,外形这三种方法,如果每个鸭子类都写这三个方法会出现代码的冗余,这时候我们可以把鸭子中的叫,飞,外形这三个方法提取出来,放到鸭父类中,让每个鸭子都继承这个鸭父类,重写这三个方法,这样封装的代码可移植性强,当用户提出新的需求比如鸭子会游泳,那么对于我们oo程序员来讲就非常简单了我们只需要在鸭父类中加一个游泳的方法,让会游泳的鸭子重写游泳方法就可以了。 c)工厂模式:简单的工厂模式主要是统一提供实例对象的引用,通过工厂模式接口获取实例对象的引用。比如一个登陆功能,后端有三个类,controller类,interface类,实现接口的实现类。当客户端发出一个请求,当请求传到controller类中时,controller获取接口的引用对象,而实现接口的实现类中封装好了登陆的业务逻辑代码。当你需要加一个注册需求的时候只需要在接口类中加一个注册方法,实现类中实现方法,controller获取接口的引用对象即可,不需要改动原来的代码,这种做法是的可拓展性强。 5、冒泡排序、二分查找 a)冒泡 publicstaticvoidmp(inta[]){ intswap=0;
其中weight=1表示权重,用于后端服务器性能不均的情况,访问比率约等于权重之比,权重越大访问机会越多;upstream是配置nginx与后端服务器负载均衡非常重要的一个模块,并且它还能对后端的服务器的健康状态进行检查,若后端服务器中的一台发生故障,则前端的请求不会转发到该故障的机器;
Thebean标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。类型用于注入一列值,允许有相同的值。2.\<