您的位置:首页 > 职场人生

Java高级面试题积累(一)

2016-03-29 09:10 411 查看
个人感觉越是资深的JAVA工程师,应该越注重JAVA基础知识。

因此我在工作,面试遇到的问题,自学中积累的相关知识点把我理解的结论记录在这里,以便以后复习。(一直在更新ing)

JAVA 位运算符原理:

35 >> 2 = 8 : 将值每右移一次,就相当于该值除以2并且舍弃余数。(35 除以2的2次方)

*无符号右移>>>与带符号右移>>的区别就是 无符号始终补0

2 << 2 = 8 : 将值每左移一次,就相当于该值乘以2。(2乘以2的2次方)



HashMap 实现原理:

JDK7:底层是数组,通过传入的KEY的特定哈希算法定位数组索引。

当哈希值相同时判断equals方法如果相同就用新值替换为旧值。

如果不相同将以单项链表形式存入(一般为了性能会重写hashcode和equals方法,从而减少哈希值冲突来提高性能,因为哈希值相同的数据多时hashmap将退化为单项链表)

JDK8(相对于JDK7性能显著提高 get方法提高了20%左右):底层依然是数组,实现原理基本和JDK7一样。

不同的是单向链表长度超过指定个数时候会将链表升级为二叉树的结构来存储,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个位置的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就别指望能获得性能提升了。

赋:Set 底层其实就是HashMap的key部分。只是将没有用到的值部分统一插入了Object而已。

ConcurrentHashMap原理,还有和HashTable的区别



HashTable与ConcurrentHashMap内部结构参考图
ConcurrentHashMap是一个线程安全的HashMap,它的主要功能是提供了一组和HashMap功能相同但是线程安全的方法。ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候通过segments(默认将哈希表分割成16个segments)来将锁的粒度尽量最小化,不用对整个ConcurrentHashMap加锁。

由于ConcurrentHashMap内部是类似二维数组形式,所以为了减少哈希冲突一共进行了2次hash来定位数据。(为了均匀分布在不同的Segment上,从而提高容器的存取效率)。

get操作不需要锁,第一步访问volatile变量count,由于所以修改操作最后一步更新count变量,确保get操作得到几乎最新的结构更新(HashEntry的value也是volatile,也能保证读取最新的值)。而且除了value以外的别的变量都为final所以遍历链表也不需要加锁。

最后,如果找到的节点如果非空直接范围,否则在有锁状态下载读一次。(空值的唯一源头就是HashEntry中的默认值,因为HashEntry中的value不是final的,非同步读取有可能读取到空值)

HashTablesynchronizedMap通过同步HashTable和Map包装器中的每个方法(包括查询方法),确保一次只有一个线程访问hash表来实现线程安全。

乐观锁和悲观锁的区别

悲观锁:在整个数据处理过程中,将数据处于锁定状态(依靠数据库提供的锁机制实现),因此会大大消耗数据库开销。(传统的关系型数据库运用比较常见,比如行锁,表锁,读锁,写锁等)

乐观锁:大多是基于数据版本记录机制实现。就是为数据增加一个版本标识(version字段),在对数据更改时比对版本号如果一致,则批准更改并将版本号+1,否则更新失败。

String类为什么定义为final

Java中String、Long、Double、Integer等,这些数据类型的类,都是final,因为这些类是基本数据类型的延伸。设计成一个不变类,这样有助于共享,提高性能。可以将字符串对象保存在字符串常量池中以供与字面值相同字符串对象共享。如果String对象是可变的,那就不能这样共享,因为一旦对某一个String类型变量引用的对象值改变,将同时改变一起共享字符串对象的其他String类型变量所引用的对象的值。



ThreadLocal是什么

首先此一次接触ThreadLocal可能会被它的命名误解,这里说明一下不是本地线程,而是 一个关于创建线程局部变量的类,简单来说就是当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 (自JDK5.0之后ThreadLocal已经支持泛型。)

这里结合spring说明一下,spring作为单例模式的典范,是如何避免线程安全问题呢,我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度。这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突。

我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源。但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享。

按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但Spring的DAO模板类并未采用线程同步机制,因为线程同步限制了并发访问,会带来很大的性能损失。 此外,通过代码同步解决性能安全问题挑战性很大,可能会增强好几倍的实现难度。那模板类究竟仰丈何种魔法神功,可以在无需同步的情况下就化解线程安全的难题呢?答案就是ThreadLocal!

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个静态内部类ThreadLocalMap,用于存储每一个线程的变量的副本。

参照下面的源码实现可以看出,其实很简单每个ThreadLocal有个静态内部类ThreadLocalMap,key存储当前线程,value存储entry来实现的。

<pre name="code" class="java">public class ThreadLocal<T> {
/* ThreadLocal values pertaining to this thread. This map is maintained

* by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

/**

* Returns the value in the current thread's copy of this

* thread-local variable. If the variable has no value for the

* current thread, it is first initialized to the value returned

* by an invocation of the {@link #initialValue} method.

*

* @return the current thread's value of this thread-local

*/

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value;

}

return setInitialValue();

}

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

public void remove() {

ThreadLocalMap m = getMap(Thread.currentThread());

if (m != null)

m.remove(this);

}

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

static class ThreadLocalMap {

/**

* The entries in this hash map extend WeakReference, using

* its main ref field as the key (which is always a

* ThreadLocal object). Note that null keys (i.e. entry.get()

* == null) mean that the key is no longer referenced, so the

* entry can be expunged from table. Such entries are referred to

* as "stale entries" in the code that follows.

*/

static class Entry extends WeakReference<ThreadLocal> {

/** The value associated with this ThreadLocal. */

Object value;

Entry(ThreadLocal k, Object v) {

super(k);

value = v;

}

}

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {

table = new Entry[INITIAL_CAPACITY];

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

table[i] = new Entry(firstKey, firstValue);

size = 1;

setThreshold(INITIAL_CAPACITY);

}

private ThreadLocalMap(ThreadLocalMap parentMap) {

Entry[] parentTable = parentMap.table;

int len = parentTable.length;

setThreshold(len);

table = new Entry[len];

for (int j = 0; j < len; j++) {

Entry e = parentTable[j];

if (e != null) {

ThreadLocal key = e.get();

if (key != null) {

Object value = key.childValue(e.value);

Entry c = new Entry(key, value);

int h = key.threadLocalHashCode & (len - 1);

while (table[h] != null)

h = nextIndex(h, len);

table[h] = c;

size++;

}

}

}

}

private Entry getEntry(ThreadLocal key) {

int i = key.threadLocalHashCode & (table.length - 1);

Entry e = table[i];

if (e != null && e.get() == key)

return e;

else

return getEntryAfterMiss(key, i, e);

}

/**

* Set the value associated with key.

*

* @param key the thread local object

* @param value the value to be set

*/

private void set(ThreadLocal key, Object value) {

// We don't use a fast path as with get() because it is at

// least as common to use set() to create new entries as

// it is to replace existing ones, in which case, a fast

// path would fail more often than not.

Entry[] tab = table;

int len = tab.length;

int i = key.threadLocalHashCode & (len-1);

for (Entry e = tab[i];

e != null;

e = tab[i = nextIndex(i, len)]) {

ThreadLocal k = e.get();

if (k == key) {

e.value = value;

return;

}

if (k == null) {

replaceStaleEntry(key, value, i);

return;

}

}

tab[i] = new Entry(key, value);

int sz = ++size;

if (!cleanSomeSlots(i, sz) && sz >= threshold)

rehash();

}

private void remove(ThreadLocal key) {

Entry[] tab = table;

int len = tab.length;

int i = key.threadLocalHashCode & (len-1);

for (Entry e = tab[i];

e != null;

e = tab[i = nextIndex(i, len)]) {

if (e.get() == key) {

e.clear();

expungeStaleEntry(i);

return;

}

}

}
<span style="white-space:pre">	</span>}
}

用法其实也比较简单,将多线程中有可能发生冲突的变量通过set方法封装进threadlocal中获取时候通过get方法获取,就可以避免多线程中变量冲突的问题。

WebService是什么

Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。

webservice也就是向外界暴露一个能够通过web进行调用的API来实现分布式系统之前的数据交互。

Web Service 的优点:

可以让异构的程序相互访问(跨平台)

松耦合

基于标准协议(通用语言,允许其他程序访问)

抽象类和接口的区别

1.抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高。

2.抽象类可以有具体的方法 和属性, 接口只能有抽象方法和不可变常量。

3.抽象类主要用来抽象类别,接口主要用来抽象功能。

4.抽象的子类可以选择性的重写抽象方法,而接口的子类必须实现全部抽象方法。(一般的应用里,最顶级的是接口,然后是抽象类实现接口,最后才到具体类实现。)

5.一个类只能继承一个类,但是接口可以实现多个。

Java事务的作用

事务的作用就是保证数据的一致性和完整性。(这里最好不要说一堆概念或举例来说明,会使面试官更反感。用简单易懂一句话说明一下就好。)

线程的几种状态

线程有新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)、死亡状态(Dead)等。



线程状态参考图

Mysql存储引擎

MySQL常用的存储引擎为MyISAM、InnoDB、MEMORY、MERGE,其中InnoDB提供事务安全表,其他存储引擎都是非事务安全表。

MyISAM 是MySQL的默认存储引擎。MyISAM不支持事务、也不支持外键,但其访问速度快,对事务完整性没有要求。

InnoDB 存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是比起MyISAM存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。

MEMORY 存储引擎使用存在内存中的内容来创建表。每个MEMORY表只实际对应一个磁盘文件。MEMORY类型的表访问非常得快,因为它的数据是放在内存中的,并且默认使用HASH索引。但是一旦服务关闭,表中的数据就会丢失掉。其次它有空间大小的限制。
MERGE 存储引擎是一组MyISAM表的组合,这些MyISAM表必须结构完全相同。MERGE表本身没有数据,对MERGE类型的表进行查询、更新、删除的操作,就是对内部的MyISAM表进行的。

Archive 非常适合存储大量的独立的,作为历史记录的数据。因为它们不经常被读取。Archive拥有高效的插入速度,但其对查询的支持相对较差。

Federated 将不同的Mysql服务器联合起来,逻辑上组成一个完整的数据库。非常适合分布式应用。

Cluster/NDB 高冗余的存储引擎,用多台数据机器联合提供服务以提高整体性能和安全性。适合数据量大,安全和性能要求高的应用。

CSV 把数据以逗号分隔的格式存储在文本文件中。

BLACKHOLE 接受但不存储数据,并且检索总是返回一个空集。

赋:欢迎广大java程序猿将自身经历的面试题留言,博主会更新到文章中,谢谢支持~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: