Java基础面试(一)

1、基本数据类型

基本类型

位数

字节

默认

取值范围

int

32

4

0

-2147483648 ~ 2147483647

long

64

8

0L

9223372036854775808(-2^63) ~ 9223372036854775807(2^63 -1)

byte

8

1

0

-128 ~ 127

boolean

1

false

true、false

char

16

2

'u0000'

0 ~ 65535(2^16 - 1)

short

16

2

0

-32768(-2^15) ~ 32767(2^15 - 1)

double

64

8

0d

4.9E-324 ~ 1.7976931348623157E308

float

32

4

0f

1.4E-45 ~ 3.4028235E38

2、JMM内存模型
 (1)JMM内存模型交互图

 

 (2)主内存

所有线程创建的实例对象都存放在内存中,不管该实例对象是成员变量,还是局部变量,类信息、常量、静态变量都是放在主内存中,属于所有线程共享区域,所以存在线程之间安全问题。

 (3)工作内存

主要是存储局部变量(方法内部的变量,存储着主内存中变量的副本),每个线程只能在自己的工作内存中操作变量副本,对其他线程是不可见的。就算两个线程同时执行同一段代码,也是都在自己的工作内存中对变量进行操作。由于线程的工作内存是私有,所以线程之间是不可见的,同时也是线程安全。

(4)原子性 

提供了互斥访问,同一时刻只能有一个线程来对它进行操作;除了jvm自身提供的对基本类型的原子性操作以外,可以通过synchronized和Lock实现原子性。synchronized与lock在同一时刻始终只会存在一个线程访问对应的代码块。

(5)可见性 

一个线程对主内存的操作可以及时地被其他线程观察到,volatile关键字保证了可见性,synchronized和Lock也保证了可见性。

(6)有序性 

个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序,volatile关键字保证了有序性,synchronized和Lock也保证了有序性。

  3、深拷贝和浅拷贝
(1)浅拷贝

浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。

(2)深拷贝 

深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

(3)引用拷贝

引用拷贝就是两个不同的引用指向同一个对象  

4、String、StringBuffer、StringBuilder的区别 

String是不可变的,另外两个是可变的
String和StringBuffer是线程安全的,StringBuilder是线程不安全的

 5、IO字节流和字符流
(1)读写单位不同

字节流是以字节(8bit)为单位进行读写,字符流是根据字符进行读写,在读写的时候根据码表映射,有时候可能会一次性读取多个字节 。


(2)缓冲区要求不同

字节流在操作时本身不会用到缓冲区,是对文件本身进行操作;字符流在操作的时候使用到了缓冲区,所以在操作字符流的时候,不关闭流是没办法对写入 数据进行保存的。 


(3)处理对象不同

字节流可以处理图片等所有文件,字符流只能处理文本类文件 。

(4)处理方式不同 

字节流对应inputStream输入流和outputStream输出流;字符流是Reader输入流和Write输出流  。

6、IO涉及模式总结 
  1.  装饰器(Decorator)模式
  2. 适配器(Adapter Pattern)模式
  3. 工厂模式
  4. 观察者模式
7、IO模型总结 
(1)BIO(同步阻塞) 

同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。 

 

在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量 

(2) NIO(同步非阻塞)

同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。

 

相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞,但是这种IO模型同样存在问题,比较消耗CPU资源。 

(3) I/O多路复用模型

    IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。

 

I/O多路复用模型减少了无效的系统调用,减少了对CPU资源的消耗;Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

 

(4) AIO(异步非阻塞)

异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。 

 

8、HashMap和HashTable的区别 
(1)线程是否安全 

HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); 

(2)效率 

因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它; 

(3)对Null Key和Null Value的区别 

HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。 

(4)初始容量和每次扩展容量大小不一样 

① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。

② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。 

(6)底层数据结构 

JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间,Hashtable 没有这样的机制。 

9、 HashMap和HashSet的区别

hashSet是基于hashMap实现的 

10、HashMap的底层实现 
(1)JDK1.8 之前

HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashcode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。 

 

(2)JDK1.8 之后

相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。 

 

11、concurrentHashMap和HashTable的区别 
(1)底层数据结构 

JDK1.7 的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; 

(2)线程安全的实现方式 
  1. 在 JDK1.7 的时候,ConcurrentHashMap 对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 
  2. 到了 JDK1.8 的时候,ConcurrentHashMap 已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
  3. hashTable使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
12、JDK1.7和JDK1.8的concurrentHashMap实现有什么不同 
(1)线程的安全方式 

JDK 1.7 采用 Segment 分段锁来保证安全, Segment 是继承自 ReentrantLock。JDK1.8 放弃了 Segment 分段锁的设计,采用 Node + CAS + synchronized 保证线程安全,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点。 

(2) Hash碰撞的解决方法

JDK 1.7 采用拉链法,JDK1.8 采用拉链法结合红黑树(链表长度超过一定阈值时,将链表转换为红黑树)。

(3)并发度 

JDK 1.7 最大并发度是 Segment 的个数,默认是 16;JDK 1.8 最大并发度是 Node 数组的大小,并发度更大。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值