1.ArrayList与LinkedList的区别
面试官:说一下ArrayList与LinkedList的区别?
求职者:
1.首先,它们的底层数据结构不同,顾名思义,ArrayList底层是基于数组实现的,LinkedList底层则是基于链表实现的;
2.由于底层数据结构不同,所以它们所适用的场景也不同,ArrAyList更适合随机查找(数组的特性),而LinkedList更适合删除和添加(链表的特性),且两者之间的查询,添加,删除的时间复杂度均不同;
3.另外ArrayList和LinkedList都实现了List接口,但是LinkedLlist还额外实现了Deque接口,所以LinkedList还可以当做队列使用。
public class Interview {
public static void main(String[] args) {
ArrayList<Integer> arrayList=new ArrayList();
arrayList.get(0);//指定下标查询,查询效率更快
arrayList.add(1);//添加某个元素到集合的尾端,可能会考虑到扩容
//将元素2添加到索引为1的位置上,但是添加到指定位置可能会涉及到底层数组元素的移动,因此对应的效率比较慢
arrayList.add(1,2);
LinkedList<Integer> linkedList=new LinkedList<>();
linkedList.get(0);//遍历链表查询,查询效率慢
linkedList.add(1);//此时直接将元素添加到末端即可,不需要考虑扩容
linkedList.add(1,2);
//遍历找到对应的下标,找到后直接修改指针的指向即可,不需要移动元素
//在linkedList内部有属性first与last,持续记录则底层链表的第一个元素和最后一个元素
linkedList.getFirst();//获取LinkedList第一个元素
linkedList.getLast();//获取linkedList最后一个元素
}
}
2.==与equals的区别
==对比的是栈中的值(也可以理解为栈中的指向),基本数据类型是变量值,引用类型是堆中内存对象的地址;
equals:Object中默认也是采用 = =比较,但通常会重写,在String类中重写后的equals方法源码如下所示:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String中重写后的equals方法就是依次比较字符串中的每一个字符,如果一旦存在对应的字符不同,则返回false,全部对应相同则返回true;
public class equals {
public static void main(String[] args) {
String str1="Hello";
String str2=new String("Hello");
String str3=str2;
System.out.println(str1==str2);//false
System.out.println(str1==str3);//false
System.out.println(str2==str3);//true
System.out.println(str1.equals(str2));//true
System.out.println(str1.equals(str3));//true
System.out.println(str2.equals(str3));//true
}
}
上述代码对应的内存结构图如下所示:
3.String、StringBuffer、StringBuilder区别以及使用场景
面试官:请你谈谈String、StringBuffer、StringBuilder区别以及使用场景?
求职者:
String是final修饰的,不可变,每次操作都会产生新的String对象;(不断的创造对象可能会造成内存浪费比较多)
StringBuffer和StringBuilder都是在原对象上操作的;
StringBuffer是线程安全的,StringBuilder线程不安全的,即StringBuffer方法都是synchronize修饰的。
场景:
经常需要改变字符串内容使用后面两个,
因为StringBuilder的性能要好一些,所以优先使用StringBuilder,而多线程使用共享变量时使用StringBuffer。
4.接口与抽象类的区别
面试官:请你谈谈接口与抽象类的区别?
求职者:
作为一个初级程序员,答出以下三点即可:
抽象类可以存在普通的成员函数(已经实现的方法),而接口中只能存在public abstract(公共抽象)方法;
抽象类只能继承一个,而接口可以实现多个;
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
但是,如果作为一个中高级程序员,答出上述三点远远不够,还需要进行拓展,即需要你探索更深层次的内容:
我们从设计目的上面来分析:
接口的设计目的,是对类的行为进行约束(更准确的说是一种有约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
抽象类的设计目的:是代码复用。当不同的类具有某些相同的行为(行为即方法,行为记为集合A),且其中一部分行为的实现方式一致时(A的非真子集,即为B),可以让这些类都派生与一个抽象类。在这个抽象类中实现了B(抽象类中可以有普通方法),避免让所有的子类都实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不予许实例化出来(否则当调用到A-B时,无法执行);
抽象类是对类本质的抽象,表达的是is a的关系,例如BMW is a Car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现;
而接口是对行为的抽象,表达的是like a的关系,例如Bird like a aircraft,接口的核心是定义行为,即类可以做什么。
使用场景:当你关注一个事务的本质的时候,则用抽象类;当你关注一个操作的时候,则用接口;
5.如何在不加锁的情况下实现线程安全
面试官:请你谈谈在Java中如何在不加锁的情况下实现线程的安全?
求职者:
所谓的线程安全问题其实是指多个线程同时对于某个共享资源的访问,导致的原子性,可见性和有序性的问题,而这些问题会导致共享数据存在一个不可预测性,使得程序在执行过程中会出现一些超出预期的一个结果,一般情况下,解决线程安全问题的方式是增加同步锁,常见的是synchronized、Lock等等,由于导致线程安全问题的根本原因是多线程并行访问共享资源,对共享资源加锁之后呢,多个线程在访问这些资源的时候,必须要先获得锁,也就是先获得访问资格,而同步锁的特征是在同一个时刻只允许一个线程访问这样一个资源,直到锁被释放,虽然这种方式可以解决线程安全性问题,但同时带来的是加锁和释放锁带来的一个性能开销,因为加锁会涉及到用户空间到内核空间的一个转换以及上下文切换。因此,如何在性能和安全性之间去取得一个平衡,这就引出了一个无锁并发的概念,一般来说呢,会有以下几种方法:
1.通过自旋锁CAS,所谓自旋锁是指在没有抢占的锁的情况下先自旋指定的次数去尝试获得锁;
2.乐观锁,给每个数据增加一个版本号,一旦数据发生变化,则去修改版本号,在Java里面有一个交CAS的一个机制,可以去完成乐观锁的一个功能;
3.在程序设计中,尽量去减少共享对象的一个使用,从业务上去实现隔离避免并发。