《疯狂Java讲义》读书笔记(五):Java集合

第八章 Java集合

1Java集合大致可以分为SetListQueueMap四个体系。其中Set表示无序、不可重复的集合;List表示有序、重复的集合。Map则代表具有映射关系的集合,Queue体系代表一种队列集合实现。Map里的key是不可以重复的。访问List集合的元素可以根据索引来访问,访问Map元素可以根据key来访问value访问Set集合只能根据元素本身来访问,这也是Set集合里元素不允许重复的原因。常用的集合有:Set:(HashSetTreeSet);List:(ArrayListLinkList);Queue:(ArrayDeque);Map:(HashMapTreeMap)。

2CollectionIterator接口:CollectionListSetQueue的父接口,接口里定义的方法可以操作它们:

boolean add(obj);添加元素;②boolean addAll(Collection c);把集合c里的所有元素添加到指定集合里。③void clear();清除集合里的所有元素,集合长度将变成0。④boolean contains(obj);集合里是否包含指定元素;⑤boolean containsAll(Collection c);集合里是否包含集合c的所有元素;⑥boolean isEmpty();返回集合是否是空的,长度为0true;⑦Iterator iterator():返回一个遍历集合的对象;⑧boolean remove(obj);删除集合里指定元素,如果包含多个,则只删除第一个;⑨boolean removeAll(Collection c);从集合里删除集合c里包含的所有元素;(10boolean retainAll(Collection c);从集合中删除集合c里不包含的元素。(11int size();返回集合元素的个数;(12Object[] toArray();把集合转换成一个数组,所有集合元素变成对应数组的元素。

例子:Collection c=new ArrayList();//利用多态

c.add("小李");c.add(5);//集合里不能放基本类型的值,但Java会自动装箱

使用System.out.println(c.toString());可以一次性输出集合对象,因为Collection类重写了toString方法。

使用Iterator接口遍历集合

Iterator iterator=ct.iterator();

while(iterator.hasNext()){

String next=iterator.next().toString();

System.out.print(next+" ");

if(next.equals("Johnson")){

iterator.remove();}}

System.out.println("Iterator的remove后输出。。");

System.out.println(c);

//小李 小红 Johnson biandan 25    Iterator的remove后输出。。   [小李, 小红, biandan, 25]

注意:Iterator必须依附与Collection对象,iterator.remove()删除的是集合中上一次next方法返回的集合元素,即遍历到谁,谁就被删除掉。但是在遍历过程中,不能使用c.remove(next);来删除集合元素,会引发异常。c是集合对象,不能让集合对象来亲自操作删除动作,只能使用Iterator的对象来操作。(只能借刀杀人,不能自己动手。)

还有,接口是不能new一个对象实例的,用于被其它类继承和实现。

使用foreach循环遍历集合元素:for(Object obj:ct){System.out.print(obj+" ");}//注意,ctObject类型。

Set集合:

Set集合不能记住元素的添加顺序,但不允许元素重复如果试图把两个相同的元素加入同一个Set集合,则添加失败,新元素不会被加入,但是编译能够通过。这是Set集合的通用知识。适用于HashSetTreeSetEnumSet

HashSet类:HashSetSet接口的典型实现,大多数时候使用它。它按Hash算法来存储集合中的元素,有较好的存取和查找性能。有如下特点:①不能保证集合的顺序,顺序会错乱;②HashSet不是同步的,如果多个线程访问同一个集合,加入有两个线程同时修改了HashSet集合,必须通过代码来保证其同步;③集合元素可以是null如果两个元素通过equals()方法返回true,但是它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。也就是说,HashSet判断两个元素相等标准是通过equals()方法和hashCode()方法返回值都相等来判断的。因此注意:把一个对象放入HashSet中时,如果需要重写该对象对应类的equals方法,则也应该重写其hashCode方法。规则是:两个对象通过equals方法返回值相同,则hashCode返回值也应相同。当从HashSet中访问元素时,它会先计算该元素的hashCode值(也就是调用该对象的hashCode方法的返回值),然后直接到该hashCode值对应的位置去取出该元素——这就是HashSet速度很快的原因。

重写hashCode方法的基本规则:①程序运行过程中,同一个对象多次调用hashCode方法的返回值应该都相同;②两个对象通过调用equals方法比较返回true时,这2个对象的hashCode方法返回值应该要相同。

当想HashSet添加可变对象时,要十分小心。如果修改HashSet集合中的对象,有可能导致该对象与集合中的其它对象相等,从而导致HashSet无法准确访问该对象。比如:HashSet原来有5个对象各不相同,取出第一个并赋值与第二个一样的值,就会导致HashSet无法准确访问该对象。例子:

HashSet set=new HashSet(); 

set.add("李白"); 

set.add("杜甫"); 

for(int k=0;k<6;k++){set.add(k);}

System.out.println(set);//输出:[0, 1, 2, 李白, 3, 4, 5, 杜甫] 解释:HashSet存储元素的位置无规律,按照算法来存储。

LinkedHashSetHashSet还有一个子类,它使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合元素。它需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素有很好的性能,输出LinkedHashSet集合里的元素时,元素的顺序总与添加的顺序一致。LinkedHashSet也不允许元素重复。例子:

LinkedHashSet set=new LinkedHashSet(); 

set.add("李白"); 

set.add("杜甫"); 

for(int k=0;k<6;k++){set.add(k);}

System.out.println(set);//输出:[李白, 杜甫, 0, 1, 2, 3, 4, 5] 解释:LinkedHashSet存储元素是按照加入的顺序的。

TreeSet类:TreeSetSortedSet接口的实现类,TreeSet可以确保元素处于排序状态,与HashSet对比,它还有如下几个方法:

Comparator comparator():如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator;如果TreeSet采用自然排序,则返回null;②Object first():返回集合元素的第一个;③Object last():返回集合最后一个元素;④Object lower(Object e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素。)⑤Object higher(Object e):返回集合中位于指定元素之后的最小元素。⑥SortedSet subSet(Object fromE,Object toEle);返回Set的子集合,从from(包括)到to(不包括)。⑦SortedSet headSet(Object toEle):返回小于toEle的元素的集合;⑧SortedSet tailSet(Object fromEle);返回此Set的子集,大于或等于fromEle的元素集合。

TreeSet并不是根据元素的插入顺序排序,而是根据元素的实际大小排序的。它采用“红黑树”的数据结构来存储集合元素。1、自然排序:TreeSet调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按照升序排序。存放到TreeSet集合的元素应该是同一个类的实例,否则引发ClassCastException异常。对于2个元素是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较是否返回0=0是相等。当两个对象通过equals方法返回true时,compareTo方法应该返回0

2、定制排序:TreeSet的自然排序是根据集合元素的大小,默认升序排序,如果需要实现定制排序,例如降序排序,则可以通过Comparator接口的帮助。例子:

TreeSet set=new TreeSet();

set.add("李白");

set.add("杜甫");

for(int k=0;k<30;k++){set.add(k+"");}

System.out.println(set);//输出:[0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20,...李白, 杜甫] 解释:如果不把k转为String类型,程序会报错。集合元素会排序,排序的效果如上,并不是从123 顺序排序的。

EnumSet类:EnumSet类是专门为枚举类设计的集合类,EnumSet中的元素都必须是指定枚举类型的枚举值。EnumSet的元素也是有序的,以枚举值在EnumSet类内的定义顺序来决定集合元素的顺序。内部以向量形式存储,非常紧凑,高效,因此EnumSet对象占用内存小,运行效率好,尤其是批量操作,如调用containsAll()方法。EnumSet不允许加入null值,如果试图加入null值,将抛出NullPointerException异常。如果想判断EnumSet里是否有null值,返回都是false。例子:

新建一个枚举类:enum Season {Spring,Summer,Fall,Winter}

main方法:

EnumSet es=EnumSet.allOf(Season.class);

System.out.println(es);//输出:[Spring, Summer, Fall, Winter]

Set实现类的性能分析:

HashSetTreeSetSet2个典型实现,到底如何选择?HashSet的性能总是比TreeSet的好(特别是添加、查询元素等操作),只有需要当一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSetHashSet还有一个子类:LinkedHashSet,对于普通的插入、删除操作,LinkedHashSetHashSet略慢,这是由维护链表所带来的额外开销决定的,但有了链表,遍历LinkedHashSet会更快。EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。上述所有的Set集合类都是线程不安全的,可以通过Collections工具类的synchronizedSortedSet方法来“包装”该Set集合,最好在创建时进行,以防止对Set集合的意外非同步访问,如:

Set set=Collections.synchronizedSet(new HashSet());Set tSet=Collections.synchronizedSortedSet(new TreeSet());


List集合:

List集合代表一个元素有序、可重复的集合,集合中每个元素都有对应的顺序索引。默认索引从0开始。可以使用Collection接口的全部方法,因为是它的子接口。此外还有一些特别的方法:

①void add(int index,Object obj);将元素插入到List集合的index处。后面的元素索引都+1;②Object get(int index):返回集合index索引处的元素;③int indexOf(Object obj):返回对象obj在List集合中第一次出现的索引位置;④int lastIndexOf(Object obj):返回对象在List集合最后一次出现的索引;⑤Object remove(int index)删除并返回索引处的元素,如果传递的参数是obj,则总是删除第一个匹配的元素⑥Object set(int index,Object obj):将index索引处的元素替换成obj,返回被替换的旧元素;⑦List subList(int fromIndex,int toIndex)返回从索引from处(包含)到索引toIndex(不包含)所有集合元素的子集合。⑧void sort(Comparator c):根据Comparator参数对List集合的元素排序。Java 8为List增加了sort()和replaceAll()方法。

使用ListIterator迭代List集合:

ListIterator lit=list.listIterator();

或者

Iterator it2=list.iterator(); 

或者

for(Object it3:list)  

以上三种常用方法。     

 while(lit.hasNext()){System.out.println(lit.next());}

 

ArrayListVector实现类:

如果向ArrayList或者Vector添加大量元素时,可以使用ensureCapacity(int minCapacity)方法一次性地增加大小,这样可以减少分配的次数,从而提高性能。默认分配是10个长度。实际上,Vector具有很多缺点,因此少用或者不用Vector实现类。

ArrayList是线程不安全的,

Queue集合:

用于模拟队列这种数据结构,队列通常先进先出(排队的时候,先排的会先到),新元素会插入到队列的尾部,访问元素poll操作会返回队列头部的元素。通常队列不允许随机访问队列中的元素。有如下方法:①void add(Object obj)将指定元素添加队列的尾部;②Object element()获取队列的头部元素,但不删除;③boolean offer(Object obj)将指定元素加入此队列的尾部,当使用容量有限的队列时,此方法通常比add方法好;④Object peek()获取队列头部元素,但不删除,如果为空,返回null;⑤Object poll()获取队列头部元素,并删除该元素,如果为空,返回null;⑥Object remove()获取队列头部元素,并删除该元素。

PriorityQueue是一个比较标准的队列实现类,但并不是非常标准,违反了先进先出(FIFO)的原则:它按照元素的大小进行重新排序,取出元素时,不是按照先进先出原则,而是取出队列中最小的元素。例子:

Queue queue=new PriorityQueue();

queue.add(6);

queue.add(20);

queue.add(-8);

queue.add(2);

System.out.println(queue);//输出:[-8, 2, 6, 20]

 

Deque接口与ArrayDeque实现类:

Deque接口是Queue接口的子接口,它代表双端队列,提供一个典型的实现类:ArrayDeque。当程序需要使用“栈”这种数据结构时,推荐使用ArrayDeque,尽量避免使用Stack——因为Stack是一种古老的集合,性能较差。ArrayDeque可以当做队列使用,也可以当做栈使用。当做队列使用按照“先进先出”的方式。

 

LinkedList实现类:

LinkedList类是List接口的实现类,意味着它是一个List集合,可以根据索引来访问元素,除此之外,LinkedList还实现了Deque接口,可以被当成双端队列来使用,因此可以当做栈来使用,也可以当做队列使用。LinkedListArrayListArrayDeque的实现机制完全不同,ArrayListArrayDeque内部以数组的形式来保存集合,因此随机访问集合元素性能较好。而LinkedList内部以链表的形式来保存集合的元素,因此随机访问元素时性能较差,但是在插入、删除元素时性能出色(只需改变指针所指的地址即可)。

说明:ArrayListArrayDeque这种内部基于数组的集合,使用随机访问的性能比Iterator迭代访问的性能要好,因为随机访问会被映射成对数组的元素访问。

各种线性表的性能分析:

List中以ArrayListLinkedList两种典型为代表ArrayDequeLinkedList集合不仅提供了List的功能,还提供了双端队列、栈的功能。内部以数组作为底层实现的集合随机访问性能较好,内部以链表作为底层实现的集合插入、删除的性能较好。总体来说:ArrayList的性能比LinkedList的性能要好,因此大部分情况选用ArrayList注意以下几点:

①要遍历List集合,对于ArrayList,应该使用get(随机访问的方法)遍历,这样效率高,对于LinkedList集合,应该使用Iterator迭代器访问;②经常执行插入、删除等操作,优先考虑LinkedList;③多线程情况下,可以使用Collections将集合包装成线程安全集合。

技巧:①铁链多了可以很快剪短,短了可以很快接长,即LinkedList在插入、删除的性能好;②老师点名,第几组第几个回答问题,很容易点到名字,即ArrayList找人很快找到,访问元素快。

 

Map集合

Map用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组保存Mapkey,另一组保存valuekeyvalue可以是任何引用类型的数据。Mapkey不允许重复(相同的key覆盖旧的键值对,编译能通过,不抛出异常),同一个Map对象的任何两个key通过equals方法比较总是返回falsekeyvalue存在单向一对一的关系,通过key能够找到唯一的、确定的valueMap有时也被称之为字典,或关联数组。C#里是用Dictionary的。常用方法:

void clear()清空该Map对象的所有key-value对;②boolean containsKey(Object key)判断该Map是否包含该key;③boolean containsValue(Object value)查询Map中是否包含一个或多个value;④Object get(Object key)返回指定key所对应的value;如果不包含该key,返回null;⑤boolean isEmpty();查询该Map是否是空,为空返回true;⑥Set keySet();返回该Map中所有key组成的Set集合;⑦Object put(Object keyObject value)添加一个key-value对,如果当前Map存在该key则新增的key-value会覆盖原来的key-valueObject remove(Object key)删除指定key所对应的key-value对,返回被删除key所关联的value,不存在则返回null;⑨int size();返回该Map里的key-value对的个数。

Map接口提供了大量的实现类,典型的有:HashMapHashtable等、HashMap的子类LinkedHashMap,还有SortedMap子接口及接口的实现类TreeMap。例子:

Map map=new HashMap();

map.put("张三", "第一个张三");

map.put("张三", "第二个张三");

map.put("李白", 80);

map.put("杜甫", 60);

System.out.println(map.size());//输出3 ,存放的是第二个张三,第一个张三被覆盖掉。

for(Object key:map.keySet()){

System.out.println("键是:"+key+",值是:"+map.get(key));}//会依次输出map的键值对

 

Java 8 Map新增的方法:

Object replace(Object key,Object value):将Map中指定key对应的value替换成新的value,只是替换,不会新增键值对,如果Map中不存在key,返回null。②boolean replace(K keyV oldValueV newValue):将key原有的value替换成新的value,替换结果有truefalse

 

HashMapHashtable实现类

Hashtable是一个古老的实现类,命名没有遵守Java的命名规范,每个单词的首字母大写,后来将错就错了,一直到现在,它是线程安全的,但是不建议使用,可以通过Collections工具类把HashMap变成线程安全的。因为HashMap不是线程安全,因此比Hashtable的性能好,HashMap允许使用null作为keyvalue,但是Hashtable不允许使用,会引发NullPointerException异常。HashMapHashtable里面的元素也是不能保证顺序的。与HashSet类似,HashMapHashtable判断两个key的相等的标准是:两个key通过equals方法返回true,两个keyhashCode值也相等。

LinkedHashMap实现类:

HashSet有一个LinkedHashSet子类,HashMap也有一个LinkedHashMap子类。LinkedHashMap可以避免对HashMapHashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可),同时可以避免使用TreeMap所增加的成本。LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能,因为它内部以链表来维护内部顺序,所以在迭代访问Map里的元素有较好的性能:迭代输出LinkedHashMap的元素时,将会按添加key-value对的顺序输出

 

SortedMap接口和TreeMap实现类:

正如Set接口派生出SortedSet子接口,SortSet接口有一个TreeSet实现类一样,Map接口也有派生的SortedMap子接口,TreeMap实现类。TreeMap就是一个红黑树数据结构,每个kye-value对即作为红黑树的一个节点。TreeMap有两种排序方式:自然排序和定制排序。Set和Map的关系十分密切,Java源代码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。

TreeMap实现类的一些常用方法:①Object firstKey()返回该Map中的最小key值,如果为空返回null;②Object lastKey()返回该Map中的最大的key值;③Object higherKey(Object key)返回该Map中位于key后一位的key值,没有则返回null;④Object lowerKey(Object key)返回key的前一个key值,没有返回null。

Map也有EnumMap枚举的实现类。类似与EnumSet的用法。

 

各个Map实现类的性能分析:

Map的常用实现类有HashMap、Hashtable(古老的、线程安全的集合),HashMap的性能比较好。TreeMap通常比它们两个的性能都略差,尤其在插入、删除时更慢,因为TreeMap底层采用红黑树来管理key-value对,不过使用TreeMap有个好处:key-value总是处于有序状态,无须专门进行排序操作。一般的应用场景,考虑使用HashMap,因为HashMap正是为快速查询设计的(底层采用数组来存储key-value对),但如果程序需要一个总是排序好的Map时,考虑使用TreeMap。LinkedHashMap比HashMap要慢一些,因为它要维护链表来保持Map中key-value时的添加顺序。

 

操作集合的工具类:Collections

Collections工具类提供了大量的方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。

1、排序操作:①void reverse(List list)反转List集合元素的顺序;②void shuffle(List list)对List随机排序,模拟“洗牌”操作;③void sort(List list)自然排序,升序排序;④void swap(List list,int i,int j)将List的i处元素和j处元素交换;⑤void rotate(List list,int distance);当distance为正数时,将list后的distance个元素整体移动到前面;负数时,将list集合的前distance个元素整体移动到后面。例子:

ArrayList list=new ArrayList();

list.add(2);

list.add(1);

list.add(8);

list.add(12);

list.add(9);

System.out.println(list);//输出[2, 1, 8, 12, 9]

Collections.reverse(list);System.out.println(list);//输出[9, 12, 8, 1, 2]

Collections.sort(list);System.out.println(list);//输出[1, 2, 8, 9, 12]

 

Collections还提供了如下常用的用于查找、替换集合元素的类方法:

①int binarySearch(List list,Object key);使用二分搜索法搜索指定的List集合,获得该对象在List的索引,为了保证正确,List集合需要排序好。

②Object Max(Collection coll)根据元素的自然排序,返回给定集合中的最大元素,Min方法返回最小的;

 

同步控制:

Collections类提供了多个synchronizedXXX()方法,该方法将指定集合包装成线程安全的集合,可以解决多线程并发访问集合元素时的安全性问题。Java中常用到的集合框架中的HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap和TreeMap都是线程不安全的,如果有多个线程访问它们,而且有超过一个线程试图修改它们,则存在线程安全问题。例子:

Collection c1=Collections.synchronizedCollection(new ArrayList<>());

Collection c2=Collections.synchronizedCollection(new HashSet<>());

List list2=Collections.synchronizedList(new ArrayList<>());

Set set=Collections.synchronizedSet(new HashSet<>()); 

Map map=Collections.synchronizedMap(new HashMap());

注:不难发现,前面类名都是跟后面synchronizedXXXXXX是相同的。

 

使用Properties读写属性文件:

Properties类是Hashtable类的子类,读取配置文件非常方便,可以把属性文件中的“属性名=属性值”加载到Map对象中,都是字符串类型,提供了3个方法来修改Properties里的key、value值。

①String getProperty(String key)获取指定属性名对应的属性值,类似Map里的get(Object key)方法;②String getProperty(String key,String defaultValue)与方法①类似,多了一个功能,如果配置文件中不存在指定的key时,则该方法指定默认值。③Object setProperty(String key,String value)设置属性值,类似于Hashtable的put()方法;④void load(InputStream inStream)从属性文件(以输入流表示)中加载key-value对,把加载到的key-value对追加到Properties里,但不保证次序。⑤void store(OutputStream out,String comments)将Properties中的key-value对输出到指定的属性文件(输出流表示)中。例子:

Properties p=new Properties();

p.setProperty("张三", "123");

p.setProperty("李斯", "520");

p.setProperty("王五", "2016");

p.setProperty("Johnson", "mynameis");

p.store(new FileOutputStream("myTest.ini"), "headLine");//会在当前目录下生成一个myTest.ini文件,效果如下:

#headLine

#Mon Oct 31 23:19:20 GMT+08:00 2016

Johnson=mynameis    \u738B\u4E94=2016     \u5F20\u4E09=123     \u674E\u65AF=520

Properties p2=new Properties();p2.setProperty("小猪", "2017");p2.load(new FileInputStream("myTest.ini"));

System.out.println(p2);//输出:{王五=2016, Johnson=mynameis, 张三=123, 小猪=2017, 李斯=520}

string url=p2.getProperty("url");

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值