1操作系统中 heap 和 stack 的区别
栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。Java自动管理栈和堆,程序员不能直接地设置栈或堆。
在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
Java中变量在内存中的分配:
1、类变量(static修饰的变量):在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期–一直持续到整个”系统”关闭。
2、实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”。 实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存。
3、局部变量:局部变量,由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放。
2什么是基于注解的切面实现
Spring框架中的AOP拦截技术,是POJO的方法层面的拦截。其底层实现原理,是动态代理技术。对于面向接口的方法拦截,依赖于jdk的动态代理技术,即java.lang.reflect.Proxy#newProxyInstance,将对被代理的目标对象的调用,委托到代理对象,触发拦截通知;而当被拦截的方法, 不是在接口中定义时,使用的是cglib,对字节码进行动态增强,生成被代理类的子对象,以实现代理。下面是两种代理技术实现原理描述的demo:
3什么是 对象/关系 映射集成模块
4什么是 Java 的反射机制
就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成对象。类在运行的时候,可以得到该类的信息,并且 可以动态的修改这些信息,自己能看到自己,跟照镜子一样,也就是说Java的反射机制是在编辑的时候并不确定哪个类被加载了,而是在程序运行的时候才加载、探知、自审,使用在编译期并不知道的类,这样的特点就是反射。
Java反射作用: 假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
5什么是 ACID6BS与CS的联系与区别
7Cookie 和 Session的区别
8fail-fast 与 fail-safe 机制有什么区别9get 和 post请求的区别10Interface 与 abstract 类的区别
11IOC的优点是什么12IO 和 NIO的区别,NIO优点13Java 8 / Java 7 为我们提供了什么新功能
14什么是竞态条件? 举个例子说明。15JRE、JDK、JVM 及 JIT 之间有什么不同16MVC的各个部分都有那些技术来实现?如何实现?17RPC 通信和 RMI 区别18什么是 Web Service(Web服务)19JSWDL开发包的介绍。20JAXP、JAXM的解释。21SOAP、UDDI,WSDL解释。22WEB容器主要有哪些功能? 并请列出一些常见的WEB容器名字。23一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制
24简单说说你了解的类加载器。是否实现过类加载器
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载1.先检查此类是否被加载过,若没有加载则调用父加载器的loadClass()方法,
2.若父加载器为空,则默认使用启动类加载器作为父加载器,
3.若父类加载失败,会抛出一个异常,然后再调用自己的findClass()方法来进行加载;
结合第一步加载可以这么理解,
1.首先要启动→启动类加载器,这时会调用启动类加载器的父加载器,但由于启动类加载器时所有类的父加载器,
所以其父加载器为空(相当于Object是所有类的父类,这种感脚~),然后它就会调用自己的findClass方法来自启动加载 ;
2.标准扩展类加载器启动时就会借助其父类 启动类加载器 作为父加载器 来启动了;
3.系统类加载器启动时就会借助其父类 标准扩展类加载器 作为父加载器 来启动了;
4.最后我们编写的普通类就会借助其父类 系统类加载器 作为父加载器 来启动了;
验证主要分为以下几个步骤:文件格式验证->元数据验证->字节码验证->符号引用验证
1.文件格式验证:主要是检查字节码的字节流是否符合Class文件格式的规范,验证该文件是否能被当前的 jvm 所处理,
如果没问题,字节里就可以进入方法区进行保存了;
2.元数据验证:对字节码描述的信息进行语义分析,保证其描述的内容符合java语言的语法规范,能被java虚拟机识别;
3.字节码验证:该部分最为复杂,对方法体内的内容进行验证,保证代码在运行时不会做出什么危害虚拟机安全的事件;
4.符号引用验证:来验证一些引用的真实性与可行性,比如代码里面引了其他类(符号中通过字符串描述的全限定名是否能找到对应的类),这里就要去检测一下那些来究竟是否存在;或者说代码中访问了其他类的一些属性,这里就对那些属性的可以访问行进行了检验。(这一步将为后面的解析工作打下基础)
准备阶段会为类变量(指的是静态变量,这就是我们常说的,静态变量/方法 在类加载的时候就执行了,通过类名.静态**来调用)分配内存并设置类的初始1.类或接口的解析;2.字段解析;3.类方法解析;4.接口方法解析;1.遇到new,get static,put static,invoke static这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。
也就是三种情况:用new实例化一个对象时、读取或设置一个雷的静态字段时、执行静态方法时;
2.使用java.lang.reflect.*的方法对类进行反射调用时,如果类还没有进行过初始化,立即马上光速对其进行初始化!!!
3.初始化一个类的时候,如果其父类还没有被初始化,那么会先去初始化其父类;
4.当 JVM 启动时,用户需要指定一个要执行的主类(包含static void main(String 【】args)的那个类),则JVM会先去初始化这个类;
5.当使用JDK1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle实力最后的解析结果为 get static,put static,invoke static 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先初始化;
25解释一下什么叫AOP(面向切面编程)
26请简述 Servlet 的生命周期及其相关的方法
1.创建servlet实例
2.当servlet实例化后,将调用这个对象的init()方法进行初始化
3.再调用对象的service()方法来处理请求,并返回处理结果,在调用service之前,
需保证init初始化已被成功执行
4.当需要释放servlet的时候,调用对象的destroy()方法来结束,并释放资源
27请简述一下 Ajax 的原理及实现步骤28简单描述Struts的主要功能29什么是 N 层架构30什么是CORBA?用途是什么
(1) SOAP:简单对象访问协议,提供一个调用远程方法的协议。而SOAP是基于XML和HTTP的分布式对象的通信协议
独立于编程语言,使用XML传输格式。
使用WSDL(Web服务描述语言)格式的服务描述文件描述web服务的接口:被调用的方法,参数和返回值类型。
WSDL文件并不说明服务 做什么,只说明参数和返回值的类型。
最吸引人的地方是语言中立。
(2)CORBA 通用对象请求代理架构,使用IIOP(Internet Inter-ORB协议)支持对象间通信。
定义了接口定义语言(IDL)和应用编程接口(API),从而通过实现对象请求代理(ORB)来激活客户/服务器的交互。
ORB提供了一种机制,通过这种机制,对象可以透明的发出请求和接收响应。
CORBA仅仅是一个规范,而不是一个实现。
31什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”32什么是正则表达式?用途是什么?哪个包使用正则表达式来实现模式匹配
java.util.regex程序包
33什么是懒加载(Lazy Loading)
懒加载模式”又叫“懒汉模式”是指当第一次使用到这个属性时才给这个属性对应的成员变量进行初始化,如果程序还没运行到这个地方就不进行相应的创建和初始化有利于节省资源提高性能。与之对应的还用一种模式叫做“饿汉模式”就是程序一启动就初始化相应的成员变量,不管当时有没有用到先创建并初始化了再说,所以这种模式相对来说不需要程序员考虑那么详细,会耗费一点资源。
单例模式的惰性加载
通常当我们设计一个单例类的时候,会在类的内部构造这个类(通过构造函数,或者在定义处直接创建),并对外提供一个static getInstance方法提供获取该单例对象的途径。例如:
public class Singleton
{
private static Singleton instance = new Singleton();
private Singleton(){
…
}
public static Singleton getInstance(){
return instance;
}
}
这样的代码缺点是:第一次加载类的时候会连带着创建Singleton实例,这样的结果与我们所期望的不同,因为创建实例的时候可能并不是我们需要这个实例的时候。同时如果这个Singleton实例的创建非常消耗系统资源,而应用始终都没有使用Singleton实例,那么创建Singleton消耗的系统资源就被白白浪费了。
为了避免这种情况,我们通常使用惰性加载的机制,也就是在使用的时候才去创建。以上代码的惰性加载代码如下:
public class Singleton{
private static Singleton instance = null;
private Singleton(){
…
}
public static Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
}
2、惰性加载在多线程中的问题
先将惰性加载的代码提取出来:
public static Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
这是如果两个线程A和B同时执行了该方法,然后以如下方式执行:
1. A进入if判断,此时foo为null,因此进入if内
2. B进入if判断,此时A还没有创建foo,因此foo也为null,因此B也进入if内
3. A创建了一个Foo并返回
4. B也创建了一个Foo并返回
此时问题出现了,我们的单例被创建了两次,而这并不是我们所期望的。
3各种解决方案及其存在的问题
3.1 使用Class锁机制
以上问题最直观的解决办法就是给getInstance方法加上一个synchronize前缀,这样每次只允许一个现成调用getInstance方法:
public static synchronized Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
这种解决办法的确可以防止错误的出现,但是它却很影响性能:每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了
3.2 double-checked locking
曾经有人为了解决以上问题,提出了double-checked locking的解决方案
public static Singleton getInstance(){
if (instance == null)
synchronized(instance){
if(instance == null)
instance = new Singleton();
}
return instance;
}
让我们来看一下这个代码是如何工作的:首先当一个线程发出请求后,会先检查instance是否为null,如果不是则直接返回其内容,这样避免了进入synchronized块所需要花费的资源。其次,即使第2节提到的情况发生了,两个线程同时进入了第一个if判断,那么他们也必须按照顺序执行synchronized块中的代码,第一个进入代码块的线程会创建一个新的Singleton实例,而后续的线程则因为无法通过if判断,而不会创建多余的实例。
上述描述似乎已经解决了我们面临的所有问题,但实际上,从JVM的角度讲,这些代码仍然可能发生错误。
对于JVM而言,它执行的是一个个Java指令。在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就使出错成为了可能,我们仍然以A、B两个线程为例:
1. A、B线程同时进入了第一个if判断
2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
3.由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
5.此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
4 通过内部类实现多线程环境中的单例模式
为了实现慢加载,并且不希望每次调用getInstance时都必须互斥执行,最好并且最方便的解决办法如下:
public class Singleton{
private Singleton(){
…
}
private static class SingletonContainer{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonContainer.instance;
}
}
JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心3.2中的问题。此外该方法也只会在第一次调用的时候使用互斥机制,这样就解决了3.1中的低效问题。最后instance是在第一次加载SingletonContainer类时被创建的,而SingletonContainer类则在调用getInstance方法的时候才会被加载,因此也实现了惰性加载。
34什么是尾递归,为什么需要尾递归
里尾递归其实还用了一个重载,然后尾递归调用,其实最精髓就是 通过参数传递结果,达到不压栈的目的。C中玩好了尾递归,代码可以很秀。尾递归是一种高效解决问题的思想,C和erlang中的尾递归都是一样的。原理相同,效果相同。
35什么是控制反转(Inversion of Control)与依赖注入(Dependency Injection)36关键字finalize37什么是finalize()方法finalize()方法什么时候被调用析构函数(finalization)的目的是什么38final 和 finalize 的区别39final关键字有哪些用法40final 与 static 关键字可以用于哪里?它们的作用是什么41final, finally, finalize的区别42final、finalize 和 finally 的不同之处?43能否在运行时向 static final 类型的赋值44使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变45一个类被声明为final类型,表示了什么意思
1. static变量
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
2. static代码块
static代码块是类加载时,初始化自动执行的。如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
3. static方法
static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
static方法只能访问static的变量和方法,因为非static的变量和方法是需要创建一个对象才能访问的,而static的变量/方法不需要创建任何对象。
********
static的数据或方法,属于整个类的而不是属于某个对象的,是不会和类的任何对象实例联系到一起。所以子类和父类之间可以存在同名的static方法名,这里不涉及重载。所以不能把任何方法体内的变量声明为static,例如:
fun() {
static int i=0; //非法。
}
其实理解static是只有一个存储地方,而使用时直接使用,不需要创建对象,就能明白以上的注意事项。
另外,一般的类是没有static的,只有内部类可以加上static来表示嵌套类。
其实,我们只需要明白用static修饰的变量是属于整个类的。但是如果你在一个用static修饰的块中定义一个静态的变量,那么这个变量就应该是属于整个类的,但是很明显,并不是这样。在这个块中的变量明显只能是属于这个块自身而已,并不是属于整个类的,因此不能在一个静态的块中定义一个静态的变量。当然在静态的块中,也只能使用已经定义好的静态的属性和方法,不能使用不是静态的属性和方法
特别要注意的是,在编程中会经常使用final来修饰容器变量。用final修饰的容器变量,这个变量就不能再被初始化了,但是容器中的内容还是可以改变的,
在Java中声明属性、方法和类时,可使用关键字final来修饰。
final变量即为常量,只能赋值一次;
final方法不能被子类重写;
final类不能被继承。
1. final变量
声明 final 字段有助于优化器作出更好的优化决定,因为如果编译器知道字段的值不会更改,那么它能安全地在寄存器中高速缓存该值。final 字段还通过让编译器强制该字段为只读来提供额外的安全级别。
其初始化可以在两个地方,一是其定义处,也就是说在final变量定义时直接给其赋值,二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造函数中给值,不能同时既在定义时给了值,又在构造函数中给另外的值。
一旦被初始化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其引用不可再变。
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
另外方法中的内部类在用到方法中的参变量时,此参变也必须声明为final才可使用。
在java中,普通变量系统是自动初始化的,数值变量自动初始化为0,其余类型变量自动初始化为空。但是final类型的变量必须显示初始化,且初始化的方法必须是在申明时或者在构造方法中直接赋值,而不能通过调用函数赋值。
2. final方法
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌inline机制,大大提高执行效率。
注意,类中所有的private方法都被隐含是final的。由于无法取用private方法,则也无法重载之。
3. final类
final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
46throws, throw, try, catch, finally分别代表什么意义
47Java 有几种修饰符?分别用来修饰什么
4种修饰符
访问权限 类 包 子类 其他包
public ∨ ∨ ∨ ∨
protect ∨ ∨ ∨ ×
default ∨ ∨ × ×
private ∨ × × ×
48volatile volatile 修饰符的有过什么实践volatile 变量是什么?
volatile 变量和 atomic 变量有什么不同volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗能创建 volatile 数组吗?
1)Java 中能创建 Volatile 数组吗?
能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。2)volatile 能使得一个非原子操作变成原子操作吗?
一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为什么?因为 Java 中读取 long 类型变量不是原子的,需要分成两步,如果一个线程正在修改该 long 变量的值,另一个线程可能只能看到该值的一半(前 32 位)。但是对一个 volatile 型的 long 或 double 变量的读写是原子。3)volatile 修饰符的有过什么实践?一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。4)volatile 类型变量提供什么保证?(答案)
volatile 变量提供顺序和可见性保证,例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。
49transient变量有什么特点
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
50super什么时候使用
子类的构造函数中不是必须使用super,在构造函数中,如果第一行没有写super(),编译器会自动插入.但是如果父类没有不带参数的构造函数,或这个函数被私有化了(用private修饰).此时你必须加入对父类的实例化构造.而this就没有这个要求,因为它本身就进行实例化的构造.如果父类的构造函数是无参的,那子类构造函数会在第一行默认调用super().
51public static void 写成 static public void会怎样
52说明一下public static void main(String args[])这段声明里每个关键字的作用
public: main方法是Java程序运行时调用的第一个方法,因此它必须对Java环境可见。所以可见性设置为pulic.static: Java平台调用这个方法时不会创建这个类的一个实例,因此这个方法必须声明为static。void: main方法没有返回值。String是命令行传进参数的类型,args是指命令行传进的字符串数组。
为什么main方法是静态的(static)正因为main方法是静态的,JVM调用这个方法就不需要创建任何包含这个main方法的实例。因为C和C++同样有类似的main方法作为程序执行的入口。如果main方法不声明为静态的,JVM就必须创建main类的实例,因为构造器可以被重载,JVM就没法确定调用哪个main方法。
静态方法和静态数据加载到内存就可以直接调用而不需要像实例方法一样创建实例后才能调用,如果main方法是静态的,那么它就会被加载到JVM上下文中成为可执行的方法。
为什么main方法是公有的(public)Java指定了一些可访问的修饰符如:private、protected、public,任何方法或变量都可以声明为public,Java可以从该类之外的地方访问。因为main方法是公共的,JVM就可以轻松的访问执行它。为什么main方法没有返回值(Void) 因为main返回任何值对程序都没任何意义,所以设计成void,意味着main不会有任何值返回总结main方法必须声明为public、static、void,否则JVM没法运行程序如果JVM找不到main方法就抛出NoSuchMethodError:main异常,例如:如果你运行命令:java HelloWrold,JVM就会在HelloWorld.class文件中搜索public static void main (String[] args) 放法
main方式是程序的入口,程序执行的开始处。main方法被一个特定的线程”main”运行,程序会一直运行直到main线程结束或者non-daemon线程终止。当你看到“Exception in Thread main”如:Excpetion in Thread main:Java.lang.NullPointedException ,意味着异常来自于main线程你可以声明main方法使用java1.5的可变参数的方式如:public static void main(String... args)除了static、void、和public,你可以使用final,synchronized、和strictfp修饰符在main方法的签名中,如:public strictfp final synchronized static void main(String[] args)main方法在Java可以像其他方法一样被重载,但是JVM只会调用上面这种签名规范的main方法。你可以使用throws子句在方法签名中,可以抛出任何checked和unchecked异常静态初始化块在JVM调用main方法前被执行,它们在类被JVM加载到内存的时候就被执行了。
53请说出作用域public, private, protected, 以及不写时的区别54sizeof 是Java 的关键字吗
55staticstatic class 与 non static class的区别static 关键字是什么意思?
static class non static class
1、用static修饰的是内部类,此时这个内部类变为静态内部类;对测试有用;
2、内部静态类不需要有指向外部类的引用;
3、静态类只能访问外部类的静态成员,不能访问外部类的非静态成员;
1、非静态内部类需要持有对外部类的引用;
2、非静态内部类能够访问外部类的静态和非静态成员;
3、一个非静态内部类不能脱离外部类实体被创建;
一个非静态内部类可以访问外部类的数据和方法;
Java中是否可以覆盖(override)一个private或者是static的方法
在学习继承的过程中,我们知道,如果在父类中修饰了一个private的方法,子类继承之后,对子类也是不可见的。那么如果子类声明了一个跟父类中定义为private一样的方法,那么编译器只当作是你子类自己新增的方法,并不能算是继承过来的。
静态类型有什么特点main() 方法为什么必须是静态的?能不能声明 main() 方法为非静态
main()方法一定是静态的。如果main()允许是非静态的,那么在调用main方法时,JVM就得实例化它的类。在实例化时,还得调用类的构造函数。如果这个类的构造函数有参数,那么届时就会出现歧义。是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用静态变量在什么时候加载?编译期还是运行期?静态代码块加载的时机呢
关于这个问题,全局变量(成员变量)是在创建对象的时候分配内存的创建对象过程为1分配空间2递归的创建父类对象(无父类这步可省略)3初始化成员变量4调用构造方法创建一个对象
静态变量是在类加载的时候分配空间的,静态变量和对象没有关系是在JVM第一次读到一个类的时候加载信息的过程中分配空间的类加载过程为1加载父类(如果父类已经加载过,则不在加载)2初始化静态属性3按顺序的初始化静态代码块初始化的前提就是分配空间而且静态变量在以后的创建对象的时候不在初始化所以一般用静态来保存共享信息
成员方法是否可以访问静态变量?为什么静态方法不能访问成员变量
static成员是在JVM的CLASSLOADER加载类的时候初始化的,而非static的成员是在创建对象,即new 操作的时候才初始化的;类加载的时候初始化static的成员,此时static 已经分配内存空间,所以可以访问;非static的成员还没有通过new创建对象而进行初始化,所以必然不可以访问。
简单点说:静态成员属于类,不需要生成对象就存在了.而非静态需要生成对象才产生,所以静态成员不能直接访问.
下面说说静态的特点:
1.随着类的加载而加载——静态会随着类的消失而消失,说明静态的生命周期最长
2.优先于对象的存在——静态是先存在的,对象是后存在的
3.被所有对象共享
4.可以直接被类名多调用
静态的使用注意事项:
1.静态方法只能访问静态成员(包括成员变量和成员方法)
非静态方法可以访问静态也可以访问非静态
2.静态方法中不可以定义this,super关键字
因为 一个类中,一个static变量只会有一个内存空间,虽然有多个类实例,但这些类实例中的这个static变量会共享同一个内存空间。静态方法在优先于对象存在,所以静态方法中不可以出现this,super关键字。
3.主函数是静态的。
静态的利弊:
利:对 对象的共享数据进行单独空间的存储,节省空间,没有必要每一个对象中都存储一份,可以直接被类名所调用
弊:生命周期过长,访问出现局限性(只能访问静态)
实例变量和类变量(static声明的变量)的区别:
1.存放位置
类变量随着类的加载存在于方法区中,实例变量随着对象的建立存在于堆内存里
2.生命周期
类变量生命周期最长,随着“类”的加载而加载,随着类的消失而消失
实例变量随着“对象”的消失而消失
56switch 语句中的表达式可以是什么类型数据switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上
switch(expr1)中,expr1是一个整数表达式。
因此传递给switch和case语句的参数应该是int、short、char或则byte。
long,string都不能作用于swtich
57while 循环和 do 循环有什么不同
while是先判断在执行如果判断不成立,就不会执行;do/while是先执行在判断,不管判断是否成立都会执行一次
操作符
58&操作符和&&操作符有什么区别?
59a = a + b 与 a += b 的区别?
1、对于同样类型的a,b来说
两个式子执行的结果确实没有什么区别。但是从编译的角度看吧(武让说的),a+=b;执行的时候效率高。
2、对于不同类型的a,b来说
2.1 不同类型的两个变量在进行运算的时候,我们经常说到的是类型的转换问题。这里,记住两点:一、运算过程中,低精度的类型向高精度类型转换。二、如果将高精度的数值赋值给低精度类型变量,则必须要进行显性的强制转换。
2.2 对于a+=b;这个式子,要明确的一点是,+=运算中,结合了强制类型转换的功能,因此,不会出现编译错误;而对于a=a+b;这个式子,因为是简单的运算,没有类型转换,在编译过程中会报错
60逻辑操作符 (&,|,^)与条件操作符(&&,||)的区别
61 3*0.1 == 0.3 将会返回什么?true 还是 false?
false,因为有些浮点数不能完全精确的表示出来。
62float f=3.4; 是否正确?
不正确。 原因:精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4 或float f = 3.4f
在java里面,没小数点的默认是int,有小数点的默认是 double;
编译器可以自动向上转型,如int 转成 long 系统自动转换没有问题,因为后者精度更高
double 转成 float 就不能自动做了,所以后面的加上个 f;
63short s1 = 1; s1 = s1 + 1;有什么错?
对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
64基础类型(Primitives)与封装类型(Wrappers)的区别在哪里
65简述九种基本数据类型的大小,以及他们的封装类
基本类型 大小(字节) 默认值 封装类
byte 1 (byte)0 Byte
short 2 (short)0 Short
int 4 0 Integer
long 8 0L Long
float 4 0.0f Float
double 8 0.0d Double
boolean - false Boolean
char 2 \u0000(null) Character
void - - Void
66int 和 Integer 哪个会占用更多的内存? int 和 Integer 有什么区别?parseInt()函数在什么时候使用到
67float和double的默认值是多少
68如何去小数四舍五入保留小数点后两位
69char 型变量中能不能存贮一个中文汉字,为什么
70怎样将 bytes 转换为 long 类型
71怎么将 byte 转换为 String如何将数值型字符转换为数字
72我们能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象
73能在不进行强制转换的情况下将一个 double 值赋值给 long 类型的变量吗74类型向下转换是什么
75如何权衡是使用无序的数组还是有序的数组
76怎么判断数组是 null 还是为空怎么打印数组? 怎样打印数组中的重复元素
77Array 和 ArrayList有什么区别?什么时候应该使用Array而不是ArrayList
78数组和链表数据结构描述,各自的时间复杂度
79数组有没有length()这个方法? String有没有length()这个方法
80队列和栈是什么,列出它们的区别
81BlockingQueue是什么
82简述 ConcurrentLinkedQueue LinkedBlockingQueue 的用处和不同之处
LinkedBlockingQueue 的API中,从队列中获取元素,有以下几个方法:
1、take():原文:Retrieves and removes the head of this queue, waiting if necessary until an element becomes available.
翻译完:从队列中取出元素E,如果队列为空,则阻塞该线程直到队列不为空拿出元素E位置;
这样可能造成的情况是:在生产者消费中模式中,如果生产者已经生产完毕了,消费中消费完毕后,队列为空,此时用take()方法会阻塞,从而导致线程无法关闭,
表现在运行后,eclipse中的terminae一直为红色;
2、poll():原文:Retrieves and removes the head of this queue, or returns null if this queue is empty.
翻译为:取出并删除队列中的首元素,如果队列为空,则返回null,不进行阻塞。
相比于take()可以根据其他条件的判断,关闭线程,不会出现上述状态。
举个例子:生产消费中,生产者一直生产直到生产完毕后,通知消费者,我生产完了,消费者通过pool()方法一直取出元素E,直到某次,从队列取出来的=null,
且此时收到生产者说我生产完了,那么你就可以顺利关闭消费者线程了。
3、poll(long timeout, TimeUnit unit):Retrieves and removes the head of this queue, waiting up to the specified wait time if necessary for an element to become available.
翻译为:取出后删除头元素E,如果取不到则等到timeout时间在取,还是取不到就返回null;
比如得到10秒,timeout为10.TimeUnit.Second
4、peek():Retrieves, but does not remove, the head of this queue, or returns null if this queue is empty.
翻译:取出第一个元素但是不删除它,没有就返回null
ConcurrentLinkedQueue:队列中提供了上面的poll()、peek()方法,因此用ConcurrentLinkedQueue队列不会进行阻塞,
这就是为什么ConcurrentLinkedQueue队列是阻塞队列而LinkedBlockingQueue 不是,当然两者都是线程安全的。
83ArrayList、Vector、LinkedList的存储性能和特性
84StringStringBufferByteBuffer 与 StringBuffer有什么区别
85HashMap的工作原理是什么内部的数据结构是什么
86HashMap 的 table的容量如何确定?loadFactor 是什么? 该容量如何变化?这种变化会带来什么问题?
87HashMap 实现的数据结构是什么?如何实现
88HashMap 和 HashTable、ConcurrentHashMap 的区别
HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。
HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的。如果仔细阅读他们的源码,就会发现HashMap是允许插入key和value是null的数据的,而ConcurrentHashMap是不允许key和value是null的89HashMap的遍历方式及效率90HashMap、LinkedMap、TreeMap的区别
Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。
Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
一般情况下,我们用的最多的是HashMap,HashMap里面存入的键值对在取出的时候是随机的,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。
TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。
1. HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key
2. Map的key和Set都有一个共同的特性就是集合的唯一性.TreeMap更是多了一个排序的功能.
3. hashCode和equal()是HashMap用的, 因为无需排序所以只需要关注定位和唯一性即可.
a. hashCode是用来计算hash值的,hash值是用来确定hash表索引的.
b. hash表中的一个索引处存放的是一张链表, 所以还要通过equal方法循环比较链上的每一个对象
才可以真正定位到键值对应的Entry.
c. put时,如果hash表中没定位到,就在链表前加一个Entry,如果定位到了,则更换Entry中的value,并返回旧value
4. 由于TreeMap需要排序,所以需要一个Comparator为键值进行大小比较.当然也是用Comparator定位的.
a. Comparator可以在创建TreeMap时指定
b. 如果创建时没有确定,那么就会使用key.compareTo()方法,这就要求key必须实现Comparable接口.
c. TreeMap是使用Tree数据结构实现的,所以使用compare接口就可以完成定位了
注意:
1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。
2、Set和Collection拥有一模一样的接口。
3、List,可以通过get()方法来一次取出一个元素。使用数字来选择一堆对象中的一个,get(0)...。(add/get)
4、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue。
5、Map用 put(k,v) / get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value。
HashMap会利用对象的hashCode来快速找到key。
* hashing
哈希码就是将对象的信息经过一些转变形成一个独一无二的int值,这个值存储在一个array中。
我们都知道所有存储结构中,array查找速度是最快的。所以,可以加速查找。
发生碰撞时,让array指向多个values。即,数组每个位置上又生成一个梿表。
6、Map中元素,可以将key序列、value序列单独抽取出来。
使用keySet()抽取key序列,将map中的所有keys生成一个Set。
使用values()抽取value序列,将map中的所有values生成一个Collection。
为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。
91如何决定选用HashMap还是TreeMap
92如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办93HashMap 是线程安全的吗?并发下使用的 Map 是什么,它们内部原理分别是什么,比如存储方式、 hashcode、扩容、 默认容量等
94HashSet和TreeSet有什么区别
95HashSet 内部是如何工作的96WeakHashMap 是怎么工作的?
96Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是 equals()? 它们有何区别?
97TreeMap:TreeMap 是采用什么树实现的?TreeMap、HashMap、LindedHashMap的区别。TreeMap和TreeSet在排序时如何比较元素?98Collections工具类中的sort()方法如何比较元素?
Comparable和Comparator接口,也是java集合框架的成员
Comparable接口给对象定义了默认的比较规则,当一个类实现了这个接口,就说明了该类的实例是可以比较大小的,可以进行自然排序。一旦类实现了Comparable接口,就必须实现compareTo()方法,当类的两个实例进行比较时,就会调用这个方法。该方法返回一个int类型的值,若为正数表示(a对像比b对象)大,负数表示小,0表示相等
Comparator-给对象定义临时的比较规则,当一个类实现了这个接口,就必须实现compare()方法,可以将comparator传递给sort方法,如Collections.sort或者Arrays.sort,也就是可以调用Collections.sort方法来使用实现接口的的具体实例。
这里要注意,在你使用这个方法以后,你的Student类(见下文代码)就不需要再实现Comparable接口了。
99TreeSet:一个已经构建好的 TreeSet,怎么完成倒排序。100EnumSet 是什么
101Hashcode 的作用102简述一致性 Hash 算法103有没有可能 两个不相等的对象有相同的 hashcode?当两个对象 hashcode 相同怎么办?如何获取值对象
104为什么在重写 equals 方法的时候需要重写 hashCode 方法?equals与 hashCode 的异同点在哪里
如果你重载了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。
使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。
而对于每一个对象,通过其hashCode()方法可为其生成一个整形值(散列码),该整型值被处理后,将会作为数组下标,存放该对象所对应的Entry(存放该对象及其对应值)。 equals()方法则是在HashMap中插入值或查询时会使用到。当HashMap中插入值或查询值对应的散列码与数组中的散列码相等时,则会通过equals方法比较key值是否相等,所以想以自建对象作为HashMap的key,必须重写该对象继承object的hashCode和equals方法。
--------------------------------------------------------------------------------
本来不就有hashcode()和equals()了么?干嘛要重写,直接用原来的不行么?
HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地址,这样即便有相同含义的两个对象,比较也是不相等的,例如,生成了两个“羊”对象,正常理解这两个对象应该是相等的,但如果你不重写 hashcode()方法的话,比较是不相等的!
HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素是否相等。
--------------------------------------------------------------------------------
105a.hashCode() 有什么用?与 a.equals(b) 有什么关系
106hashCode() 和 equals() 方法的重要性体现在什么地方
107Object:Object有哪些公用方法?Object类hashcode,equals 设计原则? sun为什么这么设计?Object类的概述
108如何在父类中为子类自动完成所有的 hashcode 和 equals 实现?这么做有何优劣。
109可以在 hashcode() 中使用随机数字吗?
110LinkedHashMap 和 PriorityQueue 的区别是什么
111List, Set, Map三个接口,存取元素时各有什么特点List, Set, Map 是否继承自 Collection 接口
List与Set都是单列元素的集合,它们有一个功共同的父接口Collection。Set里面不允许有重复的元素,
存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true;当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。
取元素:没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。List表示有先后顺序的集合,存元素:多次调用add(Object)方法时,每次加入的对象按先来后到的顺序排序,也可以插队,即调用add(int index,Object)方法,就可以指定当前对象在集合中的存放位置。取元素:方法1:Iterator接口取得所有,逐一遍历各个元素方法2:调用get(index i)来明确说明取第几个。Map是双列的集合,存放用put方法:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。取元素:用get(Object key)方法根据key获得相应的value。 也可以获得所有的key的集合,还可以获得所有的value的集合,还可以获得key和value组合成的Map.Entry对象的集合。
112遍历一个 List 有哪些不同的方式
113LinkedList 是单向链表还是双向链表
114LinkedList 与 ArrayList 有什么区别
115描述下 Java 中集合(Collections),接口(Interfaces),实现(Implementations)的概念。LinkedList 与 ArrayList 的区别是什么?
116插入数据时,ArrayList, LinkedList, Vector谁速度较快?
117ArrayList 和 HashMap 的默认大小是多数
118ArrayList 和 LinkedList 的区别,什么时候用 ArrayList?
ArrayList,LinkedList,Vestor这三个类都实现了java.util.List接口,但它们有各自不同的特性,主要如下:
一、同步性
ArrayList,LinkedList是不同步的,而Vestor是同步的。所以如果不要求线程安全的话,可以使用ArrayList或LinkedList,可以节省为同步而耗费的开销。但在多线程的情况下,有时候就不得不使用Vector了。当然,也可以通过一些办法包装ArrayList,LinkedList,使他们也达到同步,但效率可能会有所降低。
二、数据增长
从内部实现机制来讲ArrayList和Vector都是使用Objec的数组形式来存储的。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
三、检索、插入、删除对象的效率
ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行(n-i)个对象的位移操作。
LinkedList中,在插入、删除集合中任何位置的元素所花费的时间都是一样的—O(1),但它在索引一个元素的时候比较慢,为O(i),其中i是索引的位置。
一般大家都知道ArrayList和LinkedList的大致区别:
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
119ArrayList 和 Set 的区别?120ArrayList, LinkedList, Vector的区别
Arraylist和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以插入数据慢,查找有下标,所以查询数据快,Vector由于使用了synchronized方法-线程安全,所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项前后项即可,插入数据较快。
线性表,链表,哈希表是常用的数据结构,在进行java开发时,JDK已经为我们提供了一系列相应的类实现基本的数据结构,这些结构均在java.util包中,
collection
├List
│├LinkedList
│├ArrayList
│└Vector
│└Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(elements),一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
由Collection接口派生的两个接口是List和Set。
List接口
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
ArrayList类
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
Vector类
Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
Stack 类
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Hashtable类
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));
要取出一个数,比如2,用相应的key:
Integer n = (Integer)numbers.get(“two”);
System.out.println(“two = ” + n);
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
Hashtable是同步的。
HashMap类
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
WeakHashMap类
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
总结
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
使用模式
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。
121ArrayList是如何实现的,ArrayList 和 LinkedList 的区别
122ArrayList如何实现扩容
123Array 和 ArrayList 有何区别?什么时候更适合用Array
124说出ArrayList,Vector, LinkedList的存储性能和特性
1.ArrayList 采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,所以最大的缺点就是插入删除时非常麻烦
2.LinkedList 采用的将对象存放在独立的空间中,而且在每个空间中还保存下一个链接的索引 但是缺点就是查找非常麻烦 要丛第一个索引开始
3.ArrayList和Vector都是用数组方式存储数据,此数组元素数要大于实际的存储空间以便进行元素增加和插入操作,他们都允许直接用序号索引元素,但是插入数据元素涉及到元素移动等内存操作,所以索引数据快而插入数据慢.
4.Vector使用了sychronized方法(线程安全),所以在性能上比ArrayList要差些.
5.LinkedList使用双向链表方式存储数据,按序号索引数据需要前向或后向遍历数据,所以索引数据慢,是插入数据时只需要记录前后项即可,所以插入的速度快.
arraylist和vector的区别?
1).同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程不安全的,不是同步的
2).数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
125Map, Set, List, Queue, Stack126Map 接口提供了哪些不同的集合视图127为什么 Map 接口不继承 Collection 接口
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
Java基础题
本文我们将要讨论Java面试中的各种不同类型的面试题,它们可以让雇主测试应聘者的Java和通用的面向对象编程的能力。下面的章节分为上下两篇,第一篇将要讨论面向对象编程和它的特点,关于Java和它的功能的常见问题,Java的集合类,垃圾收集器,第二篇主要讨论异常处理,Java小应用程序,Swing,JDBC,远程方法调用(RMI),Servlet和JSP。Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点:面向对象编程有很多重要的特性,比如:封装,继承,多态和抽象。下面的章节我们会逐个分析这些特性。封装封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。在Java当中,有3种修饰符:public,private和protected。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。下面列出了使用封装的一些好处:通过隐藏对象的属性来保护对象内部的状态。提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。禁止对象之间的不良交互提高模块化。参考这个文档获取更多关于封装的细节和示例。多态多态是编程语言给不同的底层数据类型做相同的接口展示的一种能力。一个多态类型上的操作可以应用到其他类型的值上面。继承继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也可以在不修改类的情况下给现存的类添加新特性。抽象抽象是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细节来创建类。Java支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开。抽象和封装的不同点抽象和封装是互补的概念。一方面,抽象关注对象的行为。另一方面,封装关注对象行为的细节。一般是通过隐藏对象内部状态信息做到封装,因此,封装可以看成是用来提供抽象的一种策略。常见的Java问题
1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
2.JDK和JRE的区别是什么?Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。
3.”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
4.是否可以在static环境中访问非static变量?
static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
5. Java支持的数据类型有哪些?什么是自动拆装箱?Java语言支持的8中基本数据类型是:byteshortintlongfloatdoublebooleancha自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化Integer,double转化成double,等等。反之就是自动拆箱。
6. Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思
Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。
7. Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。8.Java支持多继承么?不支持,Java不支持多继承。每个类都只能继承一个类,但是可以实现多个接口。
8. 接口和抽象类的区别是什么?
Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。类可以实现很多个接口,但是只能继承一个象类类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。抽象类可以在不提供接口方法实现的情况下实现接口。Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。
9. 什么是值传递和引用传递?对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。Java线程
10. 进程和线程的区别是什么?进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。
11. 创建线程有几种不同的方式?你喜欢哪一种?为什么?
有三种方式可以用来创建线程:继承Thread类实现Runnable接口应用程序可以使用Executor框架来创建线程池
实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
12. 概括的解释下线程的几种可用状态。
线程在执行过程中,可以处于下面几种状态:就绪(Runnable):线程准备运行,不一定立马就能开始执行。运行中(Running):进程正在执行线程的代码。等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。睡眠中(Sleeping):线程被强制睡眠。I/O阻塞(Blocked on I/O):等待I/O操作完成。同步阻塞(Blocked on Synchronization):等待获取锁。死亡(Dead):线程完成了执行。
13. 同步方法和同步代码块的区别是什么?在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。
14. 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。16.什么是死锁(deadlock)?两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。
17. 如何确保N个线程可以访问N个资源同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。Java集合类
18. Java集合类框架的基本接口有哪些?
Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:Collection:代表一组对象,每一个对象都是它的子元素。Set:不包含重复元素的Collection。List:有顺序的collection,并且可以包含重复元素。Map:可以把键(key)映射到值(value)的对象,键不能重复。
19. 为什么集合类没有实现Cloneable和Serializable接口?
集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。
20. 什么是迭代器(Iterator)?
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素。克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化
21. Iterator和ListIterator的区别是什么?
public ListIterator listIterator(int index) {
return new ChangeIterator(list.listIterator(index));
}
ListIterator主要区别在以下方面:1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
4. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不修改下面列出了他们的区别:Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
23.Java中的HashMap的工作原理是什么?
Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。
24. hashCode()和equals()方法的重要性体现在什么地方?
Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。
25. HashMap和Hashtable有什么区别?
HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:HashMap允许键和值是null,而Hashtable不允许键或者值是null。Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。 一般认为Hashtable是一个遗留的类。
26. 数组(Array)和列表(ArrayList)有什么区别?
什么时候应该使用Array而不是ArrayList?下面列出了Array和ArrayList的不同点:Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。Array大小是固定的,ArrayList的大小是动态变化的。ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
27. ArrayList和LinkedList有什么区别?ArrayList和LinkedList都实现了List接口,他们有以下的不同点:ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。也可以参考ArrayList vs. LinkedList。
28. Comparable和Comparator接口是干什么的?列出它们的区别。
Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。
29. 什么是Java优先级队列(Priority Queue)?
PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。
30. 你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?
大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。
31. 如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。
32. Java集合类框架的最佳实践有哪些?
根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。编程的时候接口优于实现。底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。33.Enumeration接口和Iterator接口的区别有哪些?
Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。
34. HashSet和TreeSet有什么区别?HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。垃圾收集器(Garbage Collectors)
35.Java中垃圾回收有什么目的?什么时候进行垃圾回收?
垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。
36.System.gc()和Runtime.gc()会做什么事情?这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。
37.finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
在释放对象占用的内存之前,垃圾收集器会调用对象的finalize()方法。一般建议在该方法中释放对象持有的资源。38.如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?不会,在下一个垃圾回收周期中,这个对象将是可被回收的。
38Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)?
39. JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。
40.串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。
40. 在Java中,对象什么时候可以被垃圾回收?当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
41. JVM的永久代中会发生垃圾回收么?垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区
128介绍Java中的Collection FrameWork。集合类框架的基本接口有哪些
129Collections类是什么?Collection 和 Collections的区别?Collection、Map的实现130集合类框架的最佳实践有哪些
131为什么 Collection 不从 Cloneable 和 Serializable 接口继承
132说出几点 Java 中使用 Collections 的最佳实践?
133Collections 中 遗留类 (HashTable、Vector) 和 现有类的区别
134什么是 B+树,B-树,列出实际的使用场景。
135Comparator 与 Comparable 接口是干什么的?列出它们的区别
136对象拷贝(clone)
137如何实现对象克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
实现对象克隆有两种方式:
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
138深拷贝和浅拷贝区别
139深拷贝和浅拷贝如何实现激活机制
140写clone()方法时,通常都有一行代码,是什么
141在比较对象时,”==” 运算符和 equals 运算有何区别
142如果要重写一个对象的equals方法,还要考虑什么
143两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对
144构造器链是什么
145创建对象时构造器的调用顺序
146什么是不可变象(immutable object)
147为什么 Java 中的 String 是不可变的(Immutable)
148如何构建不可变的类结构?关键点在哪里
149能创建一个包含可变对象的不可变对象吗
150如何对一组对象进行排序151构造器(constructor)是否可被重写(override)152方法可以同时即是 static 又是 synchronized 的吗
153abstract 的 method是否可同时是 static,是否可同时是 native,是否可同时是synchronized153Java支持哪种参数传递类型
154一个对象被当作参数传递到一个方法,是值传递还是引用传递
155当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递
156我们能否重载main()方法如果main方法被声明为private会怎样
157GC是什么?为什么要有GC
158什么时候会导致垃圾回收
159GC是怎么样运行的
160新老以及永久区是什么
161GC 有几种方式?怎么配置
162什么时候一个对象会被GC? 如何判断一个对象是否存活
判断对象是否活着的算法
引用计数法
引用计数法的基本概念是:给对象添加一个引用计数器,每当有一个地方引用了该对象,计数器就加1当引用失效,计数器就减1;任何时刻的计数器为0的对象就是不可能在被使用的对象。虽然是一个实现简单有效的算法,但是JVM已经很少使用这种算法了。可行性分析算法现在主流的JVM都是采用“可行性分析”的算法来标记“死去”的对象。 算法的基本思路是通过一些称为“GC-Roots”的对象,从这些节点往下延伸,搜索所走过的路叫引用链,当一个对象没有被引用链搜索到,则证明该对象不可用。如下图Object-5\6\7是不可用的:现在主流的JVM都是采用“可行性分析”的算法来标记“死去”的对象。
算法的基本思路是通过一些称为“GC-Roots”的对象,从这些节点往下延伸,搜索所走过的路叫引用链,当一个对象没有被引用链搜索到,则证明该对象不可用。如下图Object-5\6\7是不可用的:
现在主流的JVM都是采用“可行性分析”的算法来标记“死去”的对象。 算法的基本思路是通过一些称为“GC-Roots”的对象,从这些节点往下延伸,搜索所走过的路叫引用链,当一个对象没有被引用链搜索到,则证明该对象不可用。如下图Object-5\6\7是不可用的:可用作GC-Roots的对象有: 1.方法区的静态类型引用的对象 2.方法区的常量引用的对象 3.方法栈中引用的对象 4.本地栈中引用的对象
163System.gc() Runtime.gc()会做什么事情? 能保证 GC 执行吗
164垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
165Minor GC 、Major GC、Young GC 与 Full GC分别在什么时候发生
1. 当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
2. 内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。
3. 执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。
4. 质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
所以 Minor GC 的情况就相当清楚了——每次 Minor GC 会清理年轻代的内存。
大家应该注意到,目前,这些术语无论是在 JVM 规范还是在垃圾收集研究论文中都没有正式的定义。但是我们一看就知道这些在我们已经知道的基础之上做出的定义是正确的,Minor GC 清理年轻带内存应该被设计得简单:
· Major GC 是清理老年代。
· Full GC 是清理整个堆空间—包括年轻代和老年代。
很不幸,实际上它还有点复杂且令人困惑。首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。
这使得我们不用去关心到底是叫 Major GC 还是 Full GC,大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程。
这种混乱甚至内置到 JVM 标准工具。下面一个例子很好的解释了我的意思。让我们比较两个不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中运行时输出的跟踪记录。
166垃圾回收算法的实现原理
167如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?垃圾回收的最佳做法是什么
168GC收集器有哪些
169垃圾回收器的基本原理是什么?
170串行(serial)收集器和吞吐量(throughput)收集器的区别是什么
171Serial 与 Parallel GC之间的不同之处
172CMS 收集器 与 G1 收集器的特点与区别
173CMS垃圾回收器的工作过程
174JVM 中一次完整的 GC 流程是怎样的? 对象如何晋升到老年代
175吞吐量优先和响应优先的垃圾收集器选择
176GC策略举个实际的场景,选择一个GC策略
177JVM的永久代中会发生垃圾回收吗
178标记清除、标记整理、复制算法的原理与特点?分别用在什么地方
179如果让你优化收集方法,有什么思路
180说说你知道的几种主要的jvm 参数
-XX:+UseCompressedOops 有什么作用
当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从 32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到 64 位的 JVM 主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位的 OOP。
类加载器(ClassLoader)Java 类加载器都有哪些
启动类加载器(Bootstrap ClassLoader):这个类加载器负责将
扩展类加载器(Extendsion ClassLoader):这个类加载器负责加载
应用程序类加载器(Application ClassLoader):这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器.
除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader类.
类加载器之间的层次关系如下图:
使用代码观察一下类加载器:
package com.wang.test;
public class TestClassLoader {
public static void main(String[] args) {
ClassLoader loader = TestClassLoader.class.getClassLoader();
System.out.println(loader.toString());
System.out.println(loader.getParent().toString());
System.out.println(loader.getParent().getParent());
}
}
第一行打印的是应用程序类加载器(默认加载器),第二行打印的是其父类加载器,扩展类加载器,按照我们的想法第三行应该打印启动类加载器的,这里却返回的null,原因是getParent(),返回时null的话,就默认使用启动类加载器作为父加载器.
Class.forname()与ClassLoader.loadClass():
Class.forname():是一个静态方法,最常用的是Class.forname(String className);根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.
如: Class.forName("com.wang.HelloWorld");
ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.
如:ClassLoader cl=.......;cl.loadClass("com.wang.HelloWorld");
JVM如何加载字节码文件
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换分析和初始化,最终形成可以被虚拟节直接使用的JAVA类型,这就是虚拟机的类加载机制。
类从被加载到虚拟机内存到卸载出内存的生命周期包括:加载->连接(验证->准备->解析)->初始化->使用->卸载
初始化的5种情况:
1.使用new关键字实例化对象时,读取或设置一个类的静态字段,除被final修饰经编译结果放在常量池的静态字段,调用类的静态方法时。 2.使用java.lang.reflect包方法对类进行反射调用时。(Class.forName())。 3.初始化子类时,如果父类没有初始化。 4.虚拟机启动时main方法所在的类。 5.当使用JDK1.7动态语言支持时,java.lang.invoke.MethodHandle实例解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,且对应类没有进行初始化。
加载 加载是类加载的第一个阶段,虚拟机要完成以下三个过程:
1.通过类的全限定名获取定义此类的二进制字节流。 2.将字节流的存储结构转化为方法区的运行时结构。 3.在内存中生成一个代表该类的Class对象,作为方法区各种数据的访问入口。
验证 目的是确保class文件字节流信息符合虚拟机的要求。
准备 为static修饰的变量赋初值,例如int型默认为0,boolean默认为false。
解析 虚拟机将常量池内的符号引用替换成直接引用。
初始化 初始化是类加载的最后一个阶段,将执行类构造器< init>()方法,注意这里的方法不是构造方法。该方法将会显式调用父类构造器,接下来按照java语句顺序为类变量和静态语句块赋值。
内存管理JVM内存分哪几个区,每个区的作用是什么
00001.
Method Area(Non-Heap)(方法区) ——线程共享
Heap(堆) ——线程共享
Program Counter Register(程序计数器) ——非线程共享
VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的)——非线程共享
Native Method Stack ( 本地方法栈 )——非线程共享
JVM运行的时候会分配好 Method Area(方法区) 和Heap(堆),而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。
非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行生命周期相同,
所以gc只发生在线程共享的区域(大部分发生在Heap上)的原因。
线程共享:
一、方法区
00001. 有时候也称为永久代(Permanent Generation),在该区内很少发生垃圾回收,在这里进行的GC主要是方法区里的常量池和类型的卸载
00002. 方法区主要用来存储已被虚拟机加载的类信息、常量、静态变量和即时编译后的代码等数据。
00003. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。运行时生成的常量也会存在这个常量池中。比如String类的intern()方法
扩展:
· -XX:MaxPermSize设置上限
· -XX:PermSize设置最小值 例:VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
二、堆
在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
堆空间分为老年代和年轻代。刚创建的对象存放在年轻代,而老年代中存放生命周期长久的实例对象。年轻代中又被分为Eden区(圣经中的伊甸园)、和两个Survivor区(From Space和To Space)。新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到老年代。
堆中垃圾回收的时候注意一个大对象回收过程,描述如下:
年轻代与老年代控件分配如下:
·
Eden:80M
·
·
S1:10M
·
·
S2:10M
·
·
老年代:100M
·
过程:
00001. 新建60M的对象O1,此时Eden有足够内存吃下,所以60M被分配到Eden区。
00002. 再新建40M对象O2,此时因为S1和S2的控件不足以容纳下O1,所以O1被直接分配到老年代,而O2进入Eden区
00003. 清空O1、O2,新建90M对象O3,发现Eden不足以放下O3,O3被直接放入老年代中
00004. 清空O3,新建110M对象O4,O4发现Eden和老年代都没有足够控件,直接返回OutOfMemoryError
结论:
·
当一个对象大于eden区而小于old区(老年代)时的时候会直接扔到old区。
而但对象大于old区时,会直接抛出OutOfMemoryError(OOM)。
·
扩展:
参数-XX:PretenureSizeThreshold:这个参数的单位是Byte,
其作用是当新对象申请的内存空间大于这个参数值的时候,直接扔到old区。
-Xms:设置最小值
-Xmx:设置最大值 例:VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
若-Xms=-Xmx,则可避免堆自动扩展。
-XX:+HeapDumpOnOutOfMemoryError:
JVM会在遇到OutOfMemoryError时拍摄一个“堆转储快照”,并将其保存在一个文件中。
·
线程私有:
一、虚拟机栈:
00001. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
00002. 局部变量表里存储的是基本数据类型和对象引用。局部变量所需的内存空间在编译器间确定
00003. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
-Xoss参数设置本地方法栈大小(对于HotSpot无效)
-Xss参数设置栈容量 例: -Xss128k
二、本地方法栈
本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。
三、程序计数器
代表着当前线程所执行字节码的行号指示器。
分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。
程序计数器是唯一一个java虚拟机规范没有规定任何OOM(Out Of Memory)情况的区域。
一个对象从创建到销毁都是怎么在这些部分里存活和转移的解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法JVM中哪个参数是用来控制线程的栈堆栈小简述内存分配与回收策略简述重排序,内存屏障,happen-before,主内存,工作内存
Java中存在内存泄漏问题吗?
请举例说明简述 Java 中软引用(SoftReferenc)、弱引用(WeakReference)和虚引用
软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用是执行以下过程,以上面的abcSoftRef为例:
1、首先将abcSoftRef的referent设置为null,不再引用heap中的new String("abc")对象。
2、将heap中的new String("abc")对象设置为可结束的(finalizable)。
3、当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, abcSoftRef被添加到它的ReferenceQueue中。
注:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有
当gc碰到弱可及对象,并释放abcWeakRef的引用,收集该对象。但是gc可能需要对此运用才能找到该弱可及对象。通过如下代码可以了明了的看出它的作用:
String abc=new String("abc");
WeakReference
abc=null;
System.out.println("before gc: "+abcWeakRef.get());
System.gc();
System.out.println("after gc: "+abcWeakRef.get());
运行结果:
before gc: abc
after gc: null
gc收集弱可及对象的执行过程和软可及一样,只是gc不会根据内存情况来决定是不是收集该对象。
虚顾名思义就是没有的意思,建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null。先看一下和gc交互的过程在说一下他的作用。
1 不把referent设置为null,直接把heap中的new String("abc")对象设置为可结束的(finalizable).
2 与软引用和弱引用不同,先把PhantomRefrence对象添加到它的ReferenceQueue中,然后在释放虚可及的对象。
你会发现在收集heap中的new String("abc")对象之前,你就可以做一些其他的事情
如果你希望能随时取得某对象的信息,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象,而不是用一般的 reference。
内存映射缓存区是什么
jstack,jstat,jmap,jconsole怎么用
一、jps
1、介绍
用来查看基于HotSpot JVM里面所有进程的具体状态, 包括进程ID,进程启动的路径等等。与unix上的ps类似,用来显示本地有权限的java进程,可以查看本地运行着几个java程序,并显示他们的进程号。使用jps时,不需要传递进程号做为参数。
Jps也可以显示远程系统上的JAVA进程,这需要远程服务上开启了jstat服务,以及RMI注及服务,不过常用都是对本对的JAVA进程的查看。
2、命令格式
jps [ options ] [ hostid ]
3、常用参数说明
-m 输出传递给main方法的参数,如果是内嵌的JVM则输出为null。
-l 输出应用程序主类的完整包名,或者是应用程序JAR文件的完整路径。
-v 输出传给JVM的参数。
二、jstat
1、介绍
Jstat是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。可见,Jstat是轻量级的、专门针对JVM的工具,非常适用。由于JVM内存设置较大,图中百分比变化不太明显一个极强的监视VM内存工具。可以用来监视VM内存内的各种堆和非堆的大小及其内存使用量。
jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id,和所选参数。
它主要是用来显示GC及PermGen相关的信息,否则其中即使你会使用jstat这个命令,你也看不懂它的输出。
2、命令格式
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
3、参数说明
1)、generalOption:单个的常用的命令行选项,如-help, -options, 或 -version。
2)、outputOptions:一个或多个输出选项,由单个的statOption选项组件,可以-t, -h, and -J选项配合使用。
statOption:
-class Option
-compiler Option
-gc Option
-gccapacity Option
-gccause Option
-gcnew Option
-gcnewcapacity Option
-gcold Option
-gcoldcapacity Option
-gcpermcapacity Option
-gcutil Option
-printcompilation Option
注:其中最常用的就是-gcutil选项了,因为他能够给我们展示大致的GC信息。
Option:指的是vmid、显示间隔时间及间隔次数等
vmid — VM的进程号,即当前运行的java进程号
interval– 间隔时间,单位为秒或者毫秒
count — 打印次数,如果缺省则打印无数次
3)、jstat命令输出参数说明
S0 — Heap上的 Survivor space 0 区已使用空间的百分比
S0C:S0当前容量的大小
S0U:S0已经使用的大小
S1 — Heap上的 Survivor space 1 区已使用空间的百分比
S1C:S1当前容量的大小
S1U:S1已经使用的大小
E — Heap上的 Eden space 区已使用空间的百分比
EC:Eden space当前容量的大小
EU:Eden space已经使用的大小
O — Heap上的 Old space 区已使用空间的百分比
OC:Old space当前容量的大小
OU:Old space已经使用的大小
P — Perm space 区已使用空间的百分比
OC:Perm space当前容量的大小
OU:Perm space已经使用的大小
YGC — 从应用程序启动到采样时发生 Young GC 的次数
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC — 从应用程序启动到采样时发生 Full GC 的次数
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒),它的值等于YGC+FGC
注:由于该工具参数过多,不再进行给出测试结果。
三、jstack
1、介绍
jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式:
jstack [-l] pid
如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
2、命令格式
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
3、常用参数说明
1)、options:
executable Java executable from which the core dump was produced.
(可能是产生core dump的java可执行程序)
core 将被打印信息的core dump文件
remote-hostname-or-IP 远程debug服务的主机名或ip
server-id 唯一id,假如一台主机上多个远程debug服务
2)、基本参数:
-F当’jstack [-l] pid’没有相应的时候强制打印栈信息
-l长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.
-m打印java和native c/c++框架的所有栈信息.
-h | -help打印帮助信息
pid 需要被打印配置信息的java进程id,可以用jps查询.
四、jmap
1、介绍
打印出某个java进程(使用pid)内存内的,所有对象的情况(如:产生那些对象,及其数量)。
可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输出成文本。使用方法 jmap -histo pid。如果连用SHELL jmap -histo pid>a.log可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以对比出GC回收了哪些对象。jmap -dump:format=b,file=outfile 3024可以将3024进程的内存heap输出出来到outfile文件里,再配合MAT(内存分析工具(Memory Analysis Tool)或与jhat (Java Heap Analysis Tool)一起使用,能够以图像的形式直观的展示当前内存是否有问题。
64位机上使用需要使用如下方式:
jmap -J-d64 -heap pid
2、命令格式
SYNOPSIS
jmap [ option ] pid
jmap [ option ] executable core
jmap [ option ] [server-id@]remote-hostname-or-IP
3、参数说明
1)、options:
executable Java executable from which the core dump was produced.
(可能是产生core dump的java可执行程序)
core 将被打印信息的core dump文件
remote-hostname-or-IP 远程debug服务的主机名或ip
server-id 唯一id,假如一台主机上多个远程debug服务
2)、基本参数:
-dump:[live,]format=b,file=
-finalizerinfo 打印正等候回收的对象的信息.
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况.
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.
-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来.
-F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.
-h | -help 打印辅助信息
-J 传递参数给jmap启动的jvm.
pid 需要被打印配相信息的java进程id,创业与打工的区别 - 博文预览,可以用jps查问.
注:平时该命令我是使用最多的。
五、jinfo
jinfo可以输出并修改运行时的java 进程的opts。用处比较简单,用于输出JAVA系统参数及命令行参数。
六、jconsole和jvisualvm
jconsole:一个java GUI监视工具,可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器VM。用java写的GUI程序,用来监控VM,并可监控远程的VM,非常易用,而且功能非常强。命令行里打 jconsole,选则进程就可以了。
需要注意的就是在运行jconsole之前,必须要先设置环境变量DISPLAY,否则会报错误,Linux下设置环境变量如下:
export DISPLAY=:0.0
jvisualvm同jconsole都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具,Jvisualvm同jconsole的使用方式一样,直接在命令行打入Jvisualvm即可启动,不过Jvisualvm相比,界面更美观一些,数据更实时。
七、Jhat
jhat用于对JAVA heap进行离线分析的工具,他可以对不同虚拟机中导出的heap信息文件进行分析,如LINUX上导出的文件可以拿到WINDOWS上进行分析,可以查找诸如内存方面的问题,不过jhat和MAT比较起来,就没有MAT那么直观了,MAT是以图形界面的方式展现结果。
八、Jdb
用来对core文件和正在运行的Java进程进行实时地调试,里面包含了丰富的命令帮助您进行调试,它的功能和Sun studio里面所带的dbx非常相似,但 jdb是专门用来针对Java应用程序的。现在应该说日常的开发中很少用到JDB了,因为现在的IDE已经帮我们封装好了,如使用ECLIPSE调用程序就是非常方便的,只要在非常特定的情况下可能会用到这个命令,如远程服务器的维护,没有IDE进行调试,那这个时候JDB应该可以帮上忙。
32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?
理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。
怎样通过 Java 程序来判断 JVM 是 32 位 还是 64 位
System.out.println(System.getProperty("sun.arch.data.model"));
JVM自身会维护缓存吗?
是的,JVM自身会管理缓存,它在堆中创建对象,然后在栈中引用这些对象。
是不是在堆中进行对象分配,操作系统的堆还是JVM自己管理堆
什么情况下会发生栈内存溢出
栈溢出有两种,一种是stackoverflow,另一种是outofmemory,前者一般是因为方法递归没终止条件,后者一般是方法中线程启动过多。
双亲委派模型是什么
类加载器的双亲委派模型:
双亲委派模型是一种组织类加载器之间关系的一种规范,他的工作原理是:如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类)时,才会交给子类加载器去尝试加载.
这样的好处是:java类随着它的类加载器一起具备了带有优先级的层次关系.这是十分必要的,比如java.langObject,它存放在\jre\lib\rt.jar中,它是所有java类的父类,因此无论哪个类加载都要加载这个类,最终所有的加载请求都汇总到顶层的启动类加载器中,因此Object类会由启动类加载器来加载,所以加载的都是同一个类,如果不使用双亲委派模型,由各个类加载器自行去加载的话,系统中就会出现不止一个Object类,应用程序就会全乱了.
多线程基本概念什么是线程多线程的优点多线程的几种实现方式用 Runnable 还是 Thread
什么是线程安全Vector, SimpleDateFormat 是线程安全类吗
1. SimpleDateFormat 不是线程安全的;
2. 创建它的实例将会耗费很大的代价。
以上两点都是由它的一个成员属性造成的。它继承了DateFormat,在DateFormat中定义了一个protected属性的Calendar类的对象:calendar。而Calendar 是一个非常复杂的类,因为涉及到时区、本地化以及闰年等等因素。在本例中,由于在业务实体类中定义了SimpleDateFormat类型的属性,每一个业务对象的创建,都会创建一个SimpleDateFormat实例对象,大量的实例对象占用了大量的jvm空间。由于它的复杂性,GC的工作量比较大,效率很低,从而导致问题的发生。
什么 Java 原型不是线程安全的
哪些集合类是线程安全的
线程安全类在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的。在jdk1.2之后,就出现许许多多非线程安全的类。 下面是这些线程安全的同步的类:vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。statck:堆栈类,先进后出hashtable:就比hashmap多了个线程安全enumeration:枚举,相当于迭代器除了这些之外,其他的都是非线程安全的类和接口。线程安全的类其方法是同步的,每次只能一个访问。是重量级对象,效率较低。其他:1. hashtable跟hashmap的区别
hashtable是线程安全的,即hashtable的方法都提供了同步机制;hashmap不是线程安全的,即不提供同步机制 ;hashtable不允许插入空值,hashmap允许!2. 多线程并发修改一 个 集合 怎么办用老的Vector/Hashtable类StringBuffer是线程安全,而StringBuilder是线程不安全的。对于安全与不安全没有深入的理解情况下,易造成这样的错觉,如果对于StringBuffer的操作均是线程安全的,然而,Java给你的保证的线程安全,是说它的方法是执行是排它的,而不是对这个对象本身的多次调用情况下,还是安全的。看看下边的例子,在StringBufferTest中有一个数据成员contents它是用来扩展的,它的每一次append是线程安全的,但众多次append的组合并不是线程安全的,这个输出结果不是太可控的,但如果对于log和getContest方法加关键字synchronized,那么结果就会变得非常条理,如果换成StringBuider甚至是append到一半,它也会让位于其它在此基础上操作的线程:
多线程中的忙循环是什么
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
如何创建一个线程
Java中创建线程主要有三种方式:
一、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。(2)创建Thread子类的实例,即创建了线程对象。(3)调用线程对象的start()方法来启动该线程。
package com.thread;
public class FirstThreadTest extends Thread{
int i = 0;
//重写run方法,run方法的方法体就是现场执行体
public void run()
{
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
System.out.println(Thread.currentThread().getName()+" : "+i);
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。
二、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
示例代码为:
package com.thread;
public class RunnableThreadTest implements Runnable
{
private int i;
public void run()
{
for(i = 0;i <100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
RunnableThreadTest rtt = new RunnableThreadTest();
new Thread(rtt,"新线程1").start();
new Thread(rtt,"新线程2").start();
}
}
}
}
三、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
实例代码:
package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable
{
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
二、创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。使用继承Thread类的方式创建多线程时优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。
编写多线程程序有几种实现方式
JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。
1、继承Thread类实现多线程
继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
在合适的地方启动线程如下:
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
2、实现Runnable接口方式实现多线程
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,如下:
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
public void run() {
if (target != null) {
target.run();
}
}
3、使用ExecutorService、Callable、Future实现有返回结果的多线程
ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。想要详细了解Executor框架的可以访问http://www.javaeye.com/topic/366591 ,这里面对该框架做了很详细的解释。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
/**
* 有返回值的线程
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序开始运行----");
Date date1 = new Date();
int taskSize = 5;
// 创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取Future对象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序结束运行----,程序运行时间【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任务启动");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}
代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads) 创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool() 创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor() 创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
什么是线程局部变量
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
线程和进程有什么区别?进程间如何通讯,线程间如何通讯
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4、处理机分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体。
从三个角度来剖析二者之间的区别
1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。.
二、多线程间通信方式:
1、共享变量2、wait/notify机制3、Lock/Condition机制4、管道
三、共享变量线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。这个简单的例子使用了一个持有信号的对象,并提供了set和check方法:
什么是多线程环境下的伪共享(false sharing)
在多核的CPU架构中,每一个核心core都会有自己的缓存空间,因此如果一个变量如果同时存在不同的核心缓存空间时,就会出现伪共享(false sharing)的问题。
此时如果一个核心修改了该变量,该修改需要同步到其它核心的缓存。
同步和异步有何异同,在什么情况下分别使用他们?举例说明
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
Java中交互方式分为同步和异步两种:
同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;
异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
哪些情况建议使用同步交互呢?比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作,其余情况都优先使用异步交互
CurrentConcurrentHashMap 和 Hashtable的区别
它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
ArrayBlockingQueue, CountDownLatch的用法
ConcurrentHashMap的并发度是什么
CyclicBarrier 和 CountDownLatch有什么不同?各自的内部原理和用法是什么
Semaphore的用法Thread
启动一个线程是调用 run() 还是 start() 方法?start() 和 run() 方法有什么区别
调用start()方法时会执行run()方法,为什么不能直接调用run()方法
sleep() 方法和对象的 wait() 方法都可以让线程暂停执行,它们有什么区别
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,
将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。
wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lockpool),如果线程重新获得对象的锁就可以进入就绪状态
可能不少人对什么是进程,什么是线程还比较模糊,对于为什么需要多线程编程也不是特别理解。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。线程的划分尺度小于进程,这使得多线程程序的并发性高;进程在执行时通常拥有独立的内存单元,而线程之间可以共享内存。
yield方法有什么作用?
Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
sleep() 方法和 yield() 方法有什么区别
① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会yield()方法只会给相同优先级或更高优先级的线程以运行的机会; ② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常; ④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性.
Java 中如何停止一个线程stop() 和 suspend() 方法为何不推荐使用
有两种实现方法,分别是继承Thread类与实现Runnable接口用synchronized关键字修饰同步方法反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。
结果是一旦将共享变量stop设置为true,则中断立即发生。
为了更加安全起见,通常需要将共享变量定义为volatile类型或者将对该共享变量的一切访问封装到同步的代码或者同步方法中去。后者所提到的技术将在第4.5节中介绍。
在多线程的程序中,当出现有两个或多个线程共享同一实例变量的情况时,每一个线程可以保持这个实例变量自己的私有副本,变量的实际备份在不同时间被更新。而问题就是变量的主备份总是需要反映它的当前状态,此时反而使效率降低。为保证效率,只需要简单地指定变量为volatile类型即可,它可以告诉编译器必须总是使用volatile变量的主备份(或者至少总是保持任何私有的备份和最新的备份一样,反之亦然)。同样,对主变量的访问必须同任何私有备份一样,精确地顺序执行。
如果需要一次中断所有由同一线程类创建的线程,该怎样实现呢?有些读者可能马上就想到了对每一个线程对象通过设置共享变量的方式来中断线程。这种方法当然可以,那么有没有更好的方法呢?
此时只需将共享变量设置为static类型的即可。
如何在两个线程间共享数据
多线程共享数据的方式:
1,如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,卖票系统就可以这么做。
2,如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,例如,设计4个线程。其中两个线程每次对j增加1,另外两个线程对j每次减1,银行存取款有两种方法来解决此类问题:将共享数据封装成另外一个对象,然后将这个对象逐一传递给各个Runnable对象,每个线程对共享数据的操作方法也分配到那个对象身上完成,这样容易实现针对数据进行各个操作的互斥和通信将Runnable对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable对象调用外部类的这些方法
如何强制启动一个线程
如何让正在运行的线程暂停一段时间
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,
将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。
wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lockpool),如果线程重新获得对象的锁就可以进入就绪状态
画一个线程的生命周期状态图线程状态转换
1、新建状态(New):新创建了一个线程对象。2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
什么是线程组,为什么在Java中不推荐使用你是如何调用 wait(方法的)?使用 if 块还是循环?为什么
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
1 2 3 4 5 6 |
// The standard idiom for using the wait method synchronized (obj) { while (condition does not hold) obj.wait(); // (Releases lock, and reacquires on wakeup) ... // Perform action appropriate to condition } |
生命周期有哪些不同的线程生命周期线程状态,BLOCKED 和 WAITING 有什么区别
当一个线程在运行,而另外一个线程在等待的时候,那后一个的线程就处于Blocked(被阻塞)状态。等开头的那个线程运行完,线程调度器将会重新选一个线程,那么后面的那个线程有可能解除阻塞状态。
当一个线程执行的时候,因为某些条件不满足,然后不得不阻塞的时候,将会进入等待队列,比如调用Object.wait.这时候,这个线程就处于waiting(等待)状态。等待的意思就是等待另外一个线程去解除的等待状态。当另外一个线程调用Object.notifyAll()的时候,等待状态的线程就会解除等待状态。
共同点:都在等待
不同点:进入等待的地方不同
ThreadLocal 用途是什么,原理是什么,用的时候要注意什么
ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。 但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。ThreadPool线程池是什么?为什么要使用它如何创建一个Java线程池ThreadPool用法与优势提交任务时,线程池队列已满时会发会生什么newCache 和 newFixed 有什么区别?简述原理。构造函数的各个参数的含义是什么,比如 coreSize, maxsize 等+我们可以通过ThreadPoolExecutor来创建一个线程池。new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);创建一个线程池需要输入几个参数:corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。 ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。PriorityBlockingQueue:一个具有优先级的无限阻塞队列。maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。 AbortPolicy:直接抛出异常。CallerRunsPolicy:只用调用者所在线程来运行任务。DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。DiscardPolicy:不处理,丢弃掉。当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
向线程池提交任务我们可以使用execute提交的任务,但是execute方法没有返回值,所以无法判断任务是否被线程池执行成功。通过以下代码可知execute方法输入的任务是一个Runnable类的实例。threadsPool.execute(new Runnable() {public void run() {}});我们也可以使用submit 方法来提交任务,它会返回一个future,那么我们可以通过这个future来判断任务是否执行成功,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。
Future
try {Object s = future.get();} catch (InterruptedException e) {} catch (ExecutionException e) {} finally {executor.shutdown();}
线程池的关闭我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。3. 线程池的分析流程分析:线程池的主要工作流程如下图:从上图我们可以看出,当提交一个新任务到线程池时,线程池的处理流程如下:首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。源码分析。上面的流程分析让我们很直观的了解了线程池的工作原理,让我们再通过源代码来看看是如何实现的。线程池执行任务的方法如下:public void execute(Runnable command) { if (command == null)throw new NullPointerException(); //如果线程数小于基本线程数,则创建线程并执行当前任务 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {//如线程数大于等于基本线程数或线程创建失败,则将当前任务放到工作队列中。 if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0)ensureQueuedTaskHandled(command); //如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量则创建一个线程执行任务。else if (!addIfUnderMaximumPoolSize(command))
//抛出RejectedExecutionException异常
reject(command); // is shutdown or saturate }}
工作线程。线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会无限循环获取工作队列里的任务来执行。我们可以从Worker的run方法里看到这点:public void run() {try Runnable task = firstTask;firstTask = null;while (task != null || (task = getTask()) != null) {runTask(task); task = null; } } finally {workerDone(this); }} 4. 合理的配置线程池要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:任务的性质:CPU密集型任务,IO密集型任务和混合型任务。任务的优先级:高,中和低。任务的执行时间:长,中和短。任务的依赖性:是否依赖其他系统资源,如数据库连接。任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能小的线程,如配置Ncpu+1个线程的线程池。IO密集型任务则由于线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。建议使用有界队列,有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点,比如几千。有一次我们组使用的后台任务线程池的队列和线程池全满了,不断的抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞住,任务积压在线程池里。如果当时我们设置成无界队列,线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。当然我们的系统所有的任务是用的单独的服务器部署的,而我们使用不同规模的线程池跑不同类型的任务,但是出现这样问题时也会影响到其他任务。5. 线程池的监控通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用taskCount:线程池需要执行的任务数量。completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不+ getActiveCount:获取活动的线程数。通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:protected void beforeExecute(Thread t, Runnable r) { }
线程池的实现策略
java线程池与五种常用线程池策略使用与解析
一.线程池
关于为什么要使用线程池久不赘述了,首先看一下java中作为线程池Executor底层实现类的ThredPoolExecutor的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
其中各个参数含义如下:
corePoolSize- 池中所保存的线程数,包括空闲线程。需要注意的是在初创建线程池时线程不会立即启动,直到有任务提交才开始启动线程并逐渐时线程数目达到corePoolSize。若想一开始就创建所有核心线程需调用prestartAllCoreThreads方法。
maximumPoolSize-池中允许的最大线程数。需要注意的是当核心线程满且阻塞队列也满时才会判断当前线程数是否小于最大线程数,并决定是否创建新线程。
keepAliveTime - 当线程数大于核心时,多于的空闲线程最多存活时间
unit - keepAliveTime 参数的时间单位。
workQueue - 当线程数目超过核心线程数时用于保存任务的队列。主要有3种类型的BlockingQueue可供选择:无界队列,有界队列和同步移交。将在下文中详细阐述。从参数中可以看到,此队列仅保存实现Runnable接口的任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 阻塞队列已满且线程数达到最大值时所采取的饱和策略。java默认提供了4种饱和策略的实现方式:中止、抛弃、抛弃最旧的、调用者运行。将在下文中详细阐述。
二.可选择的阻塞队列BlockingQueue详解
首先看一下新任务进入时线程池的执行策略:
如果运行的线程少于corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存入queue中,而是直接运行)
如果运行的线程大于等于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
主要有3种类型的BlockingQueue:
2.1 无界队列
队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列做为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。最近工作中就遇到因为采用LinkedBlockingQueue作为阻塞队列,部分任务耗时80s+且不停有新任务进来,导致cpu和内存飙升服务器挂掉。
2.2 有界队列
常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
2.3 同步移交
如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
2.4 几种BlockingQueue的具体实现原理
关于上述几种BlockingQueue的具体实现原理与分析将在下篇博文中详细阐述。
三.可选择的饱和策略RejectedExecutionHandler详解
JDK主要提供了4种饱和策略供选择。4种策略都做为静态内部类在ThreadPoolExcutor中进行实现。
3.1 AbortPolicy中止策略
该策略是默认饱和策略。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
使用该策略时在饱和时会抛出RejectedExecutionException(继承自RuntimeException),调用者可捕获该异常自行处理。
3.2 DiscardPolicy抛弃策略
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
如代码所示,不做任何处理直接抛弃任务
3.3 DiscardOldestPolicy抛弃旧任务策略
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
如代码,先将阻塞队列中的头元素出队抛弃,再尝试提交任务。如果此时阻塞队列使用PriorityBlockingQueue优先级队列,将会导致优先级最高的任务被抛弃,因此不建议将该种策略配合优先级队列使用。
3.4 CallerRunsPolicy调用者运行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
既不抛弃任务也不抛出异常,直接运行任务的run方法,换言之将任务回退给调用者来直接运行。使用该策略时线程池饱和后将由调用线程池的主线程自己来执行任务,因此在执行任务的这段时间里主线程无法再提交新任务,从而使线程池中工作线程有时间将正在处理的任务处理完成。
四.java提供的四种常用线程池解析
在JDK帮助文档中,有如此一段话:
强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)它们均为大多数使用场景预定义了设置。
详细介绍一下上述四种线程池。
4.1 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue
}
在newCachedThreadPool中如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
初看该构造函数时我有这样的疑惑:核心线程池为0,那按照前面所讲的线程池策略新任务来临时无法进入核心线程池,只能进入 SynchronousQueue中进行等待,而SynchronousQueue的大小为1,那岂不是第一个任务到达时只能等待在队列中,直到第二个任务到达发现无法进入队列才能创建第一个线程?
这个问题的答案在上面讲SynchronousQueue时其实已经给出了,要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。因此即便SynchronousQueue一开始为空且大小为1,第一个任务也无法放入其中,因为没有线程在等待从SynchronousQueue中取走元素。因此第一个任务到达时便会创建一个新线程执行该任务。
这里引申出一个小技巧:有时我们可能希望线程池在没有任务的情况下销毁所有的线程,既设置线程池核心大小为0,但又不想使用SynchronousQueue而是想使用有界的等待队列。显然,不进行任何特殊设置的话这样的用法会发生奇怪的行为:直到等待队列被填满才会有新线程被创建,任务才开始执行。这并不是我们希望看到的,此时可通过allowCoreThreadTimeOut使等待队列中的元素出队被调用执行,详细原理和使用将会在后续博客中阐述。
4.2 newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue
}
看代码一目了然了,使用固定大小的线程池并使用无限大的队列
4.3 newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
在来看看ScheduledThreadPoolExecutor()的构造函数
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPoolExecutor的父类即ThreadPoolExecutor,因此这里各参数含义和上面一样。值得关心的是DelayedWorkQueue这个阻塞对列,在上面没有介绍,它作为静态内部类就在ScheduledThreadPoolExecutor中进行了实现。具体分析讲会在后续博客中给出,在这里只进行简单说明:DelayedWorkQueue是一个无界队列,它能按一定的顺序对工作队列中的元素进行排列。因此这里设置的最大线程数 Integer.MAX_VALUE没有任何意义。关于ScheduledThreadPoolExecutor的具体使用将会在后续quartz的周期性任务实现原理中进行进一步分析。
4.4 newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
首先new了一个线程数目为1的ScheduledThreadPoolExecutor,再把该对象传入DelegatedScheduledExecutorService中,看看DelegatedScheduledExecutorService的实现代码:
DelegatedScheduledExecutorService(ScheduledExecutorService executor) {
super(executor);
e = executor;
}
在看看它的父类
DelegatedExecutorService(ExecutorService executor) { e = executor; }
其实就是使用装饰模式增强了ScheduledExecutorService(1)的功能,不仅确保只有一个线程顺序执行任务,也保证线程意外终止后会重新创建一个线程继续执行任务。具体实现原理会在后续博客中讲解。
4.5 newWorkStealingPool创建一个拥有多个任务队列(以便减少连接数)的线程池。
这是jdk1.8中新增加的一种线程池实现,先看一下它的无参实现
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
返回的ForkJoinPool从jdk1.7开始引进,个人感觉类似于mapreduce的思想。这个线程池较为特殊,将在后续博客中给出详细的使用说明和原理。
线程池的关闭方式有几种,各自的区别是什么
有三种方法可以使终止线程。 1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。3. 使用interrupt方法中断线程。1. 使用退出标志终止线程当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。下面给出了一个利用退出标志终止线程的例子。
package chapter2;
public class ThreadFlag extends Thread
{
public volatile boolean exit = false;
public void run()
{
while (!exit);
}
public static void main(String[] args) throws Exception
{
ThreadFlag thread = new ThreadFlag();
thread.start();
sleep(5000); // 主线程延迟5秒
thread.exit = true; // 终止线程thread
thread.join();
System.out.println("线程退出!");
}
}
在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值 2. 使用stop方法终止线程
使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程:
thread.stop();
虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。
3. 使用interrupt方法终止线程 使用interrupt方法来终端线程可分为两种情况:
(1)线程处于阻塞状态,如使用了sleep方法。(2)使用while(!isInterrupted()){……}来判断线程是否被中断。
在第一种情况下使用interrupt方法,sleep方法将抛出一个InterruptedException例外,而在第二种情况下线程将直接退出。下面的代码演示了在第一种情况下使用interrupt方法。
package chapter2;
public class ThreadInterrupt extends Thread
{
public void run()
{
try
{
sleep(50000); // 延迟50秒
}
catch (InterruptedException e)
{
System.out.println(e.getMessage());
}
}
public static void main(String[] args) throws Exception
{
Thread thread = new ThreadInterrupt();
thread.start();
System.out.println("在50秒之内按任意键中断线程!");
System.in.read();
thread.interrupt();
thread.join();
System.out.println("线程已经退出!");
}
}
上面代码的运行结果如下:
在50秒之内按任意键中断线程!
sleep interrupted
线程已经退出!
在调用interrupt方法后, sleep方法抛出异常,然后输出错误信息:sleep interrupted.
注意:在Thread类中有两个方法可以判断线程是否通过interrupt方法被终止。一个是静态的方法interrupted(),一个是非静态的方法isInterrupted(),这两个方法的区别是interrupted用来判断当前线是否被中断,而isInterrupted可以用来判断其他线程是否被中断。因此,while
线程池中submit() 和 execute()方法有什么区别?
线程池中的execute方法大家都不陌生,即开启线程执行池中的任务。还有一个方法submit也可以做到,它的功能是提交指定的任务去执行并且返回Future对象,即执行的结果。下面简要介绍一下两者的三个区别:1、接收的参数不一样2、submit有返回值,而execute没有3、submit方便Exception处理
意思就是如果你在你的task里会抛出checked或者unchecked exception,
而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。
线程调度Java中用到的线程调度算法是什么
1、sleep()方法——休眠 线程休眠的方法是:Thread.sleep(long millis) Thread.sleep(long millis, int nanos)线程休眠的目的是使线程让出CPU的最简单的做法之一,线程休眠时候,会将CPU资源交给其他线程,以便能轮换执行,当休眠一定时间后,线程会苏醒,进入准备状态等待执行。2、yield()方法——让步线程的让步使用Thread.yield()方法,yield() 为静态方法,功能是暂停当前正在执行的线程对象,并执行其他线程。如果有多个线程,具体把权限让给谁了,它不知道。3、join()方法——合并/联合一个线程A在占有CPU资源期间,可以让其他线程调用join()和本线程联合,如B.join()我们称A在运行期间联合了B。如果线程A在占有CPU资源期间一旦联合B线程,那么A线程将立刻中断执行,一直等到它联合的线程B执行完毕,A线程再重新排队等待CPU资源,以便恢复执行。如果A准备联合B线程已经结束,那么B.join()不会产生任何效果。
什么是多线程中的上下文切换
在多任务处理系统中,作业数通常大于CPU数。为了让用户觉得这些任务在同时进行,CPU给每个任务分配一定时间,把当前任务状态保存下来,当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。之后CPU可以回过头再处理之前被挂起任务。上下文切换就是这样一个过程,它允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。在这个过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。
你对线程优先级的理解是什么虽然前面说线程是并发运行的。然而实际情况并非如此。对于多线程的程序,任务角色的不同使得每个线程的重要程度也不尽相同,如多个线程在等待获得 CPU时间片,往往希望优先级高的线程优先抢占到CPU并得以执行。此外,多个线程交替执行时,不同优先级决定了级别高的线程将得到CPU的次数多一些且 时间长一些,这样,高优先级的线程任务处理的效率明显就会更高一些,从而满足一些特殊的需要。那么,线程的优先级是如何设置的呢?Java的线程调度器决定了某一线程什么时候该运行,该调度器采用的是一种简单、固定的调度法,即固定优先级调度算法。这种算法是根据处于可运行状态的线程的相对优先级来执行的。Java中,线程的优先级是介于Thread.MIN_PRIORITY到Thread.MAX_PRIORITY这两个常量之间的某个整数数值 (介于1到10之间)。默认情况下,线程的优先级都是5,在Java中用NORM_PRIORITY来表示。其中,MIN_PRIORITY、 MAX_PRIORITY和NORM_PRIORITY均是Thread类的静态整型常量。
当利用某一线程又创建了一个新线程对象时,这个新线程将拥有与创建它的线程一样的优先级。例如,主线程的优先级默认情况下是5,那么利用主线程创建的新线程的优先级默认情况下也是5。
线程创建后,线程的优先级可以在需要的时候对其进行修改。修改时需要使用Thread类的setPriority()方法,该方法属于Thread类:
什么是线程调度器 (Thread Scheduler) 和时间分片 (Time Slicing)线程同步请说出你所知的线程同步的方法synchronized 的原理是什么synchronized 和 ReentrantLock 有什么不同什么场景下可以使用 volatile 替换 synchronized有T1,T2,T3三个线程,怎么确保它们按顺序执行?怎样保证T2在T1执行完后执行,T3在T2执行完后执行同步块内的线程抛出异常会发生什么
当一个线程进入一个对象的 synchronized 方法A 之后,其它线程是否可进入此对象的 synchronized 方法B
使用 synchronized 修饰静态方法和非静态方法有什么区别
如何从给定集合那里创建一个 synchronized 的集合
锁
Java Concurrency API 中 的 Lock 接口是什么?对比同步它有什么优势
Lock 与 Synchronized 的区别?Lock 接口比 synchronized 块的优势是什么
ReadWriteLock是什么?
锁机制有什么用
什么是乐观锁(Optimistic Locking)?如何实现乐观锁?如何避免ABA问题
解释以下名词:重排序,自旋锁,偏向锁,轻量级锁,可重入锁,公平锁,非公平锁,乐观锁,悲观锁
什么时候应该使用可重入锁
简述锁的等级方法锁、对象锁、类锁
首先的明白Java中锁的机制synchronized 在修饰代码块的时候需要一个reference对象作为锁的对象.
在修饰方法的时候默认是当前对象作为锁的对象. 在修饰类时候默认是当前类的Class对象作为锁的对象.
线程同步的方法:sychronized、lock、reentrantLock分析 方法锁(synchronized修饰方法时)
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。
synchronized 方法控制对类成员变量的访问: 每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。对象锁(synchronized修饰方法或代码块)当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放。(方法锁也是对象锁) java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。
Java中活锁和死锁有什么区别?
什么是死锁(Deadlock)?导致线程死锁的原因?如何确保 N 个线程可以访问 N 个资源同时又不导致死锁
死锁与活锁的区别,
1、互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
2、请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
3、不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。
4、环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即:{p0,p1,p2,...pn},进程p0(或线程)等待p1占用的资源,p1等待p2占用的资源,pn等待p0占用的资源。(最直观的理解是,p0等待p1占用的资源,而p1而在等待p0占用的资源,于是两个进程就相互等待)死锁与饥饿的区别
怎么检测一个线程是否拥有锁
java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁
如何实现分布式锁有哪些无锁数据结构,他们实现的原理是什么读写锁可以用于什么应用场景
Executors类是什么? Executor和Executors的区别什么
Executor:是Java线程池的超级接口;提供一个execute(Runnable command)方法;我们一般用它的继承接口ExecutorService。
Executors:是java.util.concurrent包下的一个类,提供了若干个静态方法,用于生成不同类型的线程池。Executors一共可以创建下面这四类线程池:
newFixedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
newSingleThreadExecutor 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService:它是线程池定义的一个接口,继承Executor。有两个实现类,分别为ThreadPoolExecutor,ScheduledThreadPoolExecutor。
线程池的继承树:
是Java线程转储(Thread Dump),如何得到它
如何在Java中获取线程堆栈说出 3 条在 Java 中使用线程的最佳实践在线程中你怎么处理不可捕捉异常
实际项目中使用多线程举例。你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的
请说出与线程同步以及线程调度相关的方法
程序中有3个 socket,需要多少个线程来处理
假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有 10 个线程同时调用它,如何做到
如何在 Windows 和 Linux 上查找哪个线程使用的 CPU 时间最长
如何确保 main() 方法所在的线程是 Java 程序最后结束的线程
非常多个线程(可能是不同机器),相互之间需要等待协调才能完成某种工作,问怎么设计这种协调方案
你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它
Error 和 Exception有什么区别UnsupportedOperationException是什么
NullPointerException 和 ArrayIndexOutOfBoundException 之间有什么相同之处什么是受检查的异常,什么是运行时异常运行时异常与一般异常有何异同简述一个你最常见到的runtime exception(运行时异常)
finally
finally关键词在异常处理中如何使用
如果执行finally代码块之前方法返回了结果,或者JVM退出了,finally块中的代码还会执行吗
try里有return,finally还执行么?那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后在什么情况下,finally语句不会执行
throw 和 throws 有什么区别?OOM你遇到过哪些情况?你是怎么搞定的?SOF你遇到过哪些情况?既然我们可以用RuntimeException来处理错误,那么你认为为什么Java中还存在检查型异常
当自己创建异常类的时候应该注意什么导致空指针异常的原因
异常处理 handle or declare 原则应该如何理解
怎么利用 JUnit 来测试一个方法的异常catch块里别不写代码有什么问题你曾经自定义实现过异常吗?怎么写的
什么是 异常链在try块中可以抛出异常吗JDBC通过 JDBC 连接数据库有哪几种方式阐述 JDBC 操作数据库的基本步骤JDBC 中如何进行事务处理
什么是 JdbcTemplate什么是 DAO 模块使用 JDBC 操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能列出 5 个应该遵循的 JDBC 最佳实践
IO
FileFile类型中定义了什么方法来创建一级目录File类型中定义了什么方法来判断一个文件是否存在
流
为了提高读写性能,可以采用什么流
Java中有几种类型的流
在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。C/C++只能提供字节流。Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的.
字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。
1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
读文本的时候用字符流,例如txt文件。读非文本文件的时候用字节流,例如mp3。理论上任何文件都能够用字节流读取,但当读取的是文本数据时,为了能还原成文本你必须再经过一个转换的工序,相对来说字符流就省了这个麻烦,可以有方法直接读取。
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节, 操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!
1.字节流:继承于InputStream \ OutputStream。
OutputStream提供的方法:
void write(int b):写入一个字节的数据
void write(byte[] buffer):将数组buffer的数据写入流
void write(byte[] buffer,int offset,int len):从buffer[offset]开始,写入len个字节的数据
void flush():强制将buffer内的数据写入流
void close():关闭流
InputStream提供的方法:
int read():读出一个字节的数据,如果已达文件的末端,返回值为-1
int read(byte[] buffer):读出buffer大小的数据,返回值为实际所读出的字节数
int read(byte[] buffer,int offset,int len)
int available():返回流内可供读取的字节数目
long skip(long n):跳过n个字节的数据,返回值为实际所跳过的数据数
void close():关闭流
2.字符流,继承于InputStreamReader \ OutputStreamWriter。
字符流的类:1),BufferedReader是一种过滤器(filter)(extends FilterReader)。过滤
器用来将流的数据加以处理再输出。构造函数为:
BufferedReader(Reader in):生成一个缓冲的字符输入流,in为一个读取器
BufferedReader(Reader in,int size):生成一个缓冲的字符输入流,并指定缓冲区的大小为size
public class IOStreamDemo {
public void samples() throws IOException { //1. 这是从键盘读入一行数据,返回的是一个字符串
BufferedReader stdin =new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter a line:");
System.out.println(stdin.readLine());
//2. 这是从文件中逐行读入数据
BufferedReader in = new BufferedReader(new FileReader("IOStreamDemo.java"));
String s, s2 = new String();
while((s = in.readLine())!= null)
s2 += s + "\n";
in.close();
//3. 这是从一个字符串中逐个读入字节
StringReader in1 = new StringReader(s2);
int c;
while((c = in1.read()) != -1)
System.out.print((char)c);
//4. 这是将一个字符串写入文件
try {
BufferedReader in2 = new BufferedReader(new StringReader(s2));
PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
int lineCount = 1;
while((s = in2.readLine()) != null )
out1.println(lineCount++ + ": " + s);
out1.close();
} catch(EOFException e) {
System.err.println("End of stream");
}
} }
对于上面的例子,需要说明的有以下几点:
1. InputStreamReader是InputStream和Reader之间的桥梁,由于System.in是字节流,需要用它来包装之后变为字符流供给BufferedReader使用。
3. PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
这句话体现了Java输入输出系统的一个特点,为了达到某个目的,需要包装好几层。首先,输出目的地是文件IODemo.out,所以最内层包装的是FileWriter,建立一个输出文件流,接下来,我们希望这个流是缓冲的,所以用BufferedWriter来包装它以达到目的,最后,我们需要格式化输出结果,于是将PrintWriter包在最外层。
Java流有着另一个重要的用途,那就是利用对象流对对象进行序列化。
在一个程序运行的时候,其中的变量数据是保存在内存中的,一旦程序结束这些数据将不会被保存,一种解决的办法是将数据写入文件,而Java中提供了一种机制,它可以将程序中的对象写入文件,之后再从文件中把对象读出来重新建立。这就是所谓的对象序列化。Java中引入它主要是为了RMI(Remote Method Invocation)和Java Bean所用,不过在平时应用中,它也是很有用的一种技术。
JDK 为每种类型的流提供了一些抽象类以供继承,分别是哪些类对文本文件操作用什么I/O流
对各种基本数据类型和String类型的读写,采用什么流
1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
读文本的时候用字符流,例如txt文件。读非文本文件的时候用字节流,例如mp3。理论上任何文件都能够用字节流读取,但当读取的是文本数据时,为了能还原成文本你必须再经过一个转换的工序,相对来说字符流就省了这个麻烦,可以有方法直接读取。
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节, 操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!
1.字节流:继承于InputStream / OutputStream。
OutputStream提供的方法:
void write(int b):写入一个字节的数据
void write(byte[] buffer):将数组buffer的数据写入流
void write(byte[] buffer,int offset,int len):从buffer[offset]开始,写入len个字节的数据
void flush():强制将buffer内的数据写入流
void close():关闭流
InputStream提供的方法:
int read():读出一个字节的数据,如果已达文件的末端,返回值为-1
int read(byte[] buffer):读出buffer大小的数据,返回值为实际所读出的字节数
int read(byte[] buffer,int offset,int len)
int available():返回流内可供读取的字节数目
long skip(long n):跳过n个字节的数据,返回值为实际所跳过的数据数
void close():关闭流
2.字符流,继承于InputStreamReader / OutputStreamWriter。
字符流的类:1),BufferedReader是一种过滤器(filter)(extends FilterReader)。过滤
器用来将流的数据加以处理再输出。构造函数为:
BufferedReader(Reader in):生成一个缓冲的字符输入流,in为一个读取器
BufferedReader(Reader in,int size):生成一个缓冲的字符输入流,并指定缓冲区的大小为size
public class IOStreamDemo {
public void samples() throws IOException { //1. 这是从键盘读入一行数据,返回的是一个字符串
BufferedReader stdin =new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter a line:");
System.out.println(stdin.readLine());
//2. 这是从文件中逐行读入数据
BufferedReader in = new BufferedReader(new FileReader("IOStreamDemo.java"));
String s, s2 = new String();
while((s = in.readLine())!= null)
s2 += s + "/n";
in.close();
//3. 这是从一个字符串中逐个读入字节
StringReader in1 = new StringReader(s2);
int c;
while((c = in1.read()) != -1)
System.out.print((char)c);
//4. 这是将一个字符串写入文件
try {
BufferedReader in2 = new BufferedReader(new StringReader(s2));
PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
int lineCount = 1;
while((s = in2.readLine()) != null )
out1.println(lineCount++ + ": " + s);
out1.close();
} catch(EOFException e) {
System.err.println("End of stream");
}
} }
对于上面的例子,需要说明的有以下几点:
1. InputStreamReader是InputStream和Reader之间的桥梁,由于System.in是字节流,需要用它来包装之后变为字符流供给BufferedReader使用。
3. PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
这句话体现了Java输入输出系统的一个特点,为了达到某个目的,需要包装好几层。首先,输出目的地是文件IODemo.out,所以最内层包装的是FileWriter,建立一个输出文件流,接下来,我们希望这个流是缓冲的,所以用BufferedWriter来包装它以达到目的,最后,我们需要格式化输出结果,于是将PrintWriter包在最外层。
Java流有着另一个重要的用途,那就是利用对象流对对象进行序列化。
在一个程序运行的时候,其中的变量数据是保存在内存中的,一旦程序结束这些数据将不会被保存,一种解决的办法是将数据写入文件,而Java中提供了一种机制,它可以将程序中的对象写入文件,之后再从文件中把对象读出来重新建立。这就是所谓的对象序列化。Java中引入它主要是为了RMI(Remote Method Invocation)和Java Bean所用,不过在平时应用中,它也是很有用的一种技术。
能指定字符编码的 I/O 流类型是什么
字符流
序列化
什么是序列化?如何实现 Java 序列化及注意事项
1、序列化是干什么的?
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
2、什么情况下需要序列化
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
6、相关注意事项
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
d)并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了,比如:
1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
2. 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。
详细描述:
序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。
一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
从上面的叙述中,我们知道了对象序列化是java编程中的必备武器,那么让我们从基础开始,好好学习一下它的机制和用法。
java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
序列化机制:
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:
处理对象流:
(序列化过程和反序列化过程)
java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。
writeObject()方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
下面,让我们从例子中来了解ObjectOutputStream这个类吧。
// 序列化 today’s date 到一个文件中.
FileOutputStream f = new FileOutputStream(“tmp”);//创建一个包含恢复对象(即对象进行反序列化信息)的”tmp”数据文件
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject(“Today”); //写入字符串对象;
s.writeObject(new Date()); //写入瞬态对象;
s.flush();
现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:
//从文件中反序列化 string 对象和 date 对象
FileInputStream in = new FileInputStream(“tmp”);
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject(); //恢复对象;
Date date = (Date)s.readObject();
定制序列化过程:
序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。
例子:一个非常简单的序列化类。
public class simpleSerializableClass implementsSerializable{
String sToday=”Today:”;
transient Date dtToday=new Date();
}
序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:
//重写writeObject()方法以便处理transient的成员。
public void writeObject(ObjectOutputStream outputStream)throws IOException{
outputStream.defaultWriteObject();//使定制的writeObject()方法可以
利用自动序列化中内置的逻辑。
outputStream.writeObject(oSocket.getInetAddress());
outputStream.writeInt(oSocket.getPort());
}
//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throwsIOException,ClassNotFoundException{
inputStream.defaultReadObject();//defaultReadObject()补充自动序列化
InetAddressoAddress=(InetAddress)inputStream.readObject();
int iPort =inputStream.readInt();
oSocket = new Socket(oAddress,iPort);
iID=getID();
dtToday =new Date();
}
Serializable 与 Externalizable 的区别
四次挥手
发起断开连接请求可以是客户端也可以是服务器,即主机1,主机2可以是客户端也可以是服务器。
ACK : TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1。
FIN (finis)即完,终结的意思, 用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。
发送序列号:Sequence Number
确认序列号:Acknowledgment Number
FIN_WAIT_1:表示等待对方的FIN报文。当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态
FIN_WAIT_2:也表示等待对方的FIN报文。FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。你回复一个ACK给对方,并进入CLOSE_WAIT状态。接下来就是查看你是否还有数据要发送给对方,如果没有,就可以close这个socket,并发送FIN给对方,即关闭连接。
CLOSING:表示主机1给主机2发送FIN后,并没有收到主机2回应的ACK,而收到了主机2发送的FIN。表示双方同时close一个socket,出现同时发送FIN现象。
LAST_ACK: 发送FIN报文后,等待对方的ACK报文,当收到ACK报文后,进入到CLOSED状态。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK确认,等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态。
第一次挥手:主机1向主机2,发送FIN报文段,表示关闭数据传送,并主机1进入FIN_WAIT_1状态,表示没有数据要传输了
第二次挥手:主机2收到FIN报文段后进入CLOSE_WAIT状态(被动关闭),然后发送ACK确认,表示同意你关闭请求了,主机到主机的数据链路关闭,主机进入FIN_WAIT_2状态
第三次挥手:主机2等待主机1发送完数据,发送FIN到主机1请求关闭,主机2进入LAST_ACK状态
第四次挥手:主机1收到主机2发送的FIN后,回复ACK确认到主机2,主机1进入TIME_WAIT状态。主机2收到主机1的ACK后就关闭连接了,状态为CLOSED。主机1等待2MSL,仍然没有收到主机2的回复,说明主机2已经正常关闭了,主机1关闭连接。
孤儿连接
连续停留在FIN_WAIT2状态可能发生,客户端执行半关闭状态后,未等服务器关闭连接就直接退出了,此时客户端连接由内核接管。Linux为防止孤儿连接长时间存在内核中,定义了两个变量指定孤儿连接数目和生存时间。
为什么要四次挥手而不是三次呢?
当主机2发送ACK确认主机1的FIN时,并不代表主机2的数据发送完毕,主机1发送完FIN处于半关闭状态(不能发送数据,但可以接收数据),所以要等待主机2的数据发送完毕后,发出FIN关闭连接请求时,主机2才进入CLOSED状态,主机1再ACK确认关闭进入CLOSE状态。
为什么TIME_WAIT 状态要等2MSL才进入CLOSED状态?
MSL(Maximum Segment Lifetime):报文最大生存时间,是任何报文段被丢弃前在网络内的最长时间。当主机1回复主机2的FIN后,等待(2-4分钟),即使两端的应用程序结束。
如果主机1直接进入CLOSED状态,由于IP协议不可靠性或网络问题,导致主机1最后发出的ACK报文未被主机2接收到,那么主机2在超时后继续向主机1重新发送FIN,而主机1已经关闭,那么找不到向主机1发送FIN的连接,主机2这时收到RST并把错误报告给高层,不符合TCP协议的可靠性特点。
如果主机1直接进入CLOSED状态,而主机2还有数据滞留在网络中,当有一个新连接的端口和原来主机2的相同,那么当原来滞留的数据到达后,主机1认为这些数据是新连接的。等待2MSL确保本次连接所有数据消失。
TIME_WAIT状态过多会占用大量的端口号,处理方法:
修改内核参数
尽可能被动关闭连接
将长连接改为短连接
close和shutdown
只要TCP栈的读缓冲里还有未读取(read)数据,则调用close时会直接向对端发送RST
close把描述字的引用计数减1,仅在该计数变为0的时候才关闭套接口。而使用shutdown可以不管引用计数的值是多少都能激发TCP的正常连接终止序列,即发送FIN。
close终止数据传送的两个方向读和写。
shutdown函数进行关闭某一方向的操作。比如,有时我们只是需要告诉对方数据发送完毕,只需要关闭数据发送的一个通道,但还是要接受对方发过来的数据。
Socket
socket 选项 TCP NO DELAY 是指什么
TCP每个报文段的首部大小至少是20字节的数据,因此若用户数据为1字节,再加上网络层IP包头20字节,则整个IP数据包的大小为41字节,那么整个IP数据包的负荷率为1 / 41。这显然是不划算的,会降低网络的传输效率,当网络都充斥着这种IP数据包的时候,可想而知整个网络几乎都在传输一些无用的包头信息,这种问题被称为小包问题。特别是在Telnet协议中,当用户远程登录到一个主机,他的每一次键盘敲击实际上都会产生一个携带用户数据量小的数据包,这是典型的小包问题。为了解决这种问题,出现了Nagle's Algorithms,这个算法是John Nagle为解决实际过程中出现的小包问题而发明的。它的思想很朴素,就是将多个即将发送的小段的用户数据,缓存并合并成一个大段数据时,一次性一并发送出去。特别的是,只要当发送者还没有收到前一次发送TCP报文段的的ACK(即连接中还存在未回执ACK的TCP报文段)时,发送方就应该一直缓存数据直到数据达到可以发送的大小,然后再统一合并到一起发送出去,如果收到上一次发送的TCP报文段的ACK则立马将缓存的数据发送出去。与之相呼应的还有一个网络优化的机制叫做TCP延迟确认,这个是针对接收方来讲的机制,由于ACK包属于有效数据比较少的小包,因此延迟确认机制就会导致接收方将多个收到数据包的ACK打包成一个回复包返回给发送方。这样就可以避免导致只包含ACK的TCP报文段过多导致网络额外的开销(前面提到的小包问题)。延迟确认机制有一个超时机制,就是当收到每一个TCP报文段后,如果该TCP报文段的ACK超过一定时间还未发送就启动超时机制,立刻将该ACK发送出去。因此延迟确认机制会可能会带来500ms的ACK延迟确认时间。延迟确认机制和Nigle算法几乎是在同一时期提出来的,但是是由不同的组提出的。这两种机制在某种程度上的确对网络传输进行了优化,在通常的协议栈实现中,这两种机制是默认开启的。Nigle算法在一次性写入比较大的数据段时会出现延迟的现象,特别是对于Request-Response模式的程序来讲,通常一个请求的数据会大于MMS,这样一个请求就会跨越多个TCP报文段,因此Nigle算法会导致最后一个TCP报文段被Hold住,出现延时;同样的一个回复的数据也会大于MMS,因此也会出现这种延时。Nigle算法通常是用来防止那些写得不太好的程序,防止这些程序随意的发小包降低网络传输效率;而对于一些精心编写的程序,Nigle算法几乎没什么用,应用程序编写者应该合理的把握、判断好每次写入的数据的大小,进而采取适当的策略进行发送,要么将小包合并到MMS大小,然后一次性写入并发送;要么禁用Nigle算法。当然以上只是一种解决方案,通常的协议栈会预留接口来禁用Nigle算法,即设置TCP_NODELAY选项
Socket 工作在 TCP/IP 协议栈是哪一层
Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。socket则是对TCP/IP协议的封装和应用(程序员层面上)。也可以说,TPC/IP协议是传输层协议
TCP、UDP 区别及 Java 实现方式
他们之间的第一点并且最重要的区别是:TCP是面向连接的协议,而UDP是无连接的协议。这意味着当一个客户端和一个服务器通过TCP发送数据之前,必须先建立连接,他们可以通过TCP发送数据。建立连接的过程也被称为TCP握手,他通过控制消息在客户端和服务器之间互换来实现。下面的图形象描述了TCP握手过程。客户端,它也是TCP连接的发起者,发送一个SYN消息给服务器,该服务器端正在监听某个TCP端口。服务器接收该消息并发送一个SYN-ACK消息,客户端接受到该消息之后会再回一个ACK消息。一旦服务器收到ACK消息,TCP连接就建立成功,准备数据传输了。另一方面,UDP是无连接的协议,和点对点连接之前不需要发送消息。这就是为什么,UDP更加适合消息的多播发布,从单个点向多个点传输消息。可靠性 TCP提供交付保证,这意味着一个使用TCP协议发送的消息是保证交付给客户端的。如果消息在传输过程中丢失,那么它将重发,这是由TCP协议本身控制的。另一方面,UDP是不可靠的,它不提供任何交付的保证。一个数据报包在运输途中可能会丢失。这就是为什么UDP是不适合保证交付的项目。有序性除了提供交付保证,为TCP也保证了消息的有序性。该消息将以从服务器端发出的同样的顺序发送到客户端,尽管这些消息到网络的另一端时可能是无序的。TCP协议将会为你排好序。UDP不提供任何有序性或序列性的保证。数据包将以任何可能的顺序到达。这就是为什么TCP是适合需要顺序交付方式的应用,尽管有基于UDP的协议通过使用序列号和重传来提供有序和可靠性的应用,如TIBCO Rendezvous,他实际上就是一个基于UDP的应用。数据边界TCP不保存数据的边界,而UDP保证。在传输控制协议,数据以字节流的形式发送,并没有明显的标志表明传输信号消息(段)的边界。在UDP中,数据包单独发送的,只有当他们到达时,才会再次集成。包有明确的界限来哪些包已经收到,这意味着在消息发送后,在接收器接口将会有一个读操作,来生成一个完整的消息。虽然TCP也将在收集所有字节之后生成一个完整的消息,但是这些信息在传给传输给接受端之前将储存在TCP缓冲区,以确保更好的使用网络带宽速度总而言之,TCP速度比较慢,而UDP速度比较快,因为TCP必须创建连接,以保证消息的可靠交付和有序性,他需要做比UDP多的多的事。这就是为什么UDP更适用于对速度比较敏感的应用,例如:在线视频媒体,电视广播和多人在线游戏。重量级vs轻量级由于上述的开销,TCP被认为是重量级的协议,而与之相比,UDP协议则是一个轻量级的协议。因为UDP传输的信息中不承担任何间接创造连接,保证交货或秩序的的信息。这也反映在用于承载元数据的头的大小。
说几点 IO 的最佳实践
76)Java 中,编写多线程程序的时候你会遵循哪些最佳实践?(答案)
这是我在写Java 并发程序的时候遵循的一些最佳实践:
a)给线程命名,这样可以帮助调试。
b)最小化同步的范围,而不是将整个方法同步,只对关键部分做同步。
c)如果可以,更偏向于使用 volatile 而不是 synchronized。
d)使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通信,如 BlockingQueue,CountDownLatch 及 Semeaphore。
e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
77)说出几点 Java 中使用 Collections 的最佳实践(答案)
这是我在使用 Java 中 Collectionc 类的一些最佳实践:
a)使用正确的集合类,例如,如果不需要同步列表,使用 ArrayList 而不是 Vector。
b)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
c)使用接口代表和访问集合,如使用List存储 ArrayList,使用 Map 存储 HashMap 等等。
d)使用迭代器来循环集合。
e)使用集合的时候使用泛型。
78)说出至少 5 点在 Java 中使用线程的最佳实践。(答案)
这个问题与之前的问题类似,你可以使用上面的答案。对线程来说,你应该:
a)对线程命名
b)将线程和任务分离,使用线程池执行器来执行 Runnable 或 Callable。
c)使用线程池
79)说出 5 条 IO 的最佳实践(答案)
IO 对 Java 应用的性能非常重要。理想情况下,你不应该在你应用的关键路径上避免 IO 操作。下面是一些你应该遵循的 Java IO 最佳实践:
a)使用有缓冲区的 IO 类,而不要单独读取字节或字符。
b)使用 NIO 和 NIO2
c)在 finally 块中关闭流,或者使用 try-with-resource 语句。
d)使用内存映射文件获取更快的 IO。
80)列出 5 个应该遵循的 JDBC 最佳实践(答案)
有很多的最佳实践,你可以根据你的喜好来例举。下面是一些更通用的原则:
a)使用批量的操作来插入和更新数据
b)使用 PreparedStatement 来避免 SQL 异常,并提高性能。
c)使用数据库连接池
d)通过列名来获取结果集,不要使用列的下标来获取。
81)说出几条 Java 中方法重载的最佳实践?(答案)
下面有几条可以遵循的方法重载的最佳实践来避免造成自动装箱的混乱。
a)不要重载这样的方法:一个方法接收 int 参数,而另个方法接收 Integer 参数。
b)不要重载参数数量一致,而只是参数顺序不同的方法。
c)如果重载的方法参数个数多于 5 个,采用可变参数。
直接缓冲区与非直接缓冲器有什么区别?
直接缓冲区与非直接缓冲区:
非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在机 此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法 来创建。此方法返回的 缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区 。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。直接字节缓冲区还可以过 通过FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建 。该方法返回MappedByteBuffer 。Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
非直接缓冲区:
直接缓冲区:
代码示例:
复制代码
@Test
public void test3(){
//分配直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
System.out.println(buf.isDirect());
}
怎么读写 ByteBuffer?ByteBuffer 中的字节序是什么
我们先来看看ByteBuffer的几个主要的方法。如何创建一个ByteBuffer?可以通过,ByteBuffer buffer=ByteBuffer.allocate(256);创建或者 ByteBuffer buffer=ByteBuffer.wrap(byteArray);这里的byteArray可以包含了数据,相当于写入了数据到缓冲区。2、如何写入数据到ByteBuffer?可以通过ByteBuffer buffer=ByteBuffer.wrap(byteArray);写入数据或buffer.put(bytes);方法写入数据。3、如何把数据准备为数据传出状态?调用buffer.flip();4、如何清除缓冲区?buffer.clear(); 这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值,不必为了每次读写都创建新的缓冲区,那样做会降低性能。相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区。5、如何读取缓冲数据?调用buffer.get(bytes);
当用System.in.read(buffer)从键盘输入一行n个字符后,存储在缓冲区buffer中的字节数是多少
如何使用扫描器类(Scanner Class)令牌化
private static void tokenizeUsingScanner(String string,String regex) { Scanner scanner = new Scanner(string); scanner.useDelimiter(regex); List
面向对象编程(OOP)
解释下多态性(polymorphism),封装性(encapsulation),内聚(cohesion)以及耦合(coupling)
多态的实现原理封装、继承和多态是什么对象封装的原则是什么?
类获得一个类的类对象有哪些方式重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
说出几条 Java 中方法重载的最佳实践
下面有几条可以遵循的方法重载的最佳实践来避免造成自动装箱的混乱。
a)不要重载这样的方法:一个方法接收 int 参数,而另个方法接收 Integer 参数。
b)不要重载参数数量一致,而只是参数顺序不同的方法。
c)如果重载的方法参数个数多于 5 个,采用可变参数。
抽象类抽象类和接口的区别抽象类中是否可以有静态的main方法
抽象类是否可实现(implements)接口抽象类是否可继承具体类(concrete class)匿名类(Anonymous Inner Class)
匿名内部类是否可以继承其它类?是否可以实现接口
匿名内部类也就是没有名字的内部类
正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
内部类
内部类分为几种
java中的内部类有四种:
1.静态内部类:作为类的静态成员,存在于某个类的内部。
静态内部类虽然是外部类的成员,但是在未创建外部类的对象的情况下,可以直接创建静态内部类的对象。静态内部类可以引用外部类的静态成员变量和静态方法,但不能引用外部类的普通成员。
//静态内部类的测试程序
public class Outter {
static int a=1;
int b=5;
static void test(){
System.out.println("外部类的静态方法");
}
static class Inner{
public void test2(){
System.out.println("a的值为"+a);;//直接引用外部类的静态成员变量
test();//直接引用外部类的静态方法
//b++;试图引用外部类的非静态成员变量,不能通过编译
System.out.println("静态内部类的方法");
}
}
public static void main(String[] args) {
Inner in=new Inner();//静态内部类的对象可以直接创建,无需先创建外部类的对象
in.test2();
}
}
2.成员内部类:作为类的成员,存在于某个类的内部。
成员内部类可以调用外部类的所有成员,但只有在创建了外部类的对象后,才能调用外部的成员。
public class Outter1 {
static int a=1;
int b=0;
public static void test1(){
System.out.println("外部类的静态方法");
}
public void test2(){
System.out.println("外部类的非静态方法");
}
class Inner{
public void test(){
System.out.println("在成员内部类的方法中");
test1();//调用外部类的静态方法
test2();//调用外部类的非静态方法
System.out.println(a+b);//访问外部类的静态成员变量和非静态成员变量
}
}
public static void main(String[] args) {
//Inner in=new Inner();成员内部类的对象不能直接创建,会报错
Outter1 out=new Outter1();//先创建外部类的对象
Inner in=out.new Inner();//注意:!!成员内部类的对象必须通过外部类的对象创建
}
}
3.局部内部类:存在于某个方法的内部。
局部内部类只能在方法内部中使用,一旦方法执行完毕,局部内部类就会从内存中删除。
必须注意:如果局部内部类中要使用他所在方法中的局部变量,那么就需要将这个局部变量定义为final的。
public class Outter2 {
int a=10;
public void test(){
final int c=5;
System.out.println("在外部类的方法中");
class Inner{
int b=20;
void test1(){
System.out.println("局部内部类的方法中");
System.out.println(c);//注意:如果局部内部类中要使用他所在方法中的局部变量,那么就需要将这个局部变量定义为final的。
}
}
Inner inner=new Inner();
inner.test1();
}
public static void main(String[] args) {
Outter2 outter=new Outter2();
outter.test();
}
}
4.匿名内部类:存在于某个类的内部,但是无类名的类。
匿名内部类的定义与对象的创建合并在一起,匿名内部类一般通过如下形式定义,并且在定义的同时进行对象的实例化。
new 类或者接口的名字(){
//匿名内部类的主体,大括号中是匿名内部类的主体,这个主体就是类或者接口的实现,如果是类,那么匿名内部类是该类的子类,如果是接口,匿名内部类需要完成接口的实现。
}
class Person{
public void show(Message message){
message.show();
}
}
class Message{
public void show(){
System.out.println("在Message类中");
}
}
public class Outter3 {
public static void main(String[] args) {
Person person=new Person();
person.show(new Message(){
public void show(){
System.out.println("在匿名内部类中");
}
});
}
}
}
java中绝大多数情况下,类的访问修饰符只有public和默认这两种,但也有例外。静态内部类和成员内部类还可以有protected和private两种
内部类可以引用它的包含类(外部类)的成员吗
如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员
请说一下 Java 中为什么要引入内部类?还有匿名内部类
继承
继承(Inheritance)与聚合(Aggregation)的区别在哪里 继承和组合之间有什么不同
继承关系即is a 关系,子类继承父类的属性 方法;比如:我 is a 人;再比如菱形、圆形和方形都是形状的一种,那么他们都应该从形状类继承而不是聚合/组合关系。
聚合/组合关系即has a关系,两个对象之间是整体和部分的关系;比如:我 has a 头;再比如电脑是由显示器、CPU、硬盘这些类聚合成电脑类,而不是从电脑类继承。
聚合:表示两个对象之间是整体和部分的弱关系,部分的生命周期可以超越整体。如电脑和鼠标;
组合:表示两个对象之间是整体和部分的强关系,部分的生命周期不能超越整体,或者说不能脱离整体而存在。组合关系的“部分”,是不能在整体之间进行共享的。如人和眼睛的关系;不过,如果你要说,眼睛可以移植,是不是说可以脱离人而存在,它们就不是组合关系了?其实,UML中对象的关系都是在相应的软件环境或实际场景下定义的,这里区别聚合和组合的关系。关键还是在于它们之中整体和部分的关系强、弱,以及它们之间的依附关系。如果刚才说眼睛可以移植给别人,那你也可以把它认为是聚合,这都要结合实际场景来说明。聚合和组合都属于关联,很难区分,但是只要记住一点,区分它们就容易多了:
处于聚合关系的两个类生命周期不同步,则是聚合关系;处于组合关系的两个类的生命周期同步;
为什么类只能单继承,接口可以多继承
因为多继承容易带来安全隐患,当多个父类中实现了相同功能但内容功能不同时,子类对象不确定要运行哪一个。 接口不一样,接口需要你在子类实现功能,不会发生这样的问题
存在两个类,B 继承 A,C 继承 B,能将 B 转换为 C 么?如 C = (C) B
如果类 a 继承类 b,实现接口c,而类 b 和接口 c 中定义了同名变量,请问会出现什么问题
接口
接口是什么接口是否可继承接口为什么要使用接口而不是直接使用具体类?接口有什么优点
面向对象是解决软件需求的变化而产生的,他的目的就是让需求变化时软件的改动达到最小化。如果只是一个IA接口,一个AClass类,IA接口就是多余的,你直接用实现类就可以了
泛型
泛型的存在是用来解决什么问题泛型的常用特点
泛型是用来使类型和算法安全的工作的一种类型。在Swift中,在函数和数据结构中都可以使用泛型,例如类、结构体和枚举。泛型一般是用来解决代码复用的问题。常见的一种情况是,你有一个函数,它带有一个参数,参数类型是A,然而当参数类型改变成B的时候,你不得不复制这个函数。
List能否转为List
工具类
日历
Calendar Class的用途如何在Java中获取日历类的实例解释一些日历类中的重要方法
Calendar类的静态方法getInstance()可以初始化一个日历对象:Calendar now = Calendar.getInstance(); Calendar类的静态方法getInstance()可以初始化一个日历对象:Calendar now = Calendar.getInstance();
可以使用下面三个方法把日历定到任何一个时间:
set(int year ,int month,int date)
set(int year ,int month,int date,int hour,int minute)
set(int year ,int month,int date,int hour,int minute,int second)
如果想获得年份、月份、小时等信息可以使用:
Now.get(Calendar.Month);这样的方法 0表示一月,1表示二月
get(Calendar.DAY_OF_MONTH)获得这个月的第几天
get(Calendar.DAY_OF_WEEK)获得这个星期的第几天
get(Calendar.DAY_OF_YEAR)获得这个年的第几天
getTimeMillis()获得当前时间的毫秒表示
abstract void add(int field, int amount)
根据日历的规则,为给定的日历字段添加或减去指定的时间量。
boolean after(Object when)
判断此 Calendar 表示的时间是否在指定 Object 表示的时间之后,返回判断结果。
boolean before(Object when)
判断此 Calendar 表示的时间是否在指定 Object 表示的时间之前,返回判断结果。
void clear()
将此 Calendar 的所日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。
void clear(int field)
将此 Calendar 的给定日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义。
Object clone()
创建并返回此对象的一个副本。
int compareTo(Calendar anotherCalendar)
比较两个 Calendar 对象表示的时间值(从历元至现在的毫秒偏移量)。
protected void complete()
填充日历字段中所有未设置的字段。
protected abstract void computeFields()
将当前毫秒时间值 time 转换为 fields[] 中的日历字段值。
protected abstract void computeTime()
将 fields[] 中的当前日历字段值转换为毫秒时间值 time。
boolean equals(Object obj)
将此 Calendar 与指定 Object 比较。
int get(int field)
返回给定日历字段的值。
int getActualMaximum(int field)
给定此 Calendar 的时间值,返回指定日历字段可能拥有的最大值。
int getActualMinimum(int field)
给定此 Calendar 的时间值,返回指定日历字段可能拥有的最小值。
static Locale[] getAvailableLocales()
返回所有语言环境的数组,此类的 getInstance 方法可以为其返回本地化的实例。
String getDisplayName(int field, int style, Locale locale)
返回给定 style 和 locale 下的日历 field 值的字符串表示形式。
Map
返回给定 style 和 locale 下包含日历 field 所有名称的 Map 及其相应字段值。
int getFirstDayOfWeek()
获取一星期的第一天;例如,在美国,这一天是 SUNDAY,而在法国,这一天是 MONDAY。
abstract int getGreatestMinimum(int field)
返回此 Calendar 实例给定日历字段的最高的最小值。
static Calendar getInstance()
使用默认时区和语言环境获得一个日历。
static Calendar getInstance(Locale aLocale)
使用默认时区和指定语言环境获得一个日历。
static Calendar getInstance(TimeZone zone)
使用指定时区和默认语言环境获得一个日历。
static Calendar getInstance(TimeZone zone, Locale aLocale)
使用指定时区和语言环境获得一个日历。
abstract int getLeastMaximum(int field)
返回此 Calendar 实例给定日历字段的最低的最大值。
abstract int getMaximum(int field)
返回此 Calendar 实例给定日历字段的最大值。
int getMinimalDaysInFirstWeek()
获取一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则此方法将返回 1。
abstract int getMinimum(int field)
返回此 Calendar 实例给定日历字段的最小值。
Date getTime()
返回一个表示此 Calendar 时间值(从历元至现在的毫秒偏移量)的 Date 对象。
long getTimeInMillis()
返回此 Calendar 的时间值,以毫秒为单位。
TimeZone getTimeZone()
获得时区。
int hashCode()
返回该此日历的哈希码。
protected int internalGet(int field)
返回给定日历字段的值。
boolean isLenient()
判断日期/时间的解释是否为宽松的。
boolean isSet(int field)
确定给定日历字段是否已经设置了一个值,其中包括因为调用 get 方法触发内部字段计算而导致已经设置该值的情况。
abstract void roll(int field, boolean up)
在给定的时间字段上添加或减去(上/下)单个时间单元,不更改更大的字段。
void roll(int field, int amount)
向指定日历字段添加指定(有符号的)时间量,不更改更大的字段。
void set(int field, int value)
将给定的日历字段设置为给定值。
void set(int year, int month, int date)
设置日历字段 YEAR、MONTH 和 DAY_OF_MONTH 的值。
void set(int year, int month, int date, int hourOfDay, int minute)
设置日历字段 YEAR、MONTH、DAY_OF_MONTH、HOUR_OF_DAY 和 MINUTE 的值。
void set(int year, int month, int date, int hourOfDay, int minute, int second)
设置字段 YEAR、MONTH、DAY_OF_MONTH、HOUR、MINUTE 和 SECOND 的值。
void setFirstDayOfWeek(int value)
设置一星期的第一天是哪一天;例如,在美国,这一天是 SUNDAY,而在法国,这一天是 MONDAY。
void setLenient(boolean lenient)
指定日期/时间解释是否是宽松的。
void setMinimalDaysInFirstWeek(int value)
设置一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则使用值 1 调用此方法。
void setTime(Date date)
使用给定的 Date 设置此 Calendar 的时间。
void setTimeInMillis(long millis)
用给定的 long 值设置此 Calendar 的当前时间值。
void setTimeZone(TimeZone value)
使用给定的时区值来设置时区。
String toString()
返回此日历的字符串表示形式
GregorianCalendar 类是什么
在 Web 开发中经常需要操纵 Web Service 的响应的信息,其中可能包含日期信息。如果开发是基于 Java 的,通常可以通过返回 XMLGregorianCalendar 类的一个对象来表示一个时间,该对象封装了一个具体时间的年、月、日、时、分、秒及时区(Time Zone)信息。我们假设通过以下语句获取了一个 XMLGregorianCalendar 对象:
// 假设函数 func() 返回一个 XMLGregorianCalendar 对象
XMLGregorianCalendar xcStartTime = func ();
如果在业务上需要将该时间和系统当前时间做比较,由于 XMLGregorianCalendar 类同 GregorianCalendar 类有方便的接口进行转换,我们通常可以创建一个 GregorianCalendar 的对象以捕捉当前系统时间。
SimpleTimeZone 类是什么
java.util.SimpleTimeZone 类是时区的具体子类,它表示与公历使用的时区。以下是有关SimpleTimeZone的 要点:这个类持有GMT的偏移,称为原始偏移。这个类还拥有开始和结束的夏令时安排的规则。
Locale类是什么如何格式化日期对象
ccbDayStartTime = new JCalendarCombo(Calendar.getInstance(), Locale.getDefault(), JCalendarCombo.DISPLAY_TIME, false);
如何添加小时(hour)到一个日期对象(Date Objects)
date.setTime(date.getTime() + 6 * 60 * 60 * 1000);
System.out.println(date);
如何将字符串 YYYYMMDD 转换为日期
public static String strToDateFormat(String date) throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
formatter.setLenient(false);
Date newDate= formatter.parse(date);
formatter = new SimpleDateFormat("yyyy-MM-dd");
return formatter.format(newDate);
}
Math
Math.round()什么作用?Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?
它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整 12 -11
XML
XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?DOM 和 SAX 解析器有什么不同?
a: 两种形式 dtd schema,b: 本质区别:schema本身是xml的,可以被XML解析器解析(这也是从DTD上发展schema的根本目的),c:有DOM,SAX,STAX等
DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问
SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问
STAX:Streaming API for XML (StAX)
Java解析XML的方式
用 jdom 解析 xml 文件时如何解决中文问题?如何解析
package com.usernet.test;
import java.io.*;
public class Test13 {
private String inFile = "F://people.xml";
private String outFile = "F://people.xml";
public static void main(String args[]) {
new Test13();
}
public Test13() {
try {
javax.xml.parsers.DocumentBuilder builder =
javax.xml.parsers.DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
org.w3c.dom.Document doc = builder.newDocument();
org.w3c.dom.Element root = doc.createElement("老师");
org.w3c.dom.Element wang = doc.createElement("王");
wang.appendChild(doc.createTextNode("我是王老师"));
org.w3c.dom.Element liu = doc.createElement("刘");
liu.appendChild(doc.createTextNode("liu老师!"));
root.appendChild(wang);
root.appendChild(liu);
doc.appendChild(root);
javax.xml.transform.Transformer transformer = javax.xml.transform.TransformerFactory
.newInstance().newTransformer();
transformer.setOutputProperty(
javax.xml.transform.OutputKeys.ENCODING, "gb2312");
transformer.setOutputProperty(
javax.xml.transform.OutputKeys.INDENT, "yes");
transformer.transform(new javax.xml.transform.dom.DOMSource(doc),
new javax.xml.transform.stream.StreamResult(outFile));
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
你在项目中用到了 XML 技术的哪些方面?如何实现
1、xml有哪些解析技术?区别是什么?
2、你在项目中用到了xml技术的哪些方面?如何实现的?
3、用jdom解析xml文件时如何解决中文问题?如何解析?
4、编程用JAVA解析XML的方式.
1、xml有哪些解析技术?区别是什么?
答:有DOM,SAX,STAX等
DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问STAX:Streaming API for XML (StAX)
2、你在项目中用到了xml技术的哪些方面?如何实现的?
答:用到了数据存贮,信息配置两方面。在做数据交换平台时,将不能数据源的数据组装成XML文件,然后将XML文件压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再同XML文件中还原相关信息进行处理。在做软件配置时,利用XML可以很方便的进行,软件的各种配置参数都存贮在XML文件中。
3、用jdom解析xml文件时如何解决中文问题?如何解析?
答:看如下代码,用编码方式加以解决
package test;
import java.io.*;
public class DOMTest
{
private String inFile = "c:\people.xml";
private String outFile = "c:\people.xml";
public static void main(String args[])
{
new DOMTest();
}
public DOMTest()
{
try
{
javax.xml.parsers.DocumentBuilder builder=javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder();
org.w3c.dom.Document doc=builder.newDocument();
org.w3c.dom.Element root=doc.createElement_x("老师");
org.w3c.dom.Element wang=doc.createElement_x("王");
org.w3c.dom.Element liu=doc.createElement_x("刘");
wang.appendChild(doc.createTextNode("我是王老师"));
root.appendChild(wang);
doc.appendChild(root);
javax.xml.transform.Transformer transformer=javax.xml.transform.TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "gb2312");
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
transformer.transform(new javax.xml.transform.dom.DOMSource(doc),new javax.xml.transform.stream.StreamResult(outFile));
}
catch (Exception e)
{
System.out.println (e.getMessage());
}
}
}
4、编程用JAVA解析XML的方式.
答:用SAX方式解析XML,XML文件如下:
王小明
信息学院
6258113
男,1955年生,博士,95年调入海南大学
事件回调类SAXHandler.java
import java.io.*;
import java.util.Hashtable;
import org.xml.sax.*;
public class SAXHandler extends HandlerBase
{
private Hashtable table = new Hashtable();
private String currentElement = null;
private String currentValue = null;
public void setTable(Hashtable table)
{
this.table = table;
}
public Hashtable getTable()
{
return table;
}
public void startElement(String tag, AttributeList attrs)
throws SAXException
{
currentElement = tag;
}
public void characters(char[] ch, int start, int length)
throws SAXException
{
currentValue = new String(ch, start, length);
}
public void endElement(String name) throws SAXException
{
if (currentElement.equals(name))
table.put(currentElement, currentValue);
}
}
JSP内容显示源码,SaxXml.jsp:
<%@ page errorPage="ErrPage.jsp" contentType="text/html;charset=GB2312" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Hashtable" %>
<%@ page import="org.w3c.dom.*" %> <%@ page import="org.xml.sax.*" %>
<%@ page import="javax.xml.parsers.SAXParserFactory" %>
<%@ page import="javax.xml.parsers.SAXParser" %>
<%@ page import="SAXHandler" %>
<%
File file = new File("c:people.xml");
FileReader reader = new FileReader(file);
Parser parser;
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
SAXHandler handler = new SAXHandler();
sp.parse(new InputSource(reader), handler);
Hashtable hashTable = handler.getTable();
out.println("
姓名 | " + "" + (String)hashTable.get(new String("name")) + " |
学院 | " + "" + (String)hashTable.get(new String("college"))+" |
电话 | " + "" + (String)hashTable.get(new String("telephone")) + " |
备注 | " + "" +
(String)hashTable.get(new String("notes")) + " |
%>
动态代理
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
描述动态代理的几种实现方式,分别说出相应的优缺点
AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。
那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现AOP中用到的两种动态代理。
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。。
设计模式
创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用新的运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
· 工厂模式与抽象工厂模式 (Factory Pattern)(Abstract Factory Pattern):不同条件下创建不同实例
· 单例模式 (Singleton Pattern):保证一个类仅有一个实例
· 建造者模式 (Builder Pattern):将一个复杂的构建过程与其具表示细节相分离,使得同样的构建过程可以创建不同的表示
· 原型模式 (Prototype Pattern):通过拷贝原型创建新的对象
结构型模式
这些设计模式关注类和对象的组合。
· 适配器模式 (Adapter Pattern):使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
· 装饰器模式 (Decorator Pattern):保持接口,增强性能:修饰类继承被修饰对象的抽象父类,依赖被修饰对象的实例(被修饰对象依赖注入),以实现接口扩展
· 桥接模式 (Bridge Pattern):两个维度独立变化,依赖方式实现抽象与实现分离:需要一个作为桥接的接口/抽象类,多个角度的实现类依赖注入到抽象类,使它们在抽象层建立一个关联关系
· 外观模式 (Facade Pattern):在客户端和复杂系统之间再加一层,这一次将调用顺序、依赖关系等处理好。即封装底层实现,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的高层接口
· 代理模式 (Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问:增加中间层(代理层),代理类与底层实现类实现共同接口,并创建底层实现类对象(底层实现类对象依赖注入代理类),以便向外界提供功能接口
· 过滤器模式 (Filter、Criteria Pattern):使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来
· 组合模式 (Composite Pattern):用户对单个对象和组合对象的使用具有一致性的统一接口
· 享元模式 (Flyweight Pattern):享元工厂类控制;HashMap实现缓冲池重用现有的同类对象,如果未找到匹配的对象,则创建新对象
行为型模式
这些设计模式特别关注对象之间的通信。
· 责任链模式(Chain of Responsibility Pattern):拦截的类都实现统一接口,每个接收者都包含对下一个接收者的引用。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
· 观察者模式(Observer Pattern):一对多的依赖关系,在观察目标类里有一个 ArrayList 存放观察者们。当观察目标对象的状态发生改变,所有依赖于它的观察者都将得到通知,使这些观察者能够自动更新(即使用推送方式)
· 模板模式(Template Pattern):将这些通用算法抽象出来,在一个抽象类中公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
· 命令模式(Command Pattern):将"行为请求者"与"行为实现者"解耦:调用者依赖命令,命令依赖接收者,调用者Invoker→命令Command→接收者Receiver
· 解释器模式(Interpreter Pattern):给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子
· 迭代器模式(Iterator Pattern):集合中含有迭代器:分离了集合对象的遍历行为,抽象出一个迭代器类来负责,无须暴露该对象的内部表示
· 中介者模式(Mediator Pattern):对象与对象之间存在大量的关联关系,将对象之间的通信关联关系封装到一个中介类中单独处理,从而使其耦合松散,可以独立地改变它们之间的交互
· 策略模式(Strategy Pattern):策略对象依赖注入到context对象,context对象根据它的策略改变而改变它的相关行为(可通过调用内部的策略对象实现相应的具体策略行为)
· 状态模式(State Pattern):状态对象依赖注入到context对象,context对象根据它的状态改变而改变它的相关行为(可通过调用内部的状态对象实现相应的具体行为)
· 备忘录模式(Memento Pattern):通过一个备忘录类专门存储对象状态。客户通过备忘录管理类管理备忘录类。
· 空对象模式(Null Object Pattern):创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。不要为了屏蔽null而使用空对象,应保持用null,远比用非null的值来替代“无值”要好。(慎用)
用在什么场合你知道哪些商业级设计模式?
哪些设计模式可以增加系统的可扩展性
可扩展性:
工厂模式
抽象工厂模式
观察者模式:很方便增加观察者,方便系统扩展
模板方法模式:很方便的实现不稳定的扩展点,完成功能的重用
适配器模式:可以很方便地对适配其他接口
代理模式:可以很方便在原来功能的基础上增加功能或者逻辑
责任链模式:可以很方便得增加拦截器/过滤器实现对数据的处理,比如struts2的责任链
策略模式:通过新增策略从而改变原来的执行策略
单例模式除了单例模式,你在生产环境中还用过什么设计模式?
写 Singleton 单例模式单例模式的双检锁是什么如何创建线程安全的 Singleton写出三种单例模式实现
饿汉式单例类
public class Singleton
{
private Singleton(){
}
private static Singleton instance = new Singleton();
private static Singleton getInstance(){
return instance;
}
}
饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()都会存在一个实例在内存中
内部类式单例类
public class Singleton
{
private Singleton(){
}
private class SingletonHoledr{
private static Singleton instance = new Singleton();
}
private static Singleton getInstance(){
return SingletonHoledr.instance;
}
}
内部类式中,实现了延迟加载,只有我们调用了getInstance(),才会创建唯一的实例到内存中.并且也解决了懒汉式中多线程的问题.解决的方式是利用了Classloader的特性.
懒汉式单例类
public class Singleton
{
private Singleton(){
}
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
return instance = new Singleton();
}else{
return instance;
}
}
}
在懒汉式中,有线程A和B,当线程A运行到第8行时,跳到线程B,当B也运行到8行时,两个线程的instance都为空,这样就会生成两个实例。解决的办法是同步:
可以同步但是效率不高:
public class Singleton
{
private Singleton(){
}
private static Singleton instance;
public static synchronized Singleton getInstance(){
if(instance == null){
return instance = new Singleton();
}else{
return instance;
}
}
}
这样写程序不会出错,因为整个getInstance是一个整体的"critical section",但就是效率很不好,因为我们的目的其实只是在第一个初始化instance的时候需要locking(加锁),而后面取用instance的时候,根本不需要线程同步。
于是聪明的人们想出了下面的做法:
双检锁写法:
public class Singleton{
private static Singleton single; //声明静态的单例对象的变量
private Singleton(){} //私有构造方法
public static Singleton getSingle(){ //外部通过此方法可以获取对象
if(single == null){
synchronized (Singleton.class) { //保证了同一时间只能只能有一个对象访问此同步块
if(single == null){
single = new Singleton();
}
}
}
return single; //返回创建好的对象
}
}
思路很简单,就是我们只需要同步(synchronize)初始化instance的那部分代码从而使代码既正确又很有效率。
这就是所谓的“双检锁”机制(顾名思义)。
很可惜,这样的写法在很多平台和优化编译器上是错误的。
原因在于:instance = new Singleton()这行代码在不同编译器上的行为是无法预知的。一个优化编译器可以合法地如下实现instance = new Singleton():
1. instance = 给新的实体分配内存
2. 调用Singleton的构造函数来初始化instance的成员变量
现在想象一下有线程A和B在调用getInstance,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是instance 已经不是null了(内存已经分配),于是它开始放心地使用instance,但这个是错误的,因为在这一时刻,instance的成员变量还都是缺省值,A还没有来得及执行步骤2来完成instance的初始化。
当然编译器也可以这样实现: 1. temp = 分配内存 2. 调用temp的构造函数 3. instance = temp
如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的,因为在Java的memory model里对这个问题没有定义。
双检锁对于基础类型(比如int)适用。很显然吧,因为基础类型没有调用构造函数这一步。
什么是类的单例模式
适配器模式适配器模式是什么?
适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。又称为转换器模式、变压器模式、包装(Wrapper)模式(把已有的一些类包装起来,使之能有满足需要的接口)。
存在两种适配器模式:
(1) 对象适配器模式——在这种适配器模式中,适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。
(2) 类适配器模式——这种适配器模式下,适配器继承自已实现的类。
无论哪种适配器,它的宗旨都是:保留现有类所提供的服务,向客户提供接口,以满足客户的期望。即在不改变原有系统的基础上,提供新的接口服务。
什么时候使用适配器模式和代理模式之前有什么不同
适配器模式:适配器模式(英语:adapter pattern)有时候也称包装样式或者包装。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类别自己的接口包裹在一个已存在的类中。
代理模式:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
适配器模式和装饰器模式有什么区别
适配器模式,允许因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
装饰器模式,原有的不能满足现有的需求,对原有的进行增强。
代理模式,同一个类而去调用另一个类的方法,不对这个方法进行直接操作。
适配器的特点在于兼容,从代码上的特点来说,适配类与原有的类具有相同的接口,并且持有新的目标对象。就如同一个三孔转2孔的适配器一样,他有三孔的插头,可以插到三孔插座里,又有两孔的插座可以被2孔插头插入。适配器模式是在于对原有3孔的改造。在使用适配器模式的时候,我们必须同时持有原对象,适配对象,目标对象。。。。
装饰器模式特点在于增强,他的特点是被装饰类和所有的装饰类必须实现同一个接口,而且必须持有被装饰的对象,可以无限装饰。
代理模式的特点在于隔离,隔离调用类和被调用类的关系,通过一个代理类去调用。
总的来说就是如下三句话:适配器模式是将一个类(a)通过某种方式转换成另一个类(b).装饰模式是在一个原有类(a)的基础之上增加了某些新的功能变成另一个类(b).代理模式是将一个类(a)转换成具体的操作类(b).
什么时候使用享元模式
共享元对象。如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必
为每一次使用创建新的对象。
享元模式是为数不多的、只为提升系统性能而生的设计模式。它的主要作用就是复用大对象(重量级对象),以节省内存空间和对象创建时间。
什么时候使用组合模式
组合模式使用树结构来展示部分与整体继承关系。它允许客户端采用统一的形式来对待单个对象和对象容器。当你想要展示对象这种部分与整体的继承关系时采用组合模式。
什么时候使用访问者模式
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
* 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
* 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
* 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
总结一下,在这种地方你一定要考虑使用访问者模式:业务规则要求遍历多个不同的对象。这本身也是访问者模式出发点,迭代器模式只能访问同类或同接口的数据(当然了,如果你使用instanceof,那么能访问所有的数据,这没有争论),而访问者模式是对迭代器模式的扩充,可以遍历不同的对象,然后执行不同的操作,也就是针对访问的对象不同,执行不同的操作。
什么是模板方法模式
请给出1个符合开闭原则的设计模式的例子
开放问题
用一句话概括 Web 编程的特点
Google是如何在一秒内把搜索结果返回给用户
哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入
树(二叉或其他)形成许多普通数据结构的基础。请描述一些这样的数据结构以及何时可以使用它们
某一项功能如何设计
线上系统突然变得异常缓慢,你如何查找问题
什么样的项目不适合用框架
新浪微博是如何实现把微博推给订阅者
简要介绍下从浏览器输入 URL 开始到获取到请求界面之后 Java Web 应用中发生了什么
请你谈谈SSH整合
高并发下,如何做到安全的修改同一行数据
12306网站的订票系统如何实现,如何保证不会票不被超卖
网站性能优化如何优化的
聊了下曾经参与设计的服务器架构
请思考一个方案,实现分布式环境下的 countDownLatch
请思考一个方案,设计一个可以控制缓存总体大小的自动适应的本地缓存
在你的职业生涯中,算得上最困难的技术挑战是什么
如何写一篇设计文档,目录是什么大写的O是什么?举几个例子
编程中自己都怎么考虑一些设计原则的,比如开闭原则,以及在工作中的应用解释一下网络应用的模式及其特点设计一个在线文档系统,文档可以被编辑,如何防止多人同时对同一份文档进行编辑更新
说出数据连接池的工作机制是什么怎么获取一个文件中单词出现的最高频率描述一下你最常用的编程风格
如果有机会重新设计你们的产品,你会怎么做如何搭建一个高可用系统如何启动时不需输入用户名与密码
如何在基于Java的Web项目中实现文件上传和下载如何实现一个秒杀系统,保证只有几位用户能买到某件商品。如何实现负载均衡,有哪些算法可以实现如何设计一个购物车?想想淘宝的购物车如何实现的
如何设计一套高并发支付方案,架构如何设计如何设计建立和保持 100w 的长连接如何避免浏览器缓存。
如何防止缓存雪崩如果AB两个系统互相依赖,如何解除依如果有人恶意创建非法连接,怎么解决
如果有几十亿的白名单,每天白天需要高并发查询,晚上需要更新一次,如何设计这个功能
如果系统要使用超大整数(超过long长度范围),请你设计一个数据结构来存储这种超大型数字以及设计一种算法来实现超大整数加法运算)
如果要设计一个图形系统,请你设计基本的图形元件(Point,Line,Rectangle,Triangle)的简单实现
如果让你实现一个并发安全的链表,你会怎么做
应用服务器与WEB 服务器的区别?应用服务器怎么监控性能,各种方式的区别?你使用过的应用服务器优化技术有哪些大型网站在架构上应当考虑哪些问题
有没有处理过线上问题?出现内存泄露,CPU利用率标高,应用无响应时如何处理的
最近看什么书,印象最深刻的是什么描述下常用的重构技巧你使用什么版本管理工具?分支(Branch)与标签(Tag)之间的区别在哪里你有了解过存在哪些反模式(Anti-Patterns)吗
你用过的网站前端优化的技术有哪些如何分析Thread dump
你如何理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念你是如何处理内存泄露或者栈溢出问题的你们线上应用的 JVM 参数有哪些
怎么提升系统的QPS和吞吐量知识面
解释什么是 MESI 协议(缓存一致性)
谈谈 reactor 模型Java 9 带来了怎样的新功能Java 与 C++ 对比,C++ 或 Java 中的异常处理机制的简单原理和应用简单讲讲 Tomcat 结构,以及其类加载器流程虚拟内存是什么阐述下 SOLID 原则
请简要讲一下你对测试驱动开发(TDD)的认识CDN实现原理Maven 和 ANT 有什么区别UML中有哪些常用的图
Linux
Linux 下 IO 模型有几种,各自的含义是什么。Linux 系统下你关注过哪些内核参数,说说你知道的
Linux 下用一行命令查看文件的最后五行平时用到哪些 Linux 命令用一行命令输出正在运行的 Java 进程
使用什么命令来确定是否有 Tomcat 实例运行在机器上什么是 N+1 难题什么是 paxos 算法
什么是 restful,讲讲你理解的 restful什么是 zab 协议
什么是领域模型(domain model)?贫血模型(anaemic domain model) 和充血模型(rich domain model)有什么区别什么是领域驱动开发(Domain Driven Development)介绍一下了解的 Java 领域的 Web Service 框架
Web Server、Web Container 与 Application Server 的区别是什么微服务(MicroServices)与巨石型应用(Monolithic Applications)之间的区别在哪里描述 Cookie 和 Session 的作用,区别和各自的应用范围,Session工作原理你常用的持续集成(Continuous Integration)、静态代码分析(Static Code Analysis)工具有哪些简述下数据库正则化(Normalizations)KISS,DRY,YAGNI 等原则是什么含义
分布式事务的原理,优缺点,如何使用分布式事务?布式集群下如何做到唯一序列号网络
HTTPS 的加密方式是什么,讲讲整个加密解密流程
https:在http(超文本传输协议)基础上提出的一种安全的http协议,因此可以称为安全的超文本传输协议。http协议直接放置在TCP协议之上,而https提出在http和TCP中间加上一层加密层。从发送端看,这一层负责把http的内容加密后送到下层的TCP,从接收方看,这一层负责将TCP送来的数据解密还原成http的内容。
SSL(Secure Socket Layer):是Netscape公司设计的主要用于WEB的安全传输协议。从名字就可以看出它在https协议栈中负责实现上面提到的加密层。因此,一个https协议栈大致是这样的:
数字证书:一种文件的名称,好比一个机构或人的签名,能够证明这个机构或人的真实性。其中包含的信息,用于实现上述功能。
加密和认证:加密是指通信双方为了防止铭感信息在信道上被第三方窃听而泄漏,将明文通过加密变成密文,如果第三方无法解密的话,就算他获得密文也无能为力;认证是指通信双方为了确认对方是值得信任的消息发送或接受方,而不是使用假身份的骗子,采取的确认身份的方式。只有同时进行了加密和认真才能保证通信的安全,因此在SSL通信协议中这两者都被应。
因此,这三者的关系已经十分清楚了:https依赖一种实现方式,目前通用的是SSL,数字证书是支持这种安全通信的文件。另外有SSL衍生出TLS和WTLS,前者是IEFT将SSL标准化之后产生的(TSL1.0),与SSL差别很小,后者是用于无线环境下的TSL。
如何加密
常用的加密算法
对称密码算法:是指加密和解密使用相同的密钥,典型的有DES、RC5、IDEA(分组加密),RC4(序列加密);
非对称密码算法:又称为公钥加密算法,是指加密和解密使用不同的密钥(公开的公钥用于加密,私有的私钥用于解密)。比如A发送,B接收,A想确保消息只有B看到,需要B生成一对公私钥,并拿到B的公钥。于是A用这个公钥加密消息,B收到密文后用自己的与之匹配的私钥解密即可。反过来也可以用私钥加密公钥解密。也就是说对于给定的公钥有且只有与之匹配的私钥可以解密,对于给定的私钥,有且只有与之匹配的公钥可以解密。典型的算法有RSA,DSA,DH;
散列算法:散列变换是指把文件内容通过某种公开的算法,变成固定长度的值(散列值),这个过程可以使用密钥也可以不使用。这种散列变换是不可逆的,也就是说不能从散列值变成原文。因此,散列变换通常用于验证原文是否被篡改。典型的算法有:MD5,SHA,Base64,CRC等。
在散列算法(也称摘要算法)中,有两个概念,强无碰撞和弱无碰撞。弱无碰撞是对给定的消息x,就是对你想伪造的明文,进行运算得出相同的摘要信息。也就是说你可以控制明文的内容。强无碰撞是指能找到相同的摘要信息,但伪造的明文是什么并不知道。
SSL的加密过程
需要注意的是非对称加解密算法的效率要比对称加解密要低的多。所以SSL在握手过程中使用非对称密码算法来协商密钥,实际使用对称加解密的方法对http内容加密传输。下面是对这一过程的形象的比喻(摘自http://blog.chinaunix.net/u2/82806/showart_1341720.html):
假设A与B通信,A是SSL客户端,B是SSL服务器端,加密后的消息放在方括号[]里,以突出明文消息的区别。双方的处理动作的说明用圆括号()括起。
A:我想和你安全的通话,我这里的对称加密算法有DES,RC5,密钥交换算法有RSA和DH,摘要算法有MD5和SHA。
B:我们用DES-RSA-SHA这对组合好了。
这是我的证书,里面有我的名字和公钥,你拿去验证一下我的身份(把证书发给A)。
A:(查看证书上B的名字是否无误,并通过手头早已有的数字的证书验证了B的证书的真实性,如果其中一项有误,发出警告并断开连接,这一步保证了B的公钥的真实性)
(产生一份秘密消息,这份秘密消息处理后将用作对称加密密钥,加密初始化向量和hmac的密钥。将这份秘密消息-协议中称为per_master_secret-用B的公钥加密,封装成称作ClientKeyExchange的消息。由于用了B的公钥,保证了第三方无法窃听)
我生成了一份秘密消息,并用你的公钥加密了,给你(把ClientKeyExchange发给B)
注意,下面我就要用加密的办法给你发消息了!
HTTPS和HTTP的区别
HTTP连接池实现原理
HTTP集群方案
Nginx、lighttpd、Apache三大主流 Web服务器的区别
是否看过框架的一些代码
持久层设计要考虑的问题有哪些?你用过的持久层框架有哪些
数值提升是什么
你能解释一下里氏替换原则吗
你是如何测试一个应用的?知道哪些测试框架
传输层常见编程协议有哪些?并说出各自的特点
编程题
计算加班费
加班10小时以下加班费是时薪的1.5倍。加班10小时或以上,按4元/时算。提示:(一个月工作26天,一天正常工作8小时)
计算1000月薪,加班9小时的加班费
计算2500月薪,加班11小时的加班费
计算1000月薪,加班15小时的加班费
卖东西
一家商场有红苹果和青苹果出售。(红苹果5元/个,青苹果4元/个)。
模拟一个进货。红苹果跟青苹果各进200个。
模拟一个出售。红苹果跟青苹果各买出10个。每卖出一个苹果需要进行统计。
提示:一个苹果是一个单独的实体。
日期提取
有这样一个时间字符串:2008-8-8 20:08:08 , 请编写能够匹配它的正则表达式,并编写Java代码将日期后面的时分秒提取出来,即:20:08:08
线程
8设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
用Java写一个多线程程序,如写四个线程,二个加1,二个对一个变量减一,输出
wait-notify 写一段代码来解决生产者-消费者问题
数字
判断101-200之间有多少个素数,并输出所有素数
用最有效率的方法算出2乘以17等于多少
有 1 亿个数字,其中有 2 个是重复的,快速找到它,时间和空间要最优
2 亿个随机生成的无序整数,找出中间大小的值
10 亿个数字里里面找最小的 10 个
1到1亿的自然数,求所有数的拆分后的数字之和,如286 拆分成2、8、6,如1到11拆分后的数字之和 => 1 + … + 9 + 1 + 0 + 1 + 1
一个数如果恰好等于它的因子之和,这个数就称为 “完数 “。例如6=1+2+3.编程 找出1000以内的所有完数
一个数组中所有的元素都出现了三次,只有一个元素出现了一次找到这个元素
一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在 第10次落地时,共经过多少米?第10次反弹多高?
求100-1000内质数的和
求1到100的和的平均数
求s=a+a+aaa+aaaa+aa…a的值,其中a是一个数字。例如2+22+222+2222+22222(此时共有5个数相加),几个数相加有键盘控制。 求出1到100的和
算出1到40的质数,放进数组里
显示放组里的数
找出第[5]个数
删除第[9]个数,再显示删除后的第[9]个
有 3n+1 个数字,其中 3n 个中是重复的,只有 1 个是不重复的,怎么找出来。
有一组数1.1.2.3.5.8.13.21.34。写出程序随便输入一个数就能给出和前一组数字同规律的头5个数
计算指定数字的阶乘
开发 Fizz Buzz
给定一个包含 N 个整数的数组,找出丢失的整数
一个排好序的数组,找出两数之和为m的所有组合
将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5。
打印出所有的 “水仙花数 “,所谓 “水仙花数 “是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个 “水仙花数 “,因为153=1的三次方+5的三次方+3的三次方
原地交换两个变量的值
找出4字节整数的中位数
找到整数的平方根
实现斐波那契
网络
用Java Socket编程,读服务器几个字符,再写入本地显示
反射
反射机制提供了什么功能?
反射是如何实现的
哪里用到反射机制
反射中 Class.forName 和 ClassLoader 区别
1.Class.forName返回的Class对象可以决定是否初始化。而ClassLoader.loadClass返回的类型绝对不会初始化,最多只会做连接操作。
2.Class.forName可以决定由哪个classLoader来请求这个类型。而ClassLoader.loadClass是用当前的classLoader去请求。
反射创建类实例的三种方式是什么
如何通过反射调用对象的方法
如何通过反射获取和设置对象私有字段的值
反射机制的优缺点
数据库
写一段 JDBC 连Oracle的程序,并实现数据查询
算法
50个人围坐一圈,当数到三或者三的倍数出圈,问剩下的人是谁,原来的位置是多少
实现一个电梯模拟器用
写一个冒泡排序
写一个折半查找
随机产生20个不能重复的字符并排序
写一个函数,传入 2 个有序的整数数组,返回一个有序的整数数组
写一段代码在遍历 ArrayList 时移除一个元素
古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少
约瑟芬环游戏
正则
请编写一段匹配IP地址的正则表达式
写出一个正则表达式来判断一个字符串是否是一个数字
字符串
写一个方法,入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。
写一个程序找出所有字符串的组合,并检查它们是否是回文串
写一个字符串反转函数,输入abcde转换成edcba代码
小游戏,倒转句子中的单词
将GB2312编码的字符串转换为ISO-8859-1编码的字符串
请写一段代码来计算给定文本内字符“A”的个数。分别用迭代和递归两种方式
编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”
给定 2 个包含单词列表(每行一个)的文件,编程列出交集
打印出一个字符串的所有排列
将一个键盘输入的数字转化成中文输出(例如:输入1234567,输出:一百二拾三万四千五百六拾七)
在Web应用开发过程中经常遇到输出某种编码的字符,如从 GBK 到 ISO8859-1等,如何输出一个某种编码的字符串
日期
计算两个日期之间的差距
1)Java 中能创建 volatile 数组吗?
能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。
2)volatile 能使得一个非原子操作变成原子操作吗?
一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为什么?因为 Java 中读取 long 类型变量不是原子的,需要分成两步,如果一个线程正在修改该 long 变量的值,另一个线程可能只能看到该值的一半(前 32 位)。但是对一个 volatile 型的 long 或 double 变量的读写是原子。
3)volatile 修饰符的有过什么实践?
一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。
4)volatile 类型变量提供什么保证?
volatile 变量提供顺序和可见性保证,例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。
5)你是如何调用 wait()方法的?使用 if 块还是循环?为什么?
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
// The standard idiom for using the wait method
synchronized (obj) {
while (condition does not hold)
obj.wait(); // (Releases lock, and reacquires on wakeup)
... // Perform action appropriate to condition
}
6)什么是多线程环境下的伪共享(false sharing)?
伪共享是多线程系统(每个处理器有自己的局部缓存)中一个众所周知的性能问题。伪共享发生在不同处理器的上的线程对变量的修改依赖于相同的缓存行,如下图所示:
7)什么是 Busy spin?我们为什么要使用它?
Busy spin 是一种在不释放 CPU 的基础上等待事件的技术。它经常用于避免丢失 CPU 缓存中的数据(如果线程先暂停,之后在其他CPU上运行就会丢失)。所以,如果你的工作要求低延迟,并且你的线程目前没有任何顺序,这样你就可以通过循环检测队列中的新消息来代替调用 sleep() 或 wait() 方法。它唯一的好处就是你只需等待很短的时间,如几微秒或几纳秒。LMAX 分布式框架是一个高性能线程间通信的库,该库有一个 BusySpinWaitStrategy 类就是基于这个概念实现的,使用 busy spin 循环 EventProcessors 等待屏障。
8)什么是线程局部变量?
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
9)Java 中 sleep 方法和 wait 方法的区别?
虽然两者都是用来暂停当前运行的线程,但是 sleep() 实际上只是短暂停顿,因为它不会释放锁,而 wait() 意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。
10)什么是不可变对象(immutable object)?Java 中怎么创建一个不可变对象?
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。
11)我们能创建一个包含可变对象的不可变对象吗?
是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。
数据类型和 Java 基础面试问题
12)Java 中应该使用什么数据类型来代表价格?
如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。
13)怎么将 byte 转换为 String?
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。
14)我们能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象?
是的,我们可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果强制转化是,int 类型的高 24 位将会被丢弃,byte 类型的范围是从 -128 到 128。
15)Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。
16)a = a + b 与 a += b 的区别?
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。如果加法操作的结果比 a 的最大值要大,则 a+b 会出现编译错误,但是 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 就会编译出错
17)我能在不进行强制转换的情况下将一个 double 值赋值给 long 类型的变量吗?
不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。
18)3*0.1 == 0.3 将会返回什么?true 还是 false?
false,因为有些浮点数不能完全精确的表示出来。
19)int 和 Integer 哪个会占用更多的内存?
Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。
20)为什么 Java 中的 String 是不可变的(Immutable)?
Java 中的 String 不可变是因为 Java 的设计者认为字符串使用非常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。
21)Java 中的构造器链是什么?
当你从一个构造器中调用另一个构造器,就是Java 中的构造器链。这种情况只在重载了类的构造器的时候才会出现。
JVM 底层 与 GC(Garbage Collection) 的面试问题
22)64 位 JVM 中,int 的长度是多数?
Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。
23)Serial 与 Parallel GC之间的不同之处?
Serial 与 Parallel 在GC执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而 parallel 收集器使用多个 GC 线程来执行。
24)32 位和 64 位的 JVM,int 类型变量的长度是多数?
32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4 个字节。
25)Java 中 WeakReference 与 SoftReference的区别?
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。
26)WeakHashMap 是怎么工作的?
WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。
27)JVM 选项 -XX:+UseCompressedOops 有什么作用?为什么要使用?
当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从 32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到 64 位的 JVM 主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位的 OOP。
28)怎样通过 Java 程序来判断 JVM 是 32 位 还是 64 位?
你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。
29)32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?
理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。
30)JRE、JDK、JVM 及 JIT 之间有什么不同?
JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java 编译器,它也包含 JRE。JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。
31)解释 Java 堆空间及 GC?
当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配。
32)你能保证 GC 执行吗?
不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。
33)怎么获取 Java 程序使用的内存?堆使用的百分比?
可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。
34)Java 中堆和栈有什么区别?
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。
Java 基本概念面试题
35)“a==b”和”a.equals(b)”有什么区别?
如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。
36)a.hashCode() 有什么用?与 a.equals(b) 有什么关系?
hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。
37)final、finalize 和 finally 的不同之处?
final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。
38)Java 中的编译期常量是什么?使用它又什么风险?
公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。
Java 集合框架的面试题
39) List、Set、Map 和 Queue 之间的区别?
List 是一个有序集合,允许元素重复。它的某些实现可以提供基于下标值的常量访问时间,但是这不是 List 接口保证的。Set 是一个无序集合。
40)poll() 方法和 remove() 方法的区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
41)Java 中 LinkedHashMap 和 PriorityQueue 的区别是什么?
PriorityQueue 保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。
42)ArrayList 与 LinkedList 的不区别?
最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构书链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
43)用哪两种方式来实现集合的排序?
你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过 Collections.sort() 来排序。
44)Java 中怎么打印数组?
你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。由于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。
45) Hashtable 与 HashMap 有什么不同之处?
这两个类有许多不同的地方,下面列出了一部分:
a) Hashtable 是 JDK 1 遗留下来的类,而 HashMap 是后来增加的。
b)Hashtable 是同步的,比较慢,但 HashMap 没有同步策略,所以会更快。
c)Hashtable 不允许有个空的 key,但是 HashMap 允许出现一个 null key。
46)Java 中的 HashSet,内部是如何工作的?
HashSet 的内部采用 HashMap来实现。由于 Map 需要 key 和 value,所以所有 key 的都有一个默认 value。类似于 HashMap,HashSet 不允许重复的 key,只允许有一个null key,意思就是 HashSet 中只允许存储一个 null 对象。
47)写一段代码在遍历 ArrayList 时移除一个元素?
该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法。这有一段示例代码,是使用正确的方式来实现在遍历的过程中移除元素,而不会出现 ConcurrentModificationException 异常的示例代码。
48)我们能自己写一个容器类,然后使用 for-each 循环码?
可以,你可以写一个自己的容器类。如果你想使用 Java 中增强的循环来遍历,你只需要实现 Iterable 接口。如果你实现 Collection 接口,默认就具有该属性。
49)ArrayList 和 HashMap 的默认大小是多数?
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片段:
// from ArrayList.java JDK 1.7
private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
50)有没有可能两个不相等的对象有有相同的 hashcode?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。
51)两个相同的对象会有不同的的 hash code 吗?
不能,根据 hash code 的规定,这是不可能的。
52)我们可以在 hashcode() 中使用随机数字吗?
不行,因为对象的 hashcode 值必须是相同的。参见答案获取更多关于 Java 中重写 hashCode() 方法的知识。
53)Java 中,Comparator 与 Comparable 有什么不同?
Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。
54)为什么在重写 equals 方法的时候需要重写 hashCode 方法?
因为有强制的规范指定需要同时重写 hashcode 与 equal 是方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。
Java 最佳实践的面试问题
55)Java 中,编写多线程程序的时候你会遵循哪些最佳实践?
a)给线程命名,这样可以帮助调试。
b)最小化同步的范围,而不是将整个方法同步,只对关键部分做同步。
c)如果可以,更偏向于使用 volatile 而不是 synchronized。
d)使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通信,如 BlockingQueue,CountDownLatch 及 Semeaphore。
e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
56)说出几点 Java 中使用 Collections 的最佳实践?
a)使用正确的集合类,例如,如果不需要同步列表,使用 ArrayList 而不是 Vector。
b)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
c)使用接口代表和访问集合,如使用List存储 ArrayList,使用 Map 存储 HashMap 等等。
d)使用迭代器来循环集合。
e)使用集合的时候使用泛型。
57)说出在 Java 中使用线程的最佳实践?
a)对线程命名
b)将线程和任务分离,使用线程池执行器来执行 Runnable 或 Callable。
c)使用线程池
58)说出 IO 的最佳实践?
a)使用有缓冲区的 IO 类,而不要单独读取字节或字符。
b)使用 NIO 和 NIO2
c)在 finally 块中关闭流,或者使用 try-with-resource 语句。
d)使用内存映射文件获取更快的 IO。
59)列出应该遵循的 JDBC 最佳实践?
a)使用批量的操作来插入和更新数据
b)使用 PreparedStatement 来避免 SQL 异常,并提高性能。
c)使用数据库连接池
d)通过列名来获取结果集,不要使用列的下标来获取。
60)说出几条 Java 中方法重载的最佳实践?
a)不要重载这样的方法:一个方法接收 int 参数,而另个方法接收 Integer 参数。
b)不要重载参数数量一致,而只是参数顺序不同的方法。
c)如果重载的方法参数个数多于 5 个,采用可变参数。
Date、Time 及 Calendar 的面试题
61)在多线程环境下,SimpleDateFormat 是线程安全的吗?
不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。
62)Java 中如何格式化一个日期?如格式化为 ddMMyyyy 的形式?
Java 中,可以使用 SimpleDateFormat 类或者 joda-time 库来格式日期。DateFormat 类允许你使用多种流行的格式来格式化日期。参见答案中的示例代码,代码中演示了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。
关于 OOP 和设计模式的面试题
63)接口是什么?为什么要使用接口而不是直接使用具体类?
接口用于定义 API。它定义了类必须得遵循的规则。同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使用可随机访问的 ArrayList,也可以使用方便插入和删除的 LinkedList。接口中不允许写代码,以此来保证抽象,但是 Java 8 中你可以在接口声明静态的默认方法,这种方法是具体的。
64)Java 中,抽象类与接口之间有什么不同?
Java 中,抽象类和接口有很多不同之处,但是最重要的一个是 Java 中限制一个类只能继承一个类,但是可以实现多个接口。抽象类可以很好的定义一个家族类的默认行为,而接口能更好的定义类型,有助于后面实现多态机制。关于这个问题的讨论请查看答案。
65)除了单例模式,你在生产环境中还用过什么设计模式?
这需要根据你的经验来回答。一般情况下,你可以说依赖注入,工厂模式,装饰模式或者观察者模式,随意选择你使用过的一种即可。不过你要准备回答接下的基于你选择的模式的问题。
66)适配器模式是什么?什么时候使用?
适配器模式提供对接口的转换。如果你的客户端使用某些接口,但是你有另外一些接口,你就可以写一个适配去来连接这些接口。
67)构造器注入和 setter 依赖注入,那种方式更好?
每种方式都有它的缺点和优点。构造器注入保证所有的注入都被初始化,但是 setter 注入提供更好的灵活性来设置可选依赖。如果使用 XML 来描述依赖,Setter 注入的可读写会更强。经验法则是强制依赖使用构造器注入,可选依赖使用 setter 注入。
68)依赖注入和工程模式之间有什么不同?
虽然两种模式都是将对象的创建从应用的逻辑中分离,但是依赖注入比工程模式更清晰。通过依赖注入,你的类就是 POJO,它只知道依赖而不关心它们怎么获取。使用工厂模式,你的类需要通过工厂来获取依赖。因此,使用 DI 会比使用工厂模式更容易测试。
69)适配器模式和装饰器模式有什么区别?
虽然适配器模式和装饰器模式的结构类似,但是每种模式的出现意图不同。适配器模式被用于桥接两个接口,而装饰模式的目的是在不修改类的情况下给类增加新的功能。
70)适配器模式和代理模式之前有什么不同?
这个问题与前面的类似,适配器模式和代理模式的区别在于他们的意图不同。由于适配器模式和代理模式都是封装真正执行动作的类,因此结构是一致的,但是适配器模式用于接口之间的转换,而代理模式则是增加一个额外的中间层,以便支持分配、控制或智能访问。
71)什么是模板方法模式?
模板方法提供算法的框架,你可以自己去配置或定义步骤。例如,你可以将排序算法看做是一个模板。它定义了排序的步骤,但是具体的比较,可以使用 Comparable 或者其语言中类似东西,具体策略由你去配置。列出算法概要的方法就是众所周知的模板方法。
72)什么时候使用访问者模式?
访问者模式用于解决在类的继承层次上增加操作,但是不直接与之关联。这种模式采用双派发的形式来增加中间层。
73)什么时候使用组合模式?
组合模式使用树结构来展示部分与整体继承关系。它允许客户端采用统一的形式来对待单个对象和对象容器。当你想要展示对象这种部分与整体的继承关系时采用组合模式。
74)继承和组合之间有什么不同?
虽然两种都可以实现代码复用,但是组合比继承共灵活,因为组合允许你在运行时选择不同的实现。用组合实现的代码也比继承测试起来更加简单。
75)描述 Java 中的重载和重写?
重载和重写都允许你用相同的名称来实现不同的功能,但是重载是编译时活动,而重写是运行时活动。你可以在同一个类中重载方法,但是只能在子类中重写方法。重写必须要有继承。
76)Java 中,嵌套公共静态类与顶级类有什么不同?
类的内部可以有多个嵌套公共静态类,但是一个 Java 源文件只能有一个顶级公共类,并且顶级公共类的名称与源文件名称必须一致。
77) OOP 中的 组合、聚合和关联有什么区别?
如果两个对象彼此有关系,就说他们是彼此相关联的。组合和聚合是面向对象中的两种形式的关联。组合是一种比聚合更强力的关联。组合中,一个对象是另一个的拥有者,而聚合则是指一个对象使用另一个对象。如果对象 A 是由对象 B 组合的,则 A 不存在的话,B一定不存在,但是如果 A 对象聚合了一个对象 B,则即使 A 不存在了,B 也可以单独存在。
78)给我一个符合开闭原则的设计模式的例子?
开闭原则要求你的代码对扩展开放,对修改关闭。这个意思就是说,如果你想增加一个新的功能,你可以很容易的在不改变已测试过的代码的前提下增加新的代码。有好几个设计模式是基于开闭原则的,如策略模式,如果你需要一个新的策略,只需要实现接口,增加配置,不需要改变核心逻辑。一个正在工作的例子是 Collections.sort() 方法,这就是基于策略模式,遵循开闭原则的,你不需为新的对象修改 sort() 方法,你需要做的仅仅是实现你自己的 Comparator 接口。
79)什么时候使用享元模式?
享元模式通过共享对象来避免创建太多的对象。为了使用享元模式,你需要确保你的对象是不可变的,这样你才能安全的共享。JDK 中 String 池、Integer 池以及 Long 池都是很好的使用了享元模式的例子。
解释下多态性(polymorphism),封装性(encapsulation),内聚(cohesion)以及耦合(coupling)。
继承(Inheritance)与聚合(Aggregation)的区别在哪里。
你是如何理解干净的代码(Clean Code)与技术负债(Technical Debt)的。
描述下常用的重构技巧。
阐述下 SOLID 原则。
·
其他的譬如 KISS,DRY,YAGNI 等原则又是什么含义。
·
·
什么是设计模式(Design Patterns)?你知道哪些设计模式?
·
·
你有了解过存在哪些反模式(Anti-Patterns)吗?
·
·
你会如何设计登陆舰/数学表达式计算程序/一条龙?
·
·
你知道哪些基本的排序算法,它们的计算复杂度如何?在给定数据的情况下你会倾向于使用哪种算法呢?
·
·
尝试编写如下代码:
·
·
计算指定数字的阶乘
·
·
开发 Fizz Buzz 小游戏
·
·
倒转句子中的单词
·
·
回文字符串检测
·
·
枚举给定字符串的所有排列组合
·
·
equals 与 hashCode 的异同点在哪里?Java 的集合中又是如何使用它们的。
·
·
描述下 Java 中集合(Collections),接口(Interfaces),实现(Implementations)的概念。LinkedList 与 ArrayList 的区别是什么?
·
·
基础类型(Primitives)与封装类型(Wrappers)的区别在哪里?
·
·
final 与 static 关键字可以用于哪里?它们的作用是什么?
·
·
阐述下 Java 中的访问描述符(Access Modifiers)。
·
·
描述下 String,StringBuilder 以及 StringBuffer 区别。
·
·
接口(Interface)与抽象类(Abstract Class)的区别在哪里。
·
·
覆盖(Overriding)与重载(OverLoading)的区别在哪里。
·
·
异常分为哪几种类型?以及所谓的handle or declare原则应该如何理解?
·
·
简述垃圾回收器的工作原理。
·
·
你是如何处理内存泄露或者栈溢出问题的?
·
·
如何构建不可变的类结构?关键点在哪里?
·
·
什么是 JIT 编译?
·
·
Java 8 / Java 7 为我们提供了什么新功能?即将到来的 Java 9 又带来了怎样的新功能?
·
·
请解释下 ORM。
·
·
简述下 Hibernate 的优劣特性。
·
·
Hibernate 与 JPA 区别在哪?
·
·
Hibernate 最新版提供了哪些特性?
·
·
什么是懒加载(Lazy Loading)?
·
·
什么是 N+1 难题?
·
·
介绍一些熟悉的 Hibernate 注释。
·
·
简介下 Hibernate Session 与 SessionFactory。
·
·
Entity Beans 的状态有哪些。
·
·
Hibernate 中的缓存分为几层。
·
·
Hibernate 中事务的支持分为几级?
·
·
什么是乐观锁(Optimistic Locking)?
·
·
简述下 ACID 原则。
·
·
简述下数据库正则化(Normalizations)。
·
·
请介绍下你日常工作中优化慢查询(Slow Query)的策略。
·
·
新版的 Spring 中有哪些新特性?
·
·
介绍下 Spring 的优势与缺陷。
·
·
什么是控制反转(Inversion of Control)与依赖注入(Dependency Injection)?
·
·
你用过哪些 Spring 的模块?
·
·
Spring 中是如何使用依赖注入的?
·
·
Spring 中提供了几种自动注入的机制?
·
·
介绍下 Spring MVC。
·
·
Spring 中 Scopes 有哪些?
·
·
Spring 中 Bean 的生命周期包含哪些步骤?
·
·
Spring Bean 与 EJB Bean 的区别在哪里?
·
·
介绍下切面编程(Aspect Oriented Programming)。
·
·
概述下 GET 与 POST 的区别。
·
·
Web Server、Web Container 与 Application Server 的区别是什么?
·
·
简要介绍下从浏览器输入 URL 开始到获取到请求界面之后 Java Web 应用中发生了什么。
·
·
什么是 N 层架构?
·
·
微服务(MicroServices)与巨石型应用(Monolithic Applications)之间的区别在哪里?
·
·
你知道哪些商业级设计模式?
·
·
你是如何测试一个应用的?知道哪些测试框架?
·
·
你是如何测试单个方法的?
·
·
在你的职业生涯中,算得上最困难的技术挑战是什么?
·
·
什么是领域驱动开发(Domain Driven Development)?
·
·
介绍下一些你最爱的 IDE 的常用插件。
·
·
除了 IDE 之外,你的日常工作中还会用到哪些工具?
·
·
你使用什么版本管理工具?
·
·
分支(Branch)与标签(Tag)之间的区别在哪里?
·
·
你常用的持续集成(Continuous Integration)、静态代码分析(Static Code Analysis)工具有哪些?
· 解释下多态性(polymorphism),封装性(encapsulation),内聚(cohesion)以及耦合(coupling)。
· 继承(Inheritance)与聚合(Aggregation)的区别在哪里。
· 你是如何理解干净的代码(Clean Code)与技术负载(Technical Debt)的。
· 描述下常用的重构技巧。
· 阐述下 SOLID 原则。
· 其他的譬如 KISS,DRY,YAGNI 等原则又是什么含义。
· 什么是设计模式(Design Patterns)?你知道哪些设计模式?
· 你有了解过存在哪些反模式(Anti-Patterns)吗?
· 你会如何设计登陆舰/数学表达式计算程序/一条龙?
· 你知道哪些基本的排序算法,它们的计算复杂度如何?在给定数据的情况下你会倾向于使用哪种算法呢?
· 尝试编写如下代码:
o 计算指定数字的阶乘
o 开发 Fizz Buzz 小游戏
o 倒转句子中的单词
o 回文字符串检测
o 枚举给定字符串的所有排列组合
· equals 与 hashCode 的异同点在哪里?Java 的集合中又是如何使用它们的。
· 描述下 Java 中集合(Collections),接口(Interfaces),实现(Implementations)的概念。LinkedList 与 ArrayList 的区别是什么?
· 基础类型(Primitives)与封装类型(Wrappers)的区别在哪里?
· final 与 static 关键字可以用于哪里?它们的作用是什么?
· 阐述下 Java 中的访问描述符(Access Modifiers)。
· 描述下 String,StringBuilder 以及 StringBuffer 区别。
· 接口(Interface)与抽象类(Abstract Class)的区别在哪里。
· 覆盖(Overriding)与重载(OverLoading)的区别在哪里。
· 异常分为哪几种类型?以及所谓的handle or declare原则应该如何理解?
· 简述垃圾回收器的工作原理。
· 你是如何处理内存泄露或者栈溢出问题的?
· 如何构建不可变的类结构?关键点在哪里?
· 什么是 JIT 编译?
· Java 8 / Java 7 为我们提供了什么新功能?即将到来的 Java 9 又带来了怎样的新功能?
· 请解释下 ORM。
· 简述下 Hibernate 的优劣特性。
· Hibernate 与 JPA 区别在哪?
· Hibernate 最新版提供了哪些特性?
· 什么是懒加载(Lazy Loading)?
· 什么是 N+1 难题?
· 介绍一些熟悉的 Hibernate 注释。
· 简介下 Hibernate Session 与 SessionFactory。
· Entity Beans 的状态有哪些。
· Hibernate 中的缓存分为几层。
· Hibernate 中事务的支持分为几级?
· 什么是乐观锁(Optimistic Locking)?
· 简述下 ACID 原则。
· 简述下数据库正则化(Normalizations)。
· 请介绍下你日常工作中优化慢查询(Slow Query)的策略。
· 新版的 Spring 中有哪些新特性?
· 介绍下 Spring 的优势与缺陷。
· 什么是控制反转(Inversion of Control)与依赖注入(Dependency Injection)?
· 你用过哪些 Spring 的模块?
· Spring 中是如何使用依赖注入的?
· Spring 中提供了几种自动注入的机制?
· 介绍下 Spring MVC。
· Spring 中 Scopes 有哪些?
· Spring 中 Bean 的生命周期包含哪些步骤?
· Spring Bean 与 EJB Bean 的区别在哪里?
· 介绍下切面编程(Aspect Oriented Programming)。
· 概述下 GET 与 POST 的区别。
· Web Server、Web Container 与 Application Server 的区别是什么?
· 简要介绍下从浏览器输入 URL 开始到获取到请求界面之后 Java Web 应用中发生了什么。
· 什么是 N 层架构?
· 微服务(MicroServices)与巨石型应用(Monolithic Applications)之间的区别在哪里?
· 你知道哪些商业级设计模式?
· 你是如何测试一个应用的?知道哪些测试框架?
· 你是如何测试单个方法的?
· 在你的职业生涯中,算得上最困难的技术挑战是什么?
· 什么是领域驱动开发(Domain Driven Development)?
· 介绍下一些你最爱的 IDE 的常用插件。
· 除了 IDE 之外,你的日常工作中还会用到哪些工具?
· 你使用什么版本管理工具?
· 分支(Branch)与标签(Tag)之间的区别在哪里?
· 你常用的持续集成(Continuous Integration)、静态代码分析(Static Code Analysis)工具有哪些?
J2SE基础:
1. 九种基本数据类型的大小,以及他们的封装类。
2. Switch能否用string做参数?
3. equals与==的区别。
4. Object有哪些公用方法?
5. Java的四种引用,强弱软虚,用到的场景。
6. Hashcode的作用。
7. ArrayList、LinkedList、Vector的区别。
8. String、StringBuffer与StringBuilder的区别。
9. Map、Set、List、Queue、Stack的特点与用法。
10. HashMap和HashTable的区别。
11. HashMap和ConcurrentHashMap的区别,HashMap的底层源码。
12. TreeMap、HashMap、LindedHashMap的区别。
13. Collection包结构,与Collections的区别。
14. try catch finally,try里有return,finally还执行么?
15. Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况。
16. Java面向对象的三个特征与含义。
17. Override和Overload的含义去区别。
18. Interface与abstract类的区别。
19. Static class 与non static class的区别。
20. java多态的实现原理。
21. 实现多线程的两种方法:Thread与Runable。
22. 线程同步的方法:sychronized、lock、reentrantLock等。
23. 锁的等级:方法锁、对象锁、类锁。
24. 写出生产者消费者模式。
25. ThreadLocal的设计理念与作用。
26. ThreadPool用法与优势。
27. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。
28. wait()和sleep()的区别。
29. foreach与正常for循环效率对比。
30. Java IO与NIO。
31. 反射的作用于原理。
32. 泛型常用特点,List
33. 解析XML的几种方式的原理与特点:DOM、SAX、PULL。
34. Java与C++对比。
35. Java1.7与1.8新特性。
36. 设计模式:单例、工厂、适配器、责任链、观察者等等。
37. JNI的使用。
JVM:
1. 内存模型以及分区,需要详细到每个区放什么。
2. 堆里面的分区:Eden,survival from to,老年代,各自的特点。
3. 对象创建方法,对象的内存分配,对象的访问定位。
4. GC的两种判定方法:引用计数与引用链。
5. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
6. GC收集器有哪些?CMS收集器与G1收集器的特点。
7. Minor GC与Full GC分别在什么时候发生?
8. 几种常用的内存调试工具:jmap、jstack、jconsole。
9. 类加载的五个过程:加载、验证、准备、解析、初始化。
10. 双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。
11. 分派:静态分派与动态分派。
总体来说java考察内容包括以下这些:
1,面向对象的一些基本概念:继承,多态之类的
2, 抽象类和接口
3, 静态类,内部类
4, Java集合类,同步和非同步
5, Java类加载机制
6, Java内存模型和垃圾回收算法
7, 线程同步机制(voliate,synchronized,重入锁,threadlocal),线程间通信(wait,notify)
8, 异常处理
9, 多线程同步问题,生产者消费者,读者写者,哲学家就餐,用java实现
10, 了解java中设计模式的思想,用了哪些设计模式,有什么好处