集合
一、集合的概念
书面解释:集合是包含多个对象的简单对象,所包含的对象称为元素。集合里面可以包含任意多个对象,数量可以变化;同时对对象的类型也没有限制,也就是说集合里面的所有对象的类型可以相同,也可以不同。集合:数量不限、类型不限;数组:定长、类型单一。
个人理解:集合是数组的一种扩展,集合里面可以存放多种数据类型和对象,同时集合可自由扩充,也就是自由添加元素,而数组不能,这一点就远比数组强大。还可以借助数学中的集合来理解集合,集合中的元素不能重复。
二、数据存储结构分类
数组是顺序存储方式,而集合的存储方式就较为丰富强大,有:(1)顺序存储 (2)链式存储(3)树形存储(4)散列存储Hash (5)Map映射存储
三、集合框架
图中“实现”中的六中集合使用的比较多的,“历史集合类”的现在用的就比较少了。
1、集合框架中各接口的特点
1)Collection接口
Collection接口 是一组允许重复的对象
Collection接口用于表示任何对象或元素组。想要尽可能以常规方式处理一组元素时,就使用这一接口。
它还使用了Iterator接口,也就是Iterator迭代器,来枚举集合中的元素。做过acm的就很好理解了,简单的可以理解为:将集合中所有的元素都放在迭代器中(一个特写的容器),然后遍历里面所有的内容
实现类:HashSet、ArrayList、LinkedLis
例:
package cn.hncu.collection; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class CollectionDemo { public static void main(String[] args) { Collection<Object> col=new ArrayList<Object> (); //此处使用ArrayList()来做的话添加是什么顺序那么他存储就是什么顺序 // Collection col=new HashSet(); //此处使用HashSet()来做的话则存储方式是按照其hash值来的,位置不定, //但是总体排序还是:数字之间按照大小顺序排,字符串也按照字符的编码排;对象也同样适用 //增 col.add(1); col.add(2); col.add(6); col.add("Jack"); col.add("Tom"); //※注意,如果实现类是hashSet,那么所加入元素的存储位置是由各元素的hashCode值来定的 //此时,当Perosn类不覆盖hashCode时,person对象的位置是不定,因此这样它的hashCode是对象的内存地址。 //综上,想当对象的位置确定,则需要覆盖hashCode()方式 //如果元素是类对象,那么它的hash值就是hashCode()方法的返回值, //因此如果你写的hashCode()方法返回相同的值,则认为是相同的元素,加不进的 col.add(new Person("John", 21)); col.add(new Person("Jane", 19)); //删 // col.remove(2); // col.add("aa"); //改1 // col.remove(1);col.add(1); //改2 //这种修改方式,假如使用的是HashSet的话不管怎么改还会是按照hashcode值进行排序存储, //而如果使用的是ArrayList的话需要修改的元素原来在什么地方修改后还会是在什么地方; //对于上面的改1同样适用次原理 Object objs[]=col.toArray(); col.clear(); for (int i=0;i<objs.length;i++){ if (objs[i].equals(1)){ objs[i]="100"; } col.add(objs[i]); } Iterator<Object> it=col.iterator(); while (it.hasNext()){ Object obj=it.next(); System.out.println(obj); } } }
2)set接口
按照定义,Set接口继承Collection接口,而且它不允许集合中存在重复项。所有原始方法都是Collection中现成的,没有引入新方法。具体的Set实现类依赖添加的对象的equals()方法来检查等同性。
Set添加元素时,一定会执行被添加元素的hashCode()方法,凭此来排序。
实现类:HashSet、TreeSet
3)List接口
List接口继承了Collection接口以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。面向位置的操作包括插入某个元素或Collection的功能,还包括获取、除去或更改元素的功能。在List中搜索元素可以从列表的头部或尾部开始,如果找到元素,还将报告元素所在的位置(这个功能很强大,可以进行位置的操作)。
对List及其实现类的对象进行增删改查时可直接进行位置操作指定对具体位置进行操作。如e.add(1,800);为在1位置插入整数800,原来的1位置及后面的元素会全部往后移动一个位置;还如e.removeFirst();和e.removeLast();为在最前面和最后面移除一个元素(这里的remove是有返回值的,若要使用则需要定义一个变量接收),进行完增删改查操作后都要重新进行排序。
A)使用List(如ArrayList)时,不会自动调用hashCode()方法。因为在List中,重复了就重复了,不需判断。
B)List中添加了下标index的功能,这样对List的修改可以利用set方法对指定位置的元素直接进行替换,不需要像Set那么复杂(要转换成数组才能修改,之后还要转换回去)。
C)Collection用Iterator迭代器,而List可以用ListIterator列表迭代器。前者只能next(),后者不但包含next()方法,还包含previous()方法。因此,如果要用List做类似书的翻页功能,不但可以向后翻,还可以向前翻。
实现类:ArrayList、LinkedList
4)Map接口
Map接口不是Collection接口的继承。而是从自己的用于维护键-值关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。
可以把这个接口方法分成三组操作:改变、查询和提供可选视图。
改变操作允许从映射中添加和除去键-值对。键和值都可以为null。但是,不能把Map作为一个键或值添加给自身。
Map.Entry接口(Entry就是Map接口中的内部类):Map的entrySet()方法返回一个实现Map.Entry接口的对象Set集合,其中每个对象都是底层Map中一个特定的键-值对。
实现类:HashMap、TreeMap
2、实现类
1)ArrayList、LinkedList
使用两种List实现的哪一种取决于特定的需要:如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素,那么,ArrayList提供了可选的集合。
但如果要频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么LinkedList实现更好。
boolean | add(E e)
将指定的元素添加到此列表的尾部。 |
void | add(int index,E element)
将指定的元素插入此列表中的指定位置。 |
E |
get(int index)
返回此列表中指定位置上的元素。 |
boolean | isEmpty()
如果此列表中没有元素,则返回 true |
E |
remove(int index)
移除此列表中指定位置上的元素。 |
E |
set(int index,E element)
用指定的元素替代此列表中指定位置上的元素。 |
int | size()
返回此列表中的元素数。 |
注:此外,LinkedList添加了一些处理列表两端元素的方法,使用这些方法,可以轻松地把LinkedList当作一个堆栈、队列或其它面向端点的数据结构。
void | addFirst(E e)
将指定元素插入此列表的开头。 |
void | addLast(E e)
将指定元素添加到此列表的结尾。 |
E |
getFirst()
返回此列表的第一个元素。 |
E |
getLast()
返回此列表的最后一个元素。 |
E |
removeFirst()
移除并返回此列表的第一个元素。 |
E |
removeLast()
移除并返回此列表的最后一个元素。 |
2)HashMap、TreeMap
使用哪种实现取决于特定需要:
在Map中插入、删除和定位元素,HashMap是最好的选择。但如果要按顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()实现(助理解:Map.keySet返回的是键的Set集合,而Set集合对hashCode实现有限制,因此作为键的类也要遵守该限制)。有了TreeMap实现,添加到映射的元素一定是可排序的。
注意:HashMap和TreeMap都实现Cloneable接口。
当需要遍历当中的元素时,key视图和entrySet视图遍历前都需要先获得Map的映射放入到集合中去再使用迭代器去遍历,下面以keySet视图为例贴小块代码(entrySet视图也类似):
Set<Object> entries=map.entrySet();
Iterator<Object> it2=entries.iterator();
while (it2.hasNext()){
Entry<Object, Object> entry=(Entry)it2.next();
System.out.println("key="+entry.getKey()+",value="+entry.getValue());
}
而value视图则不同,它用的是Collection接口,下面也贴小块代码:
Collection<Object> value=map.values();
Iterator<Object> it3=value.iterator();
while (it3.hasNext()){
Object obj=it3.next();
String str=obj.toString();
System.out.println(str);
}
3)HashSet类和TreeSet类
“集合框架”支持Set接口两种普通的实现:HashSet和TreeSet。在更多情况下,会使用HashSet存储重复自由的集合。考虑到效率,添加到HashSet的对象需要采用恰当分配散列码的方式来实现hashCode()方法。当需要从集合中以有序的方式抽取元素时,TreeSet实现会有用处。为了能顺利进行,添加到TreeSet的元素必须是可排序的。
因为TreeSet是排序的,而排序就要用到被排序元素的排序方法(compareTo)----Comparable接口中的抽象方法,因此使用TreeSet,那么加入它当中的对象元素必须实现该接口(而非对象的元素的hashCode为元素本身),而因为HashSet不排序所以加入集合的元素不用实现Comparable接口。
补充(注意):TreeSet调用add()方法时,会让新添加的元素调用compareTo(obj)方法,依次把已有的元素作为参数传入,进行决定大小,由上一句,决定放在TreeXXX集合当中的元素要实现Comparable接口。但当添加的元素和已有的元素类型不一致时,新添加的元素的compareTo(obj)方法会强制将已有的元素转换成它的类型来比较,就会出现ClassCastException异常。
如(Employee为已经写好的类):
set.add(111);
set.add("aaa");
set.add("bbb");
set.add( new Employee("张三", 22));
set.add( new Employee("李四", 25));
set.add( new Employee("Jack", 23));
四、集合排序
方法1:
java.lang.Comparable接口适用于一个类有自然顺序的时候。假定对象集合是同一类型,该接口允许把集合排序成自然顺序。
该接口中的int compareTo( T obj )方法: 比较当前实例对象与对象obj,如果位于对象obj之前,返回负值;如果两个对象在排序中位置相同,则返回0;如果位于对象obj后面,则返回正值。
该排序方法的实现要点:让被放置到容器的对象类实现Comparable接口。由其中所实现的方法compareTo( )决定对象之间的排列顺序。
方法2:
java.util.Comparator接口适用于一个类有自然顺序的时候。假定对象集合是同一类型,该接口允许把集合排序成自然顺序。
该接口中的compare ( T o1, To2)方法: 比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负、零或正整数
◆该排序方法的实现要点:让容器在构造时加入比较器对象。
◆中文排序问题:
比较函数对于英文字母与数字等ASCII码中的字符排序都没问题,但中文排序则明显不正确。这主要是Java中使用中文编码GB2312或GBK时,char型转换成int型的过程出现了比较大的偏差。这偏差是由compare方法导致的,因此我们可以自己实现Comparator接口。另外,国际化问题可用Collator类来解决。
java.text.Collator类,提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。
下面是关于Comparator的例子:
package cn.hncu.sort3; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.Map.Entry; import java.text.CollationKey; import java.text.Collator; import java.util.Comparator; public class Sort3Demo { public static void main(String[] args) { Comparator<Object> comparator2=new MyCmp2(); TreeMap<Object, Object> map=new TreeMap<Object, Object>(comparator2); map.put("周军", "1001"); map.put("张军", "1002"); map.put("李fg军", "1003434"); map.put("刘一军", "1004555"); Set entries=map.entrySet(); Iterator<Object> it=entries.iterator(); while (it.hasNext()){ Map.Entry entry=(Entry) it.next(); System.out.println("key="+entry.getKey()+",value="+entry.getValue()); } } } class MyCmp2 implements Comparator{ @Override public int compare(Object o1, Object o2) { Collator collator=Collator.getInstance(); CollationKey key1=collator.getCollationKey(o1.toString()); CollationKey key2=collator.getCollationKey(o2.toString()); return key1.compareTo(key2); // return key2.compareTo(key1);//反向 } }
集合排序的应用场合:
在JDK1.2中,有14个类实现了Comparable接口,这些类中指定的自然排序如下:
1、BigDecimal,BigInteger,Byte,Double,Float,Integer,Long,Short按数字大小排序
2、Character 按Unicode值的数字大小排序
3、CollationKey 按语言环境敏感的字符串排序
4、Date 按年代排序
5、File 按系统特定的路径名的全限定字符的Unicode值排序
6、ObjectStreamField 按名字中字符的Unicode值排序
7、String 按字符串中字符Unicode值排序
如果一个类不能或不便于实现java.lang.Comparable接口,则可以用实现比较器Comparator接口的方法提供自己的排序行为。同样,如果缺省Comparable行为满足不了工程需要,也可以提供自己的Comparator。