面试
面试题基础篇
自动拆箱和装箱
装箱就是自动将基本数据类型转换为包装器类型(int–>Integer);调用方法:Integer的valueOf(int) 方法 拆箱就是自动将包装器类型转换为基本数据类型(Integer–>int)。调用方法:Integer的intValue方法 Integer的缓存范围为-128—127,因为反码补码的原因不存在-0所以原本-0的值取为-128
重载和重写的区别
重写:重写是继承的一种表现,发生在父类和子类之间,重写不修改方法名、参数列表、返回类型,专注于方法体的修改和重写,访问修饰符的限制要大于被重写的方法的访问修饰符,重写方法不能抛出新的异常检查或者更加宽泛的检查性异常 重载:重载是多态的一种表现,同名的方法若是有不同的参数列表则可以视为重载,参数列表可以数量不同,类型不同,甚至顺序不同,重载的方法返回类型也可以不同,但是不同返回类型不能作为重载的区分标准
equal和==的区别
== 较的栈内存中对象的堆内存的地址,判断对象的引用地址是否相同,是否指向的是同一个对象 equal 重写过的方法比较的则是对象的内容是否相等,因为equal继承的是Object类(所有类都继承java.lang.Object类),如果没有对方法进行重写,调用的依然是Object的 == 方法 所以比较相等通常都是用equal方法,并且与常量比较时应将常量放于前面,防止对象为null发生空指针异常
String、StringBuilder 和 StringBuffer 的区别
String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的String对象。
private final char value[];
StringBuilder和StringBuffer底层都是可变的字符数组,适用于频繁的对字符串进行操作的时候,其中StringBuilder线程不安全但执行速度快,适用于单线程操作,StringBuffer线程安全但执行速度慢,适用于多线程操作。 StringBuffer线程安全是因为加了同步锁,其所有实现方法均加了synchronized 。
ArrayList和linkedList的区别
ArrayList的底层是数组,get和set效率高 LinkedList的底层是双向链表,添加和删除的效率高 但数据量不是很大或者操作不是很频繁的时候差别不大
HashMap和HashTable的区别
HashMap线程不安全,因为方法不是Synchronized(同步的),允许null作为一个entry的key或value,效率较高,相当于轻量级HashTable实现,且两者是不有序的 HashTable线程安全,因为方法是Synchronized的,不允许null的键和值,效率略低
ConcurrentHashMap和HashMap、HashTable的区别
ConcurrentHashMap是线程的HashMap,HashTable虽然线程安全,但是是通过synchronized锁住整张Hash表来实现线程安全,线程独占整张表,效率很低。ConcurrentHashMap做到了读取不加锁,并保证了写操作的细颗粒度,允许多个修改操作并发进行,因为实现了锁分段技术。1.7版本锁的颗粒度是基于segment,包含多个HashEntry;1.8则是基于HashEntry(首节点)
ConcurrentHashMap的实现原理
在JDK7中,ConcurrentHashMap为了提高并发性,维护了一个segment的类hashtable结构,segment内部维护了一个链表数组,通过二次hash来定位,第一次hash定位到segment,第二次hash定位到元素所在的链表的首节点。因为进行二次hash导致hash过程比普通hashmap要长,但是好处是最高可以支持segment数量的写操作。
在JDK8中,ConcurrentHashMap参考了HashMap的实现,采用数组+链表+红黑树实现,内部采用了大量CAS操作,
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
get操作全程不需要加锁但不会读取到脏数据是因为 Node的成员val是用volatile修饰的 (和数组用volatile修饰没有关系)
CAS
CAS(compare and swap),是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。 而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。 CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,如果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
HashMap
hashMap中put实现
1、计算hashcode值 2、散列表为空,调用resize()函数 3、没有发生碰撞,添加进散列表 4、发生碰撞,若是key值或者equal后相同,覆盖;若是红黑树结构,则进行树的插入;若是链表结构,则进行链表插入,若是超过阈值8,则转化为红黑树 5、如果桶满了大于阈值,则resize进行扩容 链表转换成红黑树前提是数组容量大于64,并且链表长度大于8,元素长度小于6又会变成链表结构 多线程put 容易因为链表形成闭合的链路即回路,导致死循环
HashMap扩容以及实现
场景:初始化或者达到阈值即++size>load factor * capacity 看旧数组容量判断是否被初始化过 否,初始化 ········判断是否为无参构造 ·············若是则使用默认的大小和阈值 ·············否则使用指定的大小和阈值,不过容量是经过计算的2的倍数 是,则进行扩容,扩容成2倍(小于最大值),然后将元素重新运算复制到新的散列表中
为什么长度是16?为什么必须是2的幂?如果输入值不是2的幂比如10会怎么样
因为确定数组位置用的是位运算,因为位运算效率高,但是因为是使用位运算,实现均匀分布则需要数组长度为2的次幂,否则会实现不了均匀分布,增加了hash的碰撞次数且浪费数组空间,当然,若是不考虑效率则可以不用位运算,自然也可以不必是2的次幂
进程和线程的区别
进程:具有一定独立功能的程序,是系统进行资源分配和调度运行的基本单位 线程:进程的一个实体,是CPU 调度的苯单位,也是进程中执行运算的最小单位,即执行处理机调度的基本单位,如果把进程理解为逻辑上操作系统所完成的任务,线程则表示完成该任务的许多可能的子任务之一 关系:一个进程可有多个线程,至少一个;一个线程只能属于一个进程。同一进程的所有线程共享该进程的所有资源。不同进程的线程间要利用消息通信方式实现同步。 区别:进程有独立的地址空间,而多个线程共享内存;进程具有一个独立功能的程序,线程不能独立运行,必须依存于应用程序中
接口和抽象类有什么区别
接口的设计目的,是 对类的行为进行约束,强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。 抽象类的设计目的,是 代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。
内部类的作用
内部类是指在一个外部类的内部再定义一个类,类名不需要和文件夹相同。 好处: 1、实现内部类的隐藏,只知道返回的实例但不知道如何实现 2、实现多重继承 3、优化简单的接口实现 4、可以无条件地访问外围类的所有元素
wait和sleep的区别
1、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中 2、sleep不需要被唤醒,wait需要 3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字 4、sleep是线程中的方法,但是wait是Object中的方法
|