集合,就是存放数据对象的容器,类比于之前学过的一种容器–数组,集合有着比较高的优越性。就拿存放的数据来说,数组存放的数据类型是单一的,即仅能存放某一种类型的数据,而且数组一旦定义以后,其长度大小都不可再更改,而Collection集合可以存放任意类型的数据,集合的大小可以随着存储数据量的多少自动增容。(当然集合和数组中存放的都是对象的引用而非对象本身)
Collection接口类:最顶层的集合接口类:其中定义了集合的通用性方法,包含基本的操作CRUD
—–List接口:有序,可重复性
—–Set接口:无序,不可重复
上述两类都是单列集合
Collection接口的共性方法:
增加:
1:add() 将指定对象存储到容器中 add 方法的参数类型是Object 便于接收任意对象 2:addAll() 将指定集合中的元素添加到调用该方法和集合中
删除:
3:remove() 将指定的对象从集合中删除 4:removeAll() 将指定集合中的元素删除
修改
5:clear() 清空集合中的所有元素
判断
6:isEmpty() 判断集合是否为空 7:contains() 判断集合何中是否包含指定对象 所以使用contains方法进行判断的时候,可能会需要重写equals方法,因为判断包含的标准可能不一样 8:containsAll() 判断集合中是否包含指定集合 使用equals()判断两个对象是否相等
获取:
9:int size() 返回集合容器的大小
转成数组
10: toArray() 集合转换数组
List接口:有序,可重复性的元素;对于List类的集合,会涉及到对于某个索引的操作,允许在指定位置插入元素,通过索引来访问某个元素
List接口特有的方法:
1:增加
void add(int index, E element) 指定位置添加元素 boolean addAll(int index, Collection c) 指定位置添加集合
2:删除
E remove(int index) 删除指定位置元素
3:修改
E set(int index, E element) 返回的是需要替换的集合中的元素
4:查找:
E get(int index) 注意: IndexOutOfBoundsException int indexOf(Object o) // 找不到返回-1 lastIndexOf(Object o)
5:求子集合
List<E> subList(int fromIndex, int toIndex) // 不包含toIndex
List接口的具体实现类:
——-ArrayList:数组集合
实现原理:ArrayList底层是维护了一个Object数组实现的,利用ArrayList无参构造函数的时候,默认的数组长度是10,当然可以通过ArrayList的构造函数new ArrayList(20)来显示指定初始容量。当目前的容量不够存储对象时,会自动增容为原来的1.5倍。
特点:查询速度快,增删慢。
原因:因为ArrayList底层维护的是一个Object数组,我们都知道数组的空间内存地址是连续的,所以访问某个对象的时候,只需要通过索引便可以直接获取到。但是当我们向ArrayList对象添加对象的时候,首先判断目前的容量是否足够,如果足够,这种情况还好,直接添加元素就可以;如果不够,会创建一个新的ArrayList对象,容量为久对象容量的1.5倍,同时将旧数组中的所有元素拷贝到新数组中,如果数据量大,效率低得可怕。对于删除,假设有一个长度100的数组,假如删除了索引位置2处的一个元素,由于存储的元素是内存空间连续,所以说需要将索引3以后的全部元素拷贝到索引2开始的位置,其中也会有数据量的拷贝,所以效率也低下。
下面写一个小程序:去除ArrayList中重复的元素
思路:新建一个ArrayList对象,遍历旧集合,如果元素不包含在新的ArrayList集合中,则将该元素加入到新集合对象中
public class Demo6 {
public static void main(String[] args) {
ArrayList arr = new ArrayList();
Person p1 = new Person("jack", 20);
Person p2 = new Person("rose", 18);
Person p3 = new Person("rose", 18);
arr.add(p1);
arr.add(p2);
arr.add(p3);
System.out.println(arr);
ArrayList arr2 = new ArrayList();
for (int i = 0; i < arr.size(); i++) {
Object obj = arr.get(i);
Person p = (Person) obj;
if (!(arr2.contains(p))) {
arr2.add(p);
}
}
//利用Iterator
/**
Iterator it = arr.iterator();
Person p = (Person)it.next();
if(!arr2.contains(p)){
arr2.add(p);
}
}
*/
System.out.println(arr2);
}
}
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.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;
}
@Override
public int hashCode() {
return this.name.hashCode() + age * 37;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
@Override
public String toString() {
return "Person@name:" + this.name + " age:" + this.age;
}
}
在实际开发中,ArrayList是使用率最高的一个集合。
List接口的具体实现类:
——-LinkedList:链表集合
实现原理:底层是采用链表实现的,内存地址是不连续的。
特点:查找慢,增删快。
原因:因为底层是双向链表实现,每个节点存储了对象的引用和下一个节点的内存地址。内存地址不连续,所以说如果想要检索集合中的某个元素,必须通过遍历该集合,依次取出每个元素来进行比对判断是否是所要检索的元素,所以查找慢;但是增加元素的时候,链表在插入新元素的时候,只需要让前一个元素记住新元素,让新元素记住下一个元素就可以了,所以插入很快;删除元素的时候,只需要让前一个元素记住后一个元素,后一个元素记住前一个元素就可以了,所以删除也很快。
特有方法:
1:方法介绍
addFirst(E e) addLast(E e) getFirst() getLast() removeFirst() removeLast()
如果集合中没有元素,获取或者删除元
素抛:NoSuchElementException2:数据结构
1:栈 先进后出 push() pop() 2:队列 先进先出 offer() poll()
3:返回逆序的迭代器对象
descendingIterator() 返回逆序的迭代器对象
很多方法的功能无需解释,看方法就能猜个差不多,其中push()和addLast()实现的功能是一样的,都是在后面增加一个元素;pop()和removeLast()实现的功能也是一样的,都是移除最后一个元素;offer()和addLast()实现的功能是一样的,都是在后面增加一个元素;poll()和removeFirst()实现的功能是一样的,都是移除第一个元素。奇怪了?为什么设计这么多方法实现了相同的功能?
这是为了模拟堆栈结构和队列结构,下面的程序利用LinkedList集合模拟了堆栈结构和队列结构
import java.util.LinkedList;
/**
* Created by Dream on 2017/10/24.
* StackList模拟的堆栈结构
* QueueList模拟的队列结构
*/
class StackList{
LinkedList list;
public StackList(){
list = new LinkedList();
}
public void add(Object o){
list.push(o);
}
public Object pop(){
return list.pop();
}
public int size(){
return list.size();
}
}
class QueueList{
LinkedList list;
public QueueList(){
list = new LinkedList();
}
public void add(Object o){
list.offer(o);
}
public Object remove(){
return list.poll();
}
public int size(){
return list.size();
}
}
public class LinkedListPrac {
public static void main(String[] args){
StackList stack = new StackList();
stack.add("Dream11");
stack.add("Thia22");
stack.add("Sam33");
int size1 = stack.size();
for(int i=0;i<size1;i++){
System.out.print(stack.pop()+" ");
}
System.out.println();
QueueList queue = new QueueList();
queue.add("Dream11");
queue.add("Thia22");
queue.add("Sam33");
int size2 = queue.size();
for(int i=0;i<size2;i++){
System.out.print(queue.remove()+" ");
}
}
}
下面再利用LinkedList集合实现洗牌的功能
import java.util.LinkedList;
import java.util.Random;
/**
* Created by Dream on 2017/10/24.
* 所有的花色和点数分别存放在String数组中,然后通过循环首先创建扑克牌
* 然后通过随机生成0~51范围的整数作为索引值,交换两个索引值的元素
* 如此往复100次,也可以其他次数,即可得到洗牌后的结果
*/
class Poker{
String color;
String num;
public Poker(String color,String num){
this.color = color;
this.num = num;
}
public String toString(){
return "["+color+num+"]";
}
}
public class PokerLinkedList {
public static void main(String[] args){
LinkedList pokers = createPoker();
shufflePokers(pokers);
showPokers(pokers);
}
public static LinkedList createPoker(){
LinkedList pokers = new LinkedList();
String[] colors = {"黑桃","梅花","方片","红桃"};
String[] nums = {"A","2","3","4","5","6","7","8","9","10","J","Q","K"};
for(int i=0;i<colors.length;i++){
for(int j=0;j<nums.length;j++){
Poker poker = new Poker(colors[i],nums[j]);
pokers.add(poker);
}
}
return pokers;
}
public static void shufflePokers(LinkedList pokers){
Random random = new Random();
for(int i=0;i<100;i++){
int index1 = random.nextInt(52);
int index2 = random.nextInt(52);
Poker p1 = (Poker) pokers.get(index1);
Poker p2 = (Poker)pokers.get(index2);
pokers.set(index1,p2);
pokers.set(index2,p1);
}
}
public static void showPokers(LinkedList pokers){
for(int i=0;i<pokers.size();i++){
System.out.print(pokers.get(i));
if(i%10 == 9)
System.out.println();
}
}
}
List接口的具体实现类:
——-Vector:描述的是线程安全的ArrayList集合
实现原理:底层也是维护了一个Object数组实现的,实现原理与ArrayList是一样的。但是Vector线程安全,操作效率低
???ArrayList和Vector的区别
!!!相同点:底层都是通过维护一个Object数组实现的
不同点:1.ArrayList是线程不同步的,操作效率高;Vector是线程同步的,操作效率低。
2.ArrayList是JDK1.2出现的,Vector是JDK1.0就出现了
迭代方法并不是Iterator,而是通过Enumeration 接口
boolean hasMoreElements()
测试此枚举是否包含更多的元素。
E nextElement()
如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。
Vector v = new Vector();
// 遍历Vector遍历
Enumeration ens = v.elements();
while ( ens.hasMoreElements() )
{
System.out.println( ens.nextElement() );
}
迭代器:专门取出集合中的对象,通俗来讲,就是通过迭代器能够遍历集合中的对象。但是该对象是比较特殊的,不能通过new来创建,因为该类是以内部类的形式存在于集合内部,是依赖于集合而存在的
Collection接口类中定义了获取迭代器的方法iterator(),所有Collection集合体系的均可以获得自身集合的迭代器,只不过每个子类都进行了重写,因为底层存储数据的实现方式是不一样的
对于List集合,可以通过get方法或者Iterator获取元素,而Set集合没有get方法,只能通过Iterator获取
Collection集合的通用迭代器—Iterator接口该接口是集合的迭代器接口类,定义了常见的迭代方法
1:boolean hasNext()
判断集合中是否有元素,如果有元素可以迭代,就返回true。
2: E next()
返回迭代的下一个元素,注意: 如果没有下一个元素时,调用
next元素会抛出NoSuchElementException
3: void remove()
从迭代器指向的集合中移除迭代器返回的最后一个元素(可选操作)。
下面来分析一下迭代器的原理:
ListIterator it = list.listIterator(); //1
while(it.hasNext()){ //2
System.out.print(it.next()+" "); //3
}
1.返回一个迭代器,获取迭代器的时候,迭代器中的指针指向了第一个元素
2.hasNext()判断迭代器当前指针是否有指向元素
3.next()方法获取指针当前指向的元素同时将指针地址+1,使其指向下一个元素
注意:在对集合进行迭代的过程中,是不允许迭代器以外的方式对元素进行操作,因为这样会产生安全隐患,Java会抛出 java.util.ConcurrentModificationException异常,普通迭代器只支持在迭代器中删除操作。
如下列代码就会报上述异常:
List list = new ArrayList();
list.add("A1");
list.add("B2");
list.add("C3");
list.add("D4");
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
list.add("aaa");
}
ListIterator:List特有的迭代器,除了有通用的迭代器方法,该迭代器还支持添加元素,逆序遍历元素
add(E e)
将指定的元素插入列表(可选操作)。该元素直接插入到 next 返回的下一个元素的前面(如果有)
void set(E o)
用指定元素替换 next 或 previous 返回的最后一个元素
hasPrevious()
逆向遍历列表,列表迭代器有多个元素,则返回 true。
previous()
返回列表中的前一个元素。
while (it.hasPrevious()){ //1
System.out.print(it.previous()+" "); //2
}
1.hasPrevious判断是否存在上一个元素
2.如果存在上一个元素,首先将指针向上移动一个单位,然后再获取当前指针指向的元素
List list = new ArrayList();
list.add("A1");
list.add("B2");
list.add("C3");
list.add("D4");
ListIterator it = list.listIterator;
while (it.hasNext()){ //1
System.out.print(it.next()+" "); //2
it.add("aaa"); //3
}
1.hasNext判断当前指针是否指向元素
2.获取当前指针指向的元素,同时将指针向下移动一个单位
3.添加元素的时候是插入到next返回的下一个元素前面
剖析一下上述代码执行的过程:
最初集合内元素:A1,B2,C3,D4
1.迭代器指针指向A1元素
2.此时的next返回的元素是A1,next返回的下一个元素是B2,而且此时迭代器指针已经指向B2
3.添加到元素B2之前
一次循环执行结束后集合内元素:A1,aaa,B2,C3,D4
继续执行:
1.迭代器指向B2
2.此时next返回的元素是B2,返回的下一个元素是C3,而且此时迭代器指针已经指向C3
3.添加到C3元素之前
第二次循环结束后集合内的元素:A1,aaa,B2,aaa,C3,D4
以此类推,最终集合内元素为:A1,aaa,B2,aaa,C3,aaa,D4,aaa
Set集合接口:集合中元素无序,且不可重复。存入的数据顺序和我们取出的数据顺序是不一定一致的。其中没有get方法,如果想得到其中存储的元素,只能通过迭代器获得
————HashSet:Set接口具体实现类的一种,底层是通过哈希表实现的。
实现原理:底层通过哈希表存储,在向hashSet集合中添加对象时,首先计算该对象的哈希值,然后根据对象的哈希值得出在哈希表中的存放位置(可以把哈希值简单理解为内存地址)。假设现在要新添加一个对象,计算该对象得到该对象的哈希值,然后就去哈希表里寻找这个位置
1.如果该位置没有存储任何元素,则可以直接将该元素放到此存储位置上
2.如果发现该位置已经存储有数据,那么新对象会和已存储的数据进行equals方法比较,如果相等,则该元素视为相同元素,不再添加到哈希表中,此时是添加新元素失败;如果equals方法返回了false,则表示并不是同一个元素,则将该元素也添加到哈希表的该位置上。咦?奇怪,怎么不是一个萝卜一个坑?这是因为哈希表中每个位置都是一个“桶式结构”,如果有相同哈希值但是不想等的元素就会被放置到同一个“桶”里。总结一下就是,先比较hashCode值,如果相同,再equals比较,这是最后一道防线。
特点:存取速度快,通过对象的哈希值直接可以得出在哈希表中的位置,相当于直接索引,所以存取速度快!
创建一个HashSet对象,每次向元素中添加对象的时候,首先会调用该对象的hashCode()方法,如果hashCode()方法返回的结果是一样的,则会再调用该对象的equals()方法,所以每次添加对象,hashCode方法是必调用,equals只有在hashCode方法返回true时才会被调用。对于自定义的对象,需要根据添加规则来重写hashCode方法和equals方法,否则默认继承Object的方法,有可能是不满足要求的!
写一个小例子:
import java.util.HashSet;
/**
* Created by Dream on 2017/10/26.
* 对于要添加的对象,只要身份证号id一样,无论其名字是什么,视为同一人
*/
class Person{
int id;
String name;
public Person(int id,String name){
this.name = name;
this.id = id;
}
public String toString(){
return "{id:"+this.id+" name:"+this.name+"}";
}
public int hashCode(){
System.out.println("====hashCode=====");
return this.id;
}
public boolean equals(Object o){
System.out.println("======equals====");
Person p = (Person)o;
return this.id == p.id;
}
}
public class HashSetPrac {
public static void main(String[] args){
HashSet set = new HashSet();
set.add(new Person(110,"Dream"));
set.add(new Person(440,"Thia"));
set.add(new Person(220,"Marry"));
set.add(new Person(330,"Tom"));
set.add(new Person(110,"耿耿"));
System.out.println(set);
}
}
输出结果:
====hashCode=====
====hashCode=====
====hashCode=====
====hashCode=====
====hashCode=====
======equals====
false
[{id:440 name:Thia}, {id:330 name:Tom}, {id:220 name:Marry}, {id:110 name:Dream}]
添加几次元素调用几次HashCode方法,其中最后一个元素添加失败,因为id号为110的元素已经被存储了
注意:String类对象重写了hashCode方法,如果两个String对象的内容是一致的,则其hashCode得到的结果也是一致的!
——–>TreeSet集合:底层是通过红黑树(二叉树)结构实现的。
如果元素具备自然顺序的特性,那么就按照元素具备的自然顺序特性进行排序。一提到自然顺序,可能想到的就是阿拉伯数字和英文字母,确实,如果向TreeSet集合中存储这些数据,得到的都是有序的数据。
但是,如果我们想要存储自定义的对象呢?TreeSet存储的元素是有序的,可想而知肯定是具有某种比较规则,如果是自定义的对象,就需要我们提供一定的比较规则!如果不定义比较规则,就会报错
1.添加的元素不具备自然顺序的特性,则元素所属的类必须实现Comparable接口,把元素的比较规则写在方法compareTo方法中,如果方法返回0则视该元素为重复元素,不再添加。
import java.util.TreeSet;
/**
* Created by Dream on 2017/10/26.
* 人员按照工资多少排序
*/
class Employee implements Comparable{
int id;
String name;
int salary;
public Employee(int id,String name,int salary){
this.id = id;
this.name = name;
this.salary = salary;
}
public String toString(){
return "{id:"+id+" name:"+name+" salary:"+salary+"}";
}
/*负整数、零或正整数,根据此对象是小于、等于还是大于指定对象*/
public int compareTo(Object o){
Employee e = (Employee)o;
return this.salary-e.salary;
}
}
public class TreeSetPrac {
public static void main(String[] args){
TreeSet set = new TreeSet();
set.add(new Employee(110,"Dream",300));
set.add(new Employee(220,"Thia",100));
set.add(new Employee(330,"Jerry",200));
set.add(new Employee(440,"Tom",500));
System.out.println(set);
}
}
红黑树是一种特殊的二叉树,左大右小,即左节点的值小于其父节点的值,右节点的值大于其父节点的值。
实现原理:底层是通过红黑树结构存储的。添加的第一个元素作为红黑树的根节点,并且根节点自己和自己也会有比较,添加第二个元素的时候,首先与跟元素比较,如果小于则存储为左节点,如果大于,则存储为右节点,同样的道理去添加后续的元素。每次添加完节点后,都要看一下当前的“树结构”是否是二叉树,如果不是,则首先调整为二叉树,然后再添加元素。
2.添加的元素不具备自然顺序的特性,同时对象自身也没有实现Comparable接口,那么创建TreeSet的时候必须传入一个比较器,如果比较方法返回0,则视为重复元素。
自定义比较器格式:
class myClass implements Comparator{
int compare(T t1,T t2){
}
}
/*自定义比较器*/
class MyComparator implements Comparator{
public int compare(Object o1,Object o2){
Employee e1 = (Employee)o1;
Employee e2 = (Employee)o2;
return e1.id - e2.id;
}
}
public class TreeSetPrac {
public static void main(String[] args){
MyComparator comparator = new MyComparator();
TreeSet set = new TreeSet(comparator);
set.add(new Employee(110,"Dream",300));
set.add(new Employee(220,"Thia",100));
set.add(new Employee(330,"Jerry",200));
set.add(new Employee(440,"Tom",500));
System.out.println(set);
}
}
推荐使用Comparator比较器,因为这个比较器可以应用在很多类上,而Comparable的比较规则只能在一个类中使用。
如果同时传入比较器,而且对象本身也实现了Comparable接口,那么排序规则以比较器为准。
向TreeSet集合中存入字符串对象,是可以对字符串进行比较的,因为String类已经实现了Comparable接口,重写了compareTo方法,那么比较规则是什么?
1.对应位置处的字符不相同,比较的就是对应位置处不同的字符
2.如果对应上的字符都一样,比较的就是字符串的长度