集合

一、集合与数组

数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用。

集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使用。

二、层次关系

这里先用一个通俗易懂的层次图

层次图

三、接口

1.Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口,就是Set和List。Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

2.Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

3.List接口是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

4.Set接口存入Set的每个元素必须是唯一的,这也是与List不同的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。

四、工具类

1.Collections类是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。

ArrayList list = new ArrayList();
        list.add(100);
        list.add(22);
        list.add(77);
        list.add(44);
        list.add(55);
        System.out.println("操作前"+list);
        //取coll中最大的元素
        Collections.max(list);
        //asc
        Collections.sort(list);
        //desc
        Collections.sort(list, Collections.reverseOrder());
        //利用Comparator接口排序
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer num1, Integer num2) {
                // 大于0的就是asc小于0desc
                return num1 > num2 ? 1 : -1;
            }
        });
        System.out.println("操作后"+list);

Comparable与Comparator区别

Comparable 相当于 “内部比较器”,而 Comparator 相当于 “外部比较器”。

前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。可以说前者属于 “静态绑定”,而后者可以 “动态绑定”。

2.Arrays这个类包含了各种方法来操作数组(提供搜索、排序、复制等静态方法)。

//数组转集合
String[] str = new String[] { "1", "2", "3" };
List<String> kkk = Arrays.asList(str);
//集合转数组
ArrayList<String> list=new ArrayList<String>();
String[] strings = new String[list.size()];
strings = list.toArray(strings);
//数组扩容
String[] str = new String[2];
String[] sd = Arrays.copyOf(str, 5);

3.这里我们扩展一个方法System.arraycopy()

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest : 目标数组
int destPos : 目标数组的开始起始位置
int length  : 要copy的数组的长度

demo:

//源数组
byte[]  srcBytes = new byte[]{2,4,0,0,0,0,0,10,15,50};
//目标数组
byte[] destBytes = new byte[5];
System.arraycopy(srcBytes,0,destBytes ,0,5);
for (int i = 0; i <destBytes.length ; i++) {
    System.out.println(destBytes[i]);
}

创建一个一维空数组,数组的总长度为 10位,然后将srcBytes源数组中 从0位到第5位之间的数值 copy 到 destBytes目标数组中,在目标数组的第0位开始放置,那么这行代码的运行效果应该是 2,4,0,0,0

五、具体类

List接口

1.ArrayList类

ArrayList由动态数组实现。它允许所有元素,包括null。ArrayList没有同步。

实现扩容:system.arraycopy();

实现同步:List list = Collections.synchronizedList(new ArrayList(...));

操作:

便与查找:随机访问get(i)的时候,根据指定下标,直接获取。

不便修改:底层动态数组,需要数组扩容,改动一处所有下标都需要移动。

动态扩容:

可以看一下源码,初始化一个ArrayList(),list默认容量为10;只有进行add时,会将执行之前的list.size+1; 传入ensureCapacityInternal(int minCapacity);Math.max(DEFAULT_CAPACITY, minCapacity)获取最小需求容量;传入ensureExplicitCapacity(int minCapacity);如果 minCapacity - elementData.length 最小需求容量-数组容量 > 0,进行扩容elementData.length *3/2,最终调用system.arraycopy(),实现扩容。

这里有一个小列子,创建一个ArrayList(),循环20次向集合增加数据。

第一次初始化10的容量,一直到11次时,执行之前size为10,

minCapacity为10+1,elementData.length=10,判断11-10>0,则10*3/2=15

一直到16时,执行之前size为15,

minCapacity为15+1,elementData.length=15,判断16-15>0,则15*3/2=22

2.LinkedList类

LinkedList由双向链表实现。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。

这些操作使LinkedList可被用作堆栈(stack),队列(queue)。

实现同步:List list = Collections.synchronizedList(new LinkedList(...));

不便查找:LinkedList的底层是一个链表,随机访问get(i)的时候,链表只能从头往后数,第i个才返回。所以时间随着i的变大时间会越来越长。

方便修改:底层双向链表,每一个节点header(前节点信息next,业务数据信息,后节点previous)。

3.Vector类

Vector非常类似ArrayList,但是Vector是同步的。

由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的, 当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素), 这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

Set接口

1.HashSet类

HashSet能快速定位一个元素,存入HashSet的对象必须定义hashCode()和equals()。 我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低, 哈希算法提高了去重复的效率, 降低了使用equals()方法的次数。 当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象,如果没有哈希值相同的对象就直接存入集合。如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较 比较结果为false就存入, true则不存 。(对象相同,hashCode()一定相同;hashCode()相同,对象不一定相同)。

@Data
public class User {
    private String name;
    private int age;
    private String passport;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof User)) {
            return false;
        }
        User user = (User) o;
        return age == user.age &&
                Objects.equals(name, user.name) &&
                Objects.equals(passport, user.passport);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, passport);
    }
}

2.TreeSet类

保持次序的Set,底层为二叉树结构。使用它可以从Set中提取有序的序列,生成自己的类时,TreeSet需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。

3.LinkedHashSet类

具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。

迭代集合

  • for循环: 从0循环到集合的size()-1, 每次获取其中一个
  • 迭代器: 调用iterator()方法获取迭代器, 使用hasNext()判断是否包含下一个元素, 使用next()获取下一个元素
  • 增强for循环: for (类型 变量名 : 容器) { 循环体 } 容器中有多少个元素就执行多少次循环体, 每次循环变量指向容器中不同的元素

迭代时删除的问题

1、for循环遍历list

for(int i=0;i<list.size();i++){
    if(list.get(i).equals("del"))
        list.remove(i);
}

这种方式的问题在于,删除某个元素后,list的大小发生了变化,而你的索引也在变化,所以会导致你在遍历的时候漏掉某些元素。比如当你删除第1个元素后,继续根据索引访问第2个元素时,因为删除的关系后面的元素都往前移动了一位,所以实际访问的是第3个元素。

2、增强for循环

for(String x:list){
    if(x.equals("del"))
        list.remove(x);
}

这种方式的问题在于,删除元素后继续循环会报错误信息ConcurrentModificationException,因为元素在使用的时候发生了并发的修改,导致异常抛出。但是删除完毕马上使用break跳出,则不会触发报错。

3、iterator遍历

Iterator<String> it = list.iterator();
while(it.hasNext()){
    String x = it.next();
    if(x.equals("del")){
        it.remove();
    }
}

这种方式可以正常的循环及删除。但要注意的是,使用iterator的remove方法。

Map接口

1.HashMap类

Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity(16)和负载因子load factor(0.75),以调整容器的性能。

初始容量,是哈希表在创建时的容量。加载因子,是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。

如果桶的容量是40,加载因子是0.75 那么你的桶最多能装40*0.75 = 30的水,如果你装了30的水还想继续装水,那么就该用大一点的桶,调用rehash就是负责增加桶的容量的方法。

hashmap结构:哈希表是由数组+链表 组成的,数组的 默认长度为16 ( 可以自动变长。在构造HashMap的时候也可以指定一个长度 ),数组里每个元素存储的是一个链表的头结点。而组成链表的结点其实就是hashmap内部定义的一个类:Entity。Entity包含三个元素:key,value和指向下一个Entity的next。

输入图片说明

第一个键值对A进来,通过计算其key的hash得到的index=0,记做:table[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A ,table[0] = B,如果又进来C,index也等于0,那么C.next = B ,table[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。

从结构实现来讲,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下如所示。

输入图片说明

HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组,明显它是一个Node的数组。Node是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。上图中的每个黑色圆点就是一个Node对象。 即使负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能。

2.Hashtable类

Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。

Hashtable是不能存储null键和null值的,线程安全,效率低。

HashMap可以存储null键和null值,线程不安全,效率高。

3.LinkedHashMap类

类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。

4.TreeMap类

基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在 于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

5.ConcurrentHashMap类

ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁得几率,提高并发效率。或者(Collections.synchronizedMap(Map map))。

Map迭代

1.通过Map.keySet遍历key和value

for (String key : map.keySet()) {
			System.out.println("key= " + key + " and value= " + map.get(key));
		}

2.通过Map.entrySet使用iterator遍历key和value

Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String, String> entry = it.next();
			System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
		}

3.通过Map.entrySet遍历key和value(推荐)

for (Map.Entry<String, String> entry : map.entrySet()) {
			System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
		}

转载于:https://my.oschina.net/u/3056927/blog/1798389

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值