• 13.2 具体的集合


    • Set(集):集合中的元素不按特定方式排序,并且没有重复对象。他的有些实现类能对集合中的对象按特定方式排序。
    • List(列表):集合中的元素按索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象。
    • Map(映射):集合中的每一个元素包含一对键对象和值对象,集合中没有重复的键对象,值对象可以重复。他的有些实现类能对集合中的键对象进行排序。

    1、collection接口

      Collection是整个类集中单值保存的最大父接口。

    1

    Public boolean add(E e)

    普通

    向集合中添加

    2

    Public boolean addAll(Collection<? extends E> c)

    追加一个集合

    3

    Public void clear()

    4

    Public boolean contains(Object o)

    5

    Public boolean isEmpty

    6

    Public boolean remove(Object o)

    7

    Pulic int size()

    8

    Public Object[] toArray()

    将集合变为字符数组

    9

    Public Iterator<E> iterator()

     

     

    只有addIterator会常常使用,但是要记住containsremove方法需要Object的支持。

    现在基本不使用collection接口,而是使用listset这两个接口。

    13.2.1 链表

      从数组的中间位置删除一个元素要付出很大的代价,因为数组中处于被删除元素之后的元素都要向数组的前端移动,在数组中间的位置插入一个元素也是如此。

      从链表中删除一个元素是一个非常轻松操作,急需要对被删除元素附近的节点更新一下即可。

    List<String> staff = new LinkList<>();
    staff.add("Amy");
    staff.add("Bob");
    staff.add("Carl");
    Iterator iter = staff.iterator();
    String first = iter.next();
    String second = iter.next();
    iter.remove();
    

      链表是一个有序集合,每个对象的位置十分重要。LinkList.add方法将对象添加到链表的尾部,但是,常常需要将元素添加到链表的中间。由于迭代器是描述集合中位置的,所以这种依赖于位置的add方法将由迭代器负责。只有对自然有序的集合使用迭代器添加元素才有实际意义。例如,下一节中讨论的集(set)类型,其中的元素完全无序。因此,在Iterator接口中就没有add方法,相反的,集合类库提供了子接口ListIterator,其中包含add方法。

      Collection接口中声明了很多对链表操作的有用方法,其中大部分方法都是在LinkList类的超类AbstractCollection中实现的。例如,toString方法调用了所有元素的toString,并产生了一个很长的格式为[A,B,C]的字符串。这为调试工作提供了便利。可以使用contaions方法检测某个元素是否出现在链表中。例如,如果链表中包含一个等于“Harry”的字符串,调用staff.contains("Harry")后将会返回true。

      链表不支持快速地随机访问

      如果要查看链表的第n个元素,就必须从头开始,超过n-1个元素,没有捷径可走,所以在采用整数索引访问元素的时候,一般不选用链表。

      尽管如此,LinkList中提供了一个用来访问某个特定元素的get方法:

    LinkList<String> list = ... ;
    String obj = list.get(n);
    

      get方法的效率不高,而且非常不建议使用get方法对链表进行遍历。

      列表迭代器接口中有一种方法,可以告知当前位置的索引。实际上,Java迭代器指向两个元素之间的位置,所以可以 同时产生两个索引:nextIndex方法返回下一次调用next时返回元素的整数索引;previousIndex方法返回下一次调用previous方法时返回元素的整数索引。当然,这个索引只比nextIndex返回的索引值小1.这两个方法的效率非常高,因为迭代器保持着当前位置的计数值,

      如果一个整数索引是n,则list。listIterator(n)将返回一个迭代器,这个迭代器指向索引为n的元素前面的位置,也就是说,调用next与调用list.get(n)会产生同一个元素,只是get方法效率比较低。

      如果链表中只有很少的几个元素,就完全不必担心get方法和set方法的开销带来的烦恼。

      为什么优先使用链表?唯一的理由是尽可能减少在列表中间插入或删除元素所付出的代价。如果列表中只有少数几个元素,就完全可以使用ArrayList。

      如果需要对集合进行随机访问,就使用数组或者ArrayList,而不是使用链表。

        

    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.ListIterator;
    
    public class list_13_1 {
    	public static void main(String args[])
    	{
    		List<String> a = new LinkedList<>();
    		a.add("Amy");
    		a.add("Carl");
    		a.add("Erica");
    		
    		List<String> b = new LinkedList<>();
    		b.add("Bob");
    		b.add("Doug");
    		b.add("Frances");
    		b.add("Gloria");
    		
    		ListIterator<String> aIter = a.listIterator();
    		Iterator<String> bIter = b.listIterator();
    		
    		//b非空
    		while(bIter.hasNext())
    		{
    			//跳过一个
    			if(aIter.hasNext()) aIter.next();
    			//每隔一个,将b中的元素加入到a中
    			aIter.add(bIter.next());
    		}
    		
    		System.out.println(a);
    		
    		bIter = b.iterator();
    		while(bIter.hasNext())
    		{
    			bIter.next();
    			if(bIter.hasNext())
    			{
    				//每隔一个,删除一个b中的元素
    				bIter.next();
    				bIter.remove();
    			}
    		}
    		
    		System.out.println(b);
    		
    		a.removeAll(b);
    		
    		System.out.println(a);
    	}
    
    }
    

       创建两个链表,将他们合并在一起,然后从第二个链表中每隔一个元素删除一个元素,最后测试removeAll方法。 

      

    13.2.3 散列表

       链表和数组可以按照人们的意愿排列元素的次序。但如果要查询某个元素,却又忘记了位置,就必须访问所有的元素,直到找到为止。如果集合中的元素较多,就会消耗较长的时间。如果不在意元素的顺序,可以有几种快速查找元素的数据结构,缺点就是无法控制元素的位置。他们将按照有利于操作目的的原则组织数据。

      散列表(hash table)可以快速查找所需要的对象,散列表为每一个对象计算一个整数,称为散列码(hash code)。散列码是由对象的实例域产生的一个整数,更准确的说,具有不同数据域的对象产生不同的散列码。

      如果自己定义类,就需要负责实现这个类的hashCode方法,自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)为true,则a与b必须具有相同的散列码。

      在Java中,散列表用链表数组实现,每个列表称为桶(bucket)。要想查找表中对象的位置,就需要计算它的散列码,然后与桶中的总数取余,所得到的结果就是保存这个元素的桶的索引。例如,若对象的散列码是76268,并且有128个桶,对象应该保存在第108号桶中(76268除以128余108)。如果这个桶中没有其他元素,可以直接将这个元素插入到桶中,有时候可能遇到桶被占满的情况,这种现象称为散列冲突(hash collision)。如果散列码是合理且随机分布的,桶的数目也足够大,需要比较的次数就会很少。

      如果大致知道需要插入多少个元素到散列表中,就可以设置桶的数量,通常将桶的数量设置为预计元素个数的75%~150%。

      如果散列表太满,就需要再散列(rehashed)。如果要对散列表再散列,就需要创建一个桶更多的表,并将所有的元素都插入到这个表中,然后丢弃原来的表。这个装填因子决定了在什么时候对散列表进行再散列。

      散列表可以实现几个重要的数据结构,其中最简单的是set类型。set是没有重复元素的元素集合。set的add方法首先在集合中查找要添加的对象,如果不存在,就将这个对象添加进去。

      java集合类库提供了一个HashSet库,它实现了基于散列表的集。可以用add方法添加元素。contains方法被重新定义了,用来快速地查看是否某个元素已经出现在集中。它只是在某个桶中查找元素,而不必查看集合中的所有元素。

    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Scanner;
    import java.util.Set;
    
    public class set_13_2 {
    	public static void main(String args[])
    	{
    		Set<String> words = new HashSet();
    		long totalTime = 0;
    		
    		Scanner in = new Scanner(System.in);
    		while(in.hasNext())
    		{
    			String word = in.next();
    			long callTime = System.currentTimeMillis();
    			words.add(word);
    			callTime = System.currentTimeMillis() - callTime;
    			totalTime += callTime;
    			
    		}
    		
    		Iterator<String> iter = words.iterator();
    		for(int i = 0; i <= 20 && iter.hasNext(); i++)
    		{
    			System.out.println(iter.next());
    		}
    		System.out.println("...");
    		System.out.println(words.size() + " distinct words" + totalTime + "milliseconds");
    		
    	}
    
    

      这个程序将读入输入的所有单词,并且将它们添加到散列集中,然后遍历散列集中的不同单词,最后打印出单词的数量,单词以随机的顺序出现。

    13.2.4 树集

      TreeSet类与散列表十分类似,不过,它比散列表有所改进。树集是一个有序集合(sorted collection)。可以以任意顺序将元素插入到集合中。在对集合进行遍历的时候,每个值将自动地按照排序后的顺序呈现。

    SortedSet<String> sorter = new TreeSet<>();
    sorter.add("Bob");
    sorter.add("Amy");
    sorter.add("Carl");
    for(String s:sorter)
    {
          System.out.println(s);  
    }
    

      这时候,每个值都会按照顺序打印出来:Amy、Bob、Carl。排序是按照树结构来实现的(在这里使用的是红黑树red-black tree),每次讲一个数据添加到树中,都被放置在正确的排序位置上,因此,迭代器总是以排好序的顺序访问每个元素。

    13.2.5 对象的比较

      TreeSet如何知道希望元素怎样排列呢?在默认情况下,树集假定插入的元素实现了Comparable接口。

    public interface Comparable<T>
    {
        int compareTo(T other);
    }
    

      如果a和b相等,调用a.compareTo(b)一定返回0;

      如果排序后a位于b之前,则返回负值;

      如果排序后a位于b之后,则返回正值。

      有些标准的java平台类实现了Comparable接口,例如,String类。这个类的compareTo方法依据字典序对字符串进行比较。

      如果要插入自定义的对象,就必须通过实现Comparable接口自定义排列顺序,在Object类中,没有提供任何compareTo接口的默认实现。

      如下代码展示了如何使用部件编号对Item对象进行排序:

    class Item implements Comparable<Item>
    {
        public int compareTo(Item other)
        {
            return partNumber - other.partNumber;    
        }  
    }
    

      如果对两个正整数进行比较,可以直接返回它们的差。

      如果第一项位于第二项之前,就返回负值;

      如果第一项位于第二项之后,就返回正值。

      只有两个正整数进行比较的时候,才能使用上述的方法进行,直接返回它们的差值,如果x是一个较大的正整数,而y是一个绝对值较大的负整数,x - y可能会溢出。


      然而,使用Comparable接口定义排列顺序显然有局限性,对于一个给定的类,只能实现这个接口一次。如果在一个集合中需要按照部件编号进行排序,在另一个集合中却要按照描述信息进行排序,该怎么办?

      在这种情况下,可以通过将Comparator对象传递给TreeSet构造器来告诉树集使用不同的比较方法。Comparator接口声明了一个带有两个显式参数的compare方法。

    13.2.8 映射表

      集是一个集合,它可以快速查找现在的元素,但是,要查看一个元素,需要有要查找元素的精确地副本,这不是一种非常通用的查找方式。通常,我们知道某些键的信息,并想要查找与之对应的元素。映射表(map)数据结构就是为此设计的。映射表用来存放键/值对。如果提供键。就能够查到值。例如,键为员工ID,值为Employee对象。

      Java类库为映射表提供了两个通用的实现:HashMap和TreeMap,这两个类都实现了Map接口。

      散列映射表对键进行散列,树映射表用键的整体顺序对元素进行排序,并将其组织成搜索树。散列或比较函数只能作用于键。与键关联的值不能进行散列或比较。

      与集一样,散列稍微快一些,如果不需要按照排列顺序访问键,就最好选用散列。

      每当往映射表中添加对象的时候,必须同时提供一个键。在这里,键是一个字符串,对应的值是Employee对象。

      要搜索一个对象,必须提供一个键。

    String s = "987-98-9996";
    e = staff.get(s);
    

      如果在映射表中没有与给定键对应的信息,get将返回null。

      键必须是唯一的。

      如果对同一个键两次调用put方法,第二个值就会取代第一个值。实际上,put将返回这个键参数存储的上一个值。

      remove方法用于从映射表中删除给定键对应的元素;size方法用于返回映射表中的元素数。

      

      键集    Set<K> keySet()

      值集合     Collection<K> values()

      键/值对集   Set<Map.Entry<K,V>> entrySet()

      keySet既不是HashSet,也不是TreeSet,而是实现了Set接口的某个其他类的对象。Set接口扩展了Collection接口。因此,可以与任何集合一样使用了keySet。

    Set<String> keys = map.keySet();
    for(String key:keys)
    {
         ...  
    }
    

      如果要同时查看键和值,就可以通过枚举各个条目(entries)查看,以避免对值进行查找。

    for(Map.Entry<String, Employee> entry:staff.entrySet())
    {
        String key = entry.getKey();
        String value = entry.getValue();
        ...  
    }
    

      如果调用迭代器的remove方法,实际上是从映射表中删除了键以及对应的值。但是,不能将元素添加到键集的视图中。如果只添加键而不添加值是毫无意义的。如果视图调用add方法,将会抛出UnsupportedOperationException异常。

    package map_13_6;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class MapTest {
    	public static void main(String args[])
    	{
    		//String n, double s, int year, int month, int day
    		Map<String, Employee> staff = new HashMap<>();
    		staff.put("1", new Employee("Amy Lee", 20000, 1993, 12, 1));
    		staff.put("2", new Employee("Allen Iverson", 30000, 1983, 12, 1));
    		
    		System.out.println(staff);
    		staff.remove(1);
    		
    		//替换掉“1”
    		staff.put("1", new Employee("Lebron James", 30000, 1983, 2, 23));
    		
    		System.out.println(staff);
    		
    		System.out.println(staff.get("2"));
    		
    		for(Map.Entry<String, Employee> entry:staff.entrySet())
    		{
    			String key = entry.getKey();
    			Employee value = entry.getValue();
    			
    			System.out.println("key = " + key + ",value = " + value);
    		}
    	}
    }
    

      首先将键值对添加到映射表中,然后从映射表中删除掉一个键值对,接下来修改某一个键对应的值,并调用get方法查看这个值。最后,对条目进行迭代输出。

    {1=map_13_6.Employee@42a57993, 2=map_13_6.Employee@75b84c92}
    {1=map_13_6.Employee@6bc7c054, 2=map_13_6.Employee@75b84c92}
    map_13_6.Employee@75b84c92
    key = 1,value = map_13_6.Employee@6bc7c054
    key = 2,value = map_13_6.Employee@75b84c92
    

      

    能让一个男孩子热血的,不仅有梦想,还有姑娘。
  • 相关阅读:
    SWPUCTF2019 | 神奇的二维码
    CG-CTF | Hello,RE!
    CG-CTF | I wanna play CTF
    CG-CTF | MD5
    CG-CTF | 综合题2
    CG-CTF | 密码重置2
    CG-CTF | 综合题
    修改input输入框placeholder文字默认颜色
    解决前端浏览器字体小于12px办法
    box-shadow四个边框设置阴影样式
  • 原文地址:https://www.cnblogs.com/Mr24/p/6441856.html
Copyright © 2020-2023  润新知