为什么Arraylist每次扩容1.5倍?
首先扩容因子应该小于2,不然后面创建的数组会一直无法使用前面释放的空间。
例如1,2,4,8,当第三次扩容的时候,容量为8,而前面的容量加起来才7。
确定应该在1-2之间之后,再如何取就需要综合考虑,容量太小,就扩容频繁,影响性能,
容量太大,就会造成空间的浪费,1.5是权衡利弊的结果。
说说copyonwritearraylist
- CopyOnWriteArrayList是ArrayList线程安全的一种实现。CopyOnWrite也叫写诗复制容器,从字面看也就是先复制一份,然后再进行写操作。
- 在进行元素修改的时候,会先将数组拷贝一份,在拷贝的数组上进行更改,然后再将新数组更新到原来的引用上面。数组的拷贝主要使用System.arraycpoy和Arrays.copyOf.
- 在add,set,remove等方法都加了可重入锁,这样写操作就不能并发,但是在写时候还可以进行读操作,因为读操作没有锁,不过读到的数据是旧数据。
- CopyOnWriteArrayList有一个专属的迭代器,COWiterator。如果使用原生iterator迭代器,在迭代器里面对元素进行操作就会触发UnsupportedOperationException异常。
优点:
- CopyOnWriteArrayList适用于读多写少的情况,比如商品列表。
缺点:
- 内存占用大,因为写诗复制需要额外拷贝一份,需要双倍内存。
- 数据修改时间长,随着数组元素增加,进行数组拷贝会越来越慢。
- 数据一致性问题,因为使用写诗复制技术,因此只能确保最终的结果是一致的,不能保证数据的实时性。
concurrenthashmap1.7和1.8的区别
- 1.7的时候底层使用ReentrantLock+Segment数组+HashEntry数组+HashEntry链表,1.8使用synchronized+node数组+node链表+红黑树。
- 1.8相对于1.7,锁的粒度变小了。1.7锁的是Segment数组,相当于锁了多个链表的头节点,而1.8锁的是单个链表的头节点。
- 1.8开始,哈希碰撞产生的链表可以转红黑树,在链表长度大于8,就会转红黑树,这样查询复杂度从O(n)下降到O(logn),红黑树结点数少于6转换回链表。
- 1.7使用Segment分段锁来实现同步,所以1.7的并发度就是segment个数。,而1.8使用CAS+Synchronized实现同步,并发度是数组容量。
- 1.7有参构造函数初始化的时候,初始化容量是不小于参数n的最小2的m次方值。而1.8的初始化容量是不小于1.5n的2的m次方最小值。
- 1.7是单线程扩容,而1.8可以多线程进行协助扩容,通过给node数组加volatile确保扩容时可见性。
- 定位元素1.7需要进行两次hash然后再遍历链表,而1.8只需要一次hash,然后遍历链表或者红黑树就可以了。
- 读操作都不加锁,靠volatile来确保可见性,防止读到脏数据。
线程安全的容器
CurrentHashMap,CopyOnWriteArrayList,CopyOnWriteArraySet,Vector,HashTable.
CurrentHashMap扩容过程
1.7的时候CurrentHashMap是基于segment分段锁来实现的,数组的每个节点里面都是一个hashentry数组,每个segment相当于一个小型的hashmap,扩容原理和hashmap类似。创建容量两倍新数组,转移节点,重新赋值。不同segment之间互不影响,每个segment内部会单独判断是否超过阈值,需要扩容。
1.8的CurrentHashMap不再基于segment实现。当某个线程put元素的时候,如果正在扩容,就加入协助扩容;如果当前没有线程正在扩容,就会将节点加入CurrentHashMap中,然后判断链表是否超过阈值8,同时数组是否超过阈值64,如果超过,链表就转化为红黑树。最后再判断是否需要扩容。
1.8的CurrentHashMap支持协助扩容,扩容时会将原数组分组,然后每个线程每次获取一个分组进行协助扩容。
扩容的过程也是先创建一个容量为原容量两倍的数组,每个线程的扩容过程和1.8的hashmap差不多。
JDK1.7和1.8区别
- 多了lambda表达式。
- hashmap底层多了红黑树。
- currenthashmap底层线程安全实现由segment分段锁换成了CAS+synchronized。
- 用元空间替换掉了方法区。
JAVA和C,C++的区别
- JAVA有自动内存管理,不需要程序员主动释放内存;而C,C++程序员申请的内存需要手动释放,否则会出现内存泄漏的问题。
- JAVA只支持单继承,但是支持实现多个接口;而C,C++支持多继承。,支持实现多个接口。因此JAVA保留了类似于多继承的优点,减去了多继承带来的混乱。
- JAVA中没有指针这个概念,C++中可以通过指针操作内存,在实际操作中,很容易发生各种错误。而JAVA中没有指针,因此也有效地防止了一系列由指针引起的操作层失误。
- JAVA中所有的方法和数据都是类的一部分,想要访问需要通过对象或者类名访问;而C,C++可以将函数和变量定义为全局,需要的时候再调用。
- C,C++中可以进行操作符重载;而JAVA中放弃了操作符重载,不过可以通过函数来达到类似于操作符重载的效果。
- C,C++可以自动强制类似转换,而JAVA强制类似转换需要手动实现。
- C,C++中字符串是基本数据类型,程序中使用“Null”终止符代表字符串的结束;而JAVA中字符串是用类对象来实现的,并且可以通过加号进行字符串拼接。
说说异常
java中,异常分为受检异常和非受检异常。
受检异常必须手动处理,否则编译不通过。处理的方式有try catch捕捉;或者显式抛给上一层进行处理。如IO流的异常。
非受检异常不要求必须处理,因为非受检异常非常多,如果全部都处理,性能消耗大。如除数为0,空指针异常。
异常、错误关系图:java异常处理包括异常和错误;异常能被程序处理,而错误无法处理。