1.Java中的原始数据类型都有哪些,它们的大小及对应的封装类是什么?
2.谈一谈”==“与”equals()"的区别。《ThinkinJava》中说:“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。"=="判断的是两个对象的内存地址是否一样,适用于原始数据类型和枚举类型(它们的变量存储的是值本身,而引用类型变量存储的是引用);equals是Object类的方法,Object对它的实现是比较内存地址,我们可以重写这个方法来自定义“相等”这个概念。比如类库中的String、Date等类就对这个方法进行了重写。综上,对于枚举类型和原始数据类型的相等性比较,应该使用"==";对于引用类型的相等性比较,应该使用equals方法。
3.Java中的四种引用及其应用场景是什么?
强引用:通常我们使用new操作符创建一个对象时所返回的引用即为强引用
软引用:若一个对象只能通过软引用到达,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap
弱引用:若一个对象只能通过弱引用到达,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收
虚引用:虚引用是Java中最“弱”的引用,通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁。
4.object中定义了哪些方法?clone(),equals(),hashCode(),toString(),notify(),notifyAll(),wait(),finalize(),getClass()
6.ArrayList,LinkedList,Vector的区别是什么?
ArrayList:内部采用数组存储元素,支持高效随机访问,支持动态调整大小
LinkedList:内部采用链表来存储元素,支持快速插入/删除元素,但不支持高效地随机访问
Vector:可以看作线程安全版的ArrayList
7.String,StringBuilder,StringBuffer的区别是什么?
String:不可变的字符序列,若要向其中添加新字符需要创建一个新的String对象
StringBuilder:可变字符序列,支持向其中添加新字符(无需创建新对象)
StringBuffer:可以看作线程安全版的StringBuilder
8.Map,Set,List,Queue、Stack的特点及用法。
Map:Java中存储键值对的数据类型都实现了这个接口,表示“映射表”。支持的两个核心操作是get(Objectkey)以及put(Kkey,Vvalue),分别用来获取键对应的值以及向映射表中插入键值对。
Set:实现了这个接口的集合类型中不允许存在重复的元素,代表数学意义上的“集合”。它所支持的核心操作有add(Ee),remove(Objecto),contains(Objecto),分别用于添加元素,删除元素以及判断给定元素是否存在于集中。
List:Java中集合框架中的列表类型都实现了这个接口,表示一种有序序列。支持get(intindex),add(Ee)等操作。
Queue:Java集合框架中的队列接口,代表了“先进先出”队列。支持add(Eelement),remove()等操作。
Stack:Java集合框架中表示堆栈的数据类型,堆栈是一种“后进先出”的数据结构。支持push(Eitem),pop()等操作。
9.HashMap和HashTable的区别
HashTable是线程安全的,而HashMap不是
HashMap中允许存在null键和null值,而HashTable中不允许
12.TreeMap,LinkedHashMap,HashMap的区别是什么?
HashMap的底层实现是散列表,因此它内部存储的元素是无序的;
TreeMap的底层实现是红黑树,所以它内部的元素的有序的。排序的依据是自然序或者是创建TreeMap时所提供的比较器(Comparator)对象。
LinkedHashMap可以看作能够记住插入元素的顺序的HashMap。
13.Collection与Collections的区别是什么?
14.对于“try-catch-finally”,若try语句块中包含“return”语句,finally语句块会执行吗?会执行。只有两种情况finally块中的语句不会被执行:**
调用了System.exit()方法;
JVM“崩溃”了。
15.Java中的异常层次结构Java中的异常层次结构如下图所示:
17.Override,Overload的含义与区别
Override表示“重写”,是子类对父类中同一方法的重新定义
Overload表示“重载”,也就是定义一个与已定义方法名称相同但签名不同的新方法**
18.接口与抽象类的区别
抽象类本质上是一个类,使用抽象类的代价要比接口大。
接口与抽象类的对比如下:
抽象类中的方法和成员变量可以定义可见性(比如public、private等);而接口中的方法只能为public(缺省为public)。
一个子类只能有一个父类(具体类或抽象类);而一个接口可以继承一个多个接口,一个类也可以实现多个接口。
子类中实现父类中的抽象方法时,可见性可以大于等于父类中的;而接口实现类中的接口方法的可见性只能与接口中相同(public)。
19.静态内部类与非静态内部类的区别静态内部类不会持有外围类的引用,而非静态内部类会隐式持有外围类的一个引用。
21.简述Java中创建新线程的两种方法
继承Thread类(假设子类为MyThread),并重写run()方法,然后new一个MyThread对象并对其调用start()即可启动新线程。
实现Runnable接口(假设实现类为MyRunnable),而后将MyRunnable对象作为参数传入Thread构造器,在得到的Thread对象上调用start()方法即可。
22.简述Java中进行线程同步的方法
volatile:JavaMemoryModel保证了对同一个volatile变量的写happensbefore对它的读;
synchronized:可以来对一个代码块或是对一个方法上锁,被“锁住”的地方称为临界区,进入临界区的线程会获取对象的monitor,这样其他尝试进入临界区的线程会因无法获取monitor而被阻塞。由于等待另一个线程释放monitor而被阻塞的线程无法被中断。
ReentrantLock:尝试获取锁的线程可以被中断并可以设置超时参数。
23.简述Java中具有哪几种粒度的锁Java中可以对类、对象、方法或是代码块上锁。
24.给出“生产者-消费者”问题的一种解决方案使用阻塞队列:
26.concurrent包的整体架构
27.ArrayBlockingQueue,CountDownLatch类的作用
CountDownLatch:允许线程集等待直到计数器为0。适用场景:当一个或多个线程需要等待指定数目的事件发生后再继续执行。
ArrayBlockingQueue:一个基于数组实现的阻塞队列,它在构造时需要指定容量。当试图向满队列中添加元素或者从空队列中移除元素时,当前线程会被阻塞。通过阻塞队列,我们可以按以下模式来工作:工作者线程可以周期性的将中间结果放入阻塞队列中,其它线程可以取出中间结果并进行进一步操作。若工作者线程的执行比较慢(还没来得及向队列中插入元素),其他从队列中取元素的线程会等待它(试图从空队列中取元素从而阻塞);若工作者线程执行较快(试图向满队列中插入元素),则它会等待其它线程取出元素再继续执行。
28.wait(),sleep()的区别
wait():Object类中定义的实例方法。在指定对象上调用wait方法会让当前线程进入等待状态(前提是当前线程持有该对象的monitor),此时当前线程会释放相应对象的monitor,这样一来其它线程便有机会获取这个对象的monitor了。当其它线程获取了这个对象的monitor并进行了所需操作时,便可以调用notify方法唤醒之前进入等待状态的线程。
sleep():Thread类中的静态方法,作用是让当前线程进入休眠状态,以便让其他线程有机会执行。进入休眠状态的线程不会释放它所持有的锁。
29.线程池的用法与优势
用法:我们可以调用ThreadPoolExecutor的某个构造方法来自己创建一个线程池。但通常情况下我们可以使用Executors类提供给我们的静态工厂方法来更方便的创建一个线程池对象。创建了线程池对象后,我们就可以调用submit方法提交任务到线程池中去执行了;线程池使用完毕后我们要记得调用shutdown方法来关闭它。
30.for-each与常规for循环的效率对比关于这个问题我们直接看《EffectiveJava》给我们做的解答:
for-each能够让代码更加清晰,并且减少了出错的机会。下面的惯用代码适用于集合与数组类型:
for(Elemente:elements){doSomething(e);}使用for-each循环与常规的for循环相比,并不存在性能损失,即使对数组进行迭代也是如此。实际上,在有些场合下它还能带来微小的性能提升,因为它只计算一次数组索引的上限。
31.简述JavaIO与NIO的区别
JavaIO是面向流的,这意味着我们需要每次从流中读取一个或多个字节,直到读取完所有字节;NIO是面向缓冲的,也就是说会把数据读取到一个缓冲区中,然后对缓冲区中的数据进行相应处理。
JavaIO是阻塞IO,而NIO是非阻塞IO。
JavaNIO中存在一个称为选择器(selector)的东西,它允许你把多个通道(channel)注册到一个选择器上,然后使用一个线程来监视这些通道:若这些通道里有某个准备好可以开始进行读或写操作了,则开始对相应的通道进行读写。而在等待某通道变为可读/写期间,请求对通道进行读写操作的线程可以去干别的事情。
32.反射的作用与原理反射的作用概括地说是运行时获取类的各种定义信息,比如定义了哪些属性与方法。原理是通过类的class对象来获取它的各种信息。
35.常见设计模式所谓“设计模式”,不过是面向对象编程中一些常用的软件设计手法,并且经过实践的检验,这些设计手法在各自的场景下能解决一些需求,因此它们就成为了如今广为流传的”设计模式“。也就是说,正式因为在某些场景下产生了一些棘手的问题,才催生了相应的设计模式。明确了这一点,我们在学习某种设计模式时要充分理解它产生的背景以及它所解决的主要矛盾是什么。
常用的设计模式可以分为以下三大类:
创建型模式:包括工厂模式(又可进一步分为简单工厂模式、工厂方法模式、抽象工厂模式)、建造者模式、单例模式。
结构型模式:包括适配器模式、桥接模式、装饰模式、外观模式、享元模式、代理模式。
行为型模式:包括命令模式、中介者模式、观察者模式、状态模式、策略模式。
38.注解的基本概念与使用注解可以看作是“增强版的注释”,它可以向编译器、虚拟机说明一些事情。注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。注解本身是“被动”的信息,只有主动解析它才有意义。除了向编译器/虚拟机传递信息,我们也可以使用注解来生成一些“模板化”的代码。
面向过程优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。缺点:没有面向对象易维护、易复用、易扩展面向对象优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护缺点:性能比面向过程低
抽象:就是把现实生活中的某一类东西提取出来,用程序代码表示,我们通常叫做类或者接口。抽象包括两个方面:一个是数据抽象,一个是过程抽象。数据抽象也就是对象的属性。过程抽象是对象的行为特征。封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行封装隐藏。封装分为属性的封装和方法的封装。继承:是对有着共同特性的多类事物,进行再抽象成一个类。这个类就是多类事物的父类。父类的意义在于抽取多类事物的共性。多态:允许不同类的对象对同一消息做出响应。方法的重载、类的覆盖正体现了多态。
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为private则子类中就不是重写。
构造器不能被重写,不能用static修饰构造器,只能用publicprivateprotected这三个权限修饰符,且不能有返回语句。
private只有在本类中才能访问;public在任何地方都能访问;protected在同包内的类及包外的子类能访问;默认不写在同包内能访问。6是否可以继承String类#String类是final类故不可以继承,一切由final修饰过的都不能继承。
可变性String类中使用字符数组保存字符串,privatefinalcharvalue[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。线程安全性String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。性能每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险。
equals相等,hashcode必相等;hashcode相等,equals可能不相等。
语法层次抽象类和接口分别给出了不同的语法定义。设计层次抽象层次不同,抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。跨域不同抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a"关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的,仅仅是实现了接口定义的契约而已,"like-a"的关系。
装箱:将基本类型用它们对应的引用类型包装起来;拆箱:将包装类型转换为基本数据类型;Java使用自动装箱和拆箱机制,节省了常用数值的内存开销和创建对象的开销,提高了效率,由编译器来完成,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。
泛型,即“参数化类型”。创建集合时就指定集合元素的类型,该集合只能保存其指定类型的元素,避免使用强制类型转换。Java编译器生成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。泛型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。类型擦除的主要过程如下:1).将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。2).移除所有的类型参数。
List和Set继承自Collection接口。Set无序不允许元素重复。HashSet和TreeSet是两个主要的实现类。List有序且允许元素重复。ArrayList、LinkedList和Vector是三个主要的实现类。Map也属于集合系统,但和Collection接口没关系。Map是key对value的映射集合,其中key列就是一个集合。key不能重复,但是value可以重复。HashMap、TreeMap和Hashtable是三个主要的实现类。SortedSet和SortedMap接口对元素按指定规则排序,SortedMap是对key列进行排序。
1).HashTable的方法前面都有synchronized来同步,是线程安全的;HashMap未经同步,是非线程安全的。2).HashTable不允许null值(key和value都不可以);HashMap允许null值(key和value都可以)。3).HashTable有一个contains(Objectvalue)功能和containsValue(Objectvalue)功能一样。4).HashTable使用Enumeration进行遍历;HashMap使用Iterator进行遍历。5).HashTable中hash数组默认大小是11,增加的方式是old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数。6).哈希值的使用不同,HashTable直接使用对象的hashCode;HashMap重新计算hash值,而且用与代替求模。
ArrayList和Vector都实现了List接口,都是通过数组实现的。Vector是线程安全的,而ArrayList是非线程安全的。List第一次创建的时候,会有一个初始大小,随着不断向List中增加元素,当List认为容量不够的时候就会进行扩容。Vector缺省情况下自动增长原来一倍的数组长度,ArrayList增长原来的50%。
区别ArrayList底层是用数组实现的,可以认为ArrayList是一个可改变大小的数组。随着越来越多的元素被添加到ArrayList中,其规模是动态增加的。LinkedList底层是通过双向链表实现的,LinkedList和ArrayList相比,增删的速度较快。但是查询和修改值的速度较慢。同时,LinkedList还实现了Queue接口,所以他还提供了offer(),peek(),poll()等方法。使用场景LinkedList更适合从中间插入或者删除(链表的特性)。ArrayList更适合检索和在末尾插入或删除(数组的特性)。
java.util.Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。java.util.Collections是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
JDK动态代理:代理类和目标类实现了共同的接口,用到InvocationHandler接口。CGLIB动态代理:代理类是目标类的子类,用到MethodInterceptor接口。
继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。
线程安全就是多线程访问同一代码,不会产生不确定的结果。
对非安全的代码进行加锁控制;使用线程安全的类;多线程并发情况下,线程共享的变量改为方法级的局部变量。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:1).修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;2).修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;3).修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;4).修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
主要相同点:Lock能完成synchronized所实现的所有功能主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。Lock的锁定是通过代码实现的,而synchronized是在JVM层面上实现的,synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。Lock锁的范围有局限性,块范围,而synchronized可以锁住块、对象、类。
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。产生死锁的原因:一.因为系统资源不足。二.进程运行推进的顺序不合适。三.资源分配不当。
守护线程是为其他线程的运行提供服务的线程。setDaemon(booleanon)方法可以方便的设置线程的Daemon模式,true为守护模式,false为用户模式。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
一.IO是面向流的,NIO是面向缓冲区的。二.IO的各种流是阻塞的,NIO是非阻塞模式。三.JavaNIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。对象的序列化主要有两种用途:一.把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;二.在网络上传送对象的字节序列。当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
Protobuf,Thrift,Hessian,Kryo
内存溢出是指程序在申请内存时,没有足够的内存空间供其使用,出现outofmemory。内存泄漏是指分配出去的内存不再使用,但是无法回收。
一.可通过命令定期抓取heapdump或者启动参数OOM时自动抓取heapdump文件。二.通过对比多个heapdump,以及heapdump的内容,分析代码找出内存占用最多的地方。三.分析占用的内存对象,是否是因为错误导致的内存未及时释放,或者数据过多导致的内存溢出。
一.MemoryAnalyzer-是一款开源的JAVA内存分析软件,查找内存泄漏,能容易找到大块内存并验证谁在一直占用它,它是基于EclipseRCP(RichClientPlatform),可以下载RCP的独立版本或者Eclipse的插件。二.JProbe-分析Java的内存泄漏。三.JProfiler-一个全功能的Java剖析工具,专用于分析J2SE和J2EE应用程序。它把CPU、执行绪和内存的剖析组合在一个强大的应用中,GUI可以找到效能瓶颈、抓出内存泄漏、并解决执行绪的问题。四.JRockit-用来诊断Java内存泄漏并指出根本原因,专门针对Intel平台并得到优化,能在Intel硬件上获得最高的性能。五.YourKit-.NET&JavaProfiling业界领先的Java和.NET程序性能分析工具。六.AutomatedQA-AutomatedQA的获奖产品performanceprofiling和memorydebugging工具集的下一代替换产品,支持Microsoft,Borland,Intel,Compaq和GNU编译器。可以为.NET和Windows程序生成全面细致的报告,从而帮助您轻松隔离并排除代码中含有的性能问题和内存/资源泄露问题。支持.Net1.0,1.1,2.0,3.0和Windows32/64位应用程序。七.CompuwareDevPartnerJavaEdition-包含Java内存检测,代码覆盖率测试,代码性能测试,线程死锁,分布式应用等几大功能模块
(1)JSP经编译后就变成了“类servlet”。(2)JSP由HTML代码和JSP标签构成,更擅长页面显示;Servlet更擅长流程控制。(3)JSP中嵌入JAVA代码,而Servlet中嵌入HTML代码。
(1)动态include用jsp:include动作实现,如
Apache:HTTP服务器(WEB服务器),类似IIS,可以用于建立虚拟站点,编译处理静态页面,可以支持SSL技术,支持多个虚拟主机等功能。Tomcat:Servlet容器,用于解析jsp,Servlet的Servlet容器,是高效,轻量级的容器。缺点是不支持EJB,只能用于java应用。Jboss:应用服务器,运行EJB的J2EE应用服务器,遵循J2EE规范,能够提供更多平台的支持和更多集成功能,如数据库连接,JCA等,其对Servlet的支持是通过集成其他Servlet容器来实现的,如tomcat和jetty。
(1)性能对比:由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。(2)内存使用效率对比:使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。(3)Redis支持服务器端的数据操作:Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。
XML:(1)应用广泛,可扩展性强,被广泛应用各种场合;(2)读取、解析没有JSON快;(3)可读性强,可描述复杂结构。JSON:(1)结构简单,都是键值对;(2)读取、解析速度快,很多语言支持;(3)传输数据量小,传输速率大大提高;(4)描述复杂结构能力较弱。
推荐阅读数据复习!
该列表包含了入门级Java程序员和多年经验的高级开发者的问题。无论你是1、2、3、4、5、6、7、8、9还是10年经验的开发者,你都能在其中找到一些有趣的问题。这里包含了一些超级容易回答的问题,同时包含经验丰富的Java程序员也会棘手的问题。
当然你们也是非常幸运的,当今有许多好的书来帮助你准备Java面试,其中有一本我觉得特别有用和有趣的是Markham的Java程序面试揭秘(JavaProgrammingInterviewExposed)。这本书会告诉你一些Java和JEE面试中最重要的主题,即使你不是准备Java面试,也值得一读。
多线程,并发及线程基础数据类型转换的基本原则垃圾回收(GC)Java集合框架数组字符串GOF设计模式SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)设计原则抽象类与接口Java基础,如equals和hashcode泛型与枚举JavaIO与NIO常用网络协议Java中的数据结构和算法正则表达式JVM底层Java最佳实践JDBCDate,Time与CalendarJava处理XMLJUnit编程
现在是时候给你展示我近5年从各种面试中收集来的120个问题了。我确定你在自己的面试中见过很多这些问题,很多问题你也能正确回答。
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修复符的另一个作用是提供内存屏障(memorybarrier),例如在分布式框架中的应用。简单的说,就是当你写一个volatile变量之前,Java内存模型会插入一个写屏障(writebarrier),读一个volatile变量之前,会插入一个读屏障(readbarrier)。意思就是说,在你写一个volatile域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。
5)10个线程和2个线程的同步代码,哪个更容易写?从写代码的角度来说,两者的复杂度是相同的,因为同步代码与线程数量是相互独立的。但是同步策略的选择依赖于线程的数量,因为越多的线程意味着更大的竞争,所以你需要利用同步技术,如锁分离,这要求更复杂的代码和专业知识。
伪共享问题很难被发现,因为线程可能访问完全不同的全局变量,内存中却碰巧在很相近的位置上。如其他诸多的并发问题,避免伪共享的最基本方式是仔细审查代码,根据缓存行来调整你的数据结构。
9)Java中怎么获取一份线程dump文件?在Linux下,你可以通过命令kill-3PID(Java进程的进程ID)来获取Java应用的dump文件。在Windows下,你可以按下Ctrl+Break来获取。这样JVM就会将线程的dump文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。如果你使用Tomcat。
16)我们能创建一个包含可变对象的不可变对象吗?是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。
19)Java中怎样将bytes转换为long类型?这个问题你来回答:-)
20)我们能将int强制转换为byte类型的变量吗?如果该值大于byte类型的范围,将会出现什么现象?是的,我们可以做强制转换,但是Java中int是32位的,而byte是8位的,所以,如果强制转化是,int类型的高24位将会被丢弃,byte类型的范围是从-128到128。
23)Java中++操作符是线程安全的吗?(答案)23)不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。
24)a=a+b与a+=b的区别(答案)+=隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如byte、short或者int,首先会将它们提升到int类型,然后在执行加法操作。如果加法操作的结果比a的最大值要大,则a+b会出现编译错误,但是a+=b没问题,如下:bytea=127;byteb=127;b=a+b;//error:cannotconvertfrominttobyteb+=a;//ok(译者注:这个地方应该表述的有误,其实无论a+b的值为多少,编译器都会报错,因为a+b操作会将a、b提升为int类型,所以将int类型赋值给byte就会编译出错)
26)3*0.1==0.3将会返回什么?true还是false?(答案)false,因为有些浮点数不能完全精确的表示出来。
27)int和Integer哪个会占用更多的内存?(答案)Integer对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是int是一个原始类型的数据,所以占用的空间更少。
31)64位JVM中,int的长度是多数?Java中,int类型变量的长度是一个固定值,与平台无关,都是32位。意思就是说,在32位和64位的Java虚拟机中,int类型的长度是相同的。
33)32位和64位的JVM,int类型变量的长度是多数?(答案)32位和64位的JVM中,int类型变量的长度是相同的,都是32位或者4个字节。
35)WeakHashMap是怎么工作的?(答案)WeakHashMap的工作与正常的HashMap类似,但是使用弱引用作为key,意思就是当key对象没有任何引用时,key/value将会被回收。
36)JVM选项-XX:+UseCompressedOops有什么作用?为什么要使用?(答案)当你将你的应用从32位的JVM迁移到64位的JVM时,由于对象的指针从32位增加到了64位,因此堆内存会突然增加,差不多要翻倍。这也会对CPU缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到64位的JVM主要动机在于可以指定最大堆大小,通过压缩OOP可以节省一定的内存。通过-XX:+UseCompressedOops选项,JVM会使用32位的OOP,而不是64位的OOP。
3年工作经验的Java面试题
41)你能保证GC执行吗?(答案)不能,虽然你可以调用System.gc()或者Runtime.gc(),但是没有办法保证GC的执行。
47)Java中的编译期常量是什么?使用它又什么风险?公共静态不可变(publicstaticfinal)变量也就是我们所说的编译期常量,这里的public可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖JAR文件时,确保重新编译你的程序。
这部分也包含数据结构、算法及数组的面试问题
49)poll()方法和remove()方法的区别?poll()和remove()都是从队列中取出一个元素,但是poll()在获取元素失败的时候会返回空,但是remove()失败的时候会抛出异常。
55)Java中的TreeMap是采用什么树实现的?(答案)Java中的TreeMap是使用红黑树实现的。
59)我们能自己写一个容器类,然后使用for-each循环码?可以,你可以写一个自己的容器类。如果你想使用Java中增强的循环来遍历,你只需要实现Iterable接口。如果你实现Collection接口,默认就具有该属性。
在Java7中,ArrayList的默认大小是10个元素,HashMap的默认大小是16个元素(必须是2的幂)。这就是Java7中ArrayList和HashMap类的代码片段:
//fromArrayList.javaJDK1.7privatestaticfinalintDEFAULT_CAPACITY=10;//fromHashMap.javaJDK7staticfinalintDEFAULT_INITIAL_CAPACITY=1<<4;//aka1661)有没有可能两个不相等的对象有有相同的hashcode?有可能,两个不相等的对象可能会有相同的hashcode值,这就是为什么在hashmap中会有冲突。相等hashcode值的规定只是说如果两个对象相等,必须有相同的hashcode值,但是没有关于不相等对象的任何规定。
62)两个相同的对象会有不同的的hashcode吗?不能,根据hashcode的规定,这是不可能的。
66)在我Java程序中,我有三个socket,我需要多少个线程来处理?
67)Java中怎么创建ByteBuffer?
68)Java中,怎么读写ByteBuffer?
69)Java采用的是大端还是小端?
70)ByteBuffer中的字节序是什么?
73)socket选项TCPNODELAY是指什么?
75)Java中,ByteBuffer与StringBuffer有什么区别?(答案)
包含Java中各个部分的最佳实践,如集合,字符串,IO,多线程,错误和异常处理,设计模式等等。
77)说出几点Java中使用Collections的最佳实践(答案)这是我在使用Java中Collectionc类的一些最佳实践:a)使用正确的集合类,例如,如果不需要同步列表,使用ArrayList而不是Vector。b)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。c)使用接口代表和访问集合,如使用List存储ArrayList,使用Map存储HashMap等等。d)使用迭代器来循环集合。e)使用集合的时候使用泛型。
79)说出5条IO的最佳实践(答案)IO对Java应用的性能非常重要。理想情况下,你不应该在你应用的关键路径上避免IO操作。下面是一些你应该遵循的JavaIO最佳实践:a)使用有缓冲区的IO类,而不要单独读取字节或字符。b)使用NIO和NIO2c)在finally块中关闭流,或者使用try-with-resource语句。d)使用内存映射文件获取更快的IO。
89)如何测试静态方法?(答案)可以使用PowerMock库来测试静态方法。
91)你使用过哪个单元测试库来测试你的Java程序?(答案)
94)Java中如何利用泛型写一个LRU缓存?(答案<)
95)写一段Java程序将byte转换为long?(答案)
这部分包含Java面试过程中关于SOLID的设计原则,OOP基础,如类,对象,接口,继承,多态,封装,抽象以及更高级的一些概念,如组合、聚合及关联。也包含了GOF设计模式的问题。
105)除了单例模式,你在生产环境中还用过什么设计模式?这需要根据你的经验来回答。一般情况下,你可以说依赖注入,工厂模式,装饰模式或者观察者模式,随意选择你使用过的一种即可。不过你要准备回答接下的基于你选择的模式的问题。
108)适配器模式是什么?什么时候使用?适配器模式提供对接口的转换。如果你的客户端使用某些接口,但是你有另外一些接口,你就可以写一个适配去来连接这些接口。
115)什么是模板方法模式?(答案)模板方法提供算法的框架,你可以自己去配置或定义步骤。例如,你可以将排序算法看做是一个模板。它定义了排序的步骤,但是具体的比较,可以使用Comparable或者其语言中类似东西,具体策略由你去配置。列出算法概要的方法就是众所周知的模板方法。
116)什么时候使用访问者模式?(答案)访问者模式用于解决在类的继承层次上增加操作,但是不直接与之关联。这种模式采用双派发的形式来增加中间层。
117)什么时候使用组合模式?(答案)组合模式使用树结构来展示部分与整体继承关系。它允许客户端采用统一的形式来对待单个对象和对象容器。当你想要展示对象这种部分与整体的继承关系时采用组合模式。
123)抽象工厂模式和原型模式之间的区别?(答案)
124)什么时候使用享元模式?(答案)享元模式通过共享对象来避免创建太多的对象。为了使用享元模式,你需要确保你的对象是不可变的,这样你才能安全的共享。JDK中String池、Integer池以及Long池都是很好的使用了享元模式的例子。
throw用于抛出java.lang.Throwable类的一个实例化对象,意思是说你可以通过关键字throw抛出一个Error或者一个Exception,如:thrownewIllegalArgumentException(“sizemustbemultipleof2″)