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涉及模式总结
- 装饰器(Decorator)模式
- 适配器(Adapter Pattern)模式
- 工厂模式
- 观察者模式
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)线程安全的实现方式
- 在 JDK1.7 的时候,ConcurrentHashMap 对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
- 到了 JDK1.8 的时候,ConcurrentHashMap 已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
- 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 数组的大小,并发度更大。