集合概念、Collection接口( List接口、Queue接口、Set接口)、Map接口、泛型机制

*集合概念:
        用于存储多个对象的单一对象(容器)。存储的数据叫元素。
        元素都是引用类型。

      (    用于模拟现实生活中的存储容器,因此集合类型,不单单是一种。
           有很多类型,设计成各种存储的数据结构。这些类型统称为集合框架   )       
 
 --元素:必须是引用数据类型的数据,不能是基本数据类型的数据。

JDK1.5新特性:
              在1.5以前,容器可以存储Object的任何子类型,但是在对元素进行
              操作时,比如调用元素的方法等。我们必需知道元素的类型,因此
              在编程中增加了大量代码,来进行强制转换,增加了开发难度。
              因此1.5开始支持了一个新特性,叫[泛型机制]。用来规定容器中
              存储的元素的类型。此机制可以在编译期间就进行判断元素的类型。
=========================================================================

*集合与数组的区别:
      数组:可以存储基本数据类型,也可以存储引用数据类型
      集合:只能存储引用数据类型   

=========================================================================  
*Collection接口:
        是集合框架的顶级父接口,用来定义常用的抽象方法。
        子类需要实现相应的方法逻辑。
    
 --常用方法:
    boolean add(E e):
                   将对象e添加到集合中
    int size(): 
                   返回集合元素的个数
    boolean addAll(Collection c):
                   将集合c里的元素添加到此集合中
    void clear():
                   清空集合元素
    boolean contains(Object obj):
                   用于判断集合中是否存在于obj相同的元素
    boolean containsAll(Collection c)            
                   用于判断此集合中是否包含集合c中的所有元素
    boolean isEmpty():
                   用于判断集合元素是否为空
    boolean remove(Object o)
                   用于移除集合中的某一元素
    boolean removeAll(Collection c)
                   用于移除此集合中与c中共有元素
    boolean retainAll(Collection c); 
                   用于保留此集合中与c中共有元素 

    ###子接口:List, Set, Queue
    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一、List:列表
             此接口对应的实现类的特点都是:有序的,可以重复的
             重复与否与元素的equals方法有关
  
       常用方法:
       void add(int index,E element): 
                         将某一元素,插入到此集合的下标index处。
       E    get(int index):
                         返回指定下标上的元素 
       int indexOf(Object obj):
                         返回指定元素第一次出现的位置。如果没有,返回-1.

      E set(int index,E newElement):
                         使用新元素newElement替换下标index上的元素,返回原元素。
      boolean remove(int index):
                         移除此集合中下标为index上的元素   
      List<E> subList(int fromIndex,int endIndex):
                         截取此集合中的一部分,即截取子集,从fromIndex到endIndex
                         "包前不包后" 
             PS:此方法在堆中不会产生新的集合对象。
                   变量引用的父集的一部分。
                   修改子集,会影响父集         
      int lastIndexOf(Object obj):
                         返回此集合指定元素obj最后一次出现的下标。找不到返回-1.  
                                            
   --java 8 为List集合增加了sort()和replaceAll()两个常用的默认方法。
               sort():
                       方法需要一个Comparator对象(函数式接口)来控制元素升序;
                       规则:字符串长度越长,字符串越大,再以升序排列。
                       
                replaceAll():
                       方法需要一个UnaryOperator对象(函数式接口)来替换所有集合元素。
                       规则:直接用集合元素(字符串)的长度作为新的集合元素。
                       
     
                      所以,可以使用Lambda表达式来创建该函数式接口的对象。                                                            

--------------------------------------------------------------------------
*使用Lambda表达式遍历集合:
          利用forEach(Consumer action)
          
*使用java 8 增强的Iterator遍历集合元素:
    4种方法  
            
*使用Lambda表达式遍历Iterator(迭代器接口):
          利用forEachRemaining(Consumer action)
          
*使用foreach()循环遍历集合元素:
    for(Object obj : books)      

--------------------------------------------------------------------------------------------------             
*List的三个实现类:
       (1)ArrayList
                   底层是基于动态数组的数据结构.是有存放顺序的.          
       (2)LinkedList
                   底层是基于双链表的数据结构.每一个存储单元,都涉及到其他两个引用.
                  【除此之外,LinkedList还实现了Deque接口,可以被当成双端队列来使用。
                      因此,既可以被当成栈来使用,也可以被当成队列使用。】    
                      
                   优缺点: 在执行get()/set()时,ArrayList的效率高,LinkedList需要移动指针,效率低
                                    在增加/删除操作时,LinkedList效率高,ArrayList效率低(需要扩容,移动元素)
        
                    ps:当然,在元素的数量大的情况下,区别才明显。         

     (3)Vector:是一个比较古老的集合类型,
                                        线程安全,但是效率特别低;
                                        虽然安全,也不建议使用。

foreach循环:
          for(元素类型  变量:要遍历的集合或数组){
                 逻辑
          }
                   实现原理:使用了迭代器思想
                  
        【如果有多个线程需要同时访问List集合中的 元素,
           开发者可考虑使用Collection将集合包装成线程安全的集合】
 -------------------------------------------

*List排序
 --Comparable接口:        
               如何定义集合中元素之间的大小之分?我们需要在定义元素类型
               时实现Comparable接口,实现接口内的compareTo(E e)。
               实现此接口的类型的对象之间可以进行比较。
 
         方法:
                    int  compareTo(E e):
         比较规则: 
       (1)this与e比较,this-e,
                          如果大于0, 返回大于0的一个数
                          如果等于0, 返回0
                          如果小于0, 返回小于0的一个数
          "按照升序排序"
       (2)e-this,"降序排序"
 --工具类:Collections
               提供了一个sort(Collection c)方法,
               对集合里的元素进行排序               
             
 --Comparator比较器接口:
                如果元素类型已经实现了comparable接口,定义了默认的比较规则。
                之后,再想换其他比较规则时,不修改源码。可以利用比较器接口
                来重新定义比较规则  
     
          方法:
       int compare(E o1,E o2);
               比较规则:
                       升序: o1-o2
                       降序: o2-o1                      

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
*接口Queue: 
      Queue也是Collection的子接口,是一种数据结构,队列.
                
                队列:通常都是一端进(offer),另一端出(poll).
     
                进出原则:FIFO 
     
     
          因为队列要经常进行增删操作,所以使用Linkedlist
          实现了Queue接口.  
        
    --常用方法:
        boolean offer(E e):元素从队尾进入队列
        
        E poll():从队首移除元素,返回被移除的元素,当队列没有元素时,返回null.
    
        E peek(): 查看队首元素,不移除,队列中没有元素时,返回null.        
        
                      注意: 为了避免移除队列的队首时出现null,我们最好先查看队首是不是null.
         【PriorityQueue: Queue的一个实现类,
                                       保存队列元素的顺序并不是按加入队列的顺序,
                                       而是按队列元素的大小进行重新排序。
                                       因此调用poll()或peek()方法时,
                                       并不是取出最先进入队列的元素,
                                       而是取出最小的元素,               
                                       违反了队列的基本规则:FIFO 
                                                          
                           两种排序方式:1)自然排序
                                                    2)定制排序                                                 
           】        
   
    --Deque:是一个Queue的子接口,实现的是双端队列的数据结构
                  
                   双端队列: 两端都可以进,也都可以出
                  
            boolean offerFirst(E e)
            boolean offerLast(E e) 
            E pollFirst()
            E pollLast()
            E peekFirst()
            E peekLast() 
                        【ArrayDeque:
                                                  基于数组实现的双端队列(Deque的一个实现类)。
                                                  不仅可以作为队列来使用,也可以当成栈来使用。
                                                  
                                                  创建Deque时,可以指定一个numElements参数,
                                                  该参数用于指定Object[]数组的长度,
                                                  如果不指定numElements参数,Deque底层数组的长度为16.
                                                  当集合元素超出了该数组的容量时,
                                                  系统会在底层重新分配一个Object[]数组来存储集合元素。               
                         】


 栈:  双端队列的特殊情况,一端禁止进出.FILO
        方法:  void push(E e)----进栈
                     E pop()-----------出栈
------------------------------------------------------------------------------------------------   
*栈的数据结构:
                先进后出:FILO
                我们可以将双端队列的一端进行禁止操作
                另一端进或出,即Stack 
                
        void push(E e): 将元素 e推进栈中
               E pop(): 将栈中的最顶端的元素,移除。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

*Set接口:
           (1): 无序,存储的元素与添加顺序无关
           (2): 不可重复(使用元素的equals方法来判定是否重复)
           (3): 能存储null元素,只能存储一次。
 --Hash算法机制:
             Set集合在添加或查看元素时,当集合中的元素过多时,就是进行
             多次的比较,效率变低。
     
                      在设计元素类型时,提供hash算法,用于返回对象的一个int值。
              在内存中开辟很多小的区域,用于存储一定范围的返回值的对象。
              当我们想添加元素或查看元素,先计算此元素的返回值,然后去
              相应区域中查找遍历.
              
         --如果在这个区域没有找到对象,说明
            集合中可以添加这个对象。
         --如果有,然后查看两个对象的equals的返回值
                --如果为true,  不能添加  
                --如果为false, 可以添加,添加至对应的链表结构中(尽可能的避免发生)
                              
 --重写HashCode方法:
            重写规则:尽可能的让所有的成员变量都参与运算
                               尽可能的避免出现hash值碰撞
            注意:
               重写的必要性:
        (1)如果重写了equals(), 有必要重写hashCode方法
        (2)如果equals()返回true, hashCode返回值有必要相同
        (3)如果equals()返回false,hashCode返回值不一定相同,
            如果返回值不同,可以提高检索的效率
    
               反过来说:
        (1)hashCode值相同,equals方法可能不同
        (2)hashCode值不同,equals方法一定不同      

 --HashCode方法:
                Object是引用类型的父类,提供了hashCode()方法以及equals()方法
                因此我们在定义类型时,一般都重写hashCode和equals方法。
                
重写的重要性:
             equals方法:我们用来判断集合中的元素是否重复
       hashCode方法:我们在使用Set集合时,必须要重写,因为
                                  我们采用的是hash算法计算Set集合元素的存储位置。
    
    int hashCode():
               Object提供的方法是通过地址计算hash值,不可控。
               我们需要在自定义类中重写此方法。

     重写原则:    
            1)尽可能的让所有成员变量都参与运算
            2)尽可能的减少hash值的碰撞
            
    public int hashCode(){
        int result = 7;--定义一个局部变量,进行初始化
        int prime = 53 --定义一个局部变量,赋值为素数
        result = prime*result+field1;
        result = prime*result+field2;
        return result;
    }
------------------------------------------------------------------------------------------
 --Set集合的遍历
             因为Set集合是无序的,无下标可言,因此不能使用经典for循环。
             我们可以使用迭代器原理。
       
         (1)调用集合的iterator()获取迭代器
         (2)使用foreach循环
 --Set集合的元素:
                 不能轻易修改参与hash值算法的成员变量。
                 否则容易引起内存溢出。
          原因:成员变量修改后,会出现新的hash值,
                    但是存储位置还在原hash值的位置上。
                    因此操作时,找不到具体的存储位置。  
                  
 --子类:
   HashSet:
                         无序,不重复,
                         每个能存储元素的“槽位”通常称为“桶”,
                         底层使用hash算法计算存储位置。 
                         增加/删除时效率高
                         【当向HashSet中添加可变对象时,必须十分小心。
                             如果修改HashSet集合中的对象(该对象的关键实例变量),
                             有可能导致该对象与集合中的其他对象相等,
                             从而导致HashSet无法准确访问该对象】
                     
   LinkedHashSet:
                                 是HashSet的子类
                                 底层使用hash算法计算存储位置,
                                 同时使用链表来维护顺序,
                                 输出LinkedHashSet集合的元素时,顺序与添加顺序一致。      
                                 在查看检索时,效率/遍历比较高 
                    
   TreeSet:
       --是SortedSet子接口的实现类,
             --自然排序:升序
             --定制排序:
       --使用二叉树的数据结构维护元素的顺序。
       --如果两个对象通过equals()方法返回true,那么他们通过Comparable()方法也应该返回0           
       --【如果希望TreeSet能正常工作,只能添加同一种对象】
       --【当向TreeSet中添加可变对象时,必须十分小心。
             如果修改TreeSet集合中的对象(该对象的关键实例变量),
             有可能导致该对象与集合中的其他对象相等,
             从而导致TreeSet无法准确访问该对象】
        --向该集合中添加的元素对象,(只有第一个元素不必实现Comparable接口,
                                                             后面添加的所有元素都必须实现Comparable接口,
                                                             即提供了compareTo(Object obj)方法)
   EnumSet:
                      专门为枚举类设计的集合类,里面提供了多个类方法来创建EnumSet对象

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

*Map接口:集合框架中的另一个父接口
                   有时也被称作字典/关联数组
         Map集合:
                   用于储存一一对应的元素数据,
                   第一个对象可以作为索引,第二个对象作为值,
                   我们称之为key-value,键值对。
 
 --储存数据的特点:
     (1)以key-value形式进行存储。
     (2)key与value都必须是引用类型
     (3)key可以为null。
     (4)key与value是单向一对一映射。
     (5)key不能重复
     
 --存储机制:
                Map是基于数组和链表的数据结构进行存储数据。
                作为key的对象采用了hash算法计算存储的数组
                (散列数组,散列桶)的位置.如果计算出来的位置,
                数组中此位置没有元素,就可以添加到散列桶内,
                如果有元素,key的equals方法返回值为false,就会存储在散列桶元素对应的单向链表中。
                            如果key的equals方法返回值为true,就进行替换(覆盖)。
     
      PS:使用Map集合,做为key的数据类型应该重写equals和HashCode方法
        
 --常用方法:
      V  put(K k,V v):
                        作用:  用于存储一对key-value.  
                                  返回被替换的value值
                                  如果不是替换就返回null
                       
      V  get(K k):
                        作用:  通过key对象,获取对应的value对象,
                                  如果集合中没有此key,返回null 
 --Map集合的遍历:
          Set<K>  keySet();
                                   用于获取Map中所有的key对象,返回一个Set集合
            
          Set<Entry<K,V>>  entrySet();
                                  【Map提供了一个Entry内部类来封装key-value对,而计算Entry存储时则只考虑key】
                                   将key-value封装成内部类对象,返回Entry对象的Set集合
                                    
          Collection<V> values();
                                   将map集合中的所有value封装到一个Collection集合中。
 --装载因子和HashMap的优化
                  装载因子:DEFAULT_LOAD_FACTOR = 0.75f(负载极限)
                                 轻负载的hash表具有冲突少、适宜插入与查询的特点,
                                 但是使用Iterator迭代元素时比较慢。
                  默认容量:DEFAULT_INITIAL_CAPACITY  
                 

                 16,就是数组的容量
                 元素个数: size    
           
               【当我们创建一个HashMap对象时,底层数组的初始容量为16。
                   当存储的数据的元素个数 size/DEFAULT_INITIAL_CAPACITY 
                   等于 DEFAULT_LOAD_FACTOR时,
                   数组开始扩容。此时最佳。(hash表会自动成倍地增加容量(桶的数量),
                   并将原有的对象重新分配,放入新的桶内,这称为rehashing),
   
                   如果小于0.75扩容,比较占内存。
                   如果大于0.75扩容,操作的元素比较多。
                 】
   
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    --->
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        /**/          
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            /* 没有存储元素,*/
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        /* hash碰撞 */
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
                   
      内部类Node,封装了一个key-value数据,同时还存储了下一个节点的引用 
   static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }                   
-------------------------------------------------------------------------------------
*Map接口的子类:


 --HashMap与HashTable的区别
         (1)HashTable是一个古老的类。不建议使用
         (2)HashTable是一个线程安全的类,HashMap线程不安全
         (3)HashTable的key不能是null,HashMap的key可以是null(有且只有一个key为null)
     
  --LinkedHashMap:是HashMap子类.
                                使用链表来维护key-value的顺序,
                                在迭代时顺序与添加顺序一致。
  --TreeMap:
                  是SortedMap子接口的实现类,
                  使用了二叉树的数据结构维护填入集合的顺序.
    (1)自然排序:
                          往TreeMap里添加的key对象(必须实现Comparable接口)。
                          即实现compareTo方法
    (2)定制排序:
                          做为key对象的数据类型,可以不实现Comparabel接口。
                          在创建TreeMap集合对象时,                
                          需要提供一个比较器Comparator对象(实现compare方法)
                          与该TreeMap集合相关联
 --Properties:
                   是HashTable的子类型,
                   用于封装属性文件的key-value信息,
                   因为在文件里写的都是字符串,
                   因此Properties的key与value都是字符串类型
                  【相当于一个Key、value都是String类型的Map】
                  常用修改key-value方法:
          String getProperty(String key);
          String getProperty(String key,String defaultValue);
          Object setProperty(String key,String value);
                  常用读写属性文件的方法:
          void load(InputStream inStream);
          void store(OutputStream out,String comments);                  
=====================================================================================================================

复习:
一、集合概念:是一个用于存储多个对象的容器(对象).容器内的对象
                           就是元素,元素都是引用类型。
       PS:容器内存储的都是对象的地址
       
二、与数组的区别?
    --相同点:都是容器(数据结构),用来存储多个数据
    --不同点:
                    数组:可以存储基本数据类型
                    集合:只能存储引用数据类型
                    
三、集合框架中包含多种接口,抽象类,实现类等,用此来满足我们
        所需要的用于存储数据的数据结构.
        
四、Collection与Collections的区别:
   Collection:是集合的父接口,定义了集合框架中常用的抽象方法
   Collections:是集合的工具类,定义了很多用于操作集合对象的 "工厂/工具方法"
   
五、子接口:List,Set,Queue
    --List:存储此接口实现类的数据,有序,可重复
                   有序:存储时与添加的顺序相关,有对应的索引/下标标记位置,
                             从0开始
                   重复:存储的元素可以是同一个,也可以是对象内容相同的不同对象
                             根据元素的equals方法进行判断
六、数组与集合之间的转换
   1、集合转数组
      Object[] toArray()
      E[]  toArray(E[] e);
   2、数组转集合
      List Arrays.asList(数组参数);
                注意:数组转成的集合,
                          不能进行增删操作,否则会出现运行时异常.
                          可以进行替换操作,但是对数组变量有影响.
                 
                 如果想要成功进行增删操作,可以将元素存入新的集合中.
                 
七、 Iterator:迭代器接口
    (1)迭代器的作用是用来遍历集合元素。是一个接口。
       Collection接口提供一个方法 Iterator iterator()
    (2)Collection的实现类使用内部类定义了迭代器子类。
    (3)迭代器提供了统一的方法,用于遍历集合元素。
    --常用方法:
      boolean hasNext():
                        判断集合中是否有下一个元素
      E next():
                        取出集合中的下一个元素   
      void remove():          
                        在使用迭代器对集合进行遍历时,不能使用集合的移除方法
                        移除集合的元素。必须使用迭代器自己提供的移除才行。
    
    1.增强for循环-foreach循环。
      for(元素类型 变量名:要遍历的集合或者数组){
           
      }
     
                与经典for循环的区别:
          (1)增强for循环中无法使用下标。
          (2)经典for循环中可以使用下标。跟下标有关的逻辑,随便写。

===================================================================================================================

*泛型机制:
   (1)概念
       jdk1.5版本开始使用的新特性,本质是进行"参数化类型",
                   在类,接口,方法的定义上都可以使用,用来指定数据类型名的。
   (2)集合在定义时,可以用泛型机制来指定元素的类型,这样
                编译器在编译期间就可以进行检查元素类型是否匹配,避免了
                程序在运行时出现过多的错误  
   (3)集合框架中的所有类型(接口,抽象类,实现类)都是用了
                泛型机制    
   (4)泛型机制的参数只能传引用类型。         
          

                                                                                                
                 

  
                            
                      

      
      
      
          


               
       
             
       
 

猜你喜欢

转载自blog.csdn.net/yk_3215123/article/details/82988165