数据类型
String s = new String("xyz");创建了几个对象?
String对象;“xyz”字符串常量对象,放在字符串池。
基本数据类型,字符串引用都放在栈中;引用对象放在堆中,地址指向栈中的引
反射
什么是反射?
运行状态中,对任意一个类,都能知道这个类的所有属性和方法;任意一个对象都能调用他的任意属性和方法;这种动态获取信息以及动态调用对象的方法的功能在java中被称为反射。
三步
- 获得CLass对象
- 实例化对象,获得属性、方法、构造函数
- 访问属性、调用方法、调用构造函数创建对象
获取Class对象的三种方式
- 对象的geiClass()方法
- 数据类型的静态属性.class方法
- Class.forName(类的全路径)
哪里用到反射
- JDBC加载数据库驱动
- Web服务器反射调用Sevlet的方法
- Spring中Autowired依赖注入
Class.forName和ClassLoader的区别?
Class.forName除了将类的.class文件加载到JVM中,还会对类进行解释,执行类中static方法
ClassLoader只会干一件事,将.class文件加载到JVM中,不会执行static方法,只有newinstacne的时候才会执行static
什么是序列化
保存对象在内存中的状态,并且可以把保存对象的状态在读取出来
注解
JDK5.0引入的一种注释机制
作用在代码上的注解: **@Overide、@Deprecated 、@SuppressWarnings **
@Overide:检查该方法是否是个重写方法,如果发现父类、或者引用的接口中没有该方法,会编译报错
@Deprecated:标记方法是过去式,引用的时候会编译告警
@SuppressWarnings:编译时忽略警告
四种元注解:@Retention、@Documented、@Target、@Inherited
@Retention:作用范围,代码、编入class、运行时
@Documented:是否包含在用户文档中
@Target:标记该注解用来标注在哪里,属性,方法,类
@Inherited:标记这个注解继承于哪个注解类
实现自定义注解
- 1、使用关键字@interface创建自定义注解
- 2、使用元注解标注该注解行为,@Retention、@Target
- 3、需要使用的注解类添加注解
- 4、实现注解处理逻辑;获取类上的注解,通过属性判断做出决定
集合
Map
HashMap
初始容量:16,负载因子:0.75f,扩容时机:16*0.75=12 红黑树转换:链表长度大于8
为什么长度是2^n次方?
- 效率的原因,取余比按位与操作效率慢,但是如果保证长度是2^n的话,结果是一致的
- 保证了长度是2^n,这样当长度-1操作以后,保证每个位置上都是1,这样让每一位置都参与运算,更好的减少hash碰撞
put方法
底层是同Node[]数组进行存储,初始情况会检查数组长度,进行初始化
根据长度-1与hash值,计算在Node[]中的位置,检查该位置是否有值,没有值,构建新节点存放,记录修改次数,计算是否需要扩容(判断阈值)
如果该位置有值,计算该位置的hash值与传递进来的hash是否相同,key是否相同,相同就覆盖
key不相同,判断当前节点的next是否位空,如果为空,构建新节点,添加到链表尾部,并且判断链表的长度是否大于8,如果大于8,将当前链表转变成红黑树,如果下一个节点不为空,并且key相同,直接覆盖
get方法
- 计算key的hash值
- 通过数组确定key的位置,拿到Node链表第一个节点,判断hash是否相同,如果相同,判断key是否相同,相同,返回value
- 如果key不相同,遍历链表,如果链表是红黑树,通过红黑树遍历查找,如果是链表通过链表查找
为什么链表长度>8的时候转变为红黑树?
在良好hashcode影响下,链表中的节点遵从泊松分布,在概率统计下,链表长度达到8的概率已经接近千分之一了,而且此时链表的性能已经很差了,因此,使用红黑树算是一种性能提升吧,如果在理想的情况下,链表长度不到8就已经扩容了。
hash方法
key的hashcode值与hashcode的高16做异火操作。
为什么与高16做异或?
是为了让高16位参与运算,因为int是32位,取中间16位,当数值小于216次方,与自身异或结果不变,当大于216次方的时候,让高16参与计算,增加了随机性,让后面做定位桶位置的时候减少hash碰撞。
扩容操作
扩容时机:初始化、size>阈值
扩容操作:
- 判断旧数组容量是否大于0
- 大于0,判断容量是否大于2^30次方,直接将容量调整到最大,如果旧容量>=默认容量(考虑定义好初始容量<16的情况吧),进行扩容,容量变成以前的两倍,阈值也进行两倍的扩大
- 进行数据的搬移;数组扩大两倍,遍历原来的数据,所有元素重新hash存放到新数组
HashMap和Hashtable的区别?
- HashMap允许key-value位null,但是Hashtable不可以,value会报空指针
- HashMap是线程不安全的,Hashtable是线程安全的。
- 初始值和扩容因子不同;HashMap初始值是16,扩容后位2倍;Hashtable,初始值是11,每次是原始容量*2+1
- key的hash算法不同,Hashtable直接使用的是key的hashCode方法。
ConcurrentHashMap是如何保证线程安全的?
- 1.7使用分段锁技术
- 1.8放弃了分段锁,使用了CAS+sychronized实现线程安全
ConcurrentHashMap是弱一致性,迭代的时候可以添加元素,HashMap是强一致性,添加元素会报异常
为什么 ConcurrentHashMap 的 key 和 value 不能为 null?
因为空指针异常
ConcurrentHashMap 的put方法
- 定位node节点
- 拿到首节点为空,casput数据
- 不为空,查看是否有其他线程在扩容,帮助扩容
- 为空,sychronized锁住first节点,判断链表还是红黑树,插入
ConcurrentHashMap是否存在线程不安全的情况?
- 本身是线程安全的,如果你自己的操作不是原子的,比如,取出来,操作,在放回去,就可能有问题,可以使用replace方法替换。
ConcurrentHashMap的get会不会有并发问题?
不会,get逻辑比较简单,value又是volatile修饰的,保证了内存可见性
使用LinkedHashMap实现LRU缓存淘汰算法
LinkedHashMap底层使用的链表存储,迭代的时候按照插入的顺序进行迭代
构造方法传入true,这样在get以后,会移动元素的顺序,实现removeEldestEntry方法,根据这个特性进行LRU
Set
HashMap和HashSet的区别?
HashMap | HashSet |
---|---|
HashMap实现了Map接口 | HashSet实现了Set接口 |
HashMap储存键值对 | HashSet仅仅存储对象 |
使用put()方法将元素放入map中 | 使用add()方法将元素放入set中 |
HashMap中使用键对象来计算hashcode值 | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false |
HashMap比较快,因为是使用唯一的键来获取对象 | HashSet较HashMap来说比较慢 |
查询有序 | 无序 |
HashSet应该保证实现hashcode和equals方法,因为对比时是通过这两个值进行比较的
TreeMap和TreeSet如何比较元素?
- TreeSet要求存放的数据必须实现Comparable接口,接口提供compareTo方法,插入元素的时候会调用该方法进行比较
- TreeMapa要求key必须实现Comparable接口,内部put调用compareTo方法
List
Vector
底层使用数组,线程安全使用的是sychronized关键字进行保证,默认容量:10
get方法都加了线程安全关键字
LinkedList
底层使用双向链表,线程不安全,底层方法为进行同步
Arraylist
底层使用数组,线程不安全,初始容量:0,第一次添加操作后容量扩容,扩容后是10
线程安全的三种list
- Collections.synchronizedList方法可以实现线程安全的
- 使用Vector
- juc下的CopyOnWriteArrayList类是线程安全的(读写分离,最终一致、写时复制)
- CopyOnWriteArrayList不能指定容量,初始容量0,集合多大,底层数组就多大
- 只能保证数据的最终一致性,不能保证实时,如果想实时,不建议使用
ArrayList的扩容机制
- add方法添加元素,如果默认情况下,容量=10,取size+1,和容量的最大值,将得到的数值>容量,扩容
- 扩容后容量是之前的1.5倍,如果超过了集合的阈值=Interger.MAX_VALUE-8(-8因为对象头最多占用8个位),hugeCapacity()方法
- 数组拷贝
综合比较
set、list(包含queue)、map
1、Collection:集合list、set、Queue的最基本的接口
2、Iterator:迭代器,可以通过迭代器遍历集合中的数据
3、Map:映射的基础接口
Collection:
1、List:
ArrayList:
1、排列有序,可重复
2、底层使用数组
3、速度快、增删慢
4、线程不安全
5、容量不够,当前容量*1.5+1
6、可以添加null对象
Vector:
1、排序有序,可重复
2、底层使用数组
3、速度快,增删慢
4、线程安全、效率低
5、容量不够,默认是扩展一倍
LinkedList:
1、排列有序,可重复
2、底层使用双向循环链表
3、查询速度慢,增删快,add,remove方法快
4、线程不安全
2、Set
1、HashSet
1、排列无序,不可重复
2、底层使用hash表实现
3、存取速度快
4、内部使用HashMap
2、TreeSet
1、排列无序,不可重复
2、底层使用二叉树
3、排序存储
4、内部使用TreeMap和SortedSet
3、LinkedHashSet
1、采用hash表存储,双向链表记录插入顺序
2、内部是LinkedHashMap
Map
1、HashMap
1、键不可重复、值可以重复
2、底层使用哈希表
3、线程不安全、
4、允许key为null,value也可以为null
2、Hashtable
1、键不可重复,值可重复
2、哈希表
3、线程安全
4、key、value都不可以为null,value为空直接抛出异常了,key计算hash报错
comparable和Comparator的区别?
- Comparable接口在lang包下面,当前对象和其他对象的比较,有一个compareTo(Object o)方法用来排序
- Comparator接口在util包下面,比较两个传入对象的大小,有一个compare(Object o1,Object 02)方法用来排序
IO
IO
1、阻塞IO:
用户线程发出IO请求以后,内核查看数据是否就绪,没有准备好,用户线程处于阻塞状态,用户线程交出CPU,数据就绪以后,内核讲数据拷贝到用户线程,返回加过给用户线程,用户线程解除block状态
2、非阻塞IO:
用户线程发起read以后,如果没有就绪马上得到一个结果,可以再次read,一旦数据准备好,并且再次请求的时候就会复制,用户线程不会交出CPU。导致CPU占用率很高
3、多路复用IO
java的NIO实际上就是多路复用IO
由内核进行轮训,比用户线程高效
因为是轮训,因此如果当前响应事件很大,导致后续的事件得不到处理,影响新的事件轮训
4、信号驱动IO模型
用户线程发起请求的时候会给对应的socket一个注册新号,内核准备就绪給用户线程一个新号,用户线程接收到信号以后进行实际的io请求操作。
5、异步IO模型
用户线程发起read操作以后,内核立刻返回,说明read请求成功了,内核会准备好数据,并且将数据复制到用户线程,然后发送信号到用户线程,用户线程可以直接使用数据。
NIO
通道、缓冲区、selector
IO面向流、NIO面向缓冲区
Stream是单向的,channel是双向的,可以用来读,又可以进行写
Buffer:缓冲区,实际上是一个容器,是一个连续数组
Selector:检测多个注册的通道上是否有事件发生,如果有事件发生,获取事件然后针对事件进行相应的响应处理。