java面试题总结(持续更新)
java面试题总结
其中收录了一些其他博客上的面试题,欢迎大家补充和讨论
一、java基础
1、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。
Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
2、JDK、JRE、JVM关系是什么?
JDK(Java Development Kit)即为Java开发工具包,包含编写Java程序所必须的编译、运行等开发工具以及JRE。开发工具如:用于编译java程序的javac命令、用于启动JVM运行java程序的java命令、用于生成文档的javadoc命令以及用于打包的jar命令等等。
JRE(Java Runtime Environment)即为Java运行环境,提供了运行Java应用程序所必须的软件环境,包含有Java虚拟机(JVM)和丰富的系统类库。系统类库即为java提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。
JVM(Java Virtual Machines)即为Java虚拟机,提供了字节码文件(.class)的运行环境支持。
简单说,就是JDK包含JRE包含JVM。
3、Java支持的数据类型有哪些?什么是自动拆装箱?
基本数据类型:
整数值型:byte,short,int,long,
字符型:char
浮点类型:float,double
布尔型:boolean
整数默认int型,小数默认是double型。Float和long类型的必须加后缀。
首先知道String是引用类型不是基本类型,引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。引用类型包括类、接口、数组等。String类还是final修饰的。
而包装类就属于引用类型,自动装箱和拆箱就是基本类型和引用类型之间的转换,至于为什么要转换,因为基本类型转换为引用类型后,就可以new对象,从而调用包装类中封装好的方法进行基本类型之间的转换或者toString(当然用类名直接调用也可以,便于一眼看出该方法是静态的),还有就是如果集合中想存放基本类型,泛型的限定类型只能是对应的包装类型。
4、面向对象是什么?
面向对象是一种思想,世间万物都可以看做一个对象,这里只讨论面向对象编程(OOP),Java是一个支持并发、基于类和面向对象的计算机编程语言,面向对象软件开发的优点:
代码开发模块化,更易维护和修改;
代码复用性强;
增强代码的可靠性和灵活性;
增加代码的可读性。
- 面向对象的四大基本特性:
抽象:提取现实世界中某事物的关键特性,为该事物构建模型的过程。对同一事物在不同的需求下,需要提取的特性可能不一样。得到的抽象模型中一般包含:属性(数据)和操作(行为)。这个抽象模型我们称之为类。对类进行实例化得到对象。
封装:封装可以使类具有独立性和隔离性;保证类的高内聚。只暴露给类外部或者子类必须的属性和操作。类封装的实现依赖类的修饰符(public、protected和private等)
继承:对现有类的一种复用机制。一个类如果继承现有的类,则这个类将拥有被继承类的所有非私有特性(属性和操作)。这里指的继承包含:类的继承和接口的实现。
多态:多态是在继承的基础上实现的。多态的三个要素:继承、重写和父类引用指向子类对象。父类引用指向不同的子类对象时,调用相同的方法,呈现出不同的行为;就是类多态特性。多态可以分成编译时多态和运行时多态。
多态的好处
允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。主要有以下优点:
-
可替换性:多态对已存在代码具有可替换性
-
可扩充性:增加新的子类不影响已经存在的类结构
-
接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的。
-
灵活性
-
简化性
代码中如何实现多态
实现多态主要有以下三种方式:
1. 接口实现
2. 继承父类重写方法
3. 同一类中进行方法重载
虚拟机是如何实现多态的
动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法。
抽象、封装、继承和多态是面向对象的基础。在面向对象四大基础特性之上,我们在做面向对象编程设计时还需要遵循有一些基本的设计原则。
- 面向对象的七大设计原则:
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.职责链模式
主要讲一下单例模式,
单例模式:在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例
这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,
“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,
优点
缺点
-
-
public class Singleton {
-
-
-
private Singleton() {
-
-
-
-
private static Singleton single=null;
-
-
-
-
-
//静态工厂方法
-
-
-
-
-
public static Singleton getInstance() {
-
-
-
-
-
if (single == null) {
-
-
-
-
-
single = new Singleton();
-
-
-
-
-
}
-
-
-
-
-
-
return single;}Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:1、在getInstance方法上加同步public static synchronized Singleton getInstance() {if (single == null) {single = new Singleton();}return single;}2、双重检查锁定 :public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;
-
-
-
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance() {
return single;
}
}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
饿汉式和懒汉式区别
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。
工厂模式
http://www.cnblogs.com/poissonnotes/archive/2010/12/01/1893871.html
这里只是简单描述了定义和特征以及设计模式的关系,具体细节不讨论。
7、&和&&的区别?
&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
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)和方法区(method area)的用法。
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。
14、接口和抽象类的区别是什么?
从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:
接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
类可以实现很多个接口,但是只能继承一个抽象类
类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。
也可以参考JDK8中抽象类和接口的区别。
-
-
接口的意义
-
接口的意义用三个词就可以概括:规范,扩展,回调。
-
-
抽象类的意义
-
抽象类的意义可以用三句话来概括:
-
-
为其他子类提供一个公共的类型
-
封装子类中重复定义的内容
-
定义抽象方法,子类虽然有不同的实现,但是定义时一致的
-
15、String和StringBuilder、StringBuffer的区别?
Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和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接口继承?
Collection接口指定一组对象,对象即为它的元素。如何维护这些元素由Collection的具体实现决定。例如,一些如List的Collection实现允许重复的元素,而其它的如Set就不允许。很多Collection实现有一个公有的clone方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为Collection是一个抽象表现。重要的是实现。
当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。
在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制。特定的实现应该决定它是否可以被克隆和序列化。
20、为何Map接口不继承Collection接口?
尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。
如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。
21、什么是迭代器(Iterator)?
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的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()方法有何重要性?
HashMap使用Key对象的hashCode()和equals()方法去决定key-value对的索引。当我们试着从HashMap中获取值的时候,这些方法也会被用到。如果这些方法没有被正确地实现,在这种情况下,两个不同Key也许会产生相同的hashCode()和equals()输出,HashMap将会认为它们是相同的,然后覆盖它们,而非把它们存储到不同的地方。同样的,所有不允许存储重复数据的集合类都使用hashCode()和equals()去查找重复,所以正确实现它们非常重要。equals()和hashCode()的实现应该遵循以下规则:
(1)如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()总是为true的。
(2)如果o1.hashCode() == o2.hashCode(),并不意味着o1.equals(o2)会为true。
具体可以参考 http://blog.csdn.net/javazejian/article/details/51348320
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结合来做:
package test;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Test1 {
//采用set来做,因为set是不重复的
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(3);
list.add(2);
list.add(1);
System.out.println(list);
//上面的list里装的有重复的数据,我要用set来去掉重复
Set<Integer> set=new HashSet<Integer>(list);
System.out.println(set);//这里的set里的值已经是去掉了重复的了,如果我还想要将原有的list去重复,如下:
System.out.println(list);
list.clear();//将原来的list的数据全部清空
System.out.println(list);
list.addAll(set);//将set集合的值放入到list里,因为set里是没有重复的了,这个时候list也就没有重复的了
System.out.println(list);
}
}
2.采用双重循环来做:
package test;
import java.util.ArrayList;
import java.util.List;
public class Test2 {
//方法二:采用循环来做
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(3);
list.add(2);
list.add(1);
list.add(1);
list.add(4);
list.add(4);
System.out.println(list);
for (int i = 0; i < list.size()-1; i++) {//从第一个数开始,到最后一个数-1次循环
for (int j = list.size()-1; j >i;j--) {//从最后一个数开始到i+1
if(list.get(i)==list.get(j)){
list.remove(j);//这里的remove里的参数j就是角标,通过角标移除数据
}
}System.out.println(list);
}
System.out.println();
System.out.println(list);
}
}
3.采用contains()方法来做,一层循环即可解决问题:
package test;
import java.util.ArrayList;
import java.util.List;
public class Test3 {
public static void main(String[] args) {
//测试方法
List<Integer> list=new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(3);
list.add(2);
list.add(1);
list.add(1);
list.add(4);
list.add(4);
System.out.println(list);
List<Integer> newList = getNewList(list);
System.out.println(newList);
}
/**
*
* 采用contains()方法
* @param list
* @return list
* 说明:我需要一个不重复的list,那么我循环你给我的带重复数据的list,只有满足我新的list里不包含重复list里的值的时候,我再把获取的原list的值放的我新的list里,
* 也就是如果我的新的list有了值,那么我就不向像新的里面添加值了。
*/
public static List<Integer> getNewList(List<Integer> list){
List<Integer> myList=new ArrayList<Integer>();
for (int i = 0; i < list.size(); i++) {
if(!myList.contains(list.get(i))){
myList.add(list.get(i));;
}
}
return myList;
}
}
4.还是双重循环,也不用方法二来做
/**
* @author yanxu
*用双重循环去掉重复,不用任何方法,也不用那个:外层 循环从0到长度-1,后面那个相反的那个
*/
public static List<Integer> getDistinctList(List<Integer> list){
List<Integer> newList = new ArrayList<Integer>();
for (int i = 0; i < list.size(); i++) {
//标识,方便下面使用
boolean flag = false;
//只要新的list里包含循环到的值,就说明重复了。
for(int j = 0; j<newList.size(); j++){
if(list.get(i) == newList.get(j)){
flag = true;
//找到一次就可以退出了 ,不需要再进行额外的次数;而且这里也不会再出现第二次 。
break;
}
}
if(!flag){
newList.add(list.get(i));
}
}
return newList;
}
-
-
poll()方法和remove()方法区别?
-
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。
WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。
最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片段。
-
-
private static final int DEFAULT_CAPACITY = 10;
-
//from HashMap.java JDK 7
-
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
-
Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。
你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过 Collections.sort() 来排序。
你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。由于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。
双向循环列表,具体实现自行查阅源码。
采用红黑树实现,具体实现自行查阅源码。
该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法。这有一段示例代码,是使用正确的方式来实现在遍历的过程中移除元素,而不会出现 ConcurrentModificationException 异常的示例代码。
ArrayMap是Android SDK中提供的,非Android开发者可以略过。
ArrayMap是用两个数组来模拟map,更少的内存占用空间,更高的效率。
具体参考这篇文章:ArrayMap VS HashMap:http://lvable.com/?p=217%5D
1. HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2. HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上.
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
Fail-Fast即我们常说的快速失败,
更多内容参看fail-fast机制:http://blog.csdn.net/chenssy/article/details/38151189
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来比较, int c = a.compareTo(b)
c的值为 a-b
-
-
34、父类的静态方法能否被子类重写
-
不能。重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏。
-
-
35、什么是不可变对象
-
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。
-
-
36、静态变量和实例变量的区别?
-
静态变量存储在方法区,属于类所有。实例变量存储在堆当中,其引用存在当前线程栈。
-
-
37、能否创建一个包含可变对象的不可变对象?
-
当然可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。
-
-
38、java 创建对象的几种方式
-
采用new
-
通过反射
-
采用clone
-
通过序列化机制
-
前2者都需要显式地调用构造方法。造成耦合性最高的恰好是第一种,因此你发现无论什么框架,只要涉及到解耦必先减少new的使用。
-
-
39、switch中能否使用string做参数
-
在idk 1.7之前,switch只能支持byte, short, char, int或者其对应的封装类以及Enum类型。从idk 1.7之后switch开始支持String。
-
-
40、switch能否作用在byte, long上?
-
可以用在byte上,但是不能用在long上。
-
-
41、String s1=”ab”, String s2=”a”+”b”, String s3=”a”, String s4=”b”, s5=s3+s4请问s5==s2返回什么?
-
返回false。在编译过程中,编译器会将s2直接优化为”ab”,会将其放置在常量池当中,s5则是被创建在堆区,相当于s5=new String(“ab”);
-
-
42、你对String对象的intern()熟悉么?
-
intern()方法会首先从常量池中查找是否存在该常量值,如果常量池中不存在则现在常量池中创建,如果已经存在则直接返回。
比如
String s1=”aa”;
String s2=s1.intern();
System.out.print(s1==s2);//返回true
-
-
43、Object中有哪些公共方法?
-
equals()
-
clone()
-
getClass()
-
notify(),notifyAll(),wait()
-
toString
-
44、java当中的四种引用
-
强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上:
-
-
强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
-
软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
-
弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。
-
虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。
-
更多了解参见深入对象引用:
http://blog.csdn.net/dd864140130/article/details/49885811
-
-
45、WeakReference与SoftReference的区别?
-
这点在四种引用类型中已经做了解释,这里简单说明一下即可:
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。
-
-
46、为什么要有不同的引用类型
-
不像C语言,我们可以控制内存的申请和释放,在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协。有以下几个使用场景可以充分的说明:
-
-
利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.
-
通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能。
-
47、java中==和
eqauls()
的区别,equals()
和`hashcode的区别
-
==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。换句话说:基本类型比较用==,比较的是他们的值。默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。
-
-
48、equals()
和hashcode()
的联系
-
hashCode()
是Object类的一个方法,返回一个哈希值。如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值。
如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的。)
-
-
49、a.hashCode()有什么用?与a.equals(b)有什么关系
-
hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,使用 equal() 方法来判断两个相等的对象,必须具有相同的 hashcode。
将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合。如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入。
-
-
50、有没有可能两个不相等的对象有相同的hashcode
-
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。如果两个对象相等,必须有相同的hashcode 值,反之不成立。
-
-
51、可以在hashcode中使用随机数字吗?
-
不行,因为同一对象的 hashcode 值必须是相同的
-
-
52、a==b与a.equals(b)有什么区别
-
如果a 和b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。
-
-
53、3*0.1==0.3
返回值是什么
-
false,因为有些浮点数不能完全精确的表示出来。
-
-
54、a=a+b与a+=b有什么区别吗?
-
+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。如:
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
(译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会报错,因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
-
-
55、short s1= 1; s1 = s1 + 1; 该段代码是否有错,有的话怎么改?
-
有错误,short类型在进行运算时会自动提升为int类型,也就是说s1+1
的运算结果是int类型。
-
-
short s1= 1; s1 += 1; 该段代码是否有错,有的话怎么改?
-
+=操作符会自动对右边的表达式结果强转匹配左边的数据类型,所以没错。
-
-
56、一个java文件内部可以有类?(非内部类)
-
只能有一个public公共类,但是可以有多个default修饰的类。
-
-
57、如何正确的退出多层嵌套循环?
-
使用标号和break;
-
通过在外层循环中添加标识符
-
58、内部类的作用
-
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.创建内部类对象的时刻不依赖于外部类对象的创建。内部类并没有令人疑惑的”is-a”管系,它就像是一个独立的实体。
内部类提供了更好的封装,除了该外围类,其他类都不能访问。
-
-
59、final, finalize和finally的不同之处
-
final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。
-
-
60、clone()是哪个类的方法?
-
java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。
-
-
61、深拷贝和浅拷贝的区别是什么?
-
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
-
-
62、static都有哪些用法?
-
几乎所有的人都知道static关键字这两个基本的用法:静态变量和静态方法。也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享。
除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作:
-
-
public calss PreCache{
-
static{
-
//执行相关操作
-
}
-
}
-
此外static也多用于修饰内部类,此时称之为静态内部类。
最后一种用法就是静态导包,即import static
.import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名。资源名,可以直接使用资源名,比如:
-
-
import static java.lang.Math.*;
-
public class Test{
-
public static void main(String[] args){
-
//System.out.println(Math.sin(20));传统做法
-
System.out.println(sin(20));
-
}
-
}
-
-
-
63、final有哪些用法
-
final也是很多面试喜欢问的地方,能回答下以下三点就不错了:
1.被final修饰的类不可以被继承
2.被final修饰的方法不可以被重写
3.被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变。
4.被final修饰的方法,JVM会尝试将其内联,以提高运行效率
5.被final修饰的常量,在编译阶段会存入常量池中。
回答出编译器对final域要遵守的两个重排序规则更好:
1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
-
-
64、数据类型相关
-
65、java中int char,long各占多少字节?
-
类型 | 位数 | 字节数 |
---|---|---|
short | 2 | 16 |
int | 4 | 32 |
long | 8 | 64 |
float | 4 | 32 |
double | 8 | 64 |
char | 2 | 16 |
-
-
66、64位的JVM当中,int的长度是多少?
-
Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。
-
-
67、int和Integer的区别
-
Integer是int的包装类型,在拆箱和装箱中,二者自动转换。int是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象。
-
-
68、int 和Integer谁占用的内存更多?
-
Integer 对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。
-
-
69、java当中使用什么类型表示价格比较好?
-
如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。
-
-
70、如何将byte转为String
-
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。
-
-
71、可以将int强转为byte类型么?会产生什么问题?
-
我们可以做强制转换,但是Java中int是32位的而byte是8 位的,所以,如果强制转化int类型的高24位将会被丢弃,byte 类型的范围是从-128到128
-
-
72、throw和throws的区别
-
throw用于主动抛出java.lang.Throwable 类的一个实例化对象,意思是说你可以通过关键字 throw 抛出一个 Error 或者 一个Exception,如:throw new IllegalArgumentException(“size must be multiple of 2″)
,
而throws 的作用是作为方法声明和签名的一部分,方法被抛出相应的异常以便调用者能处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。
-
-
73、Java 中,Serializable 与 Externalizable 的区别
-
Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。
-
-
74、XML解析的几种方式和特点
-
DOM, SAX, PULL三种解析方式:
-
-
DOM:消耗内存:先把xml文档都读到内存中,然后再用DOM API来访问树形结构,并获取数据。这个写起来很简单,但是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机
-
SAX:解析效率高,占用内存少,基于事件驱动的:更加简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
-
PULL:与 SAX 类似,也是基于事件驱动,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。
-
75、JDK 1.7特性
-
然 JDK 1.7 不像 JDK 5 和 8 一样的大版本,但是,还是有很多新的特性,如 try-with-resource 语句,这样你在使用流或者资源的时候,就不需要手动关闭,Java 会自动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。允许 Switch 中有 String 变量和文本。菱形操作符(<>)用于类型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。
-
-
76、JDK 1.8特性
-
java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性:
Lambda 表达式,允许像对象一样传递匿名函数
Stream API,充分利用现代多核 CPU,可以写出很简洁的代码
Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用
扩展方法,现在,接口中可以有静态、默认方法。
重复注解,现在你可以将相同的注解在同一类型上使用多次。
-
-
77、IO操作最佳实践
-
使用有缓冲的IO类,不要单独读取字节或字符
-
使用NIO和NIO 2或者AIO,而非BIO
-
在finally中关闭流
-
使用内存映射文件获取更快的IO
-
78、JDBC最佳实践
-
优先使用批量操作来插入和更新数据
-
使用PreparedStatement来避免SQL漏洞
-
使用数据连接池
-
通过列名来获取结果集
-
二、关于垃圾回收
-
1、你知道哪些垃圾回收算法?
-
垃圾回收从理论上非常容易理解,具体的方法有以下几种:
1. 标记-清除
2. 标记-复制
3. 标记-整理
4. 分代回收
更详细的内容参见深入理解垃圾回收算法:
http://blog.csdn.net/dd864140130/article/details/50084471
-
-
2、如何判断一个对象是否应该被回收
-
这就是所谓的对象存活性判断,常用的方法有两种:1.引用计数法; 2.对象可达性分析。由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法。
-
-
3、简单的解释一下垃圾回收
-
Java 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性。
-
-
4、调用System.gc()会发生什么?
-
通知GC开始工作,但是GC真正开始的时间不确定。
-
-
三、进程,线程,锁相关
-
1、说说进程,线程,协程之间的区别
-
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
-
-
2、你了解守护线程吗?它和非守护线程有什么区别
-
程序运行完毕,jvm会等待非守护线程完成后关闭,但是jvm不会等待守护线程。守护线程最典型的例子就是GC线程。
-
-
3、什么是多线程上下文切换
-
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
-
-
4、创建两种线程的方式?他们有什么区别?
-
通过实现java.lang.Runnable或者通过扩展java.lang.Thread类。相比扩展Thread,实现Runnable接口可能更优.原因有二:
-
-
Java不支持多继承。因此扩展Thread类就代表这个子类不能扩展其他类。而实现Runnable接口的类还可能扩展另一个类。
-
类可能只要求可执行即可,因此继承整个Thread类的开销过大。
-
5、Thread类中的start()和run()方法有什么区别?
-
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
-
-
6、怎么检测一个线程是否持有对象监视器
-
Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着”某条线程”指的是当前线程。
-
-
7、Runnable和Callable的区别
-
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以方便获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务。
-
-
8、什么导致线程阻塞
-
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让我们逐一分析。
方法 | 说明 |
---|---|
sleep() | sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。 典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止 |
suspend() 和 resume() | 两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。 |
yield() | yield() 使当前线程放弃当前已经分得的CPU 时间,但不使当前线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程 |
wait() 和 notify() | 两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。 |
-
-
9、wait(),notify()和suspend(),resume()之间的区别
-
初看起来它们与 suspend() 和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。
首先,前面叙述的所有方法都隶属于 Thread 类,但是这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致从调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。
wait() 和 notify() 方法的上述特性决定了它们经常和synchronized关键字一起使用,将它们和操作系统进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block 和wakeup 原语(这一对方法均声明为 synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。
关于 wait() 和 notify() 方法最后再说明两点:
第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。
第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。
谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是,Java 并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。
以上我们对 Java 中实现线程阻塞的各种方法作了一番分析,我们重点分析了 wait() 和 notify() 方法,因为它们的功能最强大,使用也最灵活,但是这也导致了它们的效率较低,较容易出错。实际使用中我们应该灵活使用各种方法,以便更好地达到我们的目的。
-
-
10、产生死锁的条件
-
1.互斥条件:一个资源每次只能被一个进程使用。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
-
-
11、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用
-
这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
-
-
12、wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别
-
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
-
-
13、wait()与sleep()的区别
-
关于这两者已经在上面进行详细的说明,这里就做个概括好了:
-
-
sleep()来自Thread类,和wait()来自Object类。调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
-
sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU
-
sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用
-
14、为什么wait, nofity和nofityAll这些方法不放在Thread类当中
-
一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
-
-
15、怎么唤醒一个阻塞的线程
-
如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。
-
-
16、什么是多线程的上下文切换
-
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
-
-
17、synchronized和ReentrantLock的区别
-
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
(2)ReentrantLock可以获取各种锁的信息
(3)ReentrantLock可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。
-
-
18、FutureTask是什么
-
这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。
-
-
19、一个线程如果出现了运行时异常怎么办?
-
如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。
-
-
20、Java当中有哪几种锁
-
自旋锁: 自旋锁在JDK1.6之后就默认开启了。基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一小段时间而去挂起和恢复线程有点浪费,所以这里就做了一个处理,让后面请求锁的那个线程在稍等一会,但是不放弃处理器的执行时间,看看持有锁的线程能否快速释放。为了让线程等待,所以需要让线程执行一个忙循环也就是自旋操作。在jdk6之后,引入了自适应的自旋锁,也就是等待的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定。
-
偏向锁: 在JDK1.之后引入的一项锁优化,目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 偏向锁就是偏心的偏,意思是这个锁会偏向第一个获得他的线程,如果接下来的执行过程中,改锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不一定总是对程序运行有利,如果程序中大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑是否使用偏向锁。
-
轻量级锁: 为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。
-
21、如何在两个线程间共享数据
-
通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。
-
-
22、如何正确的使用wait()?使用if还是while?
-
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
-
-
synchronized (obj) {
-
while (condition does not hold)
-
obj.wait(); // (Releases lock, and reacquires on wakeup)
-
... // Perform action appropriate to condition
-
}
-
-
-
23、什么是线程局部变量ThreadLocal
-
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
-
-
24、ThreadLoal的作用是什么?
-
简单说ThreadLocal就是一种以空间换时间的做法在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。
-
-
25、生产者消费者模型的作用是什么?
-
(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用。
(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约。
-
-
26写一个生产者-消费者队列
-
可以通过阻塞队列实现,也可以通过wait-notify来实现。
-
-
①使用阻塞队列来实现
-
//消费者
-
public class Producer implements Runnable{
-
private final BlockingQueue<Integer> queue;
-
public Producer(BlockingQueue q){
-
this.queue=q;
-
}
-
@Override
-
public void run() {
-
try {
-
while (true){
-
Thread.sleep(1000);//模拟耗时
-
queue.put(produce());
-
}
-
}catch (InterruptedException e){
-
}
-
}
-
private int produce() {
-
int n=new Random().nextInt(10000);
-
System.out.println("Thread:" + Thread.currentThread().getId() + " produce:" + n);
-
return n;
-
}
-
}
-
//消费者
-
public class Consumer implements Runnable {
-
private final BlockingQueue<Integer> queue;
-
public Consumer(BlockingQueue q){
-
this.queue=q;
-
}
-
@Override
-
public void run() {
-
while (true){
-
try {
-
Thread.sleep(2000);//模拟耗时
-
consume(queue.take());
-
}catch (InterruptedException e){
-
}
-
}
-
}
-
private void consume(Integer n) {
-
System.out.println("Thread:" + Thread.currentThread().getId() + " consume:" + n);
-
}
-
}
-
//测试
-
public class Main {
-
public static void main(String[] args) {
-
BlockingQueue<Integer> queue=new ArrayBlockingQueue<Integer>(100);
-
Producer p=new Producer(queue);
-
Consumer c1=new Consumer(queue);
-
Consumer c2=new Consumer(queue);
-
new Thread(p).start();
-
new Thread(c1).start();
-
new Thread(c2).start();
-
}
-
}
-
27、如果你提交任务时,线程池队列已满,这时会发生什么
-
如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。
-
-
28、为什么要使用线程池
-
避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。
-
-
29、java中用到的线程调度算法是什么
-
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
-
-
30、Thread.sleep(0)的作用是什么
-
由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。
-
-
31、什么是CAS
-
CAS,全称为Compare and Swap,即比较-替换。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。
-
-
32、什么是乐观锁和悲观锁
-
乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
-
-
33、ConcurrentHashMap的并发度是什么?
-
ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?
-
-
34、ConcurrentHashMap的工作原理
-
ConcurrentHashMap在jdk 1.6和jdk 1.8实现原理是不同的。
-
-
①jdk 1.6:
-
ConcurrentHashMap是线程安全的,但是与Hashtablea相比,实现线程安全的方式不同。Hashtable是通过对hash表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁。ConcurrentHashMap是采用分离锁的方式,它并没有对整个hash表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其他线程对hash表其他地方的访问。
具体实现:ConcurrentHashMap内部有一个Segment.
-
-
②jdk 1.8
-
在jdk 8中,ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表->红黑树”的实现。
-
-
35、CyclicBarrier和CountDownLatch区别
-
这两个类非常类似,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:
-
-
CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行。
-
CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务
-
CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了。
-
36、java中的++操作符线程安全么?
-
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。
-
-
37、你有哪些多线程开发良好的实践?
-
给线程命名
-
最小化同步范围
-
优先使用volatile
-
尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore
-
优先使用并发容器而非同步容器.
-
考虑使用线程池
-
四、关于日期
-
1、SimpleDateFormat是线程安全的吗?
-
非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。
-
-
2、如何格式化日期?
-
Java 中,可以使用 SimpleDateFormat 类或者 joda-time 库来格式日期。DateFormat 类允许你使用多种流行的格式来格式化日期。参见答案中的示例代码,代码中演示了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。
Date date = new Date();
System.out.println(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH");
System.out.println(simpleDateFormat.format(date));
System.out.println(simpleDateFormat.parse(simpleDateFormat.format(date)));
-
-
1.Spring框架分为哪七大模块,各模块的主要功能作用是什么?
-
七大模块,如下: 1. Spring Core: Core封装包是框架的最基础部分,提供IOC和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。
-
-
Spring Context: 构建于Core封装包基础上的 Context 封装包,提供了一种框架式的对象访问方法,有些象JNDI注册器。Context封装包的特性得自于Beans封装包,并添加了对国际化(I18N)的支持(例如资源绑定),事件传播,资源装载的方式和Context的透明创建,比如说通过Servlet容器。
-
Spring DAO: DAO (Data Access Object)提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码。 并且,JDBC封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的POJOs(plain old Java objects)都适用。
-
Spring ORM:ORM 封装包提供了常用的“对象/关系”映射APIs的集成层。 其中包括 JPA 、 JDO 、 Hibernate 和 iBatis 。利用ORM封装包,可以混合使用所有Spring提供的特性进行“对象/关系”映射,如前边提到的简单声明性事务管理。
-
Spring AOP: Spring的 AOP 封装包提供了符合AOP Alliance规范的面向方面的编程实现,让你可以定义,例如方法拦截器(method-interceptors)和切点(pointcuts),从逻辑上讲,从而减弱代码的功能耦合,清晰的被分离开。而且,利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中。
-
Spring Web: Spring中的 Web 包提供了基础的针对Web开发的集成特性,例如多方文件上传,利用Servlet listeners进行IOC容器初始化和针对Web的ApplicationContext。当与WebWork或Struts一起使用Spring时,这个包使Spring可与其他框架结合。
-
Spring Web MVC: Spring中的MVC封装包提供了Web应用的Model-View-Controller(MVC)实现。Spring的MVC框架并不是仅仅提供一种传统的实现,它提供了一种清晰的分离模型,在领域模型代码和Web Form之间。并且,还可以借助Spring框架的其他特性。
-
2.Spring框架中的三大核心思想是什么
-
DI(依赖注入),IOC(控制反转),AOP(面向切面编程)
-
-
3.IOC的概念以及在Spring容器中如何进行IOC的操作。
-
IOC:Inversion of Control,控制反转模式(也称作依赖性介入)的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器 (在 Spring 框架中是 IOC 容器) 负责将这些联系在一起。
在Java开发中,IOC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制,这称为控制反转,就是被调用类的实例由原先的调用类控制创建、销毁现在转变成由Spring的容器管理。
-
-
4.DI的概念以及在Spring框架注入有几种方式。使用构造注入对象,必须要注意什么问题,当设值注入与构造注入同时存在时,执行的先后流程顺序?
-
注入方式: 1. 接口注入 2. 属性注入[属性的SET/GET] 3. 构造注入[构造方法注入] 使用构造函数依赖注入时,Spring保证对象其所依赖的所有对象先实例化后,才实例化这个对象。 使用set方法依赖注入时,Spring首先实例化对象,然后才实例化所有依赖的对象。 * 当设值注入与构造注入同时存在时,先执行设置注入,在执行构造注入。
-
-
5.使用DI注入时,Property代表什么意思,如果property引用的是其他Bean的话,如何注入,如果引用是字符串的话,如何设置?
-
使用DI注入时,Property代表注入类的属性,如果引用其他的bean,则用ref属性来表明被引用bean的名称,如果是引用字符串的话,用value属性。
如: \<property name=”userDao” ref=”被引用bean的名称” /> \<property name=”username” value = ”字符串”/>
-
-
6.在使用Spring的JDBCTemplate操作数据时,必须要往模板中注入哪些对象,同时模板要不要手动关闭数据库连接
-
注入DataSource数据源对象,不需要手动关闭数据库连接,JdbcTemplate会帮我们关闭数据库连接
-
-
7.AOP的概念以及使用AOP机制有什么好处。Java编程中实现AOP有几种方式?
- AOP的概念是Aspected Oriented Programming 面向切面编程 >AOP将程序分解成各个切面或者说关注点。这使得可以模块化,相当横向上分切了。它可以解决OOP和过程化方法不能够很好解决的横切(crosscut)问题,如:事务、安全、日志等横切关注。
-
个人理解:如果说汇编语言是对计算机指令的抽象,面向对象语言是对程序要解决的问题的抽象,AOP则是对程序功能本身的抽象。
-
实现AOP有几种方式: >1. Spring 1.2版本中通过ProxyFactoryBean来实现aop,即通过动态代理来实现的,Aspect必须继承MethodBeforeAdvice,MethodAfterAdvice等 >2. Spring 2.0 AOP需要改的是FBI 这个类,而且它也不需要再实现某些接口 >3. 使用标注(@AspectJ)实现AOP
-
8.Spring框架中的事务处理有几种。请分别阐述两者的区别
-
spring提供的事务管理可以分为两类:编程式的和声明式的。编程式的,比较灵活,但是代码量大,存在重复的代码比较多;声明式的比编程式的更灵活.
-
-
9.Spring的声明式事务能不能为普通的类产生代理接口,能不能在代码中使用Try/Catch能捕获异常,如果不可以,请说明原因
-
不能。Spring的声明式事务为实现类产生代理。不能在代码中使用Try/Catch,因为代码中捕获了异常,Spring容器捕获不了异常。
-
-
10.SpringMVC的理解?SpringMVC的工作流程?
-
SpringMVC是基于过滤器对servlet进行了封装的一个框架,我们使用的时候就在web.xml中配置DispatcherServlet类;SpringMVC工作是主要是通过DispatcherServlet管理接受到的请求并进行处理。 * SpringMVC的工作流程描述:
- 第一步:用户向服务器发送请求,请求被Spring前端控制Servelt DispatcherServlet捕获;
- 第二步: DispatcherServlet对请求URL进行解析,得到请求资源标识符(URL),然后根据该URL调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
- 第三步: DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
- 第四步:提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作: >> 1. HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息 >> 2. 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等 >> 3. 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期 >> 4. 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
- 第五步: Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
- 第六步:根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
- 第七步:ViewResolver 结合Model和View,来渲染视图
- 第八步:将渲染结果返回给客户端。
-
-
11.Nginx怎么实现负载均衡?
-
Nginx 是俄罗斯人编写的十分轻量级的 是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器. * Nginx实现负载均衡只需要在nginx文件中进行配置即可,配置如下: 1. 第一步:在http模块中配置upstream 模块:(举例)
upstream www.myweb.com { server 127.0.0.1:9100 weight=1; server 127.0.0.1:9200 weight=1;} <br /> 其中weight=1表示权重,用于后端服务器性能不均的情况,访问比率约等于权重之比,权重越大访问机会越多; upstream是配置nginx与后端服务器负载均衡非常重要的一个模块,并且它还能对后端的服务器的健康状态进行检查,若后端服务器中的一台发生故障,则前端的请求不会转发到该故障的机器;
第二步:在server模块里添加:(举例) location /myweb { proxy_pass http://www.myweb.com;} 其中 www.myweb.com 字符串要和 upstream 后面的字符串相等;
-
-
12.jsp静态包含和动态包含的区别?
- <%@ include file=”” %>是指令元素,静态包含。<jsp:include page=”” />是行为元素,动态包含。
- 最终编译成java文件的数目不同。 >1. 静态包含在转换成为java文件的时候将包含文件的内容“复制”到主体文件,然后作为一个整体编译。生成一个以包含页面命名的servlet和class文件。 >2. 动态包含是各个jsp文件分别转换,分别编译。最终编程成多个java文件。
- 执行时间不同 > 静态包含发生在:JSP生成class文件阶段。 > 动态包含发生在:执行class文件阶段。动态加入。
- 静态包含在两个文件中不能有相同的变量,动态包含允许。
- 无论是动态包含还是静态包含,其request对象都是相同的。也就是同一个request对象。
-
13.jsp有哪些内置对象?作用分别是什么?
- request
- response
- session
- pageContext >>javax.servlet.jsp.PageContext 的实例,对象直译时可以称作“页面上下文”对象,代表的是当前页面运行的一些属性,通过此对象可以拿到其他8大对象,使用该对象可以访问页面中的共享数据。常用的方法有getServletContextO和getServletConfigO等。
- page >>对应this关键字,JSP网页本身。page对象是当前页面转换后的Servlet类的实例。从转换后的Servlet类的代码中,可以看到这种关系: <br/> Object page = this; <br/> 在JSP页面中,很少使用page对象。
- config >>javax.servlet. ServletConfig 的实例,该实例代表该JSP 的配置信息。常用的方法有getInitPararneter(String paramNarne) 及getInitPararneternarnes() 等方法。事实上, JSP 页面通常无须配置,也就不存在配置信息。因此,该对象更多地在Servlet 中有效。
- application >>服务器启动后就产生了这个application对象,当客户在所访问的网站的各个页面之间浏览时,这个application对象都是同一个,直到服务器关闭。但是与session不同的是,所有客户的application对象都是同一个,即所有客户共享这个内置的application对象。
- exception >>该实例代表其他页面中的异常和错误。只有当页面是错误处理页面,即编译指令page 的isErrorPage 属性为true 时,该对象才可以使用。常用的方法有getMessageO和printStackTraceO等。
- out >>out对象是一个输出流,用来向客户端输出数据。out对象用于各种数据的输出。
-
其中,request、response、session、application、config这五个对象和Servlet的API是一样的。
-
-
14.struts是什么?
-
struts1是基于JSP和servlet的一个开源的Web应用框架,使用的是MVC的设计模式。struts2是基于webwork技术的框架,是sun和webwork公司联手开发的一个功能非常齐全的框架,struts2和struts1没有任何关系,是一个全新的框架
-
-
15.JSF是什么?
-
JavaServer Face是基于组件的web开发框架,跟sturts差不多的框架
-
-
16.Tomcat 有哪几种Connector 运行模式(优化)?
- bio(blocking I/O):传统的Java I/O操作,同步且阻塞IO。
- nio(non-blocking I/O):JDK1.4开始支持,同步阻塞或同步非阻塞IO
- apr(Apache Portable Runtime/Apache可移植运行库):Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作,从而大大地 提高Tomcat对静态文件的处理性能
-
17.Hibernate中SessionFactory是线程安全的吗?Session是线程安全的吗(两个线程能够共享同一个Session吗)?
-
SessionFactory对应Hibernate的一个数据存储的概念,它是线程安全的,可以被多个线程并发访问。SessionFactory一般只会在启动的时候构建。对于应用程序,最好将SessionFactory通过单例模式进行封装以便于访问。Session是一个轻量级非线程安全的对象(线程间不能共享session),它表示与数据库进行交互的一个工作单元。Session是由SessionFactory创建的,在任务完成之后它会被关闭。Session是持久层服务对外提供的主要接口。Session会延迟获取数据库连接(也就是在需要的时候才会获取)。为了避免创建太多的session,可以使用ThreadLocal将session和当前线程绑定在一起,这样可以让同一个线程获得的总是同一个session。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。
-
-
18.Hibernate中Session的load和get方法的区别是什么?
- 如果没有找到符合条件的记录,get方法返回null,load方法抛出异常。
- get方法直接返回实体类对象,load方法返回实体类对象的代理。
- 在Hibernate 3之前,get方法只在一级缓存中进行数据查找,如果没有找到对应的数据则越过二级缓存,直接发出SQL语句完成数据读取;load方法则可以从二级缓存中获取数据;从Hibernate 3开始,get方法不再是对二级缓存只写不读,它也是可以访问二级缓存的。
-
19.Hibernate中Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分别是做什么的?有什么区别?
- Hibernate的对象有三种状态:瞬时态(transient)、持久态(persistent)和游离态(detached) > 1. 瞬时态的实例可以通过调用save()、persist()或者saveOrUpdate()方法变成持久态 > 2. 游离态的实例可以通过调用 update()、saveOrUpdate()、lock()或者replicate()变成持久态。 > 3. save()和persist()将会引发SQL的INSERT语句,而update()或merge()会引发UPDATE语句。save()和update()的区别在于一个是将瞬时态对象变成持久态,一个是将游离态对象变为持久态。merge()方法可以完成save()和update()方法的功能,它的意图是将新的状态合并到已有的持久化对象上或创建新的持久化对象。 > 4. persist()方法把一个瞬时态的实例持久化,但是并不保证标识符被立刻填入到持久化实例中,标识符的填入可能被推迟到flush的时间; persist()方法保证当它在一个事务外部被调用的时候并不触发一个INSERT语句,当需要封装一个长会话流程的时候,persist()方法是很有必要的;save()方法它要返回标识符,所以它会立即执行INSERT语句,不管是在事务内部还是外部。 > 5. 至于lock()方法和update()方法的区别,update()方法是把一个已经更改过的脱管状态的对象变成持久状态;lock()方法是把一个没有更改过的脱管状态的对象变成持久状态。
-
20.锁机制有什么用?简述Hibernate的悲观锁和乐观锁机制。
-
有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。 1. 悲观锁,顾名思义悲观的认为在数据处理过程中极有可能存在修改数据的并发事务(包括本系统的其他事务或来自外部系统的事务),于是将处理的数据设置为锁定状态。悲观锁必须依赖数据库本身的锁机制才能真正保证数据访问的排他性 2. 乐观锁,顾名思义,对并发事务持乐观态度(认为对数据的并发操作不会经常性的发生),通过更加宽松的锁机制来解决由于悲观锁排他性的数据访问对系统性能造成的严重影响。最常见的乐观锁是通过数据版本标识来实现的,读取数据时获得数据的版本号,更新数据时将此版本号加1,然后和数据库表对应记录的当前版本号进行比较,如果提交的数据版本号大于数据库中此记录的当前版本号则更新数据,否则认为是过期数据无法更新。 * Hibernate中通过Session的get()和load()方法从数据库中加载对象时可以通过参数指定使用悲观锁;而乐观锁可以通过给实体类加整型的版本字段再通过XML或@Version注解进行配置。
-
-
21.阐述Hibernate实体对象的三种状态以及转换关系。
- 瞬时态:当new一个实体对象后,这个对象处于瞬时态,即这个对象只是一个保存临时数据的内存区域,如果没有变量引用这个对象,则会被JVM的垃圾回收机制回收。这个对象所保存的数据与数据库没有任何关系,除非通过Session的save()、saveOrUpdate()、persist()、merge()方法把瞬时态对象与数据库关联,并把数据插入或者更新到数据库,这个对象才转换为持久态对象。
- 持久态:持久态对象的实例在数据库中有对应的记录,并拥有一个持久化标识(ID)。对持久态对象进行delete操作后,数据库中对应的记录将被删除,那么持久态对象与数据库记录不再存在对应关系,持久态对象变成移除态(可以视为瞬时态)。持久态对象被修改变更后,不会马上同步到数据库,直到数据库事务提交。
- 游离态:当Session进行了close()、clear()、evict()或flush()后,实体对象从持久态变成游离态,对象虽然拥有持久和与数据库对应记录一致的标识值,但是因为对象已经从会话中清除掉,对象不在持久化管理之内,所以处于游离态(也叫脱管态)。游离态的对象与临时状态对象是十分相似的,只是它还含有持久化标识。
-
22.使用Spring框架的好处是什么?
- 轻量:Spring 是轻量的,基本的版本大约2MB。
- 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
- 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
- 容器:Spring 包含并管理应用中对象的生命周期和配置。
- MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
- 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
- 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
-
23.Spring的ApplicationContext通常的实现是什么?
-
Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件从解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会被推荐使用。BeanFactory 仍然可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
-
- FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
- ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
- WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。
-
24.什么是Spring beans?
Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中<bean/> 的形式定义。
Spring 框架定义的beans都是单件beans。在bean tag中有个属性”singleton”,如果它被赋为TRUE,bean 就是单件,否则就是一个 prototype bean。默认是TRUE,所以所有在Spring框架中的beans 缺省都是单件。
-
-
25.Spring的Bean工厂和Application contexts有什么区别?
-
两者都是装入bean定义信息,装配bean,根据需要分发bean。但是ApplicationContext提供更多功能,它提供了bean工厂所没有的解析信息文本工具,包括对国际化的支持,提供了载入文件资源的通用方法,如载入图片,它可以用注册为监听器的bean发送事件。另外一个很重要的区别是单例bean被载入的方式不一样。bean工厂延迟载入所有的bean,直到getbean方法被调用,才被创建。而ApplicationContext会预装入所有的单例bean,确保需要的时候单例bean都已经准备好了,这样我们的应用就不需要等待这些单例bean被创建。
-
-
26.如何给Spring 容器提供配置元数据?
-
这里有三种重要的方法给Spring 容器提供配置元数据。 1. XML配置文件。 2. 基于注解的配置。 3. 基于java的配置。
-
-
27.解释Spring支持的几种bean的作用域。
-
当定义一个<bean> 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。 Spring框架支持以下五种bean的作用域:singleton : bean在每个Spring ioc 容器中只有一个实例。缺省的Spring bean 的作用域是Singleton,且Spring框架中的单例bean不是线程安全的。 prototype:一个bean的定义可以有多个实例。 request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。 session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。 * global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
-
-
28.哪些是重要的bean生命周期方法? 你能重载它们吗?
-
有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。<br /> The bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。
-
-
29.什么是Spring的内部bean?
-
当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 \<property/>或\<constructor-arg/> 元素内使用\<bean/> 元素,内部bean通常是匿名的,它们的Scope一般是prototype。
-
-
30. 在 Spring中如何注入一个java集合?
-
Spring提供以下几种集合的配置元素: 1. \<list>类型用于注入一列值,允许有相同的值。 2. \<<set> 类型用于注入一组值,不允许有相同的值。 3. \<<map> 类型用于注入一组键值对,键和值都可以为任意类型。 4. \<<props>类型用于注入一组键值对,键和值都只能为String类型。
-
-
31.解释不同方式的自动装配 。
-
有五种自动装配的方式,可以用来指导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方式。
-
-
32.自动装配有哪些局限性 ?
-
自动装配的局限性是: 1. 重写: 你仍需用 <constructor-arg>和 <property> 配置来定义依赖,意味着总要重写自动装配。 2. 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。 3. 模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。
-
-
33.你可以在Spring中注入一个null 和一个空字符串吗?
-
可以。
-
-
34.什么是基于Java的Spring注解配置? 给一些注解的例子.
-
基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。
以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。
-
-
35.Spring常用注解有哪些
- @Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。
- @Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。
- @Qualifier 注解当有多个相同类型的bean却只有一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用以消除这种混淆,指定需要装配的确切的bean。
-
36.使用Spring通过什么方式访问Hibernate?
-
在Spring中有两种方式访问Hibernate: 1. 控制反转 Hibernate Template和 Callback。 2. 继承 HibernateDAOSupport提供一个AOP 拦截器。
用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步: 1. 配置the Hibernate SessionFactory。 2. 继承HibernateDaoSupport实现一个DAO。 3. 在AOP支持的事务中装配。
-
-
37.Spring支持的事务管理类型
-
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
-
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。 >大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。
-
38.什么是引入?
-
引入允许我们在已存在的类中增加新的方法和属性。
转自:https://www.cnblogs.com/xuzimian/p/8603060.html
(一)spring mvc 工作机制(原理):
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程
Spring mvc运行原理
1.springmvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求进行真正的处理工作。
2.DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller.
3.DispatcherServlet将请求提交到目标Controller
4.Controller进行业务逻辑处理后,会返回一个ModelAndView
5.DispathcherServlet查询一个或多个ViewResolver视图解析器,找到ModelAndView对象指定的视图对象;
6.视图负责将结果显示到客户端;
视图对象负责渲染返回给客户端。
DispatcherServlet是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工作有以下三项:
1. 截获符合特定格式的URL请求。
2. 初始化DispatcherServlet上下文对应的WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。
3. 初始化Spring MVC的各个组成组件,并装配到DispatcherServlet中。
====================================================================================================
DispatcherServlet:前端控制器;(相当于一个转发器,中央处理器,调度)
ModelAndView:模型和视图的结合体;(Spring mvc的底层对象)
HandlerMapping: 处理器映射器;
工作原理及为什么要用?
原理:
1.读取并解析配置文件
2.读取并解析映射信息,创建SessionFactory
3.打开Sesssion
4.创建事务
Transation
5. 持久化操作
6.提交事务
7.关闭Session
8.关闭SesstionFactory
为什么要用:
1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作
3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。
4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。
(三)Struts工作机制?为什么要使用Struts?
工作机制:
Struts的工作流程:
在web应用启动时就会加载初始化ActionServlet,ActionServlet从struts-config.xml文件中读取配置信息,把它们存放到各种配置对象,当ActionServlet接收到一个客户请求时,将执行如下流程.
(1)检索和用户请求匹配的Action Mapping实例,如果不存在,就返 回请求路径无效信息;
(2)如果Action Form实例不存在,就创建一个Action Form对象,把客户提交的表单数据保存到Action Form对象中;
(3)根据配置信息决定是否需要表单验证.如果需要验证,就调用Action Form的validate()方法;
(4)如果Action Form的validate()方法返回null或返回一个不包含Action Message的 ActuibErrors对象,就表示表单验证成功;
(5)Action Servlet 根据Action Mapping所包含的映射信息决定将请求转发给哪个Action,如果相应的Action实例不存在,就先创建这个实例,然后调用Action的execute()方法;
(6)Action的execute()方法返回一个Action Forward对象,Action Servlet再把客户请求转发给 Action Forward对象指向的JSP组件;
(7)Action Forward对象指向JSP组件生成动态网页,返回给客户;
为什么要用:
JSP、Servlet,JavaBean技术的出现给我们构建强大的企业应用系统提供了可能。但用这些技术构建的系统非常的繁乱,所以在此之上,我们需要一个规则、一个把这些技术组织起来的规则,这就是框架,Struts便应运而生。
基于Struts开发的应用由3类组件构成:控制器组件、模型组件、视图组件
(四)如何优化Hibernate?
1. 使用双向一对多关联,不使用单向一对多
2.灵活使用单向一对多关联
3.不用一对一,用多对一取代
4.配置对象缓存,不使用集合缓存
5.一对多集合使用Bag,多对多集合使用Set
6.继承类使用显式多态
7.表字段要少,表关联不要怕多,有二级缓存撑腰
Spring工作原理
Spring 已经用过一段时间了,感觉Spring是个很不错的框架。
内部最核心的就是IOC了,动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射,反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行时,跟xml Spring的配置文件来动态的创建对象,和调用对象里的方法的 。
Spring还有一个核心就是AOP这个就是面向切面编程,可以为某一类对象 进行监督和控制(也就是在调用这类对象的具体方法的前后去调用你指定的 模块)从而达到对一个模块扩充的功能。这些都是通过配置类达到的。
Spring目的:就是让对象与对象(模块与模块)之间的关系没有通过代码来关联,都是通过配置类说明管理的(Spring根据这些配置 内部通过反射去动态的组装对象)
Struts2 框架本身大致可以分为3个部分:
核心控制器FilterDispatcher、业务控制器Action和用户实现的企业业务逻辑组件。
核心控制器FilterDispatcher是Struts 2框架的基础,包含了框架内部的控制流程和处理机制。
业务控制器Action和业务逻辑组件是需要用户来自己实现的。
用户在开发Action和业务逻辑组件的同时,还需要编写相关的配置文件,供核心控制器FilterDispatcher来使用。
Struts 2 的工作流程相对于 Struts1 要简单,与WebWork框架基本相同,所以说Struts 2是WebWork的升级版本。
基本简要流程如下:
1.客户端初始化一个指向Servlet容器的请求;
2.这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器, 这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh?Plugin)
3.接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action;
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;
5、ActionProxy通过Configuration Manager(配置管理器)询问框架的配置文件,找到需要调用的Action类;
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。
返回结果通常是(但不总是,也可?能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。
在表示的过程中可以使用Struts2?框架中继承的标签。在这个过程中需要涉及到ActionMapper;
9.响应的返回是通过我们在web.xml中配置的过滤器;
10、如果ActionContextCleanUp是当前使用的,则FilterDispatecher将不会清理sreadlocal?ActionContext;
如果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的一个普遍问题是使用抽象类编程而不是接口。
Struts2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。
Struts2提供一个ActionSupport基类去实现常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。
2、线程模式:Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。
单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,
servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)
3、Servlet依赖:
Struts1 Action 依赖于Servlet API,因为当一个Action被调用时HttpServletRequest和HttpServletResponse被传递给execute方法。
Struts2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。
但是,其他的元素减少或者消除了直接访问HttpServetRequest和HttpServletResponse的必要性。
4、可测性:
测试Struts1?Action的一个主要问题是execute方法暴露了servlet?API(这使得测试要依赖于容器)。
一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)
Struts2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。
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,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言-- "Object Graph Notation Language "(OGNL).
7、绑定值到页面(view):
Struts1使用标准JSP机制把对象绑定到页面中来访问。
Struts2使用"ValueStack"技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。
ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。
8、类型转换:
Struts1 ActionForm属性通常都是String类型。Struts1使用Commons-Beanutils进行类型转换。每个类一个转换器,对每一个实例来说是不可配置的。
Struts2使用OGNL进行类型转换。提供基本和常用对象的转换器。
9、校验:
Struts1支持在ActionForm的validate方法中手动校验,或者通过Commons Validator的扩展来校验。
同一个类可以有不同的校验内容,但不能校验子对象。
Struts2支持通过validate方法和XWork校验框架来进行校验。XWork校验框架使用为属性类类型定义的校验和内容校验,
来支持chain校验子属性.
10、Action执行的控制:
Struts1支持每一个模块有单独的Request Processors(生命周期),但是模块中的所有Action必须共享相同的生命周期。
Struts2支持通过拦截器堆栈(Interceptor Stacks)为每一个Action创建不同的生命周期。堆栈能够根据需要和不同的Action一起使用。
为什么要使用Struts2
Struts2 是一个相当强大的Java Web开源框架,是一个基于POJO的Action的MVC Web框架。它基于当年的Webwork和XWork框架,继承其优点,同时做了相当的改进。
1.Struts2基于MVC架构,框架结构清晰,开发流程一目了然,开发人员可以很好的掌控开发的过程。
2.使用OGNL进行参数传递。OGNL提供了在Struts2里访问各种作用域中的数据的简单方式,你可以方便的获取Request,Attribute,Application,Session,Parameters中的数据。大大简化了开发人员在获取这些数据时的代码量。
(OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。)
3.强大的拦截器: Struts2的拦截器是一个Action级别的AOP,Struts2中的许多特性都是通过拦截器来实现的,例如异常处理,文件上传,验证等。拦截器是可配置与重用的,可以将一些通用的功能如:登录验证,权限验证等置于拦截器中
以完成一些Java?Web项目中比较通用的功能。在我实现的的一Web项目中,就是使用Struts2的拦截器来完成了系统中的权限验证功能。
4.易于测试: Struts2的Action都是简单的POJO,这样可以方便的对Struts2的Action编写测试用例,大大方便了5Java Web项目的测试。
5.易于扩展的插件机制在Struts2添加扩展是一件愉快而轻松的事情,只需要将所需要的Jar包放到WEB-INF/lib文件夹中,在struts.xml中作一些简单的设置就可以实现扩展;
6.模块化管理: Struts2已经把模块化作为了体系架构中的基本思想,可以通过三种方法来将应用程序模块化:将配置信息拆分成多个文件把自包含的应用模块创建为插件创建新的框架特性,即将与特定应用无关的新功能组织成插件,以添加到多个应用中去。
7.全局结果与声明式异常:
为应用程序添加全局的Result,和在配置文件中对异常进行处理,这样当处理过程中出现指定异常时,可以跳转到特定页面。
他的如此之多的优点,是很多人比较的青睐,与spring ,Hibernate进行结合,组成了现在比较流行的ssh框架,
struts2有哪些优点
1.在软件设计上Struts2的应用可以不依赖于Servlet API和struts API。 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)作用:负责拦截由<url-pattern>/*</url-pattern>指定的所有用户请求,当用户请求到达时,该Filter会过滤用户的请求。
默认情况下,如果用户请求的路径不带后缀或者后缀以.action结尾,这时请求将被转入struts2框架处理,
否则struts2框架将略过该请求的处理。 可以通过常量"struts.action.extension"修改action的后缀,
如:<constant name="struts.action.extension" value="do"/>
如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开。
<constant name="struts.action.extension" value="do,Go"/>
struts2配置文件的加载顺序:
struts.xml ——> struts.properties,常量可以在struts.xml或struts.properties中配置,如果在多个文件中配置了同一个常量,则后一个文件中配置的常量值会覆盖前面文件中配置的常量值.
struts.xml文件的作用:通知Struts2框架加载对应的Action资源
struts2是如何管理action的?这种管理方式有什么好处?
struts2框架中使用包来管理Action,包的作用和java中的类包是非常类似的。
主要用于管理一组业务功能相关的action。在实际应用中,我们应该把一组业务功能相关的Action放在同一个包下。
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)Spring AOP
(3)Spring ORM
(4)Spring DAO
(5)Spring WEB
(6)Spring上下文(Context)
(7)Spring MVC
2. 为什么要使用Spring框架,它有什么优点?
(1)轻量级的框架
(2)非侵入性的
(3)可以整合其它的框架,比如Struts,Hibernate等
(4)可以提供事务管理
4.怎么使用Spring配置事务?
Spring同时支持编程式事务策略和声明式事务策略,大部分时候都采用声明式事务策略。
声明式事务管理的配置方式,通常有以下4种:
(1)使用TransactionProxyFactoryBean为目标Bean生成事务代理的配置。此方式是最传统、配置文件最臃肿、最难以阅读的方式。
(2)采用Bean继承的事务代理配置方式,比较简洁,但依然是增量式配置。
(3)采用BeanNameAutoProxyCreator,根据Bean Name自动生成事务代理的方式。这是直接利用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里如何定义Hibernate?Mapping?
添加hibernate mapping 文件到WEB-INF目录下的applicationContext.xml文件中。
10.解释一下Dependency Injection(DI,依赖注入)和IoC(Inversion of Control,控制反转)
1.依赖注入DI是一种设计模式,通常也称作控制反转,尽管在技术上来讲,依赖注入是一个IoC的特殊实现,依赖注入是指一个对象应用另外一个对象来提供一种特殊的能力。例如,把一个数据库连接以参数的形式传到一个对象的构造函数里面而不是在那个对象内部自行创建一个连接。
2.控制反转和依赖注入的基本思想就是把类的依赖从类内部转化到外部以减少依赖。
3.应用控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用,传递给它。
也可以说,依赖被注入到对象中。所以,控制反转是,关于一个对象如何获取它依赖的对象的引用,这个责任的反转。
13.Spring中的核心类有那些,各有什么作用?
BeanFactory:产生一个新的实例,可以实现单例模式。
BeanWrapper:提供统一的get及set方法。
ApplicationContext:提供Spring框架的实现,包括BeanFactory的所有功能。
14.什么是AOP,AOP的作用是什么?
面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。
除了类(classes)以外,AOP提供了切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。
Spring的一个关键的组件就是AOP框架,可以自由选择是否使用AOP,提供声明式企业服务,特别是为了替代EJB声明式服务。
最重要的服务是声明性事务管理,这个服务建立在Spring的抽象事物管理之上。
允许用户实现自定义切面,用AOP来完善OOP的使用,可以把Spring AOP看作是对Spring的一种增强.
15.使用Spring有什么好处?
(1)Spring能有效地组织你的中间层对象,无论你是否选择使用了EJB。如果你仅仅使用了Struts或其它的包含了J2EE特有APIs的?framework,你会发现Spring关注了遗留问题。
(2)Spring能消除在许多工程上对Singleton的过多使用。
(3)Spring能消除使用各种格式的属性定制文件的需要,在整个工程中,可通过一种一致的方法来进行配置。
(4)Spring能通过接口而不是类促进好的编程习惯,减少编程代价到几乎为零。
(5)Spring被设计为让使用它创建的应用尽可能少的依赖于它的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。
(6)使用Spring构建的应用程序易于单元测试。
(7)Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local?EJBs来实现业务接口,却不会影响调用代码。
(8)Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适于许多web应用。
例如,Spring能使用AOP提供声明性事务而不通过使用EJB容器,如果你仅仅需要与单个的数据库打交道,甚至不需要JTA实现。
(9)Spring为数据存取提供了一致的框架,不论是使用JDBC或O/R?mapping产品(如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容器,它们经常是庞大与笨重的,难以使用。
(5)框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
比较Hibernate的三种检索策略优缺点?
1.立即检索:
优点:对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象;
缺点:1.select语句太多;2.可能会加载应用程序不需要访问的对象白白浪费许多内存空间;
2.延迟检索:
优点:由应用程序决定需要加载哪些对象,可以避免可执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并且能节省内存空间;
缺点:应用程序如果希望访问游离状态代理类实例,必须保证他在持久化状态时已经被初始化;
3.迫切左外连接检索
优点:
1.对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便地冲一个对象导航到与它关联的对象。
2.使用了外连接,select语句数目少;
缺点:
1.可能会加载应用程序不需要访问的对象,白白浪费许多内存空间;
2.复杂的数据库表连接也会影响检索性能;
hibernate里面的sorted collection 和ordered collection有什么区别
sorted collection是在内存中通过java比较器进行排序的
ordered collection是在数据库中通过order by进行排序的
==========================================
spring hibernate struts的笔试面试题
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. 对象的标识符属性(identifier property)在数据库中不存在或者是个暂时的值,调用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(Persistence Object),瞬时对象和脱管对象也称为VO(Value Object)。
瞬时态:
由new命令开辟内存空间的java对象, eg. Person person =new Person(”amigo”, “女”);
如果没有变量对该对象进行引用,它将被java虚拟机回收。
瞬时对象在内存孤立存在,它是携带信息的载体,不和数据库的数据有任何关联关系,在Hibernate中,可通过session的save()或 saveOrUpdate()方法将瞬时对象与数据库相关联,并将数据对应的插入数据库中,
此时该瞬时对象转变成持久化对象。
持久态:
处于该状态的对象在数据库中具有对应的记录,并拥有一个持久化标识。如果是用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(Inversion of Control)的理解,描述一下Spring中实现DI(Dependency?Injection)的几种方式。
spring的IOC有三种注入方式:
第一是根据属性注入也叫set方法注入;
第二种是根据构造方法进行注入;
第三种是根据注解进行注入,这种方式我认为比较好,方便,要是bean多的话,使用前两种方式会使得配置文件过于臃肿。
3、简单描述Spring?Framework与Struts的不同之处,整合Spring与Struts有哪些方法,哪种最好,为什么?
答、Spring是完整的一站式框架,而Struts仅是MVC框架,且着重于MVC中的C。
Spring有三种方式整合
Struts:使用Spring的ActionSupport类整合Struts;
使用Spring的DelegatingRequestProcessor覆盖Struts的RequestProcessor;
将Struts Action 管理委托给Spring框架,动作委托最好。
4、Hibernate中的update()和saveOrUpdate()的区别?
答、saveOrUpdate()方法可以实现update()的功能,但会多些步骤,具体如下:
1. 如果对象在该session中已经被持久化,不进行操作;
2. 对象的标识符属性(identifier?property)在数据库中不存在或者是个暂时的值,调用save()方法保存它;
3.如果session中的另一个对象有相同的标识符抛出一个异常;?以上皆不符合则调用update()更新之。
struts2的原理
一 工作原理:
在Struts2框架中的处理大概分为以下几个步骤 :
1. 客户端初始化一个指向Servlet容器(例如Tomcat)的请求
2.这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,iteMesh Plugin)
3.接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
4.如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy(泼若科c)
5 .ActionProxy通过Configuration Manager(康飞科润熊Manager)询问框架的配置文件,找到需要调用的Action类.ActionProxy创建一个ActionInvocation(Action印则k熊)的实例。
6 .ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
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执行流程
客户端发出http请求,web服务器将请求转发到servlet容器,servlet容器解析url并根据web.xml找到相对应的servlet,并将request、response对象传递给找到的servlet,servlet根据request就可以知道是谁发出的请求,请求信息及其他信息,当servlet处理完业务逻辑后会将信息放入到response并响应到客户端。
2、springMVC的执行流程
springMVC是由dispatchservlet为核心的分层控制框架。首先客户端发出一个请求web服务器解析请求url并去匹配dispatchservlet的映射url,如果匹配上就将这个请求放入到dispatchservlet,dispatchservlet根据mapping映射配置去寻找相对应的handel,然后把处理权交给找到的handel,handel封装了处理业务逻辑的代码,当handel处理完后会返回一个逻辑视图modelandview给dispatchservlet,此时的modelandview是一个逻辑视图不是一个正式视图,所以dispatchservlet会通过viewresource视图资源去解析modelandview,然后将解析后的参数放到view中返回到客户端并展现。
3、给定一个txt文件,如何得到某字符串出现的次数
File file = new File("E://test.txt");
InputStream is = new FileInputStream(file);
byte b[] = new byte[1024];
int a = is.read(b);
String str[] = new String(b,0,a).split("");
int count = 0;
for(int i = 0;i<str.length;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) 冒泡
public static void mp(int a[]) {
int swap = 0;
for (int i = 0; i < a.length; i++) {
for (int j = i; j < a.length; j++) {
if (a[j] > a[i]) {
swap = a[i];
a[i] = a[j];
a[j] = swap;
}
}
}
System.out.println(Arrays.toString(a));
}
b)二分查找public static int ef(int a[], int tag) {
int first = 0;
int end = a.length;
for (int i = 0; i < a.length; i++) {
int middle = (first + end) / 2;
if (tag == a[middle]) {
return middle;
}
if (tag > a[middle]) {
first = middle + 1;
}
if (tag < a[middle]) {
end = middle - 1;
}
}
return 0;
}
6、对ajax的理解
a) Ajax为异步请求,即局部刷新技术,在传统的页面中,用户需要点击按钮或者事件触发请求,到刷新页面,而异步技术为不需要点击即可触发事件,这样使得用户体验感增强,比如商城购物车的异步加载,当你点击商品时无需请求后台而直接动态修改参数。
9、父类与子类之间的调用顺序(打印结果)
a) 父类静态代码块
b) 子类静态代码块
c) 父类构造方法
d) 子类构造方法
e) 子类普通方法
f) 重写父类的方法,则打印重写后的方法
10、内部类与外部类的调用
a) 内部类可以直接调用外部类包括private的成员变量,使用外部类引用的this.关键字调用即可
b) 而外部类调用内部类需要建立内部类对象
11、多线程
a)一个进程是一个独立的运行环境,可以看做是一个程序,而线程可以看做是进程的一个任务,比如QQ是一个进程,而一个QQ窗口是一个线程。
b)在多线程程序中,多线程并发可以提高程序的效率,cpu不会因为某个线程等待资源而进入空闲状态,它会把资源让给其他的线程。
c)用户线程就是我们开发程序是创建的线程,而守护线程为系统线程,如JVM虚拟中的GC
d)线程的优先级别:每一个线程都有优先级别,有限级别高的可以先获取CPU资源使该线程从就绪状态转为运行状态。也可以自定义线程的有限级别
e)死锁:至少两个以上线程争取两个以上cpu资源,避免死锁就避免使用嵌套锁,只需要在他们需要同步的地方加锁和避免无限等待
12、AOP与IOC的概念(即spring的核心)
a) IOC:Spring是开源框架,使用框架可以使我们减少工作量,提高工作效率并且它是分层结构,即相对应的层处理对应的业务逻辑,减少代码的耦合度。而spring的核心是IOC控制反转和AOP面向切面编程。IOC控制反转主要强调的是程序之间的关系是由容器控制的,容器控制对象,控制了对外部资源的获取。而反转即为,在传统的编程中都是由我们创建对象获取依赖对象,而在IOC中是容器帮我们创建对象并注入依赖对象,正是容器帮我们查找和注入对象,对象是被获取,所以叫反转。
b) AOP:面向切面编程,主要是管理系统层的业务,比如日志,权限,事物等。AOP是将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为切面(aspect),切面将那些与业务逻辑无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
13、hibernate的核心思想
a) Hibernate的核心思想是ROM对象关系映射机制。它是将表与表之间的操作映射成对象与对象之间的操作。也就是从数据库中提取的信息会自动按照你设置的映射要求封装成特定的对象。所以hibernate就是通过将数据表实体类的映射,使得对对象的修改对应数据行的修改。
14、Struts1与Struts2的区别
15、最优删除谋字符串的某个字符
16、Arraylist与linkedlist的区别
a) 都是实现list接口的列表,arraylist是基于数组的数据结构,linkedlist是基于链表的数据结构,当获取特定元素时,ArrayList效率比较快,它通过数组下标即可获取,而linkedlist则需要移动指针。当存储元素与删除元素时linkedlist效率较快,只需要将指针移动指定位置增加或者删除即可,而arraylist需要移动数据。
17、mybaties与ibatise的区别
18、数据库优化
a) 选择合适的字段,比如邮箱字段可以设为char(6),尽量把字段设置为notnull,这样查询的时候数据库就不需要比较null值
b) 使用关联查询( left join on)查询代替子查询
c) 使用union联合查询手动创建临时表
d) 开启事物,当数据库执行多条语句出现错误时,事物会回滚,可以维护数据库的完整性
e) 使用外键,事物可以维护数据的完整性但是它却不能保证数据的关联性,使用外键可以保证数据的关联性
f) 使用索引,索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快的多的速度检索特定的行,特别是对于max,min,order by查询时,效果更明显
g) 优化的查询语句,绝大多数情况下,使用索引可以提高查询的速度,但如果sql语句使用不恰当的话,索引无法发挥它的特性。
19、Tomcat服务器优化(内存,并发连接数,缓存)
a) 内存优化:主要是对Tomcat启动参数进行优化,我们可以在Tomcat启动脚本中修改它的最大内存数等等。
b) 线程数优化:Tomcat的并发连接参数,主要在Tomcat配置文件中server.xml中配置,比如修改最小空闲连接线程数,用于提高系统处理性能等等。
c) 优化缓存:打开压缩功能,修改参数,比如压缩的输出内容大小默认为2KB,可以适当的修改。
20、HTTP协议
a) 常用的请求方法有get、post
b) Get与post的区别:传送数据,get携带参数与访问地址传送,用户可以看见,这的话信息会不安全,导致信息泄露。而post则将字段与对应值封装在实体中传送,这个过程用户是不可见的。Get传递参数有限制,而post无限制。
21、TCP/UDP协议
22、Java集合类框架的基本接口有哪些
a) Collection集合接口,List、set实现Collection接口,arraylist、linkedlist,vector实现list接口,stack继承vector,Map接口,hashtable、hashmap实现map接口
23、类加载的过程
a) 遇到一个新的类时,首先会到方法区去找class文件,如果没有找到就会去硬盘中找class文件,找到后会返回,将class文件加载到方法区中,在类加载的时候,静态成员变量会被分配到方法区的静态区域,非静态成员变量分配到非静态区域,然后开始给静态成员变量初始化,赋默认值,赋完默认值后,会根据静态成员变量书写的位置赋显示值,然后执行静态代码。当所有的静态代码执行完,类加载才算完成。
24、对象的创建
a) 遇到一个新类时,会进行类的加载,定位到class文件
b) 对所有静态成员变量初始化,静态代码块也会执行,而且只在类加载的时候执行一次
c) New 对象时,jvm会在堆中分配一个足够大的存储空间
d) 存储空间清空,为所有的变量赋默认值,所有的对象引用赋值为null
e) 根据书写的位置给字段一些初始化操作
f) 调用构造器方法(没有继承)
25、jvm的优化
a) 设置参数,设置jvm的最大内存数
b) 垃圾回收器的选择
26、高并发处理
a) 了解一点高并发性问题,比如一W人抢一张票时,如何保证票在没买走的情况下所有人都能看见这张票,显然是不能用同步机制,因为synchronize是锁同步一次只能一个人进行。这时候可以用到锁机制,采用乐观锁可以解决这个问题。乐观锁的简单意思是在不锁定表的情况下,利用业务的控制来解决并发问题,这样即保证数据的可读性,又保证保存数据的排他性,保证性能的同时解决了并发带来的脏读数据问题。
27、事物的理解
a) 事物具有原子性,一致性,持久性,隔离性
b) 原子性:是指在一个事物中,要么全部执行成功,要么全部失败回滚。
c) 一致性:事物执行之前和执行之后都处于一致性状态
d) 持久性:事物多数据的操作是永久性
e) 隔离性:当一个事物正在对数据进行操作时,另一个事物不可以对数据进行操作,也就是多个并发事物之间相互隔离。
28、Struts工作流程
a) 客户端发出一个请求到servlet容器
b) 请求经过一些列过滤被filterdispatcher调用,filterdispatch通过actionMapper去找相对应的action。
c) Actionmapper找到对应的action返回给filterdispatch,dispatch把处理权交给actionproxy
d) Actionproxy通过配置文件找到对应的action类
e) Actionproxy创建一个actionIinvocation的实例处理业务逻辑
f) 一旦action处理完毕,actioninvocation负责根据stuts.xml的配置找到对应的返回结果。返回结果通常是jsp页面。