2.集合-Collection集合

1.Collection接口实现类的特点

  1. Collection实现子类可以存放多个元素,每个元素可以是Object;
  2. 有些Collection的实现类,有些是有序的(List),有些不是有序(Set);
  3. 有些Collection的实现类,可以存放重复的元素,有些不可以;
  4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的。

2.List接口和常用方法

  1. List集合类中元素有序(即添加顺序和取出顺序一致),且可重复;
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引;
  3. List容器中的元素都有对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
List list = new ArrayList();
list.add("张先生");
list.add("黄先生");

//1.add(int index,Object ele)->在index位置插入ele元素
list.add(1,"韩先生");

//2.addAll(int index,Collection coll)->在index位置开始讲coll中所有元素添加进来
List list2 = new ArrayList();
list.add("打飞机");
list.add("大货车");
list.addAll(2,list2);

//3.indexOf(Object obj)->返回obj在集合中首次出现的位置;
list.indexOf("大飞机");

//4.lastIndexOf(Object obj)->返回obj在集合中最后出现的位置;
list.lastIndexOf("大飞机");

//5.remove(int index)->移除指定index位置的元素,并返回此元素
list.remove(0);

//6.set(int index,Object obj)->设置指定index位置的元素为obj,相当于替换
list.set(1,"大大");

//7.subList(int fromIndex,int toIndex)->返回从fromIndex到toIndex位置的子集合
list.subList(0,1);

1.Iterator遍历元素

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
  2. 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现Iterator接口的对象,即可以返回一个迭代器。
  3. Iterator仅用于遍历集合,Iterator本身并不存放对象。

1.Iterator迭代器执行原理

注意:在调用==iterator.next()方法之前必须要调用iterator.hasNext()进行检测,若不调用,且下一条记录无效,直接调用iterator.next()==会抛出NoSuchElementException异常。


Collection coll = new ArrayList();

//得到一个集合的迭代器
Iterator iterator = coll.iterator();

//hasNext();表示判断是否还有下一个元素
while(iterator.hasNext()){

   //next();作用:1.指针下移;2.将下移以后集合位置上的元素返回.
   System.out.println(iterator.next());

}

重要细节:如果想要二次变量,需要重置迭代器,不然没法遍历了,因为当第一次退出while循环后,这时iterator迭代器已经指向最后的元素了。

       //重置迭代器
       iterator = col.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();

            System.out.println("222:"+next);

        }

2.for循环增强

增强for循环,可以代替interator迭代器,特点:增强for就是简化版的interator,本质一样。只能用于遍历集合或数组。(增强for,底层仍然是迭代器

  1. 基本语法
    for(元素类型 元素名 : 集合名或数组名){
    访问元素
    }
for(Object object : col){
     System.out.println(object);
}

3.List三种遍历方式

  1. 使用iterator
Collection coll = new ArrayList();
//得到一个集合的迭代器
Iterator iterator = coll.iterator();
//hasNext();表示判断是否还有下一个元素
while(iterator.hasNext()){
   //next();作用:1.指针下移;2.将下移以后集合位置上的元素返回.
   System.out.println(iterator.next());
}
  1. 使用增强for
Collection coll = new ArrayList();
for(Object object : coll){
     System.out.println(object);
}
  1. 使用普通for
for(int i = 0;i<list.size();i++){
     Object object = list.get(i);
     System.out.println(object);
}

4.ArrayList常用方法

  1. add 添加单个元素
  2. remove 删除指定元素
  3. contains 查找元素是否存在
  4. size 获取元素个数
  5. isEmpty 判断是否为空
  6. clear 清空
  7. addAll 添加多个元素
  8. containsAll 查找多个元素是否都存在
  9. removeAll 删除多个元素

1.ArrayList底层结构和源码分析

  1. permits all elements,including null,ArrayList可以加入null,并且多个;
  2. ArrayList是由数组来实现数据存储的;
  3. ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrayList。
2.ArrayList源码分析
  1. ArrayList中维护了一个Object类型的数组elementData【transient Object[] elementData;//trransient表示瞬间、短暂,表示该属性不会被序列化】。
  2. 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0。 如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍。
  3. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则扩容elementData为1.5倍。

5.Vector底层结构和源码分析

  1. Vector类的定义说明
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. Vector底层也是一个对象数组;
protected Object[] elementData;
  1. Vector是线程同步的,即线程安全,Vector类的操作方法有synchronized;
  2. 在开发中,需要线程同步安全时,考虑使用Vector。

1.Vector和ArrayList的比较

底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jsk1.2不安全,效率高如果有参构造1.5倍;如果是无参:第一次10,第二次开始按1.5倍扩容
Vector可变数组jdk1.0安全,效率不高如果是无参,默认10,满后,就按2倍扩容;如果指定大小,则每次直接按2倍扩

6.LinkedList底层结构

  1. LinkedList底层实现了双向链表和双端队列特点;
  2. 可以添加任意元素(元素可以重复),包括null。
  3. 线程不安全,没有实现同步。

1.LinkedList的底层操作机制

  1. LinkedList底层维护了一个双向链表;
  2. LinkedList中维护了两个属性first和last分别指向首节和尾节点;
  3. 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表。
  4. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

2.ArrayList和LinkedList的比较

底层结构底层结构增删的效率
ArrayList可变数组较低,数组扩容较高
LinkedList双向链表较高,通过链表追加较低

3.如何选择ArrayList和LinkdList

  1. 如果我们改查的操作多,选择ArrayList;
  2. 如果我们增删的操作多,选择LinkedList;
  3. 一般来说,在程序中,80%到90%都是查询,因此大部分情况下会选择ArrayList;
  4. 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkdList。

3.Set接口和常用方法

  1. 无序(添加和取出的顺序不一致),没有索引;
  2. 不允许重复元素,所以最多包含一个null;

1.Set接口的常用方法

和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样。

2.Set接口的遍历方法

同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。

  1. 可以使用迭代器;
  2. 增强for;
  3. 不能使用索引的方式来获取。

3.Set接口实现类-HashSet

  1. HashSet实现了Set接口;
  2. HashSet实际上是HashMap;
public HashSet(){
  map = new HashMap<>();
}
  1. 可以存放null值,但是只能有一个null;
  2. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(不保证放元素的顺序和取出顺序一致);
  3. 不能有重复元素、对象。

4.HashSet底层机制

  1. HashSet底层是HashMap;
  2. 添加一个元素时,先得到hash值,会转成->索引值。
  3. 找到存储数据表table,看到这个索引位置是否已经存放有元素;
  4. 如果没有,直接加入;
  5. 如果有,调用equals比较,如果相同,就放弃添加,如果不同,则添加到最后;
  6. 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。

5.HashSet底层机制说明

  1. HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子,(loadFactor)是0.75 =12。
  2. 如果table数组使用到了临界值12,就会扩容到162=32,新的临界值就是320.75=24,依次类推。
  3. 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制。
 /*
      *1.执行 HashSet()
      *     public HashSet() {
              map = new HashMap<>();
            }
      *
      *2.执行add()
      *     public boolean add(E e) {//e = "java"
              return map.put(e, PRESENT)==null;
            }
      *
      * 3.执行put() ,该方法会执行hash(key),得到key对应的hash值,算法是h = key.hashCode()) ^ (h >>> 16
      *     public V put(K key, V value) {
              return putVal(hash(key), key, value, false, true);
            }
      *
      *4.执行putVal()
      * final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {


                //定义了辅助变量
                Node<K,V>[] tab; Node<K,V> p; int n, i;

                //table就是HashMap的一个数组,类型是Node[]
                //表示如果当前table是null,或者大小=0,就第一次扩容,到16
                if ((tab = table) == null || (n = tab.length) == 0)
                    //初始化数组,定义默认大小为16,临界值为12,返回初始化的数组
                    n = (tab = resize()).length;

                 //(1)根据key的hash值,计算该key应该存放到table表的那个索引位置
                 //并且把这个位置的对象,赋给P
                 //(2)再判断P是否为null
                 //(2.1)如果p为null,表示还没有存放过元素,就创建一个Node,就放在该位置
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
                else {

                    //一个开发技巧提示:在需要局部变量(辅助变量)时候,在创建.
                    Node<K,V> e; K k;

                    //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样.
                    if (p.hash == hash &&
                       //并且满足下面两个条件之一:
                       //(1)准备加入的key和p指向的Node结点的key是同一个对象
                       //(2)或者P指向的Node结点和key的equals()和准备加入的key比较后相同.
                       //就不能加入
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    //再判断p是不是一课红黑树
                    else if (p instanceof TreeNode)
                       //红黑树的比较方式
                        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

*
                    else {
                        //链表的添加方式
                        //如果table对应索引位置,已经是一个链表,就使用for循环比较
                        //(1)依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
                            //注意在把元素添加到链表后,立即判断该链表是否已经有8个结点
                            //如果达到,就调用treeifyBin对当前链表进行树化(转成红黑树)
                            //注意,在转成红黑树时,还进行一个判断,表小于64不会进行树化,会对表进行扩容

                        //(2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break

                        for (int binCount = 0; ; ++binCount) {//死循环
                            if ((e = p.next) == null) {
                                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;
                //判断当前大小是否超过临界值,然后扩容
                //size 这就是我们每加入一个结点Node
                if (++size > threshold)
                    resize();//扩容
                //空方法,为了让HashMap子类去实现该方法
                afterNodeInsertion(evict);
                //返回null,代表成功
                return null;
    }
      *  */

6.LinkedHashSet

  1. LinkedHashSet是HashSet的子类;
  2. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表;
  3. LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的;
  4. LinkedHashSet不允许添加重复元素。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值