先简单的看下思路结构:
1.Collection 集合父接口之一
集合用来存放数据,在我看来集合就是更方便的数组。集合中可以存放任意类型的数据,而且长度可变,相比数组更加的灵活和便捷。
(1) Collection接口中定义的方法:
- add(Object obj):可以存储任何引用类型
- add(int index,Object obj):插入元素
- toString():输出时会调用元素的toString方法(集合重写了toString方法)
- contains(Object obj):判断集合中是否包含obj这个元素,实际上是调用obj的equals方法与每个元素进行比较,返回boolean类型,true 表示存在,false表示不存在
- remove(Object obj) 返回值为boolean类型,调用obj的equals方法与集合中每一个元素进行比较,如果结果为true,就将这个元素移除,移除后的结果为true,如果调用equals方法时找不到该元素,就返回false
- remove(int index): 移除该下标的元素
- size():返回的是集合元素的个数
- clear():清空集合中的所有元素
- isEmpty():判断集合是否为空 返回值boolean类型
- addAll(Collection c) 返回值boolean类型 :将集合c中所有元素添加到调用对象中
- removeAll(Collection c):移除两个集合的交集部分
- retainAll(Collection c):在调用者中保留交集部分
- get(int index) 返回值Object类型: 通过下标获取对应元素
- set(int index,Object obj):使用obj替换index位置上的元素,返回值为被替换掉的元素
- add(int index,Object o):在index位置插入一个元素obj,其余元素自动向后顺延
- indexOf(Object obj): 得到obj元素的下标,返回值int,返回-1,说明没有元素
- lastIndexOf(0bject obj) 得到最后一个obj的下标(集合中可能有多个obj)
- toArray():将集合转变为数组
- toArray(Object[] obj) :参数用于规定转换后的数组类型(起到范性的作用),参数数组的长达可以为0。用相应的数组进行接收。
2.Collection有两个子接口:List 和 Set
List 和 Set 的区别;
List: 存储有序可重复的数据
Set:无序不可重复
用equals方法判断是否重复
3、List 接口
常用方法:
与父接口Collection类似
get(int index) 返回值Object类型 : 通过下标获取对应元素
实现类: ArrayList(线程不安全,效率高,增删慢,查找快):经常查询的集合实现ArrayList,使用普遍
LinkedList(增删快,查找慢):经常增删的集合实现LinkedList,类似于队列,或者是栈
Vector(线程安全性高,效率低)
父接口 | 子接口 | 实现类 |
Collection | List | ArrayList/LinkList |
//实例化ArrayList List ls1 = new ArrayList(); //实例化LinkList List ls2 = new LinkList();
4、Set
Set和List,在我看来是同级别的,同样是Collection接口,所以方法也类似,但与List不同的是,Set集合要求集合内元素不可以重复,在第一点中提到过集合使用equals方法判断元素是否重复,但是如果集合中有100个元素了,再此添加一个新元素时,我们就需要让新元素和100个元素一次进行equals方法的比较,可以说是非常繁琐了。
这就提出了本节第一个重点问题,使用HashCode方法:
- 为什么要使用hashCode方法:之前已经说的很清楚了,我们的set集合中不能有相同的元素,逐个比较又十分麻烦,所以选择比较简单的hashCode方法
- hashCode方法的原理: 在集合中,设计多个区域每个区域存储一部分的哈希值。在添加元素时,根据元素的值就可以确定所在区域,然后看这个值是否存有对象,没有对象就可以存此元素,当已经存有对象时,我们要使用equals方法比较,如果不一样,就可以存储,存在此值对应的链表中。
第二个问题,为什么要自己写equals方法和hashCode方法,而不使用原生的方法呢
- 原生的hashCode值是根据内存地址换算出来的一个值。
- 原生的equals方法是严格判断一个对象是否相等的方法(object1 == object2)。
为什么要重写equals方法了呢?简单的说原来的equals方法不适应现在的需求了,这个时候就需要重写了。
通常上只要equals方法重写了我们就需要重写hashCode方法原因是:
我们先来看一下Object.hashCode的通用约定(摘自《Effective Java》第45页)
- 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
- 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
- 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。
父接口 | 子接口 | 实现类 |
Collection | Set | hashSet、treeSet |
5.集合的遍历
不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
为什么要使用迭代器遍历集合?举例如下:
先用传统的for循环和增强for循环遍历集合
//实例化一个List集合 List ls = new ArrayList(); //向集合中添加元素 ls.add("1"); ls.add("2"); ls.add("3"); ls.add("4"); //使用普通for循环遍历集合 for(int i=0;i<ls.size();i++) { System.out.println(ls.get(i)); }
增强for
List<String> ls = new ArrayList<String>(); //向集合中添加元素 ls.add("1"); ls.add("2"); ls.add("3"); ls.add("4");
//使用增强for循环遍历集合 for(String str : ls) { System.out.println(str); }
由此可以看出List集合可以使用我们之前学习的经典for和增强for进行遍历
但是使用增强for循环时,我们必须对集合进行泛型,因为增强for取出对象的类型是固定的。除此之外,经典for和增强for相比,繁琐且需知循环范围,相对比来讲,增强for更为方便。而在java底层中,如果使用增强for遍历数组的话,会自动翻译成迭代器的写法。
综上,是我们为什么首选迭代器遍历集合的原因之一
Set集合是没有get方法的,所以我们必须使用增强for或者迭代器进行遍历,实则底层均为迭代器,这是我们选择迭代器遍历数组的原因之二
使用迭代器遍历List集合//实例化一个List集合 List<String> ls = new ArrayList<String>(); //向集合中添加元素 ls.add("1"); ls.add("2"); ls.add("3"); ls.add("4"); Iterator it =ls.iterator(); while(it.hasNext()) { Object obj = it.next(); System.out.println(obj); }
5、迭代器的集体使用原理:
6、如何重写equals方法和hashCode方法
7、hashSet和treeset的区别
- HashSet无序,TreeSet有序
- 做为集合,hashset和treeset在存储基本数据时是没有区别的,但当需要存储的对象为自定义类型的数据时,去重方式上就存在着一定的区别。HashSet是通过复写hashCode()方法和equals()方法来保证的,而HashSet通过Compareable接口的compareTo()方法来保证的
- HashSet:底层数据结构是哈希表,本质就是对哈希值的存储,通过判断元素的hashCode方法和equals方法来保证元素的唯一性,当hashCode值不相同,就直接存储了,不用在判断equals了,当hashCode值相同时,会在判断一次euqals方法的返回值是否为true,如果为true则视为用一个元素,不用存储,如果为false,这些相同哈希值不同内容的元素都存放一个桶里(当哈希表中有一个桶结构,每一个桶都有一个哈希值)
TreeSet:底层的数据结构是二叉树,可以对Set集合中的元素进行排序,这种结构,可以提高排序性能, 根据比较方法的返回值确定的,只要返回的是0.就代表元素重复
8、队列、双端队列、栈
/* * 队列 * */ //入列 Queue queue = new LinkedList(); queue.offer("111"); queue.offer("222"); queue.offer("333"); queue.offer("444"); System.out.println(queue); //出列 queue.poll(); System.out.println(queue); //查看 Object str = queue.peek(); System.out.println(str); /* * 双端队列 * */ //入列 Deque deque = new LinkedList(); deque.offer("aaa"); deque.offer("bbb"); deque.offerFirst("ccc"); deque.offerLast("ddd"); System.out.println(deque); //出列 deque.pollFirst(); System.out.println(deque); deque.pollLast(); System.out.println(deque); /* * 栈:将双端队列的一端封死,就是栈 * */ Deque stack = new LinkedList(); stack.push("one"); stack.push("two"); stack.push("three"); System.out.println(stack); stack.pop(); System.out.println(stack); stack.pop(); System.out.println(stack);
具体分析下这些代码:
队列:
集合可以说是动态的数据,队列就好像是数组元素在排队 ,一端为入口,一端为出口
第一个元素 | 第二个元素 | 第三个元素 | 第四个元素 | 第五个元素 |
queue.offer("111"); queue.offer("222"); queue.offer("333"); queue.offer("444");
现在队列为 [111,222,333,444]
remove();
队列变为[222,333,444]
双端队列:两端都可进可出
/* * 双端队列 * */ //入列 Deque deque = new LinkedList(); deque.offer("aaa"); deque.offer("bbb"); deque.offerFirst("ccc"); deque.offerLast("ddd");
双端队列的变化:
[aaa]
[aaa,bbb] //都是按照数组的位置依次向后排
[ccc,aaa,bbb] //在队列的最前方插入元素
[ccc,aaa,bbb,ddd] //在队列的最后方插入元素
移除的时候也是这个情况,我之前不太清楚哪个方向是队列的前端,offerfirst是从哪个方向插入元素,如果有人和我有一样的问题的话,可以将队列看做数组,下边从0开始的方向是队首,另一端为队尾。
而栈则不同,栈的一端是封死的,可以将栈看做是一个瓶子
/* * 栈:将双端队列的一端封死,就是栈 * */ Deque stack = new LinkedList(); stack.push("one"); stack.push("two"); stack.push("three");
现在向瓶子里放一个元素
one |
用数组表示:
[one]
再现在向瓶子里放一个元素
two |
one |
用数组表示:
[two , one]
再现在向瓶子里放一个元素
three |
two |
one |
用数组表示:
[three , two , one]
对于栈来说,数组下标小的方向依旧是入口,也是出口
遇到过的一个比较有意思的题目:
ArrayList list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
怎么用for循环删除list内的所有元素??
情况1 :for(int i=0;i<list.size();i++)//i从0开始,依次删除
当 i=0时 remove(list.get(0))
此时移除下标为0的元素 “1”
此时的list为:[2 , 3]
当 i = 1时 remove(list.get(1))
此时移除下标为1的原宿 “3”
此时的list为:[2]
当i=2时 remove(list.get(2))
报异常,list中只有1个元素
正确的方法:
for(int i=list.size()-1;i>=0;i--){
list.remove(i);
}
one