22-04-26 西安 javaSE(16) Collection、List、Set集合

Collection接口

集合的特点

集合:用于存储、获取、操作对象的容器
数组的不好处:数组的长度不可变

集合的特点:
1、集合的长度是可变的
2、集合中可以存储是任意类型的对象且只能存储对象

数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

Collection

java.util.Collection接口是 List、Set 和 Queue 接口的父接口

boolean add(E e) ;//向集合中添加元素
int size()  ;//获取集合中有效元素个数
void clear()  ;//清空集合
isEmpty();//判断集合是否为空
addAll(Collection coll);//将coll所有元素们一个一个加进去
contains();//判断集合中是否某个元素是否存在,依据是equals
containsAll();?跟顺序有关吗,无关//
remove();//要删除先判断有没有,contains
removeAll();//删除的是俩个集合的交集
retainAll();//取得是俩个集合的交集
toArray();将集合转数组

验证contains方法跟顺序无关

@Test
public void test3(){
    //将数组转集合
    Collection c1 = Arrays.asList(1, 2, 3, 4, 5, 6);
    Collection c2 = Arrays.asList(6, 3, 1);
    //如果此 c1 包含指定 c2 中的所有元素,则返回 true。
    System.out.println(c1.containsAll(c2));//true
}


List接口

List接口是Collection接口的子接口。特点:存储的元素有序且可重复的。有序 的意思是存进去的顺序和取出来的顺序是一样的。有序的原因是list集合具有索引值。

List接口在Collection接口上增加了一些根据元素索引来操作集合的特有方法

void add(int index, Object ele)
boolean addAll(int index, Collection eles)
Object get(int index);获取指定
int indexOf(Object obj)//从前往后找指定元素的索引值,找不到返回-1
int lastIndexOf(Object obj);//从后往前找
Object remove(int index);//按照指定索引删除。和Collection接口的按照元素删构成重载,删除指定位置元素  返回被删除元素
Object set(int index, Object ele);//替换指定索引元素,返回替换前的元素
List subList(int fromIndex, int toIndex)//截取子集合,左闭右开

Set接口

Set接口是Collection接口的子接口。set集合里的元素是无序不可重复的,且set集合没有下标这个说法。

Set接口实现类:
HashSet:是一个set接口的典型实现类【jdk1.7 底层:数组+链表】

   LinkedHashSet

TreeSet


迭代器 Iterator

Iterator 接口提供遍历任何 Collection 的接口。
我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器允许调用者在迭代过程中移除元素。
Iterator 使用代码如下:
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
    String obj = it. next();
    System. out. println(obj);
}
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改
的时候,就会抛出 ConcurrentModificationException 异常。

边遍历 边修改

边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
*// do something*
it.remove();
}
一种最常见的 错误 代码如下:
//边遍历集合 边修改集合
for (String i : list) {
    list.remove(i);
}
运行以上错误代码会报 ConcurrentModificationException 异常 。这是因为当使用
foreach(for(Integer i : list)) 语句时,会自动生成一个 iterator 来遍历该 list ,但同时该 list 正在被
Iterator.remove() 修改。 Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。

List接口实现类

ArrayList是List接口的实现类。ArrayList基于数组,是⼀块连续的内存空间

//new ArrayList,实际底层是个Object类型的数组
transient Object[] elementData;
ArrayList的缺点是对元素必须连续存储,当需要在ArrayList的中间位置插入或者删除元素时,需要将待插入或者删除的节点后的所有元素进行移动,其修改代价较高。
因此,ArrayList不适合随机插入和删除的操作,更适合随机查找和遍历的操作

ArrayList数组长度

ArrayList不需要在定义时指定数组的长度,在数组长度不能满足存储要求时,ArrayList会创建一个新的更大的数组并将数组中已有的数据复制到新的数组中。

jdk1.7  数组默认长度10;

jdk1.8  数组长度默认为0,做任何小操作则数组长度变为10(懒初始化)

//以下是ArrayList源码
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//以下是ArrayList源码
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //DEFAULT_CAPACITY = 10;
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

总结:new ArrayList ====  new Object[10]

ArrayList(int initialCapacity) 也可以通过有参构造器指定数组长度

//以下是ArrayList源码部分
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

ArrayList自动扩容

在插⼊时候,会先检查是否需要扩容,如果当前容量+ 1超过数组长度,就会进⾏扩容。

ArrayList的扩容是创建⼀ 个1.5倍的新数组,然后把原数组的值拷贝过去。

//以下是ArrayList源码部分
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //参数一:需要复制的原始数组 参数二:新数组的长度
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

前面是10作为初始容量,请问,第1次扩容后从10 到多少?X ;第2次扩容后从X  到多少? ??

答案,arraylist扩容机制,就是原值的一半,
10 一半  5   ==》 10+5 = 15;
15 一半  7.5  ===》 15 + 取整7 = 22;


ArrayList线程不安全

请写一个Arraylist线程不安全的案例并测试出来bug

为什么它线程不安全?

ArrayList的线程安全可以通过这些⽅案:

使⽤ Vector 代 替 ArrayList。(不推荐,Vector是⼀个历史遗留类 )
使⽤ Collections.synchronizedList 包 装 ArrayList,然后操作包 装后 的 list。
使⽤ CopyOnWriteArrayList 代 替 ArrayList。
在使⽤ ArrayList 时 ,应⽤程序通过同步机制去控制 ArrayList 的读 写。


LinkedList​​类

LinkedList​​也是List接口的实现类。LinkedList是一个双向链表

双向链表的链接方向是双向的,它由若干个节点组成,每个节点都包含下一个节点和上一个节点的指针,所以从双向链表的任意节点开始,都能很方便访问他的前驱结点和后继节点。

在对LinkedList进行插入和删除操作时, 只需要改变前驱节点、后继节点和插⼊节点的指向就⾏了,不需要移动元素 ,因此 随机插入和删除效率很高
在对LinkedList进行随机访问时,需要从链表头部一直遍历到该节点为止,因此 随机访问速度很慢

LinkedList提供了大量首尾操作的方法,因此在开发时,LinkedList集合也可以作为堆栈,队列的结构使用

LinkedList特有的方法:
void addFirst(Object obj)
void addLast(Object obj)	
Object getFirst()
Object getLast()
Object removeFirst();//移除并返回集合中第一个元素,如果集合为空报异常
Object removeLast();//移除并返回集合中最后一个元素,如果集合为空报异常


LinkedList封装队列

如下将LinkedList封装为一个队列

public class SylQueue {
    //底层使用LinkedList封装Queue
    LinkedList ll=new LinkedList();

    public SylQueue() {

    }
    //添加
    public void add(Object obj){
        ll.addFirst(obj);
    }

    //获取
    public Object get(){
//        return ll.removeFirst();//达成后进先出的堆栈效果
        return ll.removeLast();//达成后进后出的队列效果

    }

    //判断
    public boolean isNull(){
        return ll.isEmpty();
    }

    public String getAll(){
        return ll.toString();
    }

}
public class ExceptionTest {
    @Test
    public void testA() {
        SylQueue sylQueue = new SylQueue();
        sylQueue.add("a");
        sylQueue.add("b");
        sylQueue.add("c");
        sylQueue.add("d");
        sylQueue.add("e");
        String all = sylQueue.getAll();
        System.out.println(all);
        System.out.println("=================");
        Object o = sylQueue.get();
        String all2 = sylQueue.getAll();
        System.out.println(all2);
        System.out.println("=================");

    }
}

问题3:
LinkedList中get(index i)和数组有啥区别【源码】
index i <.size(),从前往后找,反之从后往前找。


Vector类

Vector的数据结构和ArrayList一样,都是基于数组实现的,不同的是Vector是线程安全的。

即同一时刻只允许一个线程对Vector进行 写操作(新增、删除、修改),以保证多线程环境下数据的一致性, 但需要频繁地对Vector实例进行加锁和释放锁操作,因此,Vector的读写效率在整体上比ArrayList低。

ListIterator

Iterator 可以遍历 Set List 集合,而 ListIterator 只能遍历 List
Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前 / 后遍历)。
ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元
素、获取前面或后面元素的索引位置。

ListIterator【列表迭代器】

是List集合特有的迭代器,会多一些方法

1.previous() //获取前一个元素

2.hasPrevious()//获取前一个元素


数组和 List转换

数组转 List :使用 Arrays. asList(array) 进行转换。
String[] array = new String[]{"123","456"};
List<String> asList = Arrays.asList(array);
Arrays.asList(…) 方法返回的 List 集合既不是 ArrayList 实例,也不是 Vector 实例而是Arrays的内部类ArrayList
List 转数组:使用 List 自带的 toArray() 方法。
List<String> list = new ArrayList<>();
list.add("123");
list.add("456");
Object[] array = list.toArray();

​​​​

java.util.Set接口

1、HashSet

HashSet 具有以下特点:

  • 不能保证元素的排列顺序
  • HashSet 不是线程安全的
  • 集合元素可以是 null

哈希算法:会将哈希码值变为数组的索引值,hash算法是一种可以从任何数据中提取出其“指纹”的数据摘要算法,它将任意大小的数据映射到一个固定大小的序列上,这个序列被称为hash code。

Hash的公式---> index = HashCode(Key) & (Length - 1)

如果我们往集合中存放自定义的对象,那么保证其唯一,就必须重写hashCode和equals方法建立属于当前对象的比较方式

重写的目的:让内容一样的对象不能存到set集合里。
重写的规则:当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。


2、HashSet集合存储过程

1、要明白 new HashSet 就相当于new HashMap

//以下HashSet类为源码部分
    public HashSet() {
        map = new HashMap<>();
    }

2、HashSet的add方法,只关注key,不理睬value,根据返回值是否为空来判断是否插⼊元素成功

//以下为源码部分   
    private static final Object PRESENT = new Object();

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

3、如何判断是同一个对象?

用hashcode比较,要比较2次

1. hashcode不同,则是不同对象,到此为止。
2. hashcode相同(发生了hash冲突),则进一步比较,看equals


3、哈希冲突

每个对象都有一个hashCode值,类似于自身的身份证号码。该hashCode值默认出厂来自于Object类的这个方法,

//发现只有方法的声明,没有方法的实现,也即java需要通过Native关键字调用系统底层函数,给我返回值。
//native方法,表示调用底层第3方函数,C语言系统生成的东西
public native int hashCode();

哈希冲突:即hashCode哈希值冲突了,两个不同的对象居然碰撞出来同一个hashCode值

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

演示哈希冲突1

//演示哈希冲突1:
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112

演示哈希冲突2:上万次的计算后,hash值会冲突

      
//演示哈希冲突2:上万次的计算后,hash值会冲突
Set set = new HashSet();
int hashCode;
for (int i = 1; i <=110000 ; i++) {
    hashCode = new Object().hashCode();
    if(set.contains(hashCode))
    {
        System.out.println("----出现了hash冲突,在第几次:"+i+"\t hashCode: "+hashCode);
        continue;
    }
    set.add(hashCode);
}
System.out.println(set.size());

哈希冲突,每个人的环境下出现的哈希冲突在第几次一般是固定的。


4、LinkedHashSet

LinkedHashSet 继承 HashSet是链表和哈希表组合的一个数据存储结构,相比于HashSet 多了一个链表维护集合中的元素,效率变低了。
LinkedHashSet遍历的时候直接一根列表,可以按添加顺序遍历,且遍历时不用过滤null值。

public class Demo {
    public static void main(String[] args) {
        LinkedHashSet set = new LinkedHashSet();
        set.add(10);
        set.add(8);
        set.add(1);
        set.add(11);
        //LinkedHashSet会按照添加顺序遍历
        System.out.println(set);
    }
}


5、TreeSet

底层采用红黑树,有自己指定的排序方式。
2.compareTo,返回正数,0,负数
3.要是不是Person类怎么返回呢,应该是抛出异常吧。在TreeSet中应用的时候返回0可以。
如果返回0,在TreeSet中会视为重复,是对的。
4。为啥compareTo方法形参不不直接写Person 【方法重写】


Collection接口遍历

1、增强for循环遍历

for(元素数据类型 变量名:被遍历的集合){
}
注意:增强for循环不擅长改变数组中的值。

代码案例如下:

 增强for循环 也可以遍历数组【但是要改变数组中某一个值,改不了】

    @Test
    public void test6() {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(1);

        System.out.println(arrayList);//[1,2,1]
        for (Integer i : arrayList) {
            System.out.println("i="+i);
            if(i==2){
               i=1;
            }
        }
        System.out.println(arrayList);//[1,2,1]
    }
}

结果:

那正确的操作方式呢?

发现,set方法是根据索引去修改值得,在增强for循环里没有索引值,自然也就不能改值了

    @Test
    public void test6() {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(1);

        System.out.println(arrayList);//[1,2,1]
        for (int i = 0; i < arrayList.size(); i++) {
            if(arrayList.get(i)==2){
                //public E set(int index, E element)
                arrayList.set(i,1);
            }
        }
        System.out.println(arrayList);//[1,2,1]
    }

2、迭代器遍历Collection

迭代器Iterator

Collection中有一个方法:Iterator  iterator() ;//返回Iterator的实例,交给迭代器遍历

首先看看迭代器对象的3个方法,真老六啊

  • hasNext()  如果仍有元素可以迭代,返回true
  • next()  返回迭代的下一个元素
  • remove() 移除元素

使用迭代器遍历集合是固定写法,如下遍历一个TreeSet

    @Test
    public void testIter(){
        TreeSet set = new TreeSet();
        set.add(10);
        set.add(8);
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next);
        }
      //  System.out.println(iterator.next()); 当next()没有值时,NoSuchElementException
    }

迭代器Iterator进行list集合元素的删除 

在集合结构发生变化时,迭代器必须重新获取,不然当你下一次调next()方法时,会报异常。

俩个remove方法

下图最重要的就是,你在while循环里调用集合自带的remove方法删除元素是不可取的,想在迭代时删除,可以,但你必须用我迭代器自己的remove方法。


Collections工具类

1、排序

均为Collections中的static方法

reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序【自然排序】对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序【定制排序】对 List 集合元素进行排序
swap(List,int i, int j):将指定 list 集合中的 i 处元素和 j 处元素进行交换

2、查找、替换

均为Collections中的static方法

int binarySearch(List list, Object key)//对List进⾏⼆分查找,返回索引,注意List必须是有序的
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中。要满足:dest.size>=src.size
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值

3、同步控制

同步控制 ( 不推荐,需要线程安全的集合类型时请考虑使⽤ JUC 包下的并发集合 )
Collections 提供了多个 synchronizedXxx() ⽅法 · ,该⽅法可以将指定集合包装成线程同步的集
合,从⽽解决多线程并发访问集合时的线程安全问题.
我们知道 HashSet TreeSet ArrayList , LinkedList , HashMap , TreeMap 都是线程不安全
的。 Collections 提供了多个静态⽅法可以把他们包装成线程同步的集合。
synchronizedCollection(Collection<T> c) //返回指定 collection ⽀持的同
步(线程安全的)collection。
synchronizedList(List<T> list)//返回指定列表⽀持的同步(线程安全的)
List。
synchronizedMap(Map<K,V> m) //返回由指定映射⽀持的同步(线程安全的)
Map。
synchronizedSet(Set<T> s) //返回指定 set ⽀持的同步(线程安全的)set。


Vector 和 Enumeration 接口

Vector采用数组结构存储元素,是线程安全的,效率低
Enumeration 接口是 Iterator 迭代器的 “古老版本”

    @Test
    public void test1() {
        Vector vec = new Vector();
        vec.addElement("AA");
        vec.addElement("BB");
        vec.addElement("CC");
        Enumeration elements = vec.elements();
        while (elements.hasMoreElements()) {
            Object element = elements.nextElement();
            System.out.println(element);
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值