集合框架

Posted 一路晨光

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了集合框架相关的知识,希望对你有一定的参考价值。

集合框架

1Collection框架

Collection接口

     |-----List接口(有序可重复)

       |------ArrayList类(底层采用变长数组实现,查找速度快,不安全,效率高)

       |------Vector类(底层采用变长数组实现,安全,效率低)

       |------LinkedList类(底层采用双向链表实现,插入删除速度快)

     |-----Set接口(无序不可重复,有序是指存储顺序和插入的顺序一样)

           |----HashSet类

            (无序不可重复,用hash表实现,底层是HashMap,屏蔽了value的Map)

                 |------LinkedHashSet类

                  (有序不可重复,底层维护了hash表和双向链表)

           |----SortedSet接口(数据排序的,不可重复)

                 |------TreeSet类

   (对插入的数据排序,不可重复,底层采用二叉排序树实现,底层就是一个TreeMap)

 

Map接口(无序,key不可重复,value可重复)

|-----HashMap类(用hash表实现,无序,线程不安全,效率高,key值允许为null)

|-----HashTable类(用hash表实现,无序,线程安全,效率低,key值不允许为null)

|-----SortedMap接口(排好序的Map)

        |------TreeMap类

(排好序的,对插入的key值进行排序,底层采用排序二叉树实现)

2Collection接口定义的方法

   Collection接口是所有集合的顶级父接口。

Collection接口中定义的方法是每个集合都具备的!

   (1)boolean add(Object o):向集合中添加新元素,返回值:true添加元素成功。

   (2)boolean remove(Object o);将给定元素从集合中删除,返回值:true删除成功

   (3)int size():返回当前集合的元素总数

   (4)boolean isEmpty();判断集合中是否含有元素。

   (5)boolean contains(Object obj);判断集合是否包含给定的元素

   (6)void clear();清空集合;

   (7)boolean addAll(Collection c);将给定集合中的所有元素添加到当前集合

   (8)boolean removeAll(Collection c);删除当前集合中与给定集合相同的元素

   (9)Iterator iterator();返回当前集合的迭代器

   (10)Object[]  toArray();用于将集合转换为数组

         T[ ]  toArray( T[ ] a)

   

     Arrays.asList()将数组转成list类型

3、List集合

   List集合:有序且可重复集

    两个常用的实现类:ArrayList和LinkedList

    用法完全一样,只是因为实现方式不同,各有千秋

    ArrayList使用数组实现,所以更适合读取存储的数据

    LinkedList使用链表实现,所以更适合插入和删除元素。

 

    List接口中定义的独有方法

   (1)Object get(int index):获取给定索引出的元素

   (2)Object set(int index,Object obj);将给定的元素存入集合指定位置

   (3)add(int index ,Object obj);向集合指定位置插入元素

   (4)remove(int index);删除指定位置的元素

   (5)int indexOf(Object obj);查询指定元素在集合中的位置

        在集合中查询给定元素第一次出现的位置。

           这里也是使用给定元素与集合元素进行equals的比较方式

   (6)int  lastIndexOf(Object  obj);

        在集合中查询给定元素最后一次出现的位置。

   (7)List<E> subList(int from,int to)

获取当前集合的部分内容。取子集

凡是表示范围的都是包含开始,不包含结束。

4、案例

   1List list = new ArrayList();

        ArrayList内部使用对象数组形式实现。

在创建ArrayList对象时,ArrayList会初始化一个数组,

当要存放的元素数量大于数组时,ArrayList会自动扩容数组长度。

        list.add("一");

list.add("二");

System.out.println(list);

ArrayList重写了Object的toString方法

返回的字符串格式为: [元素1.toString(),元素2.toString(),....]

会顺序调用集合中每个元素的toString方法,并拼接在一起。

list.clear();

System.out.println(list==null);//false

System.out.println(list.isEmpty());//true

注意:判断null和isEmpty的区别

         null指的是集合对象是否存在。

      isEmpty()指的是集合对象是存在的,只不过没有元素。

   (2List list=new ArrayList();

              Point p=new Point(1,2);

              list.add(p);

              list.add(p);

              list.add(new Point(3,4));

              Point point=new Point(1,2);

              System.out.println(list.contains(point));

              list.remove(point);

              System.out.println(list);

注意:元素的equals方法对集合的很多操作都有影响!

         判断集合是否包含给定的元素时,集合会将这个元素与集合中的元素分别进

      行equals比较,若有返回值为true的,则认为集合包含给定的元素。

      remove方法是将给定的元素与集合中每个元素进行equals比较,删除第一

      个比较结果为true的元素。

  (3 List list1 = new ArrayList();

              List list2 = new ArrayList();

              List list3 = new ArrayList();

              list1.add("一");

              list1.add("二");

              list1.add("三");

              System.out.println(list1);

              list2.add("四");

              list2.add("五");

              list3.add("一");

              list3.add("二");

              list1.addAll(list2);

              System.out.println(list1);

              list1.removeAll(list3);

              System.out.println(list1);

              list1.retainAll(list2);

取交集:只保留list1中和list2中相同的元素

              System.out.println(list1);

        以上方法比较元素相同都是使用equals方法比较的。

 (4  List list = new ArrayList();

              list.add("one");

              list.add("two");

              list.add("three");

              for(int i = 0;i<list.size();i++){

                     String str = (String)list.get(i);

                     System.out.println(str);

              }

              set方法用于替换集合中指定位置上的元素,set方法的返回值为被替换的元素

              set方法指定的索引位置不能大于数组的元素数量,否则会出现下标越界异常

              Object old = list.set(2, "二");

              System.out.println(list);

              System.out.println("被替换的元素:"+old);

(5String[] array = (String[])list.toArray(new String[0]);

     将集合转换为数组

     注意:要确保集合中存放的元素类型是一致的!

         且要转换的目标数组类型要与元素类型一致。

               toArray()方法用于将集合转换为数组

               toArray方法是Collection定义的方法。所有集合都具备 

               想转换什么类型的数组,toArray方法参数就传什么类型数组的实例

               我们给定的数组实例不需要给长度,因为不会使用,

               toArray方法只是借鉴了我们传入参数数组的类型。

(6)     List<Integer> list=new ArrayList<Integer>();

              for(int i=0;i<10;i++){

                     list.add(i);

              }

              System.out.println(list);

              List<Integer> subList=list.subList(0,4);

              System.out.println(subList);

修改子集会影响原集合中的元素

              for(int i=0;i<subList.size();i++){

                     subList.set(i, subList.get(i)*10);

              }

              System.out.println(subList);

              System.out.println(list);

5、迭代器Iterator接口

    Collection中提供了一个方法

Iterator iterator():该方法用于返回当前集合的迭代器

迭代器是用于遍历集合的。

由于每种集合的内部实现不同,迭代器遍历集合元素的方式也不尽相同,所以我们不需

要记住每一种迭代器的名字。我们就统一把他们看成迭代器去使用就可以了。

Iterator接口

定义了三个方法

boolean hashNext()::询问迭代器迭代的集合是否还有元素。

Object next()::next方法获取集合中下一个元素与get方法一样,

             存放的时候以Object存的,取的时候也是以Object返回的,所以要造型。

void remove():删除刚刚迭代出来的元素。

使用迭代器的流程

问拿删。可以不删除元素。但一定要注意,问一次拿一次!

List迭代

List list = new ArrayList();

list.add("one");

list.add("#");

       list.add("two");

       list.add("#");

       list.add("three");

       list.add("#");

       java.util.Iterator:迭代器是专门为while循环设计的

              Iterator it = list.iterator();

       while(it.hasNext()){

              String element = (String)it.next();

              if("#".equals(element)){

                     //将上面通过next方法获取的元素从集合中删除

                     it.remove();

                     迭代器在迭代过程中,不能通过使用集合定义的删除方法去删除集合元

            素,一定要使用迭代器的删除方法,否则迭代过程中会产生异常!

//list.remove(element);

              }           

System.out.println(element);

       }

   如下迭代:对于链表性能低下(每次找元素都是从第一个开始往后找)

         没有迭代器性能好!迭代器是集合的最优遍历算法

    for(int i=0;i<eggs.size();i++){

              System.out.println(eggs.get(i)) ;

    }  

6、泛型

java1.5之后支持的一个新特性

可以在我们使用某个类的时候,动态的给该类的属性或方法的参数,返回值指定类型。

public class ArrayList<E>{

   public boolean add(E e){...}

   public E get(int index){...}

}

List<String> list = new ArrayList<String>();

list.add("123");

String s =list.get(0);

当支持泛型的类我们在使用的时候不指定泛型类型时,那么泛型类型默认就是Object

集合对泛型的支持

集合中的泛型指定的是存放的元素是什么类型的。

              List<String> list = new ArrayList<String>();

       list.add("123");

       list.add("456");

       list.add("789");

//list.add(123);//参数类型不匹配!

       for(int i =0;i<list.size();i++){

              get方法获取元素时直接是泛型指定的类型,无需再进行造型了

              String element = list.get(i);

              System.out.println(element);

       }

       迭代器也支持泛型

       但要注意!迭代器指定的泛型类型一定要和遍历的集合的泛型类型一致!

              Iterator<String> it = list.iterator();

       while(it.hasNext()){

              使用迭代器获取元素时也不再需要造型

              String element = it.next();

              System.out.println(element);

       }

自定义泛型

泛型的语法

在定义类的时候,在类名之后用<>定义泛型

泛型命名可以是字母与数字的组合,数字不能是第一个字符

若指定多个泛型,中间用","分开

class Point<X,Y>{

              private X x;

              private Y y;

              public Point(X x, Y y) {

                     this.x = x;

                     this.y = y;

              }

              public X getX() {

                     return x;

              }

              public void setX(X x) {

                     this.x = x;

              }

              public Y getY() {

                     return y;

              }

              public void setY(Y y) {

                     this.y = y;

              }

泛型是一个动态的过程,是用于告知jvm运行时该类的属性类型。

所以,不指定泛型时,默认就是Object

              public static void dosome(Point p){

                     p.setX("12");

                     p.setY("12.2");

                     System.out.println(p);

              }

       }

public static void main(String[] args) {

              Point<Integer, Double> point1=

new Point<Integer, Double>(1,1.2);

              int x=point1.getX();

              double y=point1.getY();

              Point.dosome(point1);

}

JAVA泛型的? extends和? super的比较

   (1)? extends叫做向上造型.

    ArrayList<? extends BaseCls> list1 = new ArrayList<BaseCls>();

    意味着这个list1里面放的都是BaseClse的子类,保证你可以通过list.get(index)

    得到的类都是BaseCls或者BaseCls的子类.

    BaseCls cls = list1.get(0);//合法的

    list1.add(new BaseCls());或者list1.add(new CldCls());都是不合法的.

    这里面是不能通过add函数放东西进去的,那这样有什么用呢.

    一般来讲,定义成? extends BaseCls的参数通常只能用来从里面取数据.

    如下downbound方法可以接受? extends BaseCls类型的list

    public void downbound(List<? extends BaseCls> list) {  

        for(BaseCls cls:list){

            cls.func();

        }      

    }

    ArrayList<BaseCls> list1 = new ArrayList<BaseCls>();

    ArrayList<CldCls> list2 = new ArrayList<CldCls>();

    downbound(list1);和downbound(list2);都是合法的。

  (2)? super BaseCls叫做向下造型.

       ArrayList<? super BaseCls> list2 = new ArrayList<BaseCls>();

       意味着这个list2里面只能放BaseClse或者它的子类.

       list2.add(new BaseCls());或者list2.add(new CldCls());都是合法的.

       list2.get(index)返回的是Object类型,因为不知道是那个具体类.

       限制了放进去的对象的类型

       public void upperbound(List<? super BaseCls> list) {

               list.add(new BaseCls()

               list.add(new CldCls());

       }

7、增强for循环

java1.5后的有一个新特性

增强for循环 又叫新循环

新循环的作用是遍历集合和数组的。不能用新循环代替传统循环。

for(TYPE ele : array){

....

}

TYPE:集合或数组的元素类型

ele:元素的引用变量

array:要遍历的集合或数组的实例

新循环的循环次数有遍历的集合或数组的长度决定。

每次循环时,会将集合或数组中的元素依次赋值给ele,然后进入循环体。

 使用新循环注意两点

(1)新循环是在编译时动态将新循环转化为迭代器方式遍历

(2)因为新循环使用迭代器方式遍历,所以在遍历集合时,不能通过集合删除元素。

java1.5后出现的特性有:泛型  增强for循环  包装类的自动拆装箱

8、队列Queue接口(extends Collection

队列是一种非常常用的数据结构,存取数据本着先进先出原则。

java中提供了Queue接口来描述队列。

常用的实现类为LinkedList。

boolean offer(E e):向队列末尾追加元素,追加成功返回true

E poll():从队首获取元素。注意:获取后,队首元素就从队列中删除了。

        如果队列为空时,返回null。

E peek():获取队首元素,但该元素不会从队列中删除。

9、栈结构Deque接口

Deque接口

栈也可以保存一组数据,在存取方式上也有要求,本着先进后出原则。

LinkedList也是其一个实现类

push(E e):向栈顶压入新元素

E pop():返回栈顶元素,并从栈中删除。

       当栈中没有元素时,调用该方法会引发异常

E peek():获取栈顶元素,但不删除

遍历栈结构:

   (1)Iterator<String> it=deque.iterator();

              while(it.hasNext()){

                     String str=it.next();

                     System.out.println(str);

              }

   (2)while(deque.peek() != null){

                     String element = deque.pop();

                     System.out.println(element);

              }

10、CollectionCollections的区别?

  Collection 抽象的集合概念,实现它的有List和Set。

  Collections 集合静态工具类, 包含集合的工具方法,如

    sort、reverseshuffle、binarySearch等。

11Comparable接口和Comparator接口

集合的排序

若要进行排序,就要确定元素的大小。对象与对象间的大小关系如何确定?

   (1)java提供了一个接口Comparable

Comparable接口定义了一个抽象方法compareTo()

compareTo():用于定义对象间的比较规则。

当我们定义的一个类若实现了该接口,那么就说明这个类的实例是可比较的。

String类实现了Complarable接口,String对象时可以比较的。、

定义Point类可以比较

public class Point implements Comparable<Point>{

       private int x;

       private int y;

       public Point(int x,int y){

              this.x = x;

              this.y = y;

       }

       返回的int值不关心具体的值是多少,只关心取值范围

       当返回值大于0 表示当前对象比参数对象大(当前对象往右移)

       当返回值小于0 表示当前对象比参数对象小(当前对象往左移)

       当返回值等于0 表示当前对象和参数对象相等

       public int compareTo(Point o) {

              int r = this.x * this.x + this.y * this.y;

              int r1 = o.x * o.x + o.y * o.y;

              return r-r1;//升序

       }

}

public static void main(String[] args) {

                     List<Point> list=new ArrayList<Point>();

                     list.add(new Point(3,2));

                     list.add(new Point(5,2));

                     list.add(new Point(1,6));

注意:使用Collections的sort()方法,集合里的元素必须是可比较的

                     Collections.sort(list);

                     System.out.println(list);

}

   (2)当类中的比较规则不能满足我们对排序的要求时,可以使用Collections的重载sort

        方法。给定一个比较规则,按照我们的规则比较后进行自然排序。

Collections.sort(Collection c,Comparator cc)

Comparator接口:用于定义一种比较规则。

        通常Comparator不需要定义额外的子类实现,都是使用匿名类的方式创建实例。

List<String> list = new ArrayList<String>();

       list.add("mary");

       list.add("Killer");

       list.add("able");

       字符串实现了Comparable接口,所以字符串本身是具备可比较的

       Collections.sort(list);

       System.out.println(list);

       需求:按照字符串的长短来排序

       字符串的比较规则不能满足这个排序需求时,

    我们可以额外的定义比较规则来满足该排序的需求

       Comparator<String> comparator =

                     new Comparator<String>(){

                            public int compare(String o1, String o2) {                               

                                   return o1.length() - o2.length();

                            }           

                     };

             

       Collections.sort(list, comparator);

       System.out.println(list);

12Set集合

 Set集有别于List集最大的特点的是不存放相同的元素。Set集合不能通过索引的形式获取元素。

常见的实现类

HashSet:使用散列算法实现的Set集合

TreeSet:使用二叉树算法实现的Set集合(底层就是TreeMap,key是存放的数据,value为null。底层都是调用TreeMap的方法来实现的)

遍历Set集合的元素只有一种方式,迭代器。Set集合不支持索引,也不具备List集合的get方法。

(1)遍历Set集合中的元素

 Set<String> set = new HashSet<String>();       

        无序是指元素存放的顺序与取出来的顺序不一致

 但是在元素不修改的前提下,如论以什么顺序存放在Set集合中的顺序都是一定的。

        set.add("two");

        set.add("three");

        set.add("one");

        //迭代器

        Iterator<String> it = set.iterator();

        while(it.hasNext()){

              String str = it.next();

              System.out.println(str);

        }

         增强for循环同样可以遍历Set集合, 对于编译器而言,增强for循环在编译后会转换为iterator,所以可以用增强循环遍历

              for(String str : set){

                     System.out.println(str);

              }

(2)随机生成20个不重复的数字:

 Set<Integer> set = new HashSet<Integer>();

 Random r = new Random();

 while(size<20){

set.add(r.nextInt(100));

 }

    hashcode方法与HashSet之间的关系

    HashSet在存放某个元素时,会先获取该元素的hashcode值,然后进行一系列的运算,之后确定将元素存放在什么位置。这里通过的算法确定位置,算法就是“散列算法”。

可以看出,一个元素要想存入HashSet需要依赖hashcode()方法的返回值。

hashcode()方法是Object定义的方法,所有类都具有该方法。我们应该妥善的重写hashcode()方法。

javaAPI中对重写该方法有说明:

若我们重写了equals()方法,就应该重写hashcode()方法。

hashcode()方法应与equals()方法返回一致。

即:当两个对象equals()方法返回true时,hashcode的返回值应该相同。

在对象没有修改的前提下,多次调用hashcode方法返回的数字不应该发生改变。

当两个对象的equals方法返回false时,hashcode值不是必须不同的。

根据HashSet实现原理可以看出,若不同对象的hashcode值都相同,那么使用HashSet的效率会大大的降低。

13、Map接口

 (1)Map是一种以键值(key-value)对的形式存放数据的结构。

      Map的存储要求是,key不能重复。

  散列表中元素的顺序是散列数组中元素的顺序,与散列算法的计算结果有关,与添加顺序无关称为“无序集合”。无序!=随机 

 (2)最常用实现类

      HashMap:以散列算法实现的Map

      TreeMap:以二叉树算法实现的Map

  TreeMap是添加元素时会按照某种排序规则来寻找存放的位置,

如何排序:先看有没有传入比较器TreeMap tm = new TreeMap(new MyComparator());

  有的话按照给的比较器来排序,没有的话根据TreeMap中的key自身的比较规则来排序(Comparable),这时key值如果是不可比较的,则报错。

 (3)HashSet实际上使用HashMap实现的。

      当我们将一个元素add到HashSet中时,HashSet将这个元素作为key存入了HashMap,value值设置为null。因为HashMap对key的要求是不重复且存放顺序与获取顺序不同,正好满足Set集合的特征。

  HashMap要求Key对象在hashCode值不发生改变的情况下,只能保存一次。

 (4)HashMap的性能

  HsahMap散列表算法,面向查找优化的算法,查找性能优异(无论多少数据,查找次数少于3次)

      Capacity 散列表容量:HashMap的散列数组的大小(能存储key-value数量的最大值)。

  Initial capacity 初始容量:创建HashMap实例时,默认创建的散列数组的大小。默认为16。可以改变。但一般不会这样做。

  Size 大小: HashMap中存储的数据总数。存储(key-value)的数量。

  散列桶:散列值相同的元素集合(散列桶长度越小,性能越好)

          size/容量<=加载因子(75%)

  load factor 加载因子:加载因子的值默认为0.75。这是一个比值,size/capacity。当size与capacity的比值大于0.75时,hashmap会对散列数组进行扩容,并重新分配内部元素位置(重新散列rehash)。rehash是需要消耗一定的性能的。应减少rehash的次数来提高性能。

 性能优化

      影响性能:散列表容量和加载因子

  加载因子较小时候散列查找性能会提高,同时也浪费了散列桶空间容量。

  0.75是性能和空间相对平衡结果。在创建散列表时候指定合理容量,减少rehash提高性能。

 (5)Map的存取元素的方法

  V put(K k,V v):将key-value对存入Map。若Key值已经在Map中存在的话,那么就将Value值替换。返回值则是被替换的Value,若该Key不存在,则将Value存入,返回值为null。

  V get(Object k):根据给定的key值获取对应的Value。若当前给定的key在Map中不存在,则返回null。

  boolean containsKey(Object key):查看当前Map中是否包含给定的key,包含返回true。

  boolean containsValue(Object value):查看当前Map中是否包含给定的Value,包含返回true。

(6)遍历Map

 遍历Map有三种方式:

     a、遍历Map中所有的key

        public Set keySet():调用keySet()方法会返回一个Set集合的实例,其中保存

     的元素为Map中的所有key。

 b、遍历Map中所有的键值对(Entry)

public Set entrySet():调用entrySet()方法会返回一个Set集合的实例,其中保存的元素为Map中的每一组键值对,每个键值对用一个Entry实例保存。

 c、遍历Map中所有的value(不常用)

案例:

(1)统计str字符串中每组数字出现的次数

 思路:

  先将字符串按照","拆分,将每组数字作为key,将出现次数作为value存入map。这样每当统计一组数字时,我们只需要看这组数字作为key是否在map中存在,不存在则是第一次统计。若存在,则将出现次数累加即可。

 

String str = "123,456,778,908,123,454,678,234,908,123";

String[] array = str.split(",");

Map<String,Integer> map = new HashMap<String,Integer>();

for(String sub : array){

       if(map.containsKey(sub)){

              map.put(sub, map.get(sub) + 1);         

       }else{

              map.put(sub, 1);

                           

       }

}

System.out.println(map);

(2)遍历Map中的所有Key

                    Map<String, Integer> map=new HashMap<String, Integer>();

            map.put("A", 1);

            map.put("C", 3);

            map.put("B", 2);

            Set<String> set=map.keySet();

            for(String key:set){

                Integer value=map.get(key);

                System.out.println(key+","+value);

    }

(3)遍历Map的键值对

     Map中有一个内部类Entry,其每一个实例描述一组键值对。当我们调用put方法存放数据时,Map会创建一个Entry实例,并将key,value存入该对象后保存到Map中。所以,散列数组中,每一项的LinkedList中保存的都是Entry。

                 Map<String, Integer> map=new HashMap<String, Integer>();

            map.put("A", 1);

            map.put("C", 3);

            map.put("B", 2);

            Set<Entry<String, Integer>> set=map.entrySet();

            for(Entry<String, Integer> entry:set){

                String key=entry.getKey();

                Integer value=entry.getValue();

                System.out.println(key+":"+value);

        }

(4)遍历Map中的所有Value

                 Map<String, Integer> map=new HashMap<String, Integer>();

            map.put("A", 1);

            map.put("C", 3);

            map.put("B", 2);

 为什么返回Collection而不返回Set?

 因为Set集合不能存放重复元素,而Map中value是可以重复的,若返回为Set集合,可能会丢失信息。

            Collection<Integer> set=map.values();

            for(Integer integer:set){

                System.out.println(integer);

}

以上是关于集合框架的主要内容,如果未能解决你的问题,请参考以下文章

集合框架

集合框架

复习集合框架

Java集合框架介绍

java集合框架

集合框架