面试宝典
从各处整理一些面试题以及答案,方便随时巩固。
Java基础
一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以包含多个类,但是只能有一个public修饰的类,public修饰的类只能与文件名相同
Java有没有goto?
有,只是保留字,目前没有被使用
说说&和&&的区别。
相同点:都可作为逻辑与运算,当两边都为true时才是true
不同点:&&可以作为短路与,当第一个表达式为false时,不再计算第二个表达式
&还可以作为按位与进行双目运算,即将两边的数字转换为二进制,两个二进制位都为1时,对应结果的二进制位才为1,否则为0
在JAVA中如何跳出当前的多重嵌套循环?
1.可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出多重循环,例如:
public class xunhuan { public static void main(String[] args) { ok: for(int i=0;i<10;i++) { for(int j=0;j<10;j++) { System.out.println("i=" + i +",j=" + j); if(j == 5) break ok; } } } }
2.让外层的循环条件表达式的结果可以受到里层循环体代码的控制,例如:
public class xunhuan { public static void main(String[] args) { int arr[][] = {{1,2,3},{4,5,6,7},{9}}; boolean found = false; for(int i=0;i<arr.length && !found;i++) { for(int j=0;j<arr[i].length;j++){ System.out.println("i=" + i + "j=" + j); if(arr[i][j] == 5) { found = true; break; } } } } }
switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
switch可以作用在char,byte,short,int以及他们的包装类上
switch不可作用于long,double,float,boolean以及他们的包装类
switch可以作用在String上(JDK1.7之后)
switch还可以是枚举类型(JDK1.5之后)
short s1= 1; s1 = 1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?(没有错)
short s1= 1; s1 = s1 + 1;
由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
short s1 = 1; s1 += 1;没有错
由于 +=是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
char型变量中能不能存贮一个中文汉字?为什么?
可以,因为char类型占两个字节,Java默认采用Unicode编码,一个Unicode编码16位,占两个字节,汉字可以用Unicode编码表示
用最有效率的方法算出2乘以8等于几?
利用位运算 2 << 3
浅析final关键字
可以用来修饰类,修饰的类无法被继承。例如Math、String、Integer类等。
可以用来修饰方法,修饰的方法无法被重写。用final修饰的方法比非final方法要快,因为final方法在编译时已经被静态绑定了,不需要再运行时再动态绑定。
可以用来修饰变量,表示常量,包括成员变量和局部变量,该变量只能被赋值一次且值不可以被改变,对于成员变量,必须在声明时或者构造方法中对其进行赋值
final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化之后就不能发生变化。
final修饰一个引用类型时,则在对其初始化之后就不能再指向其他对象了,但是该引用指向的对象的内容是可以发生变化的。
当函数的参数类型声明为final时,说明该参数是只读的,无法改变该参数的值
浅析static关键字
static可以用来修饰 变量、方法、内部类、代码块
static修饰成员方法的最大作用是可以使用 类名.方法 的方法操作,避免了先new对象的繁琐和资源消耗。
静态代码块是在类加载期间运行的代码块,由于类只加载一次,所以静态代码块只执行一次,静态代码块的用途很常见,一般用来在类加载以后初始化一些静态资源时候使用。如,加载配置文件
静态块用法:将多个类成员放在一起初始化,使得程序更加规整,对理解对象的初始化过程非常关键。
static修饰的资源属于类级别,是全体对象实例共享的资源
使用static修饰的属性,是在类的加载期间初始化的,使用类名.属性访问
静态变量和实例变量的区别?
静态变量需要用static修饰,实例变量不需要
静态变量是属于类的,调用时直接用类名点,实例变量是属于对象的,要用对象点
静态变量存在于方法区中,实例变量存在堆中
是否可以从一个static方法内部发出对非static方法的调用?
不可以。因为在调用static方法时不需要new对象,直接用类名点,但是非static方法在被调用时必须先创建对象。
Integer与int的区别
int是基本数据类型,Integer是int的包装类
int默认值为0,Integer默认值为null
int直接存储数据值,Integer实际是对象的引用
重载(Overload)和重写(Override)的区别?
方法的重载与重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。
重载发生在一个类中,重写发生在父子类中
重载方法名相同,参数列表不同,重载对返回值类型没有特殊要求,不能根据返回值类型进行区分
重写方法名相同,参数列表相同,返回值相同或子类方法的返回值是父类方法的返回值类型的子类,子类方法的访问修饰符范围大于等于父类,子类方法抛出的异常小于等于父类
接口和抽象类的区别
相同点:都不能被实例化
接口的实现类和抽象类的子类只有全部实现了接口或者抽象类中的方法才能被实例化
不同点:
String常见的API
length(); //计算字符串的长度
charAt(); //截取一个字符
subString(); //截取字符串
getChars(); //截取多个字符
equals(); //比较两个字符串
startsWith(); //判断字符串是否以特定的字符串开始
endWith(); //判断是否以特定的字符串结尾
equalsIgnoreCase(); //忽略大小写判断字符串是否相等
indexOf(); //查找字符或子串第一次出现的位置
lastIndexOf(); //查找字符或子串最后一次出现的位置
concat(); //连接两个字符串
replace(); //替换
trim(); //去掉字符串开头和结尾的空格
valueOf(); //转换为字符串
toLowerCase(); //转换为小写
toUpperCase(); //转换为大写
String为什么是不可变的?
什么是String的不可变性?
String内部是使用一个被final修饰的char数组value存储字符串的值
数组value的值在对象构造的时候就已经进行了赋值
String不提供方法对数组value中的值进行修改
String中对value进行修改的方法(如replace等)则是直接返回一个新的String对象。
所以String是不可变的
为什么将String设计成不可变的?
多线程下安全性
防止被意外修改,如果HashSet中存的值是可变的String,就破坏了唯一性,不可被写所以线程安全
使用常量池可以节省空间
如str1和str2都用something赋值,它们其实都指向同一个内存地址
这样大量使用字符串的情况下,可以节省空间,提升效率
类加载中体现安全性
类加载器要用到字符串,不可变提供了安全性,以便正常的类被加载,例如,想加载java.sql.Connection类,而这个值被改成了xxx.Connection,那么会对你的数据库造成不可知的破坏。
什么是字符串常量池?
Java设计者为String提供了字符串常量池以提高其性能。
字符串常量池的设计意图:
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
1.为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。
2.如果字符串已经存在池中,就返回池中的实例引用
3.如果字符串不存在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享
实现的基础:
1.实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享
2.运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着他们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。
如何操作字符串常量池?
String.intern()
通过new操作符创建的字符串对象不指向字符串池中的任何对象,但是可以通过使用字符串的intern()方法来指向其中的某一个。java.lang.String.intern()返回一个保留池字符串,就是一个在全局字符串池中有了一个入口。如果以前没有在全局字符串池中,那么它就会被添加到里面。
例如:
String s1 = "Hello"; String s2 = new String("Hello"); String s3 = s2.intern(); System.out.println("s1 == s3? " + (s1 == s3)); // true
为什么String用 + 拼接字符串效率低?
大多数情况,在使用循环大批量的修改数据情况下会造成用 + 拼接字符串效率低下,因为每做一次+就产生一个StringBuilder对象,然后append后就扔掉,如此循环直至结束。如果我直接采用StringBuilder对象进行append的话,可以节省创建和销毁对象的时间。如果只是简单的字面量拼接或者很少的字符串拼接,性能是差不多的。
从源码角度分析,StringBuilder和StringBuffer的区别
StringBuilder和StringBuffer都继承自AbstractStringBuilder
它们的扩容机制都在AbstractStringBuilder中实现,当发现长度不够时(默认长度是16),会自动进行扩容工作,扩展为原数组的2倍+2,创建一个新的数组,并将原数组的数据复制到新数组,所以对于拼接字符串效率比String要高。
StringBuffer线程安全,效率低,因为StringBuffer中有很多方法都被synchronized修饰,多线程访问时,有加锁和释放锁的过程,所以效率低,线程安全。
StringBuilder线程不安全,效率高。
Java中IO流分为几种
按功能来分:输入流(input)、输出流(output)
按类型来分:字节流和字符流
字节流和字符流的区别是:字节流是按8位传输以字节为单位输入输出数据;字符流是按16为传输以字符为单位输入输出数据
BIO、NIO、AIO有什么区别?
BIO:Block IO 同步阻塞式IO,就是我们平常使用传统的IO。它的特点是模式简单使用方便、并发处理能力低。
NIO:New IO 同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通信)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
Files的常用方法:
Files.exists():检测文件路径是否存在
Files.createFile():创建文件
Files.createDirectory():创建文件夹
Files.delete():删除一个文件或目录
Files.copy():复制文件
Files.move():移动文件
Files.size():查看文件个数
Files.read():读取文件
Files.write():写入文件
容器
Collection和Collections的区别。
java.util.Collection是集合的顶级接口。它提供了对集合对象进行基本操作的通用接口方法,Collection接口的意义是对各种具体的集合提供了最大化的统一操作方式。List和Set就继承了此接口。
java.util.Collections是针对集合类的一个帮助类,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
List、Set、Map之间的区别
HashMap 和 HashTable 的区别
HashMap去掉了Hashtable的contains()方法,但是加上了containsValue()和containsKey()方法
Hashtable是同步的,HashMap是非同步的,效率上比Hashtable高
HashMap允许null键值,而hashtable不允许
如何决定使用HashMap 还是 TreeMap
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
HashMap的实现原理
概述:HashMap是基于哈希表的Map接口的非同步实现,此实现提供所有可选的映射操作,并允许使用null键和null值。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
数据结构:在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有数据结构都可以用这两个基本结构来构造,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
当我们往HashMap中put元素时,首先根据key的hashcode重新计算hash值,根据hash值得到这个元素在数组中的位置,如果在这个位置上已经存放了其他元素,那么这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾,如果数组中该位置没有元素,就直接将元素放在该位置上。
JDK1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到现在的O(logn)
HashSet的实现原理
HashSet底层由HashMap实现
HashSet的值存放于HashMap的key上
HashMap的value统一为PRESENT
ArrayList 和 LinkedList 的区别
最明显的区别是ArrayList底层是的数据结构是数组,支持随机访问,而LinkedList的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList的时间复杂度是O(1),而LinkedList是O(n)。对于新增和删除,LinkedList比较占优势。
如何实现数组和List之间的转换
List转换成数组:调用ArrayList的toArray方法
数组转换成List:调用Arrays的asList方法。
ArrayList 和 Vector 的区别是什么?
Vector是同步的,线程安全的,而ArrayList不是。
ArrayList比Vector快
ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
Array 和 ArrayList 的区别
Array类型的变量在声明的同时必须进行实例化(至少得初始du化数组的大小),而ArrayList可以只是先声明。
Array只能存储类型相同的对象,而ArrayList可以存储异构的对象。
Array是始终是连续存放的,而ArrayList的存放不一定连续。
Array对象的初始化必须只定指定大小,且创建后的数组大小是固定的,而ArrayList的大小可以动态指定,其大小可以在初始化时指定,也可以不指定,也就是说该对象的空间可以任意增加。
在Queue中 poll() 和 remove() 有什么区别
poll和remove都是从队列中取出一个元素,但是poll()在获取元素失败的时候会返回null,但是remove()失败时会抛出异常
哪些集合类是线程安全的?
vector:就比arrayList多了个同步优化机制(线程安全),因为效率低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率是优先考虑的。
statck:堆栈类,先进后出。
hashtable:就比HashMap多了个线程安全。
enumeration:枚举,相当于迭代器。
迭代器 Iterator 是什么?
是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被为“轻量级”对象,因为创建它的代价小。
Iterator 怎么使用?有什么特点
Java中的Iterator 比较简单,并且只能单向移动
1.使用方法iterator()要求容器返回一个iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法时java.lang.Iterator接口,被Collection继承。
2.使用next()获得序列中的下一个元素
3.使用hashNext()检查序列中是否还有元素
4.使用remove()将迭代器返回的元素删除
Iterator是java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
Iterator 和 ListIterator 有什么区别?
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
多线程
并行和并发有什么区别?
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。