java面试题
为什么java只有值传递?
- 按值调用,表示方法接收的是调用者的值
- 按引用调用,表示方法接收的是调用者提供的变量地址
一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值
java采用按值传递,也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它任何参数变量的内容。
ArrayList
ArrayList,他的底层结构是数组,在初始化的时候,数据量为零,当你add的时候,它会默认变为10,扩容机制是每次扩容为之前的1.5倍,他的特性是查询比较快,删除效率比较低。
首先它是有序的,可重复的数据,作为List接口的主要实现者。
线程不安全,效率高,底层数据结构是数组,在jdk1.7的情况下,在new的时候,底层会创建一个长度为10的数组,但在1.8的时候,new的时候并不会创建数组,而是在第一次调用add的时候,底层才创建了一个长度为10的数组,也就是说在jdk1.7线程不安全,效率高,底层数据结构是数组,在jdk1.7的情况下。在new的时候,底层创建的是一个长度为10的Object数组,但在jdk1.8的时候,new的时候并不创建数组,而是在第一次调用add的时候,底层才创建了一个长度为10的数组。也就是说在jdk1.7的ArrayList的对象的创建类似于单例的饿汉式,在jdk1.8,ArrayList对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
在扩容方面,是原来的1.5倍,同时将原来的数组中的数据复制到新的数组中。
LinkedList
LinkedList底层结构是一个带有头结点和尾结点的双向链表,他提供了两种插入方式,一个是头插LikedFirst,还有一个是尾插LikedLast,他的特性非常适合增加 删除的场景,他的查询在量大的时候比较慢
LinkedList为什么查询慢呢
他在查询的时候,因为是链表查询,从第一个开始一个一个的去比较
ArrayList 查询的时候就会很快吗
它是根据角标查询的,所以会很快。
对于频繁的插入和删除操作,效率比ArrayList高,底层使用双向链表存储。
Vector
他跟ArrayList一样底层都是一个数组,它又有一点区别,它其中大部分的方法都被Synchronized关键字所修饰,所以说它是一个线程安全的,他扩容的时候与ArrayList还是有点区别的,他的扩容的大小是以两倍扩容。
HashMap
HashMap首先从他的底层结构来说,在1.7和1.8的时候是不一样的,在1.7的时候,底层数据结构是一个长度是16的数组和一个单链表,到了1.8的时候,改成了数组加上一个单链表或者红黑树的一个方式,在单链表和红黑树之间转换,它的单链表的长度大于等于8,并且它的Hash桶大于等于64的时候,它会将单链表转换为红黑树的形式存储,要是红黑树的结点的数量小于或等于6的时候,它会重新再转成一个单链表,这是它底层结构的一个变化,另外一个关于它的hash桶的数量,它的默认是16个,有一个默认的阈值,阈值默认是0.75,这关系到它的扩容
扩容它是首先检查数组里元素的个数,因为有一个loadFactor(负载因子)默认值是0.75,哈希桶默认是16,他的阈值是16*0.75=12,当它哈希桶占用的容量大于12的时候,就会触发一个扩容,他扩容成之前哈希桶容量的两倍,并将原来的数据复制过来
LinkedHashMap
底层使用的结构与HashMap相同,因为LinedHashMap继承与HashMap,他们的区别在于LinedHashMap内部提供了Entry,替换HashMap中的Node,能够记录添加元素的先后顺序。
TreeMap
向TreeMap中添加key-value,要求key必须是由一个类创建的对象
因为要安装key进行排序:自然排序,定制排序
HashSet
添加过程:
- 调用HashCode,计算哈希值,根据某种算法求出索引的位置
- 如果该位置没有其他元素,就添加成功
- 有元素,但hash值不相同,添加成功
- 有元素,Hash值相同,但equal不相同,添加成功
- 有元素,Hash值相同且equal元素也相同,添加失败
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7:元素a放到数组中,指向原来的元素。
jdk 8:原来的元素在数组中,指向元素a
LinkedHashMap
LinkedHashSet是HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
优点:对于频繁的遍历操作,LinedHashSet效率高于HashSet
Array和ArrayList的不同点
- Array可以包含基本类型和对象类型,ArrayList只能包含对象类型
- Array大小是固定的,ArrayList的大小是动态变化的
TreeSet
向TreeSet中添加的数据,要求是相同类的对象
- 两种排列顺序,自然排序(实现Comparable接口)和定制排序(Comparatot)
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
- 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
ConcurrentHashMap
ConcurrentHashMap具有锁分段的技术,使用起来比HashMap和HashTable要优良。
Hashmap是线程不安全的,所以一般在并发环境下不建议使用;而HashTable虽然是线程安全的,但是由于使用了同步锁,在并发环境下效率低下,因为所有访问HashTable的线程都必须竞争同一把锁。假如容器中有多把锁,每把锁用于用于锁容器其中一部分的数据,那么当线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发效率,这就是ConcurrentHashMap锁所使用的锁分段技术。
Map和ConcurrentHashMap的区别
- Map是线程不安全的,put在多线程情况下,会形成环从而导致死循环
- ConcurrentHashMap是线程安全的,采用分段锁机制,减少锁的粒度
Synchronized
- Synchronized修饰的静态方法以及同步代码块锁的是类,线程想要执行对应同步代码,需要获得类锁
- Synchronized修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁
volatile
- 可见性:可以使得一个线程修改后的变量立即对其他线程可见
- 不保证原子性
- 禁止指令重排
sychronized和lock
sychronized是java的关键字,当它用来修饰一个方法或一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段。jdk1.5之后引入了自旋锁,锁粗化,轻量级锁,偏向锁来有优化关键字的性能。
Lock是一个接口,而synchronized是java中的关键字。sychronized在发生异常时,会自动释放线程占有的锁。因此不会导致死锁现象的发生,而Lock在发生异常时,如果没有主动unLock()释放锁,则很有可能导致死锁的现象。因此使用Lock时需要在finally块中释放锁。Lock可以让等待锁的线程响应中断,而synchroniezd却不行,使用synchrnized时,等待的线程会一直等待下去,不能够响应中断。通过Lock可以知道没有成功获取锁,而synchronized却无法办到。
final
- final修饰一个类时,表示该类无法被继承
- final修饰一个方法时,表示该方法不可被覆盖
- final修饰一个属性时,表示该属性一旦被初始化便不可更改
static
- static表示一个成员变量或者成员方法可以在没有所属类的实例变量的情况下能被访问
- java中的static方法不能被覆盖。因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的,static方法跟类的任何实例都不相关
StringBuffer和StringBuilder
- StringBuffer是线程安全的,StringBuildr是线程不安全的,底层实现上的话,StringBuffer其实就是比StringBuilder多了一个Synchronized修饰符
接口和抽象类的区别
不同点:
- 接口中所有的方法隐含都是抽象的,而抽象类中则可以同时包含抽象和非抽象的方法
- 类可以实现多个接口,但是只能继承一个抽象类
- java接口中声明的变量都是final的,而抽象类可以包含非final的变量
- 接口的成员函数默认都是public的,抽象类的成员函数可以是private,protected或者是public
- 接口是绝对抽象的,不可以被实例化,抽象类也不可以被实例化,但是如果它包含main方法的话是可以被调用的
Comparable和Comparator
- 自然排序:使用Comparable接口重写compareTo(obj)方法
- 定制排序:使用Comparator接口重写compare(Object o1,Object o2)方法
反射
反射机制是指,在运行状态中,对于任意一个类,可以知道它的任意属性和方法,对于任意一个对象,都能够调用它的任意方法和属性,对于这种动态调用对象方法的功能成为java语言的反射机制
反射创建对象
- 通过类对象调用newInstance()方法
- getConstructor()或者getDeclaredConstructor()方法获得构造器并调用其newInstance()方法创建对象。
获取和设置对象的私有字段的值
通过类对象getDeclaredField()方法,再通过setAccessible(true)将其设置为可以访问,接下来通过get/set方法类获取/设置字段的值了
进程和线程的区别
- 进程是资源分配的最小单位,线程是cpu调度的最小单位
start和run的区别
- start方法会创建一个新的子线程并启动
- run()方法只是Thread的一个普通方法的调用(还是在主线程里执行)
Thread和Runnable的去呗
- Thread是一个类,Runnable是一个接口,Thread类实现了Runnable接口
- Thread是实现了Runnable接口的类,通过start给Runnable的run方法赋上多线程的特性,因为java是单一继承原则,为了提升系统的可扩展性,推荐通过使业务类实现Runnable接口将业务逻辑封装在run方法里
线程的状态
- 新建
- 运行
- 限期等待
- 无限期等待
- 阻塞
- 结束
sleep和wait的区别
sleep()方法是Thread类方法,wait方法是Object类中定义的方法
sleep可以在任何地方使用
wait方法只能在sychronized方法或者synchronized块中使用
最本质的差别
sleep方法只会让出cpu,不会导致锁的变化,如果当前线程是拥有锁的,那么Thread.sleep方法不会让线程释放锁,而只会主动让出cpu,让出cpu,cpu就可以去执行其他任务了
wait方法不仅会让出cpu,而且还会释放以及占有的同步资源锁,以便它在等待该资源的线程得到该资源进而去执行
notify和notifyAll的区别
- notifyAll会让所有处于等待池中的线程全部进入锁池去竞争获取锁的机会,没有获取到锁的只能等待其他机会去获取锁,不能主动回到线程池中。
- notify 只能随机选取一个处于等待池中的线程去竞争获取锁的机会
yield函数
- 当调用Thread.yield()方法时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器会忽略这个暗示。
- yield方法对锁的行为不会有影响的,不会让当前线程让出锁
interrupt
调用interrupt方法,通知线程应该中断了,该线程到底是中断还是继续执行,应该由这个线程自己去处理
如果线程处于被阻塞状态,例如:sleep,wait,join状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常
如果线程处于正常活动状态,那么会将该线程的中断标识设置为true,被设置中断标示的线程将继续正常运行,不受影响