Java学习(集合)

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

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

–元素:必须是引用数据类型的数据,不能是基本数据类型的数据。

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 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)泛型机制的参数只能传引用类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值