黑马程序员_Java中的集合

来源:互联网 发布:千兆端口路由器排行 编辑:程序博客网 时间:2024/05/17 05:16

                    ----------------------android培训、java培训、期待与您交流! ----------------------

一、什么是集合?

集合是一个容器,是用来存储对象的。
为什么会有集合?
在面向对象的语言当中,一个事物用一个对应的实例对象进行描述,实例对象有很多,为了方便操作多个对象,需要将这些对象存储起来,
而集合就是一个常用的存储对象的工具。
数组和集合的区别:
集合存储对象,长度可以改变,存储的对象的类型也可以不同。集合只能操作对象
数组也可以存储对象,但长度是固定的,存储的对象的类型也不许相同。数组还可以操作基本数据类型

集合容器有很多种,为什么?
不同容器存储对象的方式(数据结构)不同。就象,ArrayList在内存中的存储是一个数组,LinkedList是一个链表,而HashSet是一个哈希表
这时,对对象进行不同的操作,就要使用不同的容器。
集合中存放的不是对象实体,而是对象的引用(地址)。要获取集合中的元素,必须通过迭代器迭代。
集合中的主要集合都是不同步的,真要用到多线程时,自己加锁太麻烦,Collections提供了方法让集合安全。
二、集合体系。
1.Collection集合体系--->存储多个 独立对象的容器
Collection<E> 最常用的是:ArrayList、LinkedList、HashSet
|--List:元素是有序的,元素可以重复。因为该集合体系有索引。
面试 |--ArrayList: 底层使用数组数据结构。特点:查询速度快,增删速度慢。线程不同步。
|--LinkedList:底层使用链表数据结构。特点:增删速度很快,查询速度稍慢。
|--Vector:   底层使用数组数据结构。线程同步的,被ArrayList替代,即使多线程也不使用Vector,
可以自己给ArrayList加锁,Java也提供了方法给ArrayList加锁。
|--Set:元素是无序的(存入和取出的顺序不一定一致),元素不可以重复。
|--HashSet:底层数据结构是哈希表。线程不同步。保证元素唯一性使用方法hashCode和equals
|--TreeSet:底层数据结构是二叉树。可以对集合中的元素排序。保证元素唯一用compareTo方法返回0

1.1使用注意:
1.List中,比较元素使用方法equals方法。所以List存储不重复的自定义对象时,自定义类要重写equals方法。
2.什么时候使用ArrayList和LinkedList?
对集合元素的操作,涉及到频繁的增删,用LinkedList
 涉及到增删但不频繁,用ArrayList
 涉及到了增删,也涉及到查询,建议用ArrayList。因为频繁的增删操作不常见,经常是做查询。
3.HashSet,如何保证元素的唯一性?
通过元素的两个方法,equals和hashCode来实现的。
会先比较hashCode值,相等时才会去比较equals。查询和删除也是使用这两个方法。
4.ArrayList和HashSet的区别
面试题 HashCode的作用?--->灵活应变:Java中有内存泄露吗?为什么?
/*HashSet的存储机制中,根据哈希值分成不同的区域。
* 1.存储时,先用hashCode方法计算出对象的哈希值,
* 根据计算出的哈希值再到相对应的区域中逐一取出已经存储的对象和要存储的对象用equals方法比较内容是否相同。
* 若内容相同就不存储了,而不是覆盖;内容不同,就存储该对象。

* 查询时,也是先用hashCode方法计算出要查询的对象的哈希值,根据计算出的值到相应区域用equals比较查找。

* 2.对于像rp1、rp2这种,同一类型的内容不同的两个对象,程序员应该让他们在内存中只存储一个,所以要重写hashCode方法,
* 但是当存储机制不是HashSet时,也就无所谓了,因为不会用到hashCode方法

* 3.当一个对象已经被存储到HashSet集合当中时,就不要再修改对象中参与计算哈希值的字段了,因为修改以后对象的哈希值变了,
* 在这种情况下,当用contains检索该对象时,会到新的哈希值对应的区域中查找,这就无法发现在旧区域中的对象,用remove删除
* 时,就会发生内存泄露*/
/*ArrayList的底层是数组结构的,存储时会插入到数组的尾部,查找时会从数组的开始依次向后查找。*/
5.TreeSet如何实现对元素的排序?
有两种方法:
方法一:让元素自身具有比较性。即,元素所属类,实现接口Comparable,覆盖int compareTo(Object)方法
方法二:让容器自身具有比较性。给集合添加一个比较器,在集合初始化时传入--->自定义一个类,实现接口Comparator,覆盖compare(obj1,obj2)方法
当元素自身不具备比较性,或者已有的比较性不是自己想要的,这时,使用方法二。
6.泛型。解决安全问题的一个安全机制。在集合框架中很常见。
泛型的引入:集合中可以存储不同类型的对象,当获取对象时,若进行强制类型转换就会抛出ClassCastException异常,而这个异常在运行时期
才能被发现,这不方便于程序员解决问题。为了将问题转换到编译时期,使用泛型来限定集合中只能存入某一类型的对象。
好处:将运行时期的问题,转移到了编译时期; 避免了强制转换的麻烦。
格式:<>内写上要存储的对象类型。ArrayList<String> al=new ArrayList<String>();
 泛型写在类上时,在整个类中有效。
 泛型写在方法上(放在返回值类型前),可以让不同方法操作不同类型的数据,而数据的类型还不确定。
 注意,静态方法的泛型不可以和类上的相同。如果静态方法操作的数据类型也不确定,可以在静态方法上,定义独立的泛型。
 泛型写在接口上 
 
1.2.各集合中的方法
1.2.1 Collection 该层次结构的根接口。该类容器存储的多个对象表示一组对象
Collection中的方法:(增删改查)
添加:boolean add(E e);
 boolean addAll(Collection<? extends E> c);
删除:boolean remove(Object o);清除一个对象
 boolean removeAll(Collection<?> c);删除一堆元素,并不是全部
 void clear();清空
判断:boolean isEmpty();-->相当于collection.size==0
 boolean contains(Object o);
 boolean containsAll(Collection<?> c);
 boolean equals(Object o);比较此 collection 与指定对象是否相等。
获取:int size();元素个数
 Object[] toArray() 将此 collection 转换成数组。
 boolean retainAll(Collection<?> c);取交集,且此集合内容也会改变,只存交集。
 Iterator iterator();获取集合中的迭代器
获取Collection集合中的元素:
要获取集合中的元素,只能通过迭代器。步骤:
1.获取集合内部的迭代器(Iterator接口)。
2.利用迭代器中的方法,获取元素。
Iterator迭代器中的三个方法:boolean hasNext();判断容器中是否还有元素
E next():获取迭代的下一个元素。
void remove():移除迭代器返回的最后一个元素
代码实现:第一种:Iterator it=arrayList.iterator();
 while(it.hasNext()){
 System.out.println(it.next());
 } 
 第二种:for(Iterator it=arrayList.iterator();it.hasNext(); ){
  System.out.println(it.next());
  }
迭代器:就是获取集合中元素的方式。每个集合容器中都有自己的迭代器,这样就可以直接访问集合内部的元素。
实际上,是在类中定义了一个内部类Itr实现了Iterator接口,然后在iterator方法中返回Itr的实例对象
         
1.2.2 List
List中的特有方法:凡是可以操作角标的方法都是该体系特有的方法。
增:boolean 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);获取指定位置上的元素
List<E> subList(int fromIndex,int toIndex);返回集合中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图
ListIterator<E> listIterator()返回List的迭代器,List中特有的迭代器。
int indexOf(Object o);
int lastIndexOf(Object o);
List集合特有的迭代器ListIterator:是Iterator的子接口
在迭代时,不可通过集合对象中的方法操作元素。因为会发生ConcurrentModificationException异常。
所以,在迭代时,想要操作元素只能用迭代器中的方法,可是Iterator中的方法是有限的,只能对元素进行判断、取出、删除操作。
要想进行其他操作如添加、修改等,就必须使用迭代器ListIterator。但,该迭代器只能用于List。
ListIterator特有方法:
boolean hasPrevious() 逆向遍历列表 
E previous() 返回列表中的前一个元素。 
void add(E e) 迭代时添加元素,添加到获取元素的后面
void set(E e) 迭代时修改元素,修改当前获取到的元素
int nextIndex() 
int previousIndex() 
代码测试:
ListIterator li=al1.listIterator();
while(li.hasNext()){
Object obj=li.next();
if(obj.equals("java02")){
//al1.remove(obj);//编译失败。在迭代时,不可通过集合对象的方法操作集合中的元素。
//it.remove();//Iterator迭代器只能进行删除
li.remove();//删除之后,就不能用set、add方法
/* li.set("j03");
li.add("aaa");*/
}
}   
1.2.3 Vector
 Vector的方法:凡是带有element的都是特有方法
  Enumeration<E> elements();获取集合的枚举,相当于迭代器。
1.2.4 LinkedList
LinkedList特有方法:
addFirst(); addLast();
getFirst(); getLast(); 获取元素但不删除。如果集合中没有该元素,会抛出异常NoSuchElementException
removeFirst(); removeLast(); 获取元素并删除。如果集合中没有该元素,会抛出异常NoSuchElementException
JDK1.6中出现了替代方法:
offerFirst(); offerLast();添加
peekFirst(); peekLast(); 获取。如果集合中没有该元素,返回null。
pollFirst(); pollLast(); 获取并删除。如果集合中没有该元素,返回null。
1.2.5 Set.底层使用了Map
Set中的方法和Collection一样。

2.Map集合体系--->存储多个 映射(两个对象关联)的容器
Map 特点:将键映射到值的对象.键和值一一映射,键不能重复。最常用
|--HashTable:底层数据结构是哈希表。不能存入null键null值。线程同步的。jdk1.1  存入null键null值。线程同步的。jdk1.1
|--HashMap:  底层数据结构是哈希表。可以存入null键null值。线程不同步的。jdk1.2。将HashTable替代。效率高
|--LinkedHashMap
|--TreeMap:  底层数据结构是二叉树。线程不同步。可以用于给Map集合中的键进行排序。


|--Properties
|--AbstractMap

2.1什么时候使用Map集合?
当数据之间存在着映射关系时,就要先想到Map集合。

2.2 各集合方法
2.2.1 Map。也是该层次的根接口
1.增加。
V put(K key,V value);存入一个键值对。如果键已经存在,就将旧值覆盖。该方法返回的是key之前对应的值,所以,第一次存入时返回null。
putAll(Map<? extends K,? extends V> m)
2.删除。
clear();
remove(Object key);根据键删除一个映射。注意,不能根据值删除。
3.判断。
isEmpty();
containsKey(Object key);
containsValue(Object value);
4.获取。
V get(Object key);根据键获取对应的值。注意:不能根据值获取对应的键。在HashMap中,可以根据null键获取到值,而HashTable不可以有null键。
只能根据一个键获取一个值,不能获取所有值。若有什么键都不知道怎么获取呢?
Set<K> keySet();将Map中所有的键存入到Set集合中。
Set<Map.Entry<K,V>> entrySet();将Map集合中的所有映射关系存入到Set中。

获取Map集合中的元素:Map中没有提供获取迭代器的方法。所以。将Map集合转换成Set,然后迭代取出。有两种方法:
方法一、将Map中所有的键存入到Set集合中,再根据get方法,获取每一个键对应的值。
方法二、将Map集合中的所有映射关系存入到Set中。再用映射关系的方法getValue、getKey获取键和值。
这个映射关系的类型是Map.Entry。其实,Entry也是一个接口,它是Map接口中的一个内部接口。
Map.Entry接口中的方法:getValue()
getKey();
V setValue(V vulue);将Map集合中的所有映射关系存入到Set中。
hashCode(); equals();

三、Collections 操作集合的工具类
引入Collections:List集合允许元素重复,但不能排序;Set集合能实现排序但不能元素重复。如果,既要重复又要排序。怎么办?
Java提供了工具类Collections,专用来操作集合的,可以实现这一功能。
Collections和Collection的区别?
Collection是Collection集合体系的根接口。Collections是专用来操作集合的工具类。
Collections类中的方法:
特点:该类中的方法都是静态的,所以泛型都要定义在方法上。

1.对集合排序。只能对List集合排序。
 static <T extends Comparable<? super T>> void sort(List<T> list):对List集合排序,List集合中的元素自身具备比较性。这就实现了,既要重复又要排序的功能。
  <T extends Comparable<? super T>>:List操作的元素类型T(例如:Student),要具备比较性就必须实现接口Comparable,但接口可能是继承自父类(Person)的(父类实现了的接口,子类就不用继承了,实际上也不能),
  排序时先找子类中的compareTo方法,子类中没有复写时,再找父类。
 static <T> void sort(List<T> list, Comparator<? super T> c) 根据指定比较器对List进行排序,这时T就不需要具备比较性了。
2.获取最大值。
 static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 元素自身具有比较性。根据元素的自然顺序,返回给定 collection 的最大元素。   
 static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) 根据指定比较器产生的顺序,返回给定 collection 的最大元素。
3.二分搜索法查找指定元素
 static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key):使用二分搜索法搜索指定列表,以获得指定对象。 
 static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)
4.替换集合中的元素
 static <T> void fill(List<? super T> list, T obj) 将集合中的所有元素替换成指定元素
 static <T> boolean replaceAll(List<T> list, T oldVal, T newVal):将集合中 指定的所有旧值 替换成 指定的新值。
  5.反转
   static void reverse(List<?> list) 
   static <T> Comparator<T> reverseOrder() 返回一个比较器,它强行逆转实现了 Comparable 接口的对象 collection 的自然顺序。
    例如:TreeSet<String> ts=new TreeSet<String>(Collections.reverseOrder());将TreeSet中元素的自然顺序强行逆转了 
 static <T> Comparator<T> reverseOrder(Comparator<T> cmp) 返回一个比较器,它强行逆转指定比较器的顺序。
  例如:TreeSet<String> ts=new TreeSet<String>(Collections.reverseOrder(new StrLenComparator()));将按长度排序的比较器强行逆转。
6.同步集合
 static <T> Collection<T> synchronizedCollection(Collection<T> c) 将指定 collection 转换成线程同步的并返回
 static <T> List<T> synchronizedList(List<T> list) 
          static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) 
          static <T> Set<T> synchronizedSet(Set<T> s) 
  7.交换元素
   static void swap(List<?> list, int i, int j);将两个指定位置上的元素交换
  8.随机重排集合元素
   static void shuffle(List<?> list) 使用默认随机源对指定列表进行置换。----->纸牌游戏 洗牌 
 static void shuffle(List<?> list, Random rnd) 使用指定的随机源对指定列表进行置换。 
四、Arrays 操作数组的工具类.
Arrays中的方法:都是静态的。
1. 将数组变成集合。
  static <T> List<T> asList(T... a); 返回一个固定大小的列表。 Arrays.asList(arr);
  将数组变成集合的好处:可以按照集合的方法来处理数组。如,判断数组是否包含某个值。数组时得遍历数组,集合一步搞定
  注意:1.将数组变成集合,不可以使用集合的增删方法。因为返回的集合大小是固定的。如果进行了增删,则抛出异常UnsupportedOperationException
     可以使用的方法:contains()、get()、indexOf()、subList().
    2.如果数组中的元素都是对象。那么变成集合时,数组中的元素就直接转换为集合中的元素。
     如果数组中的元素都是基本类型的。那么变成集合时,会把该数组作为集合中的元素。
     例:int[] nums1={1,2,3};  Integer[] nums2={1,2,3};
     List<int[]> li1=Arrays.asList(nums1);//输出:[[I@2453f89f]
     List<Integer> li2=Arrays.asList(nums2);//输出:[1, 2, 3]     
2. 将集合变成数组。
  Collection接口中的toArray方法:
    Object[] toArray(); 方法内自动创建了一个数组。
    <T> T[] toArray(T[] a); 存入指定类型的数组  a:存储此 collection 元素的数组
    注意:1.指定类型的数组要定义多长?
    创建一个刚刚好的最优,即,长度为集合的size
    因为,当指定数组的长度小于了集合的size,那么该方法内部会创建一个新的数组,长度为集合的size
     当指定数组的长度大于了集合的size,就不会创建新数组,而使用传入的数组。
     2.为什么要将集合变成数组?
     为了限定对元素的操作。不需要进行增删了。    
  3.查找。
   二分查找指定元素:static int binarySearch(byte[] a, byte key) 使用二分搜索法来搜索指定的 byte 型数组,以获得指定的值。 
static int binarySearch(byte[] a, int fromIndex, int toIndex, byte key)从指定区域查找。
   可以是任意类型数组:byte[].... 
 复制数组:复制整个数组:static boolean[] copyOf(boolean[] original, int newLength);original是要复制的数组,newLength是新数组的长度。
    数组类型任意:byte[]。。。
    复制数组的一部分:static boolean[] copyOfRange(boolean[] original, int from, int to) 
   深度比较: static boolean deepEquals(Object[] a1, Object[] a2) ;比较数组的内容是否相同。注意:传入的数组必须是对象的引用,不能是基本数据类型的数组。比较基本数据类型的数组用equals方法。
   比较数组: public static boolean equals(boolean[] a,boolean[] a2):以相同顺序包含相同的元素,则两个数组是相等的.如果两个数组都null,也是相等的。
   替换数组内容:static void fill(boolean[] a, boolean val)
    static void fill(boolean[] a, int fromIndex, int toIndex, boolean val)  
   排序:     static void sort(byte[] a) 
          static void sort(byte[] a, int fromIndex, int toIndex)不包含toIndex 
五、既操作数组又操作集合
增强for循环:既可以操作数组又可以操作集合 jdk1.5新特性
格式:for(数据类型 变量名:被遍历的集合(Collection)或 数组)
 { 。。。 }
注意:增强for对集合进行遍历。只能获取集合元素。不能对集合进行操作。
 迭代器除了遍历,还可以进行remove删除集合中的元素的操作。如果是用ListIterator迭代器,迭代时还可进行增加修改操作。
传统for循环和增强for循环有什么区别?
高级for有一个局限性。必须有被遍历的目标。
建议在遍历数组的时候,使用传统for循环。因为有角标。
 
                                     ---------------------- android培训、java培训、期待与您交流!

                                ----------------------详细请查看:http://edu.csdn.net/heima