数组的插入
在原数组为{12,23,33,45,56,89}将53插入到数组中并保持升序
1、寻找下标——>2、创建新数组——>3、拷贝数组——>4、下标以右的数往右移——>5、插入数据
ArrayList是可变长度的数组,怎么实现可变长度呢?
ArrayList每个对象中都有Capacity是容量的常量(默认值为10),当不给ArrayList初始化容量时,调用add()添加元素,其方法里有调用ensureCapacityinternal(int minCapacity),其方法里用if判断数组里若没有元素,则设置一个容量默认值为10,又设一if判断:当添加的元素已经大于容量值时,则调用grow()添加capacity的值,其算法是ArrayList是原来的50%,即newCapacity = oldCapacity+(oldCapacity/2)
开发中常用的两个集合来存储数据ArrayList和HashMap哪个性能更快
HashMap更快,因为当添加新的元素时,
HashMap通过哈希函数算出哈希码值(如83)映射到表中一个位置来访问记录(则存储在p(83)上,即指针数组),以加快查找的速度。所以查数据时是有迹可寻的。
而ArrayList里面每个单元存储的数据地址,即指针数组,是按顺序放的,但找的时候,也只能按顺序逐个匹配,消耗性能。
hash表的优点是将读取的效率做到极致。常见的哈希函数有:直接寻址发,数字分析方法,随机数法
集合的特点以及其的数据结构
Collection(单列集合)
A:List(插入有序,可重复)
ArrayList
底层数据是数组,查询快,增删慢,
线程不安全(不同步),效率高
Vector
底层数据是数组,查询快,增删慢,
线程安全(同步,put,set等操作元素的方法都是synchronized方法),效率低
LinkedList
底层数据是链表,查询慢,增删快,
线程不安全(不同步),效率高
B;Set(插入无序,唯一)
HashSet
底层数据是哈希表
怎么确定唯一性?
答: 哈希表依赖两个方法:hashCode()和equals()
执行顺序:
先执行hashCode(),判断hashCode()的值是否相同?
是:
再执行equals(),看其返回值:
是true的,说明元素重复,不添加,
是false的,说明元素不重复,添加到集合
否:
直接添加到集合
最终的方法:
eclipse自动生产hashCode()和equals()方法
LinkedHashSet
底层数据结构是链表和哈希表
链表保证元素有序
哈希表保证元素唯一
TreeSet(自动排序)
底层数据是红黑树(是一种自平衡的二叉树)
如何保证元素的唯一性
发现add(E e)方法底层根据实现Comparable的方式来实现唯一性,通过compare(Object o)比较的返回值是否是0来判断是否为同一元素
如何保证元素的有排序性
compare() == 0,元素不入集合。
compare() > 0 ,元素入右子树。
compare() < 0,元素入左子树。
因为自平衡的二叉树做前、中、后序遍历即可保证TreeSet的有序性
两种排序方式:
自然排序(元素具有比较性)
让元素所属的类实现Comparable接口
比较器排序(集合具有比较性)
让集合接收一个Comparator的实现类对象
Map(双列集合)
A:存储的是键值对形式的元素,键唯一
B:Map集合的数据结构是针对键有效的,与值无关
Map的实现类:
HashMap 线程不安全,性能较好,能有null值,继承AbstractMap类,扩容能力较弱,Iterator迭代器是fail-fast机制的
底层数据是哈希表
怎么确定唯一性?
答: 哈希表依赖两个方法:hashCode()和equals()
执行顺序:
先执行hashCode(),判断hashCode()的值是否相同?
是:
再执行equals(),看其返回值:
是true的,说明元素重复,不添加,
是false的,说明元素不重复,添加到集合
否:
直接添加到集合
最终的方法:
eclipse自动生产hashCode()和equals()方法
LinkedHashMap
底层数据结构是链表和哈希表
链表保证元素有序
哈希表保证元素唯一
Hashtable 线程安全,性能较差,不能有null值,继承Dictionary类,扩容能力较强,Enumerator不是fail-fast机制的
底层数据结构是哈希表
TreeMap
底层数据是红黑树(是一种自平衡的二叉树)
如何保证元素的唯一性
发现add(E e)方法底层根据实现Comparable的方式来实现唯一性,通过compare(Object o)比较的返回值
是否是0来判断是否为同一元素
如何保证元素的有排序性
compare() == 0,元素不入集合。
compare() > 0 ,元素入右子树。
compare() < 0,元素入左子树。
因为自平衡的二叉树做前、中、后序遍历即可保证TreeSet的有序性
两种排序方式:
自然排序(元素具有比较性)
让元素所属的类实现Comparable接口
比较器排序(集合具有比较性)
让集合接收一个Comparator的实现类对象
为什么Hashtable不叫HashTable?
原因:Hashtable 是在 Java 1.0 的时候创建的,而集合的统一规范命名是在后来的 Java 2 开始约定的
Hashtable和HashMap的区别
1、Hashtable是线程安全的,HashMap是线程不安全的
因为Hashtable的put,get等方法都有synchronized关键字修饰
public synchronized V put(K key, V value);
public synchronized V get(Object key);
而HashMap的put,get方法没有synchronized
public V put(K key, V value);
public V get(Object key);
2、既然Hashtable是线程安全的,每个方法都要阻塞其他线程,所以其性能较差,反之,HashMap性能就较好,使用广泛
若想要线程安全的同时保证性能的话,建议JUC包下的ConcurrentHashMap
3、Hashtable的key键或value值都不能为null,而HashMap的key值允许一次为null,value值允许无数次为null
原因:
Hashtable的put方法逻辑: 做非空判断
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
.......
HashMap的put方法逻辑: 没有做非空判断
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
4、继承的父类不一样,Hashtable继承Dirctionary类,HashMap继承AbstractMap类
5、容量扩容不同,Hashtable比HashMap扩容能力强,HashMap的初始容量为16,Hashtable的初始容量为11,两者的负载因子都是0.75
原因: 当现有容量大于总容量*负载因子时,HashMap的扩容规则为当前容量的翻倍,Hashtable的扩容规则为当前容量的翻倍+1
6、使用的迭代器不同,HashMap的Iterator迭代器是fail-fast的,而Hashtable的Enumerator不是fail-fast的
所以,当其他线程改变HashMap的结构时,如插入元素、删除元素,将会抛出ConcurrentModificationException 异常;而Hashtable不会
可以来看下这个区别的演示:
public static void main(String[] args) {
Map<String, String> hashtable = new Hashtable<>();
hashtable.put("t1", "1");
hashtable.put("t2", "2");
hashtable.put("t3", "3");
Enumeration<Map.Entry<String, String>> iterator1 = (Enumeration<Map.Entry<String, String>>) hashtable.entrySet().iterator();
hashtable.remove(iterator1.nextElement().getKey());
while (iterator1.hasMoreElements()) {
System.out.println(iterator1.nextElement());
}
Map<String, String> hashMap = new HashMap<>();
hashMap.put("h1", "1");
hashMap.put("h2", "2");
hashMap.put("h3", "3");
Iterator<Map.Entry<String, String>> iterator2 = hashMap.entrySet().iterator();
hashMap.remove(iterator2.next().getKey());
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
}
输出信息为:
t2=2
t1=1
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
at java.util.HashMap$EntryIterator.next(HashMap.java:1476)
at java.util.HashMap$EntryIterator.next(HashMap.java:1474)
at cn.javastack.Test.main(Test.java:37)
所以,这条同样也是 Enumeration 和 Iterator 的区别
foreach循环的原理是Iterator迭代
fail-fast 机制
即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合(Iterator)的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。
线程安全的集合:Collection中有Vector,
Dictionary中有Hashtable,
Collections工具类中有synchrniedList(返回List)、synchrniedMap(返回Map)
属于有条件的线程安全——所有单个的操作都是线程安全的,但是多个操作组成的操作序列却可能导致数据争用(并发性差)
Concurrent中的ConcurrentHashMap(并发优先选择)
线程安全,性能较好,并发性好——ConcurrentHashMap比Hashtable、synchrniedMap提供高得多的可伸缩性
ConcurrentHashMap 通过稍微地松弛它对调用者的承诺而获得了更高的并发性。检索操作将可以返回由最近完成的插入操作所插入的值,也可以返回在步调上是并发的插入操作所添加的值(但是决不会返回一个没有意义的结果)。由 ConcurrentHashMap.iterator()返回的 Iterators将每次最多返回一个元素,并且决不会抛出 ConcurrentModificationException异常,但是可能会也可能不会反映在该迭代器被构建之后发生的插入操作或者移除操作。在对 集合进行迭代时,不需要表范围的锁就能提供线程安全性。在任何不依赖于锁整个表来防止更新的应用程序中,可以使用 ConcurrentHashMap来替代 synchronizedMap或 Hashtable。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
各种集合常见功能以及遍历方法
Collection<E>接口:
功能
增添:add();
删除:remove();
判断:contains();
获取:iterator();
长度:size();
遍历方法:
A:增强for
B;迭代器
Iterator it =c.iterator();
Object oj=it.next();
List:(是Collection的子接口,继承了它的方法)
常用的实现它们的实现类:ArrayList,Vector,LinkedList
特有的功能:
get();
特有的遍历:
普通for()
用get();
Set:(是Collection的子接口,继承了它的方法)
常用的实现它们的实现类:HashSet,TreeSet
Map<K,V>接口(特点与Set接口类似):
常用的实现它们的实现类:HashMap,TreeSet
功能:
增添:put();
删除:remove();
判断:
containKey();
containValue();
获取:
keySet(); 获取键的集合
entrySet(); 获取值的集合
get(); 获取键的元素
vaules(); 获取值的元素
长度:
size();
遍历方法:
A:根据键找值
B:根据键值对对象分别找键和值
如何选择使用哪个集合呢?
看需求:
是否是键值对形式:
是:Map
键是否需要排序:
是:TreeMap
否:HashMap
不知道就用HashMap
否:Collection
元素是否唯一:
是:Set
元素是否需要排序:
是:TreeSet
否:HashSet
不知道就用HashSet
否:List
是否需要安全:
是:Vector(其实我们也不用它,后面讲多线程时,会用到另外一种方法
否:ArrayList或者LinkedList
查询快:
ArrayList
增删快:
LInkedList
不知道就用ArrayList