集合
一、集合框架的概述
-
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中存储) -
数组在存储多个数据方面的特点:
-
—旦初始化以后,其长度就确定了。
-
数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。比如:String[] arr;int[] arr1;object[] arr2;
-
数组在存储多个数据方面的缺点:
-
一旦初始化以后,其长度就不可修改。
-
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便
同时效率不高。 -
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
-
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
-
二、集合框架
Collection接口:单列集合,用来存储一个一个的对象
List接口:存储有序的,可重复的数据—>“动态数组”
ArrayList、LinkedList、Vector
Set接口:存储无序的,不可重复的数据
HashSet、LinkedHashSet(HashSet的子类)、TreeSet
Map接口:双列集合,用来存储一堆(key-value)一对的数据
HashMap、LinkedHashMap(子类)、TreeMap、Hashtable、Properties(子类)
框架图解
Collection接口:
Map接口:
三、Collection子接口—List接口
向List接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
List接口:存储有序的、可重复的数据
3.1 常用方法
@Test
public void test5(){
Collection coll=new ArrayList();
Collection coll1=new ArrayList();
//add(Object e):将元素e添加到集合coll中
coll.add("aa");
coll.add(123);
coll.add(new Date());
System.out.println(coll.size());//3
//addAll(Collection coll)将coll集合中的元素添加到当前集合中
coll1.add(456);//自动装箱
coll.addAll(coll1);
System.out.println(coll.toString());
System.out.println(coll.size());//4
coll.clear();//不是null,只是清空里面的元素
//isEmpty():判断当前集合是否为空,即size是否为0
System.out.println(coll.isEmpty());
}
@Test
public void test() {
//contains(Object obj):判断当前集合中是否包含obj
Collection coll = new ArrayList();
coll.add(123);
coll.add("ss");
coll.add("java");
coll.add(new String("java"));
coll.add(new Person("Jay", 30));
//在判断时会调用obj对象所在类的equals()
boolean contains = coll.contains(123);
System.out.println(contains);
System.out.println(coll.contains(new String("java")));//true
System.out.println(coll.contains(new Person("Jay", 30)));//false,没有重写equals方法
//containsAll(Collection coll):判断coll中的所有元素是否在当前集合中
Collection coll1 = Arrays.asList(123, "java");
System.out.println(coll.containsAll(coll1));//true
}
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add(123);
coll.add("ss");
coll.add("java");
coll.add(new String("java"));
coll.add(new Person("Jay", 30));
//remove(Object obj):移除obj元素,返回布尔值
boolean remove = coll.remove(123);
System.out.println(remove);//true
System.out.println(coll);
//removeAll(Collection coll1):从当前集合中移除coll1中所有元素(差集)
Collection coll1 = Arrays.asList(123, "ss", "java");
coll.removeAll(coll1);
System.out.println(coll);
}
@Test
public void test2() {
Collection coll = new ArrayList();
coll.add(123);
coll.add("ss");
coll.add("java");
coll.add(new String("java"));
coll.add(new Person("Jay", 30));
Collection coll1 = new ArrayList();
coll1.add(123);
coll1.add("ss");
coll1.add("java");
coll1.add(new String("java"));
coll1.add(new Person("Jay", 30));
//equals():当前集合与形参集合是否相同,如果是ArraysList需要考虑顺序
System.out.println(coll1.equals(coll));//true
//hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//retainAll(Collection coll1);求交集,返回个当前集合
Collection coll2 = Arrays.asList(123, 456, 789);
coll.retainAll(coll2);
System.out.println(coll);
}
3.2 与index有关的常用方法
@Test
public void test(){
ArrayList list=new ArrayList(10);
list.add(123);
list.add(new String("666"));
list.add(new Person("Jay",30));
System.out.println(list);
//add(int index,Object obj):指定位置添加元素
list.add(1,"JDBC");
System.out.println(list);
//addAll(int index,Collection coll):从指定位置开始,将coll中的所有元素添加到当前集合中
List list1= Arrays.asList("888",4396);
list.addAll(0,list1);
System.out.println(list);
//Object get(int index):获取指定位置的元素
System.out.println(list.get(2));
}
@Test
public void test1(){
ArrayList list=new ArrayList(10);
list.add(123);
list.add(new String("666"));
list.add(new Person("Jay",30));
//int indexOf(Object obj):返回obj首次在集合中出现的位置,如果不存在,返回-1
System.out.println(list.indexOf(123));//0
System.out.println(list.indexOf(122));//-1
//int lastIndexOf(Object obj):返回obj最后一次出现在集合中的位置,不存在返回-1
System.out.println(list.lastIndexOf(new Person("Jay",30)));//2,Person类中重写过equals()
//remove(int index)
Object o=list.remove(0);
System.out.println(o);//被删除的元素
System.out.println(list);
//Object set(int index,Object obj):将指定位置上的元素设置为obj
Object set = list.set(0, 777);
System.out.println(set);//被修改的元素
System.out.println(list);
//System.out.println(list.set(2,56));//报错,index不能超出list.size()
//List subList(int fromIndex,int toIndex):返回从fromList到toList位置的左闭右开区间的子集合
list.add(55);
list.add(2,13);
List list1 = list.subList(0, 2);
System.out.println(list);
System.out.println(list1);
}
3.3 集合与数组的转化
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("ss");
coll.add("java");
coll.add(new String("java"));
coll.add(new Person("Jay", 30));
//集合--->数组
Object[] array = coll.toArray();
for (int i = 0; i <array.length ; i++) {
System.out.println(array[i]);
}
//数组--->集合
List list = Arrays.asList("123", 123, "java");
List integer=Arrays.aslist(new int[]{11,22});
System.out.println(integer.size());//1
List integers = Arrays.asList(new Integer[]{11, 22});
System.out.println(integers.size());//2
System.out.println(list);
System.out.println(integers);
}
3.4 集合的遍历
集合元素的遍历操作,使用迭代器Iterator接口
- 内部的方法:hasNext()和next()
- 集合对象每次调用iterator()方法,都会得到一个全新的迭代器对象,默认游标都在集合的第一个元素.
@Test
public void test(){
//集合元素的遍历操作,使用迭代器Iterator接口
Collection coll = new ArrayList();
coll.add(123);
coll.add("ss");
coll.add("java");
coll.add(new String("java"));
coll.add(new Person("Jay", 30));
Iterator iterator=coll.iterator();
//不推荐
// for (int i = 0; i <coll.size() ; i++) {
// System.out.println(iterator.next());
// }
//推荐使用
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//错误的遍历方式一:会跳着输出
// while (iterator.next()!=null){
// System.out.println(iterator.next());
// }
//错误的遍历方式二:会重复死循环输出第一个元素
// while (coll.iterator().hasNext()){
// System.out.println(coll.iterator().next());//死循环
// }
}
@Test//增强for循环
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add("ss");
coll.add("java");
coll.add(new String("java"));
coll.add(new Person("Jay", 30));
//内部任然调用迭代器
for (Object obj:coll){
System.out.println(obj);
}
}
@Test//遍历数组
public void test1(){
int [] arr=new int[]{1,5,7};
for (int i:arr){
System.out.println(i);
}
}
3.5 ArrayList源码理解
3.5.1 jdk 7情况下
-
ArrayList list = new ArrayList(); //底层创建了长度是10的object[]数组eLementData
-
list.add(123); //eLementData[e] = new Integer(123);
-
如果此次的添加导致底层eLementData数组容量不够,则扩容。默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
-
结论:建议开发中使用带参的构造器:
ArrayList list = new ArrayList(int capacity)
3.5.2 jdk 8中Arraylist的变化:
-
ArrayList list = new ArrayList();//底层object[] eLementData初始化为{}.并没有创建长list.add(123);
-
第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到eLemen…
后续的添加和扩容操作与jdk7无异。 -
小结: jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
3.6 List接口实现类的比较
ArrayList、LinkedList、Vector三者的异同?
同:三个类都实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
异:
- ArrayList:List接口的主要实现类,线程是不安全的,但是效率高,底层使用Object[] elementData存储
- LinkedList:底层使用双向链表存储,对于频繁的插入、删除操作,使用LinedList比ArrayList效率高
- Vector:线程安全,效率低,底层是Object[] elementData
四、Collection子接口—Set接口
4.1 Set接口实现类比较
set接口:存储无序的、不可重复的数据
无序性:不等于随机性。存储的数据在底层数组中并非按照数组的索引顺序添加,而是根据数据的哈希值决定
不可重复性:保证添加的元素按照equals()方法添加时,不能返回true,即相同的元素只能添加一个
set接口没有额外定义新的方法,使用的都是Collection中声明过的方法。
-
Hashset:作为set接口的主要实现类:线程不安全的,可以储存null值
-
LinkHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历
-
TreeSet:可以按照添加对象的指定属性,进行排序,向TreeSet中添加数据,要求是相同类的对象。
- 自然排序中,比较两个对象是否相同的标准为:compareTo返回0,不再是equals()
4.2 添加元素的方式
- 向set接口的实现类添加数据时,要求其所在类一定要重写hashCode()和equals()
- 要保证重写的hashCode()和equals()尽可能保持一致:相等的对象必须有相同的散列码
我们向HashSet中添加元素a,首先调用元素α所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
- 如果此位置上没有其他元素,则元素a添加成功。—>情况1
如果此位置上有其他元素(或以链表形式存在的多个元素),则比较元素a与元素b的hash值: - 如果hash值不相同,则元素α添加成功。—>情况2
如果hash值相同,进而需要调用元素α所在类的equlas()方法: - equals()返回true,元素a添加失败
equals()返回false,则元素α添加成功。—>情况3
对于添加成功的情况2和情况3而言:元素与已经存在指定索引位置上数据以链表的方式存储。
jdk 7:元素a放到数组中,指向原来的元素。
jdk 8:原来的元素在数组中,指向元素α
总结:七上八下
HashSet底层:数组+链表的结构。
4.3 一道关于set的经典题目
体会set添加元素的过程
//Perosn已经重写equals()和hashCode()
@Test
public void test4(){
HashSet set=new HashSet();
Person p1= new Person("aa",20);
Person p2= new Person("bb",30);
set.add(p1);
set.add(p2);
System.out.println(set);//[Person{name='aa', age=20}, Person{name='bb', age=30}]
p1.setName("cc");
set.remove(p1);
System.out.println(set);//[Person{name='cc', age=20}, Person{name='bb', age=30}]
set.add(new Person("cc",20));
System.out.println(set);//[Person{name='cc', age=20}, Person{name='cc', age=20}, Person{name='bb', age=30}]
set.add(new Person("aa",20));
System.out.println(set);//[Person{name='cc', age=20}, Person{name='cc', age=20}, Person{name='aa', age=20}, Person{name='bb', age=30}]
}
五、Map接口
Map接口:双列数据,存储key-value对的数据
-
HashMap:主要实现类,线程不安全,效率高,能存储null的key和value
- LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历
- 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
- LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历
-
TreeMap:可以按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序和定制排序
底层使用的是红黑树
-
Hashtable:古老实现类,线程安全,效率低,不能存储null的key和value
- Properties:常用来处理配置文件,key和value都是String类型
HashMap底层:数组+链表(jdk7之前)
数组+链表+红黑树(jdk8)
5.1 Map结构的理解
Map中的key:无序的,不可重复的,使用Set存储所有的key
- key所在的类要重写equals()和HashMap()
Map中的value:无序的,可重复的,使用List存储所有的value
- value所在的类要重写equals()
一个键值对:key-value构成一个Entry对象
Map中的Entry:无序的,不可重复的,使用Set存储所有的Entry
5.2 HashMap常用方法
@Test
public void test(){
HashMap hashMap=new HashMap();
//put()
hashMap.put("123",10);
hashMap.put("java",777);
//修改
hashMap.put("java",4396);
System.out.println(hashMap);
//putAll()
HashMap hashMap1=new HashMap();
hashMap1.put("kass",2200);
hashMap1.put("zed",123);
hashMap.putAll(hashMap1);
System.out.println(hashMap);
//remove(Object key):移除指定key的键值对
System.out.println(hashMap.remove("123"));
System.out.println(hashMap);
//clear():清空数据,不是置为null
hashMap1.clear();
System.out.println(hashMap1.size());
}
@Test
public void test1(){
HashMap hashMap=new HashMap();
hashMap.put("123",10);
hashMap.put("java",777);
hashMap.put("java",4396);
//get(Object key)
Object o = hashMap.get("123");
System.out.println(o);
//containsKey(Object key):是否包含指定的key
boolean containsKey = hashMap.containsKey("java");
System.out.println(containsKey);
//containsKey(Object value):是否包含指定的value
boolean containsValue = hashMap.containsValue("777");
System.out.println(containsValue);
//isEmpty()
HashMap hashMap1=new HashMap();
hashMap1.put("aa",123);
System.out.println(hashMap1.isEmpty());//false
hashMap1.clear();
System.out.println(hashMap1.isEmpty());//true
}
@Test
public void test2(){
//Set keySet():返回所有key构成的Set集合
HashMap hashMap=new HashMap();
hashMap.put("123",10);
hashMap.put("java",777);
hashMap.put("java",4396);
Set set = hashMap.keySet();
Collection values = hashMap.values();
Iterator iterator = set.iterator();
Iterator iterator1 = values.iterator();
while (iterator.hasNext()&&iterator1.hasNext()){
System.out.print(iterator.next()+"=");
System.out.print(iterator1.next());
System.out.println();
}
Set entrySet = hashMap.entrySet();
for (Object obj:entrySet){
System.out.println(obj);
}
//Collection values():返回所有value构成的Collection集合
//Set entrySet():返回所有key-value对构成的Set集合
}
5.3 HashMap的底层原理
JDK7
HashMap map = new HashMap( ):
在实例化以后,底层创建了长度是16的一维数组Entry[ ] table
map.put( key1, value1):
首先,调用key1所在类的nashCode()计算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倍,并将原有的数据复制过来。
jdk8的改变
- new hashMap():底层没有创建一个长度为16的数组
- jdk 8底层的数组是 Node[] ,而非Entry[]
- 首次调用put方法时,底层创建长度为16的数组
- jdk7底层只有数组+链表,jdk8中加入了红黑树
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储
5.4 TreeMap
@Test
public void test3(){
//向treeMap中添加key-value,要求key必须是同一个类创建的对象
//因为要按照key排序:自然排序,定制排序
TreeMap treeMap=new TreeMap();
treeMap.put(new Person("jj",20),80);
treeMap.put(new Person("aa",20),70);
treeMap.put(new Person("cc",20),60);
treeMap.put(new Person("gg",20),50);
treeMap.put(new Person("kk",20),40);
Set entrySet = treeMap.entrySet();
for (Object obj:entrySet){
System.out.println(obj);
}
}
六、Collections工具类
Collections:操作Collection、Map的工具类
6.1 常用方法
@Test
public void test(){
List list=new ArrayList();
list.add(123);
list.add(456);
list.add(456);
list.add(789);
//reverse(List list):反转list
System.out.println(list);
Collections.reverse(list);
System.out.println(list);
//shuffle(List list):集合元素进行随机排序
Collections.shuffle(list);
System.out.println(list);
//sort(List list):自然排序
Collections.sort(list);
System.out.println(list);
//swap(List list,int i,int j):将i与j索引位置的元素交换
Collections.swap(list,0,1);
System.out.println(list);
//frequency(Collection,Object):返回指定集合中指定元素的出现次数
int frequency = Collections.frequency(list, 456);
System.out.println(frequency);
}
@Test
public void test1(){
List list=new ArrayList();
list.add(123);
list.add(456);
list.add(456);
List dest= Arrays.asList(new Object[list.size()]);
//将list中的元素复制到dest中。
Collections.copy(dest,list);
System.out.println(dest);
}
6.2 线程问题
Collections 类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
@Test
public void test(){
List list=new ArrayList();
list.add(123);
list.add(456);
list.add(456);
list.add(789);
//reverse(List list):反转list
System.out.println(list);
Collections.reverse(list);
System.out.println(list);
//shuffle(List list):集合元素进行随机排序
Collections.shuffle(list);
System.out.println(list);
//sort(List list):自然排序
Collections.sort(list);
System.out.println(list);
//swap(List list,int i,int j):将i与j索引位置的元素交换
Collections.swap(list,0,1);
System.out.println(list);
//frequency(Collection,Object):返回指定集合中指定元素的出现次数
int frequency = Collections.frequency(list, 456);
System.out.println(frequency);
}
@Test
public void test1(){
List list=new ArrayList();
list.add(123);
list.add(456);
list.add(456);
List dest= Arrays.asList(new Object[list.size()]);
//将list中的元素复制到dest中。
Collections.copy(dest,list);
System.out.println(dest);
}
6.2 线程问题
Collections 类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
@Test
public void test2(){
//线程问题
List list=new ArrayList();
list.add(123);
list.add(456);
List list1 = Collections.synchronizedList(list);
//list1是线程安全的集合
}