集合
集合与数组
集合、数组都是对多个数据进行存储操作的结构,简称Java容器
说明:此时的存储都主要指的是内存层面的存储,不涉及持久化的存储(.txt,.jpg,.avi)
数组存储多个数据方面的特点:
- 初始化后,长度确定
- 数组定义完成,元素的类型也确定,只能操作指定类型的数据
数组缺点:
- 初始化后,长度不可修改
- 数组中提供的方法有限,对于添加、删除、插入数据等操作,效率不高
- 没有获取数组中实际元素的个数方法
- 数组存储数据有序、可重复,对于无序、不可重复,不能满足
Collection:存储单列集合的接口,定义了一些规范,内部有常用的List和Set接口
Collections:操作Collection和Map的工具类
Java集合可分为Collection和Map两种体系
Collection
-----List
-----LinkedList
LinkedList:非同步,对于频繁的插入、删除操作,使用此类效率比ArrayList高,底层是用双向链表存储的,不需要扩容
源码分析:内部声明了Node类型的fist和last属性,默认值为null,当调用add方法时才创建Node对象,创建Node对象
-----ArrayList
ArrayList:非同步,实现了可变大小的元素数组,作为List的主要实现类,线程不安全,效率高,底层是用数组存储的
底层源码:
jdk7:底层先创建了长度为10的数组,如果添加导致数组容量不够,则扩容。默认情况下,扩容为原来容量
的1.5倍,同时需要将原有的数组复制到新的数组中
jdk8:底层数组初始化时,并没有创建数组,而是在第一次调用add时,底层才创建了长度为10的数组,并将
数据添加到数组,后续和jdk7一致
-----Vector 同步
Vector:作为List的古老实现类,线程安全、效率低,底层是用数组存储的
源码分析:jdk7和jdk8当中通过Vector构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,
默认扩容为原来长度的2倍(开发中基本不会用到,涉及线程问题,同样使用ArrayList,Collections接口有方法能使线程安全)
------Stack
作为Vector的子类
-----Set
Set:元素无序,不可重复的集合(HashSet、LinkedHashSet(按照输入顺序)、TreeSet(按照自然顺序))
- Set接口是Collection的子接口,set接口没有提供额外的方法
- Set集合不允许包含相同的元素
- Set中判断两个对象是否相同不使用==运算符,而是根据equals()方法
-----HashSet
HashSet:作为Set接口的主要实现类;线程不安全的,可以存储null值
------LinkedHashSet
LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序排序
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,纪录此数据的前一个数据和后一个数据,对于频繁的遍历操作,LinkedHashSet高于HashSet
底层结构为:数组+链表
----TreeSet
TreeSet:可以按照添加对象的指定属性,进行排序
特点:
- 向TreeSet里面添加的数据,要求是相同类的对象
- 两种排序方式:自然排序(实现Comparable接口)和定制排序(comparator接口)
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不是equals()
- 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不是equals()
Map
-----HashTable
同步,实现一个key–value映射的哈希表,作为古老的实现类,线程安全,效率低,不能存储null的key和value
HashTable:数组默认大小是11,增加的方式为oldX2+1
-----HashMap
非同步,作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value,key所在的类要重写equals()和hashCode(),value所在类要重写equals() ,数组默认大小是11,增加的方式为2Xold
jdk8相较于jdk7在底层实现方面的不同:
- new HashMap():底层没有创建一个长度为16的数组
- jdk8底层的数组是:Node[],而非Entry[]
- 首次调用put()方法时,底层创建长度为16的数组
- jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR: HashMap 的默认加载因子:0.75
threshold:扩容的临界值,=容量X填充因子:16X0.75 =>12
TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64HashMap的底层实现原理?以jdk7为例说明: HashMap map = new HashMap(): 在实例化以后,底层创建了长度是16的一维数组Entry[] table。 ...可能已经执行过多次put.. . map.put ( key1 , vaLue1): 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。----情况1 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值: 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-vaLue1添加成功。----情况2 如果key1的哈希值和已经存在的某一个数据( key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2) 如果equals()返回false:此时key1-value1添加成功。----情况3 如果equals()返回true:使用value1替换value2。 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。 在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
-----LinkedHashMap
保证在遍历map元素时,可以按照添加的顺序进行遍历,对于频繁操作,执行效率高于HashMap
继承于HashMap,内部提供了Entry,替换HashMap中的Node
-----WeakHashMap
改进的HashMap,实现了“弱引用”,如果一个key不被引用,则被GC回收
-----TreeMap
保证按照添加的key-value进行排序,实现排序遍历,此时考虑key的排序,底层是由红黑树来实现的
//三种遍历Collection的方法
@Test
public void test01(){
ArrayList list = new ArrayList();
//add()方法将元素添加到集合里面
list.add(123);
list.add("dfsa");
list.add("AAA");
list.add("AAA");
list.add(54645);
list.add(new Person("Tom",12));
System.out.println(list);
//通过for循环遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//通过迭代器循环遍历集合
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//通过foreach循环遍历集合
for (Object obj: list) {
System.out.println(obj);
}
对于HashSet底层数据存储实现的理解:
@Test
public void Test01(){
HashSet set = new HashSet();
Person p1 = new Person("tom", 11);
Person p2 = new Person("jenny", 111);
set.add(p1);
set.add(p2);
System.out.println(set);
p1.name = "haha";
//由于p1的name属性修改,导致set在调用remove方法时,在底层根据对应的哈希值删除对象,属性修改导致哈希值发生了改变
//查找找到的是p1("haha",11)的哈希值,此哈希值对应的数组中位置为空,这样就无法删除p1
set.remove(p1);
System.out.println(set);
//这里用add()方法添加的对象,对应的是p1("haha",11)的哈希值,此哈希值对应的数组中位置为空,可以添加数据
set.add(new Person("haha",11));
System.out.println(set);
//这里用add()方法添加的对象,对应的是p1("tom",11)的哈希值,此哈希值相同,通过equals()方法判断,发现两个数据不一样,
// 所以可以以链表的形式添加数据
set.add(new Person("tom",11));
System.out.println(set);
}
遍历Map集合的方法:
HashMap hashMap = new HashMap();
hashMap.put(1,233454);
hashMap.put(3,23904);
hashMap.put(4,23764);
hashMap.put(12,2334);
hashMap.put(31,233244);
//方法一
Set entrySet = hashMap.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
//entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---------->" + entry.getValue());
}
//方法二
Set keySet = hashMap.keySet();
Iterator iterator1 = keySet.iterator();
while (iterator1.hasNext()){
Object key = iterator1.next();
Object value = hashMap.get(key);
System.out.println(key + "=====" + value);
}