(6) - 集合 (图)

---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ---------------------- 

 

1、  集合框架

为什么出现集合框架?

没有集合框架之前,多个对象的保存只能用对象数组,但受数组固定长度限制,而通过一些数据结构的操作可以达到动态数组的操作,如链表,但链表的构建和操作有我们来做十分繁琐。于是集合框架就出现了,它是对一些实现好的数据结构进行了包装,我们在使用时十分方便,而且不受对象数组长度的限制。

集合与数组的区别:

数组:数组长度固定,存储元素为基本的数据类型,操作方法局限性很大。

集合:作为容器,容量(长度)可变,存储的是引用型对象,操作方法灵活并可扩充。

 

2、  Collection

Collection接口是单值存储的最大父接口,其子接口有ListSet等。

Collection的常用共性方法:

boolean add(E e) 
//添加元素对象(可选操作)。
boolean addAll(Collection<? extends E> c) 
//将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。
void clear() 
//移除此 collection 中的所有元素(可选操作)。
boolean contains(Object o) 
//如果此 collection 包含指定的元素,则返回 true。
boolean containsAll(Collection<?> c) 
//如果此 collection 包含指定 collection 中的所有元素,则返回 true。
boolean equals(Object o) 
//比较此 collection 与指定对象是否相等。
int hashCode() 
//返回此 collection 的哈希码值。
boolean isEmpty() 
//如果此 collection 不包含元素,则返回 true。
Iterator<E> iterator() 
///返回在此 collection 的元素上进行迭代的迭代器。
boolean remove(Object o) 
//从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
boolean removeAll(Collection<?> c) 
//移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。
boolean retainAll(Collection<?> c) 
//仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。
int size() 
//返回此 collection 中的元素数。
Object[] toArray() 
//返回包含此 collection 中所有元素的数组。
<T> T[] toArray(T[] a) 
//返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同。

3、  List集合

ListCollection的子接口,属于线性数据结构。元素的存储附带索引,可以通过索引进行元素的访问。

List集合的特点:存储含有索引,因此元素有序并可重复。其有序体现在我们存入的顺序跟输出顺序一致。

List集合对Collection接口方法进行了扩充,含有自己的特性方法,另外由于它的线性结构特性,有了个专门用于List集合的迭代器ListIteratorList的特性方法如下:

//增:
void add(int index, E element)            //在指定索引处添加元素
boolean addAll(int index, Collection<? extends E> c) //在指定索引处添加另一个集合的所有元素
//删:
E remove(int index)      //移除掉指定索引处的元素
//改:
E set(int index, E element)   //设置指定索引处的元素为一个新元素
//查:
E get(int index)             //获取指定索引处的元素
int indexOf(Object o)       //获取一个元素内容所在的索引
int lastIndexOf(Object o)    //反向获取一个元素内容所在的索引
List<E> subList(int fromIndex, int toIndex)  //根据指定的开始与结束点获取一个子集合(含开始不含结束)
ListIterator<E> listIterator(int index)   //列表集合专用迭代器

List接口下有多个子类,如VectorArrayListLinkedList等:

(1)    ArrayList:

ArrayList底层的数据结构是数组结构,可变长度。

特点:继承了些数组特点,查询速度快,但增删慢,它是线程不同步的。

 

ArrayList默认初始化长度为10,当超过时50%延长,new50%空间,将原序列拷贝到新增长度的ArrayList中。Verctor初始也是10,但延长为100%的元素。

 

Vector是元老级的结构,其操作和性能都不如新的ArrayList,因此基本被抛弃,

Vector的用addElement(E o)添加元素。

下面看看ArrayListVector的对比:

比较点

ArrayList

Vector

推出时间

JDK1.2后推出,属于新的操作类

JDK1.0推出,属于旧的操作类

性能

采用异步处理方式,性能高

采用同步处理方式,性能低

线程安全

非线程安全的操作类

线程安全的操作类

输出

Iteratorforeach

IteratorforeachEnumeration

(2)    LinkedLsit:

特点:底层为链表结构(双向),增删速度快,查询慢。

由于是双向链表,增加了从尾部添加(addLast),删除(removeLast)和获取(getLast)的方法。JDK1.6后分别替换成offerLastpollLastpeekLast,从开头的方法将后缀Last改成First即可。

4、  Set集合

Set接口底层是树形结构,元素存储无序(输入顺序不一定时输出顺序),方法与Collection方法没什么区别。 

这里说下实现Set接口的HashSetTreeSet

(1)    HashSet:

特点:底层是哈希表结构,无序,不重复。通过hashCodeequals方法来保证元素的唯一性。

HashSet哈希表的散列式存储方式:

哈希表简单说有点像分级存储,只不过需要进行二次比较。存入元素时,得到一个计算来的哈希值(HashSet通过元素对象的内存地址计算),如果计算得到的是新的哈希值则直接存入,如果是存在的哈希值,则确定哈希值属于某一区域,而后再在这一区域比较与这一区域的元素是否有相同,相同不存储,不相同(不重复),则保存。那么hashCode完成上一级的比较,如果它比较不出,在交给equals进行二次比较。

同样,contains方法与remove方法在比较元素是否存在,都是依赖hashCodeequals

 

②自定义对象存储保证唯一性

HashSet将对象内存地址通过计算作为哈希值,保证唯一性。但对于自定义的引用类型,我们要比较的是不依据对象的内存地址的,而是对象内部的成员数据来判断我加入的是否是同一个人的数据,这时我们不得不自己去依照HashSet的比较原则写一套比较方式。这时我们需复写hashCodeequals,产生自定比较方式。

import java.util.HashSet;
import java.util.Iterator;
class Test 
{
  public static void main(String[] args) 
  {
         HashSet hs = new HashSet();
         /*
         Person 中a2对象相同,先比较hashCode,若相同,进一步比较
         equals
         */
         hs.add(new Person("a1",11));
         hs.add(new Person("a2",12));
         hs.add(new Person("a3",13));
         hs.add(new Person("a2",12));         

         Iterator it = hs.iterator();
         sop("存入:");
         while(it.hasNext())
         {
                Person p = (Person)it.next();
                sop(p.getName()+"::"+p.getAge());
         }
  } 
  public static void sop(Object obj)
  {
         System.out.println(obj);
  }
}
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;
  }
  //复写hashCode
  public int hashCode()
  {
         //检测进行hashCode了比较
         System.out.println(this.name+".....hashCode");
         //产生一个哈希值,为保证哈希值唯一,最好age*37,乘以个奇数
         return name.hashCode()+age;
  }
  //复写equals
  public boolean equals(Object obj)
  {
         if(!(obj instanceof Person))
                return false;
         Person p = (Person)obj; 
         System.out.println(this.name+"...equals..."+p.name);
         //在现实中,判断是否为同一人,需多个数据比较
         //这里若是同名又是同岁,这仅有的两个特点我们判断为同一个人
         return this.name.equals(p.name) && this.age == p.age;
  }
}

对于自定义的哈希值,HashSet依旧会按数值大小存放,我们可以利用这一点进行排序,按某数据计算出值,在hashCode中返回就可以按该值大小存放。

③HashSet存储的弊端:

存入对象后,如果修改了对象中与哈希值计算有关的数据从而改变哈希值,导致与记录的哈希值不想等,这将导致我们取不出该对象或者删除不掉而内存泄露。

Removecontains的操作,他们在执行时,会按保存时的方式计算出哈希值,从而找到相应元素,若这时哈希值被改,将找不到元素,以致于该元素无法获取或删除。

 

(2)    TreeSet

特点:底层结构是二叉树结构,会自动排序,要求传入的引用类型具有可比性,其底层是依靠TreeMap来实现。

     二叉树的存储方式

其实具体应该是红黑二叉树,即每棵子树都是左小右大(或左大右小),将存入元素(可比元素)二分,通过compareTo返回值确定元素该存入哪边子树,构架成值二分的结构,这样的存储结构在元素多时,进行查询能提高效率,查询时有点像二分查找了。

 

     TreeSet的排序的方式:

二叉树的结构要求两元素得具可比性,必须得有个比较值,若返回的是正数,则比较元素前者大于后者,负数则相反,若相等则表示相等,不进行存储。

我们可以通过改写这个比较只达到人为控制顺序,如逆序。

 

第一种方式:让元素自身具备比较性。元素类需要实现Comparable接口,覆盖compareTo方法。这种方式也成为元素的自然顺序,或者叫做默认顺序。

第二种排序方式:

    TreeSet增加一个自定义的比较器。当元素自身不具备比较性时,或者具备的比较性不是所需要的,这时就需要让集合自身具备比较性。在集合初始化时,就有了比较方式。自定义一个类实现Comparator接口,复写compare方法,在创建TreeSet时,将该类对象传入TreeSet的构造函数时即可。当然compare中的比较数据应该与元素相关。

 

两种方式同时存在时,以自定义的比较器为主,即第二种排序方式。

         以下是两种方式的代码:

import java.util.*;

/*
 * 程序中存在两种比较方式。
 * 方式一:元素自身具备比较性,
 * 比较依据为年龄,
 * 方式二:还自定义了个比较器MyCompare,比较依据为名字(字串的字典序)
 * 通过输出结果可知,最终顺序取决于自定义的比较器
*/
class Test
{
           public static void main(String[] args) 
           {
              //构造函数中传入MyCompare自定义比较器的对象。
              //若元素也具备比较性,这时比较依据比较器MyCompare
              TreeSet ts = new TreeSet(new MyCompare());
               ts.add(new Student("a1",25));
              ts.add(new Student("a2",22));
              ts.add(new Student("a0",22));
              ts.add(new Student("a4",23));
              ts.add(new Student("a4",21)); 

              Iterator it = ts.iterator();
              while(it.hasNext())
              {
                     Student stu = (Student)it.next();
                     System.out.println(stu.getName()+"..."+stu.getAge());
              }
           }
}
//方式一:元素的具备比较性,强制让学生具备比较性
class Student implements Comparable
{
           private String name;
           private int age;
           Student(String name, int age)
           {
              this.name = name;
              this.age = age;
           }
           public int compareTo(Object obj)
           {
              //复写compareTo,人为控制排序。返回1,表示输入顺序=输出顺序。
              //返回-1.为逆序。返回0,只会输出一个结果,相等不会存入。
              //return 1;                                   
              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;
           }
           public String getName()
           {
              return name;
           }
           public int getAge()
           {
              return age;
           }
}
//方式二:定义比较器
class MyCompare implements Comparator
{
           public int compare(Object o1, Object o2)
           {
              Student s1 = (Student)o1;
              Student s2 = (Student)o2;
              int num =  s1.getName().compareTo(s2.getName());
              if(num == 0)
              {
                     return new Integer(s1.getAge()).compareTo(s2.getAge());
                     /*if(s1.getAge() > s2.getAge())
                            return 1;
                     if(s1.getAge() == s2.getAge())
                            return 0;
                     return -1;*/
              }
              return num;
           }
}

 ③补充一个主意点,在论坛跟别人讨论一个问题时得出的,

在上述代码中,add的添加部分,其比较顺序是拿当前的Student对象与TreeSet中上一个Student来比较,

如:(下面add方法参数简写,明白就行)

有s1和s2两个Student对象,s1先被add(s1)添加,当添加s2时,TreeSet会让其进行比较,其方式应该是

int retVal  =  s2.compareTo(s1);

而TreeSet只做一个管理的容器,根据返回值retVal,再将s2对象放入对应顺序的位置。

那么在添加过程中,添加一个String类型(非第一个),像add(String);,当然String是具可比性的,

那么应该是String.compareTo(Student)这样比较,是调用了String的compareTo,

所以会出现Student向String类型不能转换的异常错误:

Exception in thread "main" java.lang.ClassCastException: com.Test.student cannot be cast to java.lang.String,

而不是我们自己写的throw new RuntimeException("不是学生对象");异常。


5、  Map集合

MapCollecton不同,它是双值存储,即键—值<KV>成对存储。键保持唯一性,对应一个值。

//方法:
//增:
V put(K key, V value)  //添加新元素,如果键已存在,覆盖原有元素的值并且返回它。否则返回null。
void putAll(Map<? extends K,? extends V> m)   //集合之间的Copy 

//删:
V remove(Object key)   //根据一个键删除其对应的元素,如果删除成功反正该元素对应的值。否则返回null。
void clear()                    //清空所有映射关系 

//判断:
boolean containsKey(Object key)               //是否包含键
boolean containsValue(Object value)           //是否包含值
//取:
V get(Object key)                           //根据键获取值,如果键不存在返回null 

//由于Map为键值对存储,没有实现Iterator的迭代器功能,这里采取转移到Set集合的方式取出数据,下面将会介绍:
Set<K> keySet()                          //取出所有键值存入一个Set集合中
Set<Map.Entry<K,V>> entrySet()       //将Map元素映射到Map内部类Entry的对象中,再将这个对象映射到一个新的Set集合中。  

下面说下Map的子类HashTable,HashMap,TreeMap:

(1)    HashMap

特点:底层是哈希表结构,无序存放,键值不允许重复,允许存入null键和null值,线程不同步。

HashMap集合保持键唯一性的方法与HashSet一样,只是变成双列值存储了,其实HashSet的底层就是HashMap

代码:
import java.util.*;
class MapDemo1
{
  public static void main(String[] args) 
  {
         Map<String,String> map = new HashMap<String,String>();

         map.put("01","a1");
         map.put("02","a2");
         map.put("03","a3");
         map.put("04","a4");
 
         //先获取map集合的所有键的Set集合,keySet();
         Set<String> keySet = map.keySet();
         //有了Set集合,就可以获取其迭代器。
         Iterator<String> it = keySet.iterator();
         while(it.hasNext())
         {
                String s = it.next();
                //有了键可以通过map集合的get方法获取器对应的值
                System.out.println("keySet: "+s+" ==> "+map.get(s));
         }
}
}

 

HashMapHashTable的比较:

比较点

HashMap

HashTable

推出时间

JDK1.2

JDK1.0

性能

异步处理方式,高效

同步处理方式,低效

线程安全

非线程安全

线程安全

 

(2)    TreeMap

特点:底层是二叉树结构,可以排序的Map,按键排序,键不允许重复,线程不同步。

 

TreeMapTreeSet类似,只不过TreeMap是键-值存储,要求键必须具备比较性,若是键是自定义对象,要么让对象自身具备比较性,实现Comparable,复写compareTo方法,要么让TreeMap具备比较器,自定义一个比较器类,实现Comparator接口,复写compare方法,在新建TreeMap的构造函数中传入比较器对象即可。

 

两者存在,以比较器为主。

 

泛型初识:格式<Object obj><>中的类型为限定作用,保证集合中的所有元素都是该类型,在后面篇幅中具体讲解。

下述代码加了泛型:

/*

  需求:对学生对象的年龄进行升序排序。

  其原理类似TreeSet,只不过TreeMap要就比较的是键

  这里加了<E e>括号的泛型,这里简单说是对存入元素起限定作用,

  保证元素为一个类型。

*/
import java.util.*; 
//实现Comparable接口,Student具备比较性。
class Student implements Comparable<Student>
{
  private String name;
  private int age; 
  Student(String name,int age)
  {
         this.name = name;
         this.age = age;
  }
  //复写compareTo
  public int compareTo(Student t)
  {
         int num = age - t.getAge();
         return num;
  }
  public String getName()
  {
         return name;
  }
  public int getAge()
  {
         return age;
  }
}
//定义比较器
class Comp implements Comparator<Student>
{
  //复写compare
  public int compare(Student t1,Student t2)
  {
         int num = t1.getName().compareTo(t2.getName());
         if(num == 0)
         {
                return t1.getAge() - t2.getAge();
         }
         return num;
  }
}
class TreeMapDemo
{
  public static void main(String[] args) 
  {
         //传入比较器对象,比较器存在,采用比较器的方式排序
         Map<Student,String> map = new TreeMap<Student,String>(new Comp());
         map.put(new Student("a",11), "aaaaaaaaa");
         map.put(new Student("b",13), "bbbbbbbbb");
         map.put(new Student("c",12), "ccccccccc");
         map.put(new Student("a",11), "vvvvvvvvv");//键重复,同时会替换旧值。
         map.put(new Student("a",10), "eeeeeeeee");
        //map不具备迭代性,map键序列存入set中,在利用Map的get方法取键的值
         Set<Student> keySet = map.keySet();
        Iterator<Student> it = keySet.iterator();
         while(it.hasNext())
         {           
               Student t = it.next();
               System.out.println(t.getName()+"..."+t.getAge()+"..."+map.get(t));
         }
  }
} 

(3)    Map.Entry

其实Entry也是一个接口,它是Map接口中的一个内部接口。

MapHashMap中的源码

interface Map
{
              public static interface Entry
              {
                     public abstract Object getKey();
                     public abstract Object getValue();
              }
}
class HashMap implements Map
{
              class Hash implements Map.Entry
              {
                     public Object getKey(){}
                     public Object getValue(){}
              }
}

之所以要定义在Map接口内部,是因为要先有Map映射关系,接口Entry才偶意义。并且方便访问map内部数据。

应用:

import java.util.*;
class MapDemo1
{
  public static void main(String[] args) 
  {
         Map<String,String> map = new HashMap<String,String>(); 
         map.put("01","a1");
         map.put("02","a2");
         map.put("03","a3");
         map.put("04","a4");
         //取出map的映射关系存入Set中。
         Set<Map.Entry<String,String>> entrySet = map.entrySet();
          Iterator<Map.Entry<String,String>> et = entrySet.iterator(); 
         while(et.hasNext())
         {
                Map.Entry<String,String> me = et.next(); 
                String key = me.getKey();
                String value = me.getValue(); 
                System.out.println("entrySet: "+key+" ==> "+value);
         }
  }
}

6、  Iterator迭代器

用迭代器对集合进行输出是标准的做法,因为集合的内部由专门的接口,所谓迭代,就是将元素一个个进行判断,有内容就输出。

 

迭代器就是集合的一种取出方式,由于集合存储的引用型类型任意,他们存取的方式也有所不同,那么对于他们的存取操作不足以用一个方法来描述,需要多种操作,则封装成类来描述,而这个类定义在集合内部方便取集合的元素。那么迭代器就是集合的内部类。我们将各种集合的取出操作的共性抽出,封装成Iterator接口,而集合提供返回Iterator对象的方法Iterator()。那么这个对象就可对集合进行取出操作。

//方法:
boolean hasNext()//判断是否有下一个值
E next()//取出当前元素
void remove()//移除当前元素

 实例代码在上面的例子中有用到,不在摆出。

注意:在使用迭代器时,集合自身不因同时操作集合,如在循环迭代时,集合调用添加或删除操作,这样破换了集合的内容,导致迭代终止。

ListIterator:双向迭代器,List集合的专属迭代器,这也与List底层为双向链表的结构有关,即可从后输出。

7、  工具类Collections和Arrays

工具类:一个全是静态方法的类,这里是专门对集合进行操作的工具类,不能创建对象,因方法全是静态,直接用类调用。

(1)    Collections

void sort(List<T> list)<>排序,要求T需具有比较性,即T为Comparable的实现类。

void sort(List<T> list,Comparator<? super T> c)

该方法为sort的重载,要求传入一个比较器,对T无限制。

 

binarySearch()二分查找,传入集合必须事先有序,返回key值下标,若无返回插入位置,以负数表示,若集合对象无比较性,可定义比较器。

 

reverseOrder():返回一个比较器,它强行逆转实现了Comparable接口的对象 collection 的自然顺序。不仅能逆转原序列,而且可以逆转一个现有的比较器。

public static <T> Comparator<T> reverseOrder(Comparator<T> cmp)

若传入一比较器则会逆转该比较器的比较结果。

(2)    Arrays

有方法asList , deepequalsfillsort等。

操作Array集合的工具类,依旧全是静态方法

 

asList(数组)将数组变成List

把数组变成List集合的好处:可以使用集合的思想和方法来操作数组中的元素。

如判断某元素是否存在,数组需遍历再确定是否存在,而变成List,调用封装的函数即可。

 注意:将数组变成集合,不可以使用集合的增删方法,因为数组的长度是固定的。

   但像containsgetindexOfsubList等不改变其长度的方法可以使用。

   如果使用了增删,如list.add("ss");那么会抛出UnsupportedOperationException异常。

 

String类型的ArrayList输出为正常元素序,而当数组为int型等基本数据类型,输出list时,只会输出器哈希值,所以在数组定义时定义为Integer型,由int自动装箱成Integer类型。

如果数组中的元素都是对象。那么变成集合时,数组中的元素就直接转成集合中的元素。

如果数组中的元素都是基本数据类型,那么将该数组作为集合的元素存在。只输出数组的哈希值。这其中的主要原因是集合只接收引用类型,不接受基本数据类型,<>中的int应改写成Integer

---------------------- ASP.Net+Android+IO开发S.Net培训、期待与您交流! ----------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值