黑马程序员——Java之集合框架(一)

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


一 、集合概述

面向对象语言对对象的体现都是以对象的形式,所以为了对多个对象的操作,就对对象进行存储,集合就是存储对象的最常用的一种方式。

特点:集合的长度是可变的,只能存储对象。

数组和集合都是容器有何不同?

数组虽然也可以存储对象,但长度是固定的,数组中可以存储基本类型的数据数据。


二、 集合体系结构图

 

 


三 、Collection接口

       Collection接口是最基本的集合接口,它不提供直接的实现,JavaSDK提供的类都是继承自Collection的“子接口”如List和Set。Collection所代表的是一种规则,它所包含的

元素都必须遵循一条或者多条规则。如有些允许重复而有些则不能重复、有些必须要按照顺序插入而有些则是散列,有些支持排序但是有些则不支持。


实现其接口的子接口区别:

list元素是有序的,元素可以重复,因为该体系集合有索引。

set元素是无序的,元素不可以重复。

Quenu为队列,阻塞式队列和双端队列。

 

基本操作:

(1) 单元素添加、删除操作:

boolean add(Object o):将对象添加给集合

boolean addAll(Collection<? extendsE> c) 将指定集合中的所有元素都添加到此 集合中

boolean remove(Object o): 如果集合中有与o相匹配的对象,则删除对象o

boolean removeAll(Collection<?> c)移除此集合中那些也包含在指定集合中的所有元素

boolean retainAll(Collection<?> c)从当前集合中删除c集合中不包含的元素(取交集)

void clear()清空集合中的所有元素


(2) 获取操作

int size() :返回当前集合中元素的数量

Iterator iterator() :返回一个迭代器,用来访问集合中的各个元素

(3)判断操作

boolean isEmpty() :判断集合中是否有任何元素

boolean contains(Object o) :判断查找集合中是否含有对象o

boolean containsAll(Collection<?> c) 判断此集合是否包含指定集合中的所有元素,


(4) Collection转换为Object数组 :

Object[] toArray() :返回一个内含集合所有元素的array

<T> T[] toArray(T[] a):返回一个内含集合所有元素的array。运行期返回的array和参数a的型别相同,需要转换为正确型别。

 

注意:可以把集合转换成其它任何其它的对象数组。但是不能直接把集合转换成基本数据类型的数组,因为集合必须持有对象。


1、List 接口

List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。




List集合判断元素是否相同,依据的是元素的equals方法。

List集合中的contains方法和remove方法都会会调用元素本身的equals方法来进行判断。

特有方法:凡是可以操作角标的方法都是该体系特有的方法。


(1)    添加和删除

void add(int index, Object element): 在指定位置index上添加元素element

boolean addAll(int index, Collection c): 将集合c的所有元素添加到指定位置index

Object remove(int index) :删除指定位置上的元素


(2)获取

Object get(int index): 返回List中指定位置的元素

List<E> subList(int fromIndex,inttoIndex)获取部分对象元素

int indexOf(Object o): 返回第一个出现元素o的位置,否则返回-1

int lastIndexOf(Object o) :返回最后一个出现元素o的位置,否则返回-1


(3)修改

Object set(int index, Object element) :用元素element取代位置index上的元素,并且返回旧的元素


(4)遍历集合

list集合特有的迭代器listiterator是Interator的子接口。

在迭代时,不可以通过集合对象的方法操作集合的元素。因为会发生ConcurrentModificationException并发异常。所以在迭代时,只能使用迭代器的操作元素的方法。可是

interator方法时有限的,只能对元素进行判断,去除删除等操作,不能添加元素。如果想要其他操作如添加、修改等就需要使用其子接口listiterator。该接口只能通过list集合的

listiterator方法获取。


基本操作:
(1) 添加元素

void add(Object o): 将对象o添加到当前位置的前面

(2)修改

void set(Object o): 用对象o替代next或previous方法访问的上一个元素。如果上次调用后列表结构被修改了,那么将抛出IllegalStateException异常。

 (3) 正向迭代

boolean hasNext()以正向遍历列表时,判断后一个元素是否存在。

Object next() 返回列表中的下一个元素。

int nextIndex(): 返回下次调用next方法时将返回的元素的索引

(4)反向迭代

 boolean hasPrevious():以逆向遍历列表时判断前一个元素是否存在。

Object previous():返回前一个对象

nt previousIndex(): 返回下次调用previous方法时将返回的元素的索引7


(1)Arraylist集合

ArrayList是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着

容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多

少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。


在向一个ArrayList对象添加大量元素的程序中,可使用ensureCapacity方法增加capacity。这可以减少增加重分配的数量。


 (1)void ensureCapacity(int minCapacity): 将ArrayList对象容量增加minCapacity

 (2)void trimToSize(): 整理ArrayList对象容量为列表当前大小。程序可使用这个操作减少ArrayList对象存储空间。

 

ArrayList三种遍历方式写法:

    

public static void method_3(){
         ArrayList a= new ArrayList();
         a.add( "aaa");
         a.add( "bbb");
         a.add( "ccc");
         a.add( "ddd");
    //第一种方式:迭代器
       Iterator it=a.iterator();
       while(it.hasNext()){//使用完后对象还依然存在;
       System.err.println(it.next());
      }
    //第二种方式:for循环迭代器
        for(Iterator it=a.iterator();it.hasNext();){
           //使用完后对象不存在,节省内存;
          System. err.println(it.next());
        }
    //第三种方式:for普通循环,get获取
        for(int x=0;x<a.size();x++){
            System. err.println(a.get(x));
       }
    }

练习一:去除ArrayList中的重复元素。
public class ArrayListTest {
    //去除ArrayList中的重复元素。
    public static ArrayList SingElement(ArrayList al){
         ArrayList a= new ArrayList();
          for(Iterator in=al.iterator();in.hasNext();){
             
              Object ob=in.next();
               if(!a.contains(ob)){
                   a.add(ob);
              }
         }
          return a;
    }
    public static void main(String[] args) {
          // TODO Auto-generated method stub
         ArrayList al= new ArrayList();
         al.add( "1");
         al.add( "2");
         al.add( "3");
         al.add( "1");
         al.add( "2");
         System. err.println(al);
         al= SingElement(al);
         System. err.println(al);
    }
}
 

练习三;去除ArrayList中的重复对象元素。
public class Person {
    private String name;
    private int age ;
    Person(String name, int age){
          this.name =name;
          this.age =age;
    }
    public String getName() {
          return name ;
    }
    public int getAge() {
          return age ;
    }
    public boolean equals(Object obj){//contains会调用元素的equals方法。
          if(!(obj instanceof Person))
               return false ;
         Person p=(Person)obj;
          //System.err.println(this.name+p.name);
          return this .name .equals(p.name) &&this.age ==p.age ;
    }
}
 
public class ArrayListTest1 {
    //判断元素是否相同。
    public static ArrayList SingElement(ArrayList al){
          //建立一个新容器。
         ArrayList all= new ArrayList();
          for(Iterator it=al.iterator();it.hasNext();){
              Object obj=it.next();
               if(!all.contains(obj)){
                   all.add(obj);
              }
         }
          return all;
    }
    public static void main(String[] args) {
         ArrayList al= new ArrayList();
         al.add( new Person("lisi001.." ,30));
         al.add( new Person("lisi002.." ,30));
         al.add( new Person("lisi003.." ,30));
         al.add( new Person("lisi001.." ,30));
         al= SingElement(al);
         Iterator it=al.iterator();
          while(it.hasNext()){
              Person p=(Person)it.next();
              System.err.println(p.getName()+p.getAge());
         }
    }
}

(2)Linkedlist集合

同样实现List接口的LinkedList与ArrayList不同,ArrayList是一个动态数组,而LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get,

remove,insert方法在LinkedList的首部或尾部。

由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这

样做的好处就是可以通过较低的代价在List中进行插入和删除操作。

 

特有方法:

LinkedList类添加了一些处理列表两端元素的方法。

(1)添加元素

 void addFirst(Object o): 将对象o添加到列表的开头

void addLast(Object o):将对象o添加到列表的结尾

(2) 获取元素

Object getFirst(): 返回列表开头的元素

Object getLast(): 返回列表结尾的元素

(3) 删除元素

Object removeFirst(): 删除并且返回列表开头的元素

Object removeLast():删除并且返回列表结尾的元素

(4) 构造方法

LinkedList(): 构建一个空的链接列表

LinkedList(Collection c): 构建一个链接列表,并且添加集合c的所有元素

<pre name="code" class="java">//模拟堆栈:先进后出。队列:先进先出;
class MyLinkedList
{
	private LinkedList ll;
	public MyLinkedList(){
		ll=new LinkedList();
	}
	//添加元素到开头;
	public void  myadd(Object obj){
		ll.addFirst(obj);
	}
	//先进先出
	public Object fifo(){
		return ll.removeLast();
	}
	//先进后出
	public Object fiao(){
		return ll.removeLast();
	}
	//元素是否为空;
	public boolean isnull(){
		return ll.isEmpty();
	}
}
public class Test3
{
	public static void main(String[] args){
		MyLinkedList mll=new MyLinkedList();
		mll.myadd("11");
		mll.myadd("22");
		mll.myadd("33");
		mll.myadd("44");
		while(!mll.isnull()){
			System.out.println(mll.fifo());
		}
	}
}


 

(2) vector枚举

与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。

枚举是vector的特有的取出方式,枚举和迭代其实一样。因为枚举的名字以及方法名过长而别迭代器替换掉了,

ArrayList和Vector的异同点:

三种遍历方式:   

<pre name="code" class="java">public class Test3
{
	public static void main(String[] args){
	Vector v=new Vector();
	v.add("111");
	v.add("222");
	v.add("333");
	v.add("444");
	//第一种方式:随机访问;
	for(int x=0;x<v.size();x++){
		System.out.println(v.get(x)+"...get...");
	}
	//第二种:迭代器。
	Iterator it=v.iterator();
	while(it.hasNext()){
		System.out.println(it.next()+"...迭代器(iterator)...");
	}
	ListIterator li=v.listIterator();
	while(li.hasNext()){
		System.out.println(li.next()+"...迭代器(listiterator)...");
	}
	//第三种方式:枚举遍历
	Enumeration e=v.elements();
	while(e.hasMoreElements()){
		System.out.println(e.nextElement()+"...elements...");
	}
	//第四种方式:for循环
	for(Iterator it1=v.iterator();it1.hasNext();){
		System.out.println(it1.next()+"...for循环(iterator)...");
	}
	for(ListIterator li1=v.listIterator();li1.hasNext();){
		System.out.println(li1.next()+"...for循环(listiterator)...");
	}
	}
}


 
<pre name="code" class="java">运行结果:
 
<pre name="code" class="java">111...get...
222...get...
333...get...
444...get...
111...迭代器(iterator)...
222...迭代器(iterator)...
333...迭代器(iterator)...
444...迭代器(iterator)...
111...迭代器(listiterator)...
222...迭代器(listiterator)...
333...迭代器(listiterator)...
444...迭代器(listiterator)...
111...elements...
222...elements...
333...elements...
444...elements...
111...for循环(iterator)...
222...for循环(iterator)...
333...for循环(iterator)...
444...for循环(iterator)...
111...for循环(listiterator)...
222...for循环(listiterator)...
333...for循环(listiterator)...

 

(4)stack

Stack继承自Vector,实现一个后进先出的堆栈。

栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的。每一个栈都包含一个栈顶,每次出栈是将栈顶的数据取出。

Stack提供5个额外的方法使得Vector得以被当作堆栈使用。

         操作                                          说明

empty()

测试堆栈是否为空。

peek()

查看堆栈顶部的对象,但不从堆栈中移除它。

pop()

移除堆栈顶部的对象,并作为此函数的值返回该对象。

push(E item)

把项压入堆栈顶部。

search(Object o)

返回对象在堆栈中的位置,以 1 为基数。

2、Set接口

 Set 接口继承 Collection 接口,而且它不允许集合中存在重复项,每个具体的 Set 实现类依赖添加的对象的 equals()方法来检查独一性。Set接口没有引入新方法,所以Set就

是一个Collection,只不过其行为不同。实现了Set接口的集合有:HashSet、TreeSet、LinkedHashSet、EnumSet。

(1)Hashset集合

HashSet堪称查询速度最快的集合,因为其内部是以HashCode来实现的。它内部元素的顺序是由哈希码来决定的,所以它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。

hashset底层结构是哈希表,如何保证唯一性?

是通过元素的俩个方法,hashcode和equals来完成。

如果元素的hashcode值相同,才会判断equals是否为ture.

如果元素的hashcode值不相同,则不会调用equals。

注意:对于判断元素是否存在,以及删除等操作,依赖的方法时元素的hashcode和equals方法。

<pre name="code" class="java">class Person
{
	private String name;
	private int age;
	Person(String name,int age){
		this.name=name;
		this.age=age;
	}
	public void setName(String name)
	{
		this.name=name;
	}
	public String getName(){
		return name;
	}
	public void setAge(int age)
	{
		this.age=age;
	}
	public int getAge(){
		return age;
	}
	//重写hashCode方法,可以提高运行效率。
	public int hashCode(){
		System.out.println(this.name+"...code...");
		return name.hashCode()+age*15;
	}
	//重写equals方法,保证数据唯一性。
	public boolean equals(Object obj){
		if(!(obj instanceof Person))
			return false;
		Person p=(Person)obj;
		System. err.println(this .name +"重复...判断equals..." +p.name );
		return this.name.equals(p.name)&&this.age==age;
	}
}
 class Test3
{
	public static void main(String[] args){
	HashSet hs=new HashSet();
	hs.add(new Person("zhangsan",30));
	hs.add(new Person("lisi",40));
	hs.add(new Person("wangwu",50));
	hs.add(new Person("zhangsan",30));
	hs.remove("lisi");
	for(Iterator it=hs.iterator();it.hasNext();){
		Person p=(Person)it.next();
		System.out.println("姓名:"+p.getName()+" 年龄:"+p.getAge());
	}
	}
}


 

运行结果:

zhangsan...code...
lisi...code...
wangwu...code...
zhangsan...code...
zhangsan重复...判断equals...zhangsan
姓名:wangwu 年龄:50
姓名:lisi 年龄:40
姓名:zhangsan 年龄:30


从运行结果上可以看出:

当向Set集合插入数据时候,首先比较hashCode编码,如果集合中已有此hashCode,则进而去比较equals方法,若返回true,则两个数据相同,此时不予插入,若返回false,则可以插入,此时数据插入同一个hashCode的“筒”,若集合中没有此hashCode,则可以直接插入,不需要比较equals方法

(2)Treeset集合

基于TreeMap,生成一个总是处于排序状态的set,内部以TreeMap来实现。底层数据结构是二叉树。它是使用元素的自然顺序对元素进行排序,或者根据创建Set 时提供的 

Comparator 进行排序,具体取决于使用的构造方法。


 Comparable接口

    在“集合框架”中有两种比较接口:Comparable接口和Comparator接口。像String和Integer等Java内建类实现Comparable接口以提供一定排序方式,但这样只能实现该接口

一次。对于那些没有实现Comparable接口的类、或者自定义的类,您也可以通过Comparator接口来定义您自己的比较方式。

  Comparable接口强行对实现它的每个类的对象进行整体排序。此排序被称为该类的自然排序 ,类的 compareTo 方法被称为它的自然比较方法 。实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort )进行自动排序。


问题一:需要重写hashcode和equals方法吗?

如果只拿TreeSet来说,不用重写equals。因为TreeSet里边的对象都要实现Comparable接口并重写compareTo方法,TreeSet判断元素是否相同以及元素的顺序,都是靠这个方法。
以HashSet为代表的就要重写hashcode 和 equals 方法。


Treeset排序一:自然排序

让对象自身具备比较性,需要实现comparable接口,覆盖compareto方法。


代码实现:

public class Student implements Comparable{//该接口强制让学生具有比较性。
     private String name;
     private int age ;
    Student(String name, int age){
          this .name =name;
          this .age =age;
    }
     public String getName() {
          return name ;
    }
     public int getAge() {
          return age ;
    }
     public int compareTo(Object o) {
          // TODO Auto-generated method stub
          if (!(o instanceof Student))
               throw new RuntimeException("此对象不是学生" );
         Student s=(Student)o;
          if (this .age >s.age )
          return 1;
          if (this .age ==s.age ){
               return this .name .compareTo(s.name);
    //如果参数字符串等于此字符串,则返回值 0;
    //如果此字符串按字典顺序小于字符串参数,则返回一个小于 0 的值;
    //如果此字符串按字典顺序大于字符串参数,则返回一个大于 0 的值。
         }
          return -1;//return new Integer(this.age).compareTo(new Integer(s.age));
    }
}
public class TreesetDemo {
     public static void main(String[] args) {
          // TODO Auto-generated method stub4
         TreeSet ts= new TreeSet();
         ts.add( new Student("lisi001" ,20));
         ts.add( new Student("lisi007" ,25));
         ts.add( new Student("lisi003" ,19));
         ts.add( newStudent("lisi05" ,23));
         ts.add( new Student("lisi04" ,23));
          for (Iterator it=ts.iterator(); it.hasNext();){
              Student s=(Student) it.next();
               System. err.println(s.getName()+"...." +s.getAge());
         }
    }
}

2)     Comparator接口

若一个类不能用于实现java.lang.Comparable,或者具备的比较性不是自己所需要的。这时就需要让集合本身具备比较性,在集合初始化时,就有了比较方式。定义比较器,将

比较器对象对象作为参数传递给treeset集合的构造函数。


覆盖方法:

(1)int compare(Object o1, Object o2):

对两个对象o1和o2进行比较,如果o1位于o2的前面,则返回负值。

如果在排序顺序中认为o1和o2是相同的,返回0。

如果o1位于o2的后面,则返回正值。


(2)boolean equals(Object obj): 指示对象obj是否和比较器相等。

该方法覆写Object的equals()方法,检查的是Comparator实现的等同性,不是处于比较状态下的对象。”

 

Treeset排序二:自定义排序

如何定义比较器?

定义一个类,类实现comparator接口,覆盖compare方法。

<pre name="code" class="java">//自然排序
class Student implements Comparable
{
	private String name;
	private int age;
	Student(String name,int age){
		this.name=name;
		this.age=age;
	}
	public void setName(String name)
	{
		this.name=name;
	}
	public String getName(){
		return name;
	}
	public void setAge(int age)
	{
		this.age=age;
	}
	public int getAge(){
		return age;
	}
	public int compareTo(Object obj){
		if(!(obj instanceof Student))
			throw new RuntimeException("不是学生");
		Student s=(Student)obj;
		//第一种写法:
		if(this.age>s.age)
			return 1;
		if(this.age==s.age)
			return this.name.compareTo(s.name);
		return -1;
		//return new Integer(this.age).compareTo(new Integer(s.age));
	}
}

//定义比较器
class Mycomparator implements Comparator<Student>
{
	public int compare(Student s1,Student s2){
      int num=new Integer(s1.getName().length()).compareTo(new Integer(s2.getName().length()));
	  if(num==0)
		  return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
	  return num;
	}
}

 class Test3
{
	public static void main(String[] args){
	TreeSet<Student> ts=new TreeSet<Student>(new Mycomparator());
	ts.add(new Student("aa",30));
	ts.add(new Student("bbb",50));
	ts.add(new Student("aaaaa",40));
	ts.add(new Student("dddd",70));
	ts.add(new Student("dddd",50));
	for(Iterator it=ts.iterator();it.hasNext();){
     Student s=(Student)it.next();
	 System.out.println("姓名:"+s.getName()+" 年龄:"+s.getAge());
	}
	}
}

注意:从结果中也可以看出,当两种方式都存在时,以比较器为主。
 


(3)LinkedHashSet

LinkedHashSet扩展HashSet。如果想跟踪添加给HashSet的元素的顺序,LinkedHashSet实现会有帮助。LinkedHashSet的迭代器按照元素的插入顺序来访问各个元素。它

提供了一个可以快速访问各个元素的有序集合。同时,它也增加了实现的代价,因为哈希表元中的各个元素是通过双重链接式列表链接在一起的。

“为优化HashSet空间的使用,您可以调优初始容量和负载因子。TreeSet不包含调优选项,因为树总是平衡的。”

(4)EnumSet

是枚举的专用Set。所有的元素都是枚举类型。


3、Quenu

队列,它主要分为两大类,一类是阻塞式队列,队列满了以后再插入元素则会抛出异常,主要包括ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。另一种

队列则是双端队列,支持在头、尾两端插入和移除元素,主要包括:ArrayDeque、LinkedBlockingDeque、LinkedList。

 

 

------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值