Java面试
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试相关的知识,希望对你有一定的参考价值。
1 同步方法 VS 同步代码块:
java中,每一个对象都有一把锁,线程用synchronized获取对象上的锁。
非静态同步方法:锁是类的对象的锁。
静态同步方法:锁的是类本身。
同步方法块:锁是可以选择的。所以能更加精确的控制。粒度更细致,可以更精确的控制对象锁。
2 确保N个线程可以访问N个资源而不死锁?
指定资源获取顺序。所有线程都按照同样的顺序请求资源。
3 Java集合框架接口
Collection: 代表一组对象。
Set:
List:
Map:
4 为什么集合类没有实现Cloneable和Serializable接口?
应该集合中的元素自己实现,定义自己的行为。
5 什么是迭代器(Iterator)?
它可以使得对于序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部.
主要方法: hasNext( ) / next( ) / remove(删除后,指向下一个)
迭代器优点:提供统一的迭代方法;可以在对客户透明的情况下,提供不同的迭代方法;快速失败机制,防止多线程下迭代的不安全操作。
6 快速失败&&安全失败:
(注意:以下的集合的修改是指,集合结构的变化,增加、删除节点,修改某一个节点的对象的内容不算是集合结构的变化。)
快速失败(fail-fast):
在用迭代器遍历一个集合时,遍历过程中,其他线程对集合内容进行修改(增,删),则
会抛出异常ConCurrentModificationException。迭代器在遍历的时候直接访问集合的元素,并且在遍历过程中使用一modCount变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器用hasNext()/next()遍历一个元素的之前,都会检测modCount,如果改过,就抛出异常。Java.util包中的集合都是快速失败的,不能在多线程下发生并发修改。
安全失败(fail-safe):
如果在迭代器遍历concurrentHashMap的过程中,往map中增/删/改,是不会抛出异常。但是会导致不一致的问题。采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问,而是先复制原集合内容,在拷贝的集合上进行遍历。缺点是:不能访问修改后的内容,
java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。
安全失败迭代器在迭代中被修改,不会抛出任何异常,因为它是在集合的克隆对象迭代的,所以任何对原集合对象的结构性修改都会被迭代器忽略,但是这类迭代器有一些缺点,其一是它不能保证你迭代时获取的是最新数据,因为迭代器创建之后对集合的任何修改都不会在该迭代器中更新,还有一个缺点就是创建克隆对象在时间和内存上都会增加一些负担。
7 Iterator和ListIterator区别
Iterator可以用来遍历Set,Map和List,但是ListItrator只能用来遍历List。
Iterator对集合只能向前遍历(hasNext(), Next()).ListIterator是双向都可以遍历。
ListIterator实现了Iterator接口,并包含了其他功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引。
8 HashMap的工作原理
键值对形式存储元素。需要一个hash函数,使用hashCode() 和equals()方法来向集合添加,检索元素。
Put(key, value) : 计算key的hash值,然后把键值对存储在集合中合适对索引上。Entry存储在LinkedList中,所以如果存在entry,它使用equals()方法来检查传递的key是否已经存在,如果存在,它会覆盖value,如果不存在,它会创建一个新的entry然后保存。
Get(key): 首先是用hashcode找到桶,然后key,equals(),判断是否相等。
重要特性是:容量(默认初始值是32),负载因子(默认是0.75),阈值是负载因子乘以容量,如果大于阈值,就会扩容。扩容极限。建议如果在开始的时候知道元素的个数,设定初始值。
容量:桶的数量。
负载因子:有多少桶被占用了。
9 HashMap VS Hashtable
都实现了Map接口,很相似。有如下不同点:
hashMap的键和值是null,hasttable不允许键或值是null.
HashMap不是同步的(适合单线程,是快速失败的),hashtable(适合多线程)是同步的。
HashMap提供了键集合;而Hashtable提供了对键的枚举。
Hashtable是遗留的类。
10 数组(Array)和列表(ArrayList)有什么区别?各自的应用场合?
数组:可以存放基本类型+对象类型;列表:only对象类型(因为LIst里的元素必须继承自object)。
Array大小固定的; ArrayList是动态变化的。
ArrayList提供了很多方法: addAll(), removeall(), iterator()等等。
对于基本类型,集合使用自动装箱来减少代码量;但是,当处理固定大小的基本数据类型的时候,这种方式比较慢。
11 ArrayList VS LinkedList
都是实现了List接口。
ArrayList底层是数组。可以以o(1)时间复杂度对元素进行随机访问。
LinkedList: 是以元素列表的形式存储数据,链表。查找的时间复杂度是0(n),但是插入,添加,删除更快。
LinkedList更费内存,因为要存储指针。
12 Comparable 和 Comparator?
实现了Comparable,表明可以比较元素,可以用SORT排序。Comparator看做是算法的实现,将算法和数据分离。
用 Comparator 是策略模式(strategy design pattern),就是不改变对象自身,而用一个策略对象(strategy object)来改变它的行为。
Comparable接口:只包含了一个compareTo()方法,可以进行排序。
Comparator接口:包含compare()和 equals()两个方法。
二者用法不同:比如在集合中的元素可以自己实现Comparable,这样就可以用Collections.sort()调用进行排序;如果想定义格外的排序方法,可以实现Comparator接口,Collections.sort()另外一个重载方法可以接收一个compareator.
13 java集合类的最佳实践
根据需要正确的选择要使用的。比如:元素大小固定,事先知道,用 Array 而不是ArrayList
对于一些容器(hashMap):如果知道容量,可以提前给一个初始容量,避免扩容,再哈西的性能损耗。
为了类型安全,可读性和健壮性总是要使用泛型,泛型可以避免ClassCastException
用JDK提供的不变类(immutable class)作为Map的键可以避免为我们的类实现hashCode() 和
equals() 方法。
编程的时候接口优于实现。
底层的集合实际上是空的情况下,返回长度是0的集合或数组,不要返回null。
14 java优先级队列(Priority Queue)
每次出队的是优先级最高的。是用大顶堆实现的。要提供一个比较器comparator,否则按照自然顺序排列。
Queue<Test> priQueue = new PriorityQueue<Test>(11,OrderIsdn); // 指定一个comparator
// 默认队列大小是11
Test t1 = new Test("t1",1);
Test t3 = new Test("t3",3);
priQueue.add(t3);
基于优先级堆无界队列,它的元素按照自然顺序排序,在创建的时候可以提供一个比较器。不允许null值。不是线程安全的,入队和出对的时间复杂度是log(n).
15 什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
如何做到线程安全:
四种方式
sychronized关键字
1. sychronized method(){} // 同步方法
2. sychronized (objectReference) {/*block*/} // 同步块
3. static synchronized method(){} // static 同步方法
4. sychronized(classname.class) // 指定同步类
其中1和2是代表锁当前对象,即一个对象就一个锁,3和4代表锁这个类,即这个类的锁。要注意的是sychronized method()不是锁这个函数,而是锁对象,即:如果这个类中有两个方法都是sychronized,那么只要有两个线程共享一个该类的reference,每个调用这两个方法之一,不管是否同一个方法,都会用这个对象锁进行同步。
注意:long 和double是简单类型中两个特殊的咚咚:java读他们要读两次,所以需要同步。
16 如何权衡用无序数组还是有序数组
有序数组查找的时间复杂度是O(logn),但是插入操作的时间复杂度是O(n).
无序的是O(n),但是插入的时间复杂度是O(1).
17 Java中,int 是否是类型安全的。
Int 的赋值是原子的,但是i++不是。
18 HashSet VS TreeSet
HashSet 元素是无序的,最多放入一个NULL值,add(), remove(), contains(), 这些方法的复杂度都是O(1)
TreeSet: 树形结构实现,元素是有序的,不能放入NULL值。 Add(), remove(), contains()复杂度是log(n).
以上是关于Java面试的主要内容,如果未能解决你的问题,请参考以下文章