集合的整体框架:
一、集合的框架
- 1. 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
-
说明;此时的存储,主要是指能存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)。
- 2.1 数组的特点:
-
一旦初始化以后,它的长度就确定了;
-
数组一旦定义好,它的数据类型也就确定了。我们就只能操作指定类型的数据了;
-
比如:String[] arr;int[] str。
- 2.2 数组在存储多个数据方面的劣势:
-
一旦初始化以后,其长度就不可修改;
-
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高;
-
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用;
-
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
因为图可能会有点复杂,我手写了一个集合的框架图,这样看起来会更加明了一些:
List接口和Set接口都是Collection的子接口,在开发中一般多使用List的子接口ArrayList()来进行开发使用,但是也会考虑到特殊的场景使用LinkedList,这就需要从源码的层面去解释说明了,下面是关于Collection接口的和List接口的一些常用的方法介绍,以及底层源码的分析:
Collection接口中常用的方法:
添加:
add(Object obj) —>添加元素到集合中
addAll(Collection coll) —>将coll集合中的所有元素添加到当前的集合中
获取有效元素的个数
int size()
清空集合
void clear()
是否是空集合
boolean isEmpty()
是否包含某个元素
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
删除
boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集
取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
集合是否相等
boolean equals(Object obj)
转成对象数组
Object[ ] toArray()
获取集合对象的哈希值
hashCode()
遍历
iterator():返回迭代器对象,用于集合遍历
这里要注意: 在往list中添加数据的时候,其所有的类一定要重写equals()方法。在开发中这点很重要。
以上的14中方法是在开发中常用的方法,可以具体先了解大概,到开发中遇到不懂的可以返回来看API进行巩固
一、List接口的框架:
list接口中常用的方法:
关于List()接口中以上的方法必须熟练掌握,其中使用的方法是经常要使用的方法。
集合在存储数据的时候为何不用考虑大小,其中最重要的原因就是在源码中的设置,下面是我通过对源码的分析进行总结:
从源码中可以看出(以JDK8为例):在集合初始化的时候,就创建了一个{ },但是并没有声明其长度,这就是跟JDK7截然不同的地方,但是在数据添加的时候,就会初始化一个长度为10的数组,一直添加直到数组的容量不够,这个时候就需进行扩容,扩大到原来数组的1.5倍,源码在扩容中有一些小的细节点:如果扩容到原来的1.5倍,还是放不下元素,那么就把元素的长度作为数组的长度,从扩容来看,这样去判断并不断的扩容,在开发的过程中,运行效率极其的低,所以我们一般在开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity); 减少扩容,提高项目开发的效率。
在Collection中所有的方法中,其中比较重要的是数组的遍历,这里是Collection独有的一种方式
遍历Collection有三种方式可以进行遍历:
- 使用iterator()迭代器进行遍历,这是最好用的一种,也是必须掌握的一种;
- foreach(),也就是对for()循环的改进,增强for循环,使用起来更加的简单快捷;
- 普通的for循环,也是最基本的写法了
下面就通过代码来进行三种方式的实现:
//方式一:使用iterator()迭代器进行遍历:
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add("AA");
coll.add(550);
coll.add(new String("tom"));
coll.add(new Person("tom", 25));
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//方式二:使用增强for循环来进行遍历
@Test
//集合的增强for循环的使用
public void test1() {
Collection coll = new ArrayList();
coll.add("AA");
coll.add(550);
coll.add(new String("tom"));
coll.add(new Person("tom", 25));
//for(集合元素的类型 局部变量 :集合对象)
//内部仍然使用了迭代器
for (Object obj : coll
) {
System.out.println(obj);
}
}
@Test
//对数组进行遍历:
public void test2(){
//for(数组元素的类型 局部变量 :数组对象)
int [] arr = new int[]{1,22,66,99,8888};
for (int i :
arr) {
System.out.println(i);
}
}
普通for循环就很简单了,这里就不写代码去演示了。
二、Set接口的框架:
HashSet和LinkedHashSet、TreeSet都作为Set的子接口,但是LinkedHashSet和TreeSet在开发当中,由于局限性,不会被经常的使用,也是在特殊的场景下才能被使用几次。所以这里主要去理解HashSet这个子接口。
Set接口中没有定义新的方法,使用的都是Collection()中声明的方法,所以这里就没有新的方法了。
但是这里要强调的是:向set中添加的数据,其所在的类一定要重写hashCode()和equals()方法
-
要求:重写的hashCode()和equals()方法尽可能的保持一致:相等的对象必须具有相等的散列码
-
重写两个方法的小技巧:对象中用作equals()方法比较的Field(),都应该用来计算hashCode()
Set()的底层源码不是那么的重要,因为Set()底层是Map,所以这里可以简单介绍下底层源码:
从上面的总结中可以清楚的明白和了解Set接口底层源码存储数据的整个具体过程。
从源码中我们主要是要深入剖析理解Set接口存储的无序性和不可重复性:
无序性:Set接口中的无序性不是随机性,因为存储的数据在底层的数组中并非按照数组的索引的顺序添加,而是使用HashCode()计算出每一个添加元素的哈希值,然后通过某种特殊的算法求出在数组中存储的位置。
不可重复性是指:保证添加的元素按照equals()判断时,不能返回true:即:不能添加两个相同的元素
关于TreeSet接口,主要去理解两点:
- 向TreeSet中添加数据,必须类型一致,否则就会报错;
- 两种排序的方式:自然排序 和 定制排序
第二点可以通过代码来演示一下自然排序和定制排序:
因为定制排序和自然排序使用了User,所以,下面代码是自然排序实现Comparable接口重写了compareTo方法:
public class User implements Comparable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
//自然排序的方法重写,compareTo()
//要求按照姓名从大到小排列,年龄从小到大
public int compareTo(Object o) {
if (o instanceof User){
User user = (User) o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if (compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else {
throw new RuntimeException("输入的类型不匹配");
}
}
}
自然排序测试:
@Test
public void test2() {
TreeSet set = new TreeSet();
set.add(new User("Tom", 56));
set.add(new User("Lisa", 5));
set.add(new User("Jerry", 69));
set.add(new User("Blink", 22));
set.add(new User("Softer", 18));
set.add(new User("Ala", 20));
set.add(new User("Ala", 18));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
代码运行结果:
从中可以看出是按照姓名从大到小排列,年龄从小到大排列的顺序。
定制排序测试:
@Test
public void test3() {
Comparator com = new Comparator() {
//按照年龄从小到大
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User) {
User g1 = (User) o1;
User g2 = (User) o2;
return Integer.compare(g1.getAge(), g2.getAge());
} else {
throw new RuntimeException("插入的类型不一致");
}
}
};
TreeSet set = new TreeSet(com);//定制排序的参数放入其中就按照定制排序的方式进行排序
set.add(new User("Tom", 56));
set.add(new User("Lisa", 5));
set.add(new User("Jerry", 69));
set.add(new User("Blink", 22));
set.add(new User("Softer", 18));
set.add(new User("Ala", 20));
set.add(new User("Ala", 18));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
代码运行结果:
可以看出是按照年龄的从小到大排序的
到这里Collertion接口以及其所有的子接口都介绍完毕,主要去理解List的子接口ArrayList接口方法的使用和底层源码的过程是如何实现的。
**
三、Map接口的框架:
Map接口中常用的方法:
添加、删除、修改操作:
-
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
-
void putAll(Map m):将m中的所有key-value对存放到当前map中
-
Object remove(Object key):移除指定key的key-value对,并返回value
-
void clear():清空当前map中的所有数据
-
元素查询的操作:
-
Object get(Object key):获取指定key对应的value
-
boolean containsKey(Object key):是否包含指定的key
-
boolean containsValue(Object value):是否包含指定的value
-
int size():返回map中key-value对的个数
-
boolean isEmpty():判断当前map是否为空
-
boolean equals(Object obj):判断当前map和参数对象obj是否相等
-
元视图操作的方法:
-
Set keySet():返回所有key构成的Set集合
-
Collection values():返回所有value构成的Collection集合
-
Set entrySet():返回所有key-value对构成的Set集合
- 总结:
-
添加: Object put(Object key,Object value)
-
删除:remove(Object key)/clear()
-
修改: Object put(Object key,Object value)
-
查询:get()
-
长度:size()
-
遍历:KeySet()\values()\enTrySet()
这里主要是数组的遍历,因为iterator只能使用在Collection接口中,所以Map接口的遍历需要转换成Set或者Collection接口再能使用iterator实现遍历操作。Map接口有三种方式:KeySet()返回所有key构成的Set集合,values()返回所有value构成的Collection集合,enTrySet()返回所有key-value对构成的Set集合。
代码如下:
public void test3(){
HashMap map1 = new HashMap();
map1.put(11,"dandan");
map1.put(356,"aoao");
map1.put(44,"zhangsan");
map1.put(255,"lisai");
//keySet();
Set set = map1.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//values()
Collection values = map1.values();
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
//enTrySet
//方式一
Set set1 = map1.entrySet();
Iterator iterator2 = set1.iterator();
while (iterator2.hasNext()){
Object obj = iterator2.next();
Map.Entry entry = (Map.Entry) obj;//enTrySet集合中的元素都是entry
System.out.println(entry.getKey() + "----->" + entry.getValue());
}
System.out.println("*********************");
//方式二:
Set set2 = map1.keySet();
Iterator iterator3 = set2.iterator();
while (iterator3.hasNext()){
Object key = iterator3.next();
Object value = map1.get(key);
System.out.println(key + "-------//-------" + value);
}
}
对HashMap的理解:这里的小知识点的总结有助于一会底层源码分析的理解
- 1.Map结构中的key:无序的,不可重复的,使用set存储所有的key ---->key所有类要重写equals()方法和 HashCode(),(以HashMap为例)
- 2.Map中的Value():无序的,可重复的,使用Collection() ---->value所在的类要重写equals()方法。
- 3.一个键值对:key-value构成了一个Entry对象
- 4.Map中的Entry:无序的,不可重复的,也是使用Set()存储所有的Entry
下面是底层HashMap以及LinkedHashMap(简单了解即可)的理解:
从底层的源码可以看出HashMap主要是由数组 + 链表 + 红黑树,与Set的底层源码如出一辙,更可以印证Set接口的底层就是HashMap,只不过是在Set的基础上,增加了红黑树,还有在重写equals方法时的添加过程有所不同,其余部分完全一致。这里面涉及到了红黑树,是数据结构中的二叉排序树,感兴趣的可以了解一下,这里就不具体的细说了。
TreeMap接口跟TreeSet接口的使用完全一致。
- 1.输入的key关键字类型必须一致,因为排序是按照key关键字进行的排序;
- 2.排序的方式:自然排序、定制排序
自然排序:
public class Student implements Comparable{
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
//要求按照姓名从大到小排列,年龄从小到大
public int compareTo(Object o) {
if (o instanceof Student){
Student student = (Student) o;
int compare = -this.getName().compareTo(student.getName());
if (compare!=0){
return compare;
}else {
return Integer.compare(this.getAge(),student.getAge());
}
}
throw new RuntimeException("输入的类型不一致");
}
自然排序的测试:
@Test
//自然排序
public void test1(){
TreeMap map = new TreeMap();
Student s1 =new Student("Tom",22);
Student s2 =new Student("Jerry",32);
Student s3 =new Student("Lisa",19);
Student s4 =new Student("Rose",24);
map.put(s1,98);
map.put(s2,63);
map.put(s3,78);
map.put(s4,100);
Set set = map.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "------>" + entry.getValue());
}
代码运行结果:
定制排序:
@Test
public void test2(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Student && o2 instanceof Student){
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return Integer.compare(s1.getAge(),s2.getAge());
}
throw new RuntimeException("输入的类型不一致");
}
});
Student s1 =new Student("Tom",22);
Student s2 =new Student("Jerry",32);
Student s3 =new Student("Lisa",19);
Student s4 =new Student("Rose",24);
map.put(s1,98);
map.put(s2,63);
map.put(s3,78);
map.put(s4,100);
Set set = map.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "------>" + entry.getValue());
}
}
代码运行结果:
Map接口开发的过程中经常使用的也就是HashMap接口,所以这个接口需要去深入的了解一下其使用的方法和底层的源码分析。
Collections工具类的使用:
Collections:是操作Collection、Map的工具类,不同于Collection接口,两者之间不要混淆了。
Collections中常用的方法:
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
这里要注意一下copy操作:
如果在使用copy操作的时候,没有初始化dest的长度,是会报IndexOutOfBoundsException(“Source does not fit in dest”)异常的。
所以以下是开发中最常用声明dest长度的方式:
@Test
public void test2(){
List list = new ArrayList();
list.add(123);
list.add(23);
list.add(424);
list.add(-82);
list.add(-82);
List desc = Arrays.asList(new Object[list.size()]);
Collections.copy(desc,list);
System.out.println(desc);
这样所有集合的基础部分就都在上面了,补充一点,因为ArrayList接口和HashMap接口是不安全的线程那么就可以使用synchronizedList和synchronizedMap来创建多线程进行操作,就会使得线程安全了。
最后希望大家可以看的满足,学的快乐!!!我是小白,但我很努力,加油!!