Java集合框架学习笔记

时间:2023-12-05 10:30:32

集合类的由来:

  对象用于封装特有数据,对象多了需要存储,如果对象的长度不确定,就使用集合存储。

  集合特点

    1、用于存储对象的容器。

    2、集合的长度可变。

    3、集合中不可以存储基本类型

  集合容器因为内部的数据结构不同,有多种具体容器。不断的向上抽取就形成了集合框架

  框架的顶层就是:Collection接口。
  顶层抽出的共有方法:
    1、添加

    2、删除

    3、判断

    4、获取

  Iterator iterator();获取元素的方式 :迭代器

  迭代器的使用:

  使用for循环节省内存   循环结束后it不会驻留在内存中

  如果用While   迭代器会驻留在内存中

  for(Iterator it = coll.iterator();it.hasNext();){
    System.out.println(it.next());
  }

  迭代器原理:

    该对象必须依赖于具体容器,因为每一个容器的数据结构都不相同。

    所以该迭代器对象是在容器中进行内部实现的

    对于使用容器者而言,具体的实现并不重要,只要通过容器获取到该实现的迭代器的对象即可。

    也就是iterator方法。

  Iterator接口就是对所有的Collection容器进行元素取出的公共接口

    MyEclipse快捷键:

      查看接口的实现类:Ctrl+T

    查看类或方法的引用 Ctrl+Shift+G

    查看类的声明:F3

    5、其它

      retainAll(Collection coll)  获取两个集合中的交集

      注意:retainAll与removeAll,removeAll删除集合中含有的所有对象.

      eg:c1={1,2,3} c2={2,3,4} 吧c2放入c1中   removeAll后得到{1}

      retainAll后{2,3}

Collection  下面的两个重要的子接口:

  |--List:有序(存入和读出的顺序一致),元素都有索引,元素可以重复

  |--Set:元素不能重复。(与List最大不同) 无序(也有可能出现有序的)

List:常见方法-->Collection有的List都有

  特有方法:

    修改:Object set(index,element);

    获取:Object get(index);

      int indexOf(Object o);

      int lastIndexOf(Object o);

      List<E> subList(int fromIndex, int toIndex);(包头不包尾)

  总结:List可以对元素进行增删改查

  注意:进行集合操作时候不进行迭代操作,进行迭代操作时候不进行集合操作

       否则会出现对该对象进行了并发修改,但是不允许修改时候会报这种错误

       改进方法-->迭代的时候不要使用iterator();方法。要使用listIterator();方法进行迭代 

       此方法为Iterator接口下面的子接口的方法的实现。


List接口下面的实现类:

  |--Vector:(内部数据结构为数组数据结构,是同步的,意味着线程安全,但是效率低,现在已经基本上不用)注:最早的单列集合

    Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。

    但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。

    增删查都很慢

  遍历Vector的方法有三种(较ArrayList和LinkedList多出一种)

  枚举遍历:

  Enumeration en = Vector.elments();

  Enumeration 有两个方法 hasMoreElement();nextElement()

  while(vector.hasMoreElements()){

    System.out.println(vector.nextElement)

  }

  关于Enumeration:

    此接口的功能与 Iterator 接口的功能是重复的。此外,Iterator 接口添加了一个可选的移除操作,并使用较短的方法名。

    新的实现应该优先考虑使用 Iterator 接口而不是 Enumeration 接口。

  |--ArrayList(内部数据结构为数组数据结构,是不同步的。效率高。所以替代了Vector。如果是多线程 给ArrayList加锁)

    可变长度数组实现原理,ArrayList会提前生成一个固定长度数组,如果大小不够大,新生成一个数组增加原来数组的一半。

    读、查询速度快

  |--LinkedList(内部数据结构为链表,非同步,效率高)

    增删元素速度快

    特有方法:addFrist(); addLast();

    理解:因为LinkedList内部数据结构为链表,所以中间都是一环扣一环,但是头和尾部不是,所以很好添加新元素。

    getFirst();获取但不删除 removeFirst();获取但是删除

    重点:用LinkedList模拟堆栈或者队列的数据结构

  堆栈:

    栈特点:先进后出

  队列:

    特点:先进后出

  实现:描述一个容器,给使用者提供一个容器对象完成这两种结构中的一种。

class MyQuere{
private LinkedList link;
MyQuere{
link = new LinkedList();
}
public void myAdd(Object obj){
link.addLast(obj);
}
public Object myGet(){
return link.removeFirst();
}
public boolean isNull(){
return link.isEmpty();
}
}
class MyStack{
private LinkedList link;
MyQuere{
link = new LinkedList();
}
public void myAdd(Object obj){
link.addLast(obj);
}
public Object myGet(){
return link.removeLast();
}
public boolean isNull(){
return link.isEmpty();
}
}

    LinkdList---->JDK1.6新特性与1.5对比

      addFirst();addLast();-->无返回

      offerFirst();offerLast();   -->在此列表插入指定的元素。返回:true(根据 Deque.offerFirst(E) 的规定)(1.6新特性)

      getFirst();getLast();    -->NoSuchElementException - 如果此列表为空.

      peekFirst();peekLast();  -->此列表的第一个元素;如果此列表为空,则返回 null.(1.6新特性)

      removeFirst();removeLast();  -->无返回

      pollFirst();pollLast();   --> 获取并移除此一个元素;如果此列表为空,则返回 null。(1.6新特性)

    总结:以上方法都来自于接口Quere,1.6新特性中的方法有特殊返回值。特殊返回值可以用来做判断标识。

    补充:什么时候自动装箱?

      基本类型值赋值给了对象引用的时候会自动装箱。


Set:一个不包含重复元素的 collection。

  更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。无序的。

  Set接口中的方法与Collection中的方法一致。

  注意:Set取数只有一种方式:迭代器Iterator

  |--HashSet   保证唯一的特点。该容器中不会有重复的对象。

    此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序.

    内部结构数据为哈希表,是不同步的。

  补充:哈希表-->实质还是数组

    一般数组的存入都是以脚标来存入,查找的时候顺序对比,产生结果。

    而哈希表在存入的时候会按照存入的对象的特性,存入指定的位置(无序的原因)

    function(Element){

      一个算法,这个算法,对元素进行运算,并获取其位置。

      注意:此算法就是hashcode();这个方法是由底层的windows做的,看不到。

      我们可以做自己的算法:(简易原理实现:以字符串为例)另数组长度为10 即:0-9;

      存放"ab"根据ab的asc值97+98=195   

      让195%10=5   ---->找到下标为5的位置进行比较,有就是有,没有就是没有。

      ---->插入的时候,当有5存在的时候就不会插入(这就是为什么Set不会有对象重复的原因)

      ---->注意:并不能说明地址(下标)相同就是相同的内容(例如:"ba")

      ---->所以当地址(下标)相同的时候,再要比较内容。

      return index;

    }

    当要获取的时候再用一次该方法获取其位置,直接找到该元素。

    总结哈希表:

      哈希表确定元素是否相同

      1.判断两个元素的哈希值是否相同。如果相同,进而比较两个对象的内容是否相同。

      2.判断哈希值是否相同,其实判断的是对心的hashCode();的方法。

      判断内容是否相同用的是equals();方法。

      注意:如果哈希值不同,则不需要判断内容是否相同。

      如果哈希值相同,但是内容不同(这种情况是很少的,因为哈希算法非常复杂,叫做哈希冲突),解决办法很多,可以顺延。

    自定义对象的存储:(重点)

      HashSet集合数据结构是哈希表,所以使用hashCode();方法来确定位置;如果位置相同,用equals();方法来确认内容是否相同。

      如果不重写hashCode()方法,那么会调用父类(Object)的hashCode()方法。

      如果不重写equals()方法,那么也会调用父类(Object)的equals()方法。父类的equals()方法比较的是地址,而没创建一个对象

      肯定有一个新地址,所以equals()肯定不会相同,所以即便内容会相同,也会储存。

  ArrayList和HashSet的区别:

    ArrayList判断对象依赖的是equals();方法。

    而HashSet判断对象用到的是hashCode();方法和equals();方法

    所以当用ArrayList的remove()方法的时候,虽然对象的地址(下标)不同,但是只要内容相同,都会移除。

    而HashSet就要用到hashCode()和equals()两个方法。

    当我们想要保证对象唯一,并且还想要有序。在Set下面提供了一个实现类-->LinkedHashSet<E>

    定义:

      具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。

      此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。

      链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。

    注意,插入顺序不 受在 set 中重新插入的 元素的影响。(如果在 s.contains(e) 返回 true 后立即调用 s.add(e),则元素 e 会被重新插入到 set s 中。)

    理解:就是HashSet与链表相结合。


|--TreeSet

  使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。

  即:要想对自定义对象排序,必须将该类实现Comparator类,并实现其方法:compareTo()。

  int compareTo(Object o){

    比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

    1、比较数值的时候,直接用逻辑符号>、<、=来判断。

    2、比较字符串的时候,可以用public int compareTo(String anotherString)方法。

    技巧:做差。

  }

  重要:判断元素唯一行的方法,就是根据比较方法的返回结果是否是0,如果是0,就是相同元素,不存。

  总结:

    TreeSet对元素进行排序的方式之一:

      让元素自身具备比较功能,元素需要实现Comparator接口,并且覆盖compareTo()方法。

      如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序怎么办?

    可以使用TreeSet的第二种排序方式:

      让集合自身具备排序功能。集合创建时就要有。即:构造方法。

      创建一个比较器,实现Comparator接口。实现compare()方法:两两元素进行比较。

    两种方式的区别:

      第一种是利用元素自身的比较方法比,只需要传入一个元素即可。

      第二种是利用集合自身的比较,要传入两个元素。

    两个都存在的情况下面以构造器为主。实际开发用第二种比较多。

  底层数据结构:

    经典二叉树:当保存的时候使用二叉树保存,大的在右边,小的在左部。

    int compare(Object o1,Object o2){ // 比较用来排序的两个参数。

      根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。

      如果指定返回一个正整数,则意味着存入的时候都往二叉树的右侧存,即有了一定的顺序:插入的顺序是怎么样的,查询的顺序就是怎么样的。

    }

  注意:只要不是按照自然排序就利用比较器


Map:将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。

  与Collection区别:Map一次添加一对元素,Collection一次添加一个元素;Map也称为双列集合,Collection也称为单列集合。

  根据定义:Map必须保证键的唯一性。

  常用方法:

    1、添加

      V put(K key, V value):返回前一个key关联的值,如果没有返回null。

      理解:如果该key之前没有对应的值,那么返回null,并且成功存入。如果key之前有值,那么返回之前的值(即把原来的值删除),并存入当前的值。

    2、删除

      void clear() 清空map集合

      V remove(Object key):根据key值删除键值对

    3、判断

      boolean  containsKey(Object key):有键吗?

      boolean  containsValue(Object value) :有值吗?

      boolean  isEmpty() :存在吗?

    4、获取

      V get(Object key) :通过键获取值。如果没有该键返回null。

      int size() :返回键值对个数。

    5、遍历

      第一种:

        Set<K>  keySet() :得到所有键。-->既然是Set集合,那么就可以用迭代器迭代,为什么不反悔List?因为List不唯一。

      第二种:

        Set<Map.Entry<K,V>>entrySet(): 返回此映射中包含的映射关系的 Set 视图。(entry :登记)

        原理:将键值对两个对象,封装成一个对象

        补充:内部接口

        Map接口下面有一个内部接口Entry

          interface Map{
            interface Entry{
              public void get();
            }
          }

      为什么要这么做?因为Entry是将键值对封装成一个对象,如果要用Entry那么前提肯定是Map,外部规则中有内部规则,内部规则访问外部规则的内容。

      所以要写成内部接口的形式。

      Collection<V>  values() :为什么返回Collection?因为键虽然唯一,但是值不唯一。

  常用子类:

    |--Hashtable:内部数据结构哈希表,是同步的。表注:最早的双列集合   重点:不允许null作为键,不允许null作为值。

      Properties:Properties 类表示了一个持久的属性集。用来存储键值对形式的配置文件。可以和I/O技术相结合才能发挥最大的水平。

    |--HashMap:内部结构是哈希表,不是同步的。允许null作为键,允许null作为值。(与Hashtable的主要区别)

  LinkedHashMap<K,V>:

    Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。

    此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。

    此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。

    注意,如果在映射中重新插入 键,则插入顺序不受影响。

    |--TreeMap:内部结构是二叉树,不同步,可以对Map集合中的键进行排序

      注意:HashSet此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。

    Map集合练习:

      获取字符串中每个字符出现的次数。

    Utilities:

      工具类:便捷开发。

    |--Collections

      此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。

      扩展:自定义排序方法

      public static <T extends Comparable<? super T>> void MySort(List<T> list){
        说明:<T extends Comparable<? super T>> 加限制,因为要保证调用该方法的集合可以比较。
      }

补充:

  Comparable接口与Comparator接口的区别:

    Comparable 是排序接口。

      若一个类实现了Comparable接口,就意味着“该类支持排序”。

      即然实现Comparable接口的类支持排序,假设现在存在“实现Comparable接口的类的对象的List列表(或数组)”,则该List列表(或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。

    Comparator 是比较器接口。

      我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);

      那么,我们可以建立一个“该类的比较器”来进行排序。这个“比较器”只需要实现Comparator接口即可。

      Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序。

      所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法。

      Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。

    |--Arrays  

      此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。

      除非特别注明,否则如果指定数组引用为 null,则此类中的方法都会抛出 NullPointerException。

      注意:

        数组转集合

        static <T> List<T>asList(T... a) :返回一个受指定数组支持的固定大小的列表。

        此方法同 Collection.toArray() 一起,充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。

        返回的列表是可序列化的,并且实现了 RandomAccess。

        注意:

          转换之后的List集合不能进行增删操作,因为数组为固定长度。

          如果数组中的元素是对象,那么转成集合时,直接将【数组中的元素】作为集合中的元素进行存储。

          如果数组中的元素是基本数据类型,那么在转成集合时,会将【数组】作为集合中元素进行存储。

          因为集合中不能存储基本数据类型,所以在转型的时候,List的泛型要注意:之前数组怎么定义的,泛型就怎么写。

        集合转数组

        Collection接口中的Object[]toArray()和<T> T[] toArray(T[] a)方法。

        集合转成数组,可以对集合中的元素进行限定,不允许增删。

    补充:给非同步的集合加锁。

class MyCollections{
  public List synList(List list){
    return new MyList(list);
  }   class MyList implements List{
    private List list;
    private static final Object lock = new Object();
    MyList(List list){
      this.list = list;
    }
    public boolean add(Object obj){
      synchronized(lock){
        return list.add(obj);
      }
    }
    public boolean remove(Object obj){
      synchronized(lock){
        return list.remove(obj);
      }
    }
    ...//实现其他方法并且都加锁。
  }  
}