Java集合(下)

简介: Java集合(下)

五、Collection子接口之二: Set接口及其实现类


Set接口是Collection的子接口,set接口没有提供额外的方法。 Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。 Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法。


* Set接口的框架

*

* |---Collection接口:单列集合,用来存储一个一个的对象


*          |---Set接口:存储无序的、不可重复的数据      

*                   |---HashSet:作为Set接口的主要实现类:线程不安全的:可以存储null值

*                          |---LinkedHashSet:作为HashSet的子类:遍历其内部数据时,可以按 *                                             照添加的顺序遍历

*                                             对于频繁的遍历操作,LinkedHashSet效率高于   *                                             HashSet

*                   |---TreeSet:可以按照添加对象的指定属性,进行排序

*

*    1.Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法

*

*    2.要求:向Set中添加的数据,其所在的类一定要重写hashCode()和equals()方法

*      要求:重写的hashCode()和equals()尽可能保持一致性。


一、Set:存储无序的、不可重复的数据


以HashSet为例说明

1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的


2.不可重复性:保证添加元素按照equals()判断时,不能返回true,即:相同元素只能添加一个


二、添加数据的过程:以HashSet为例


我们向HashSet中添加元素a,首先调用元素a所在类的hashCode方法,计算元素a的哈希值,

此哈希值接着通过某种算法计算出在HashSet底层数组中存放位置(即为:索引位置),判断

数组此位置上是否已经有元素:

       如果此位置上没有其他元素,则元素a添加成功。--->情况1

       如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的哈希值:

               如果哈希值不相同,则元素a添加成功。--->情况2

               如果哈希值相同,进而需要调用元素a所在类的equals()方法:

                       equals()返回true,元素a添加失败

                       equals()返回false,则元素a添加成功。--->情况3



对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储

jdk 7 :元素a放到数组中,指向原来的元素

jdk 8 : 原来的元素在数组中,指向元素a

总结:七上八下


HashSet底层:数组+链表的结构。


①HashSet


32825460be015c474eade9e42684df2f_3bbe8125a09845298dce7a7645b86c20.png



c9f2e7b70f5d3ebd8fcfc6a6971723b3_eb4db3cb144b49d6b5018907ece6d4cd.png


5e43e63bc1617056e90ed27f22730091_b2c8adf0732b4b60b05e0cad134cb8cf.png


f4873f5eefee6cdd340b04913416b2b3_434dc156080649ca918f7e887ed598c7.png


LinkedHashSet的使用

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。

优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet


5717e5b1473a1e473ce5652f90e5f8c2_84c97ac260e141389317d7a50511e617.png


public class TreeSetTest {
    /*
    1.向TreeSet中添加的数据,要求是相同类的对象
    2.两种排序方式:自然排序(实现comparable接口) 和 定制排序(comparator)
    3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()。
    4.定制排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()。
     */
    @Test
    public void test1(){
        TreeSet set = new TreeSet();
        //不能添加不同类的对象
//        set.add(123);
//        set.add(456);
//        set.add("AA");
//        set.add(new User("Tom",12));
        //举例一:
//        set.add(34);
//        set.add(-34);
//        set.add(43);
//        set.add(11);
//        set.add(8);
        //举例二:
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
    @Test
    public void test2(){
        Comparator com = new Comparator() {
            //按照年龄从小到大排列
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else {
                    throw new RuntimeException("输入的数据类型不匹配");
                }
            }
        };
        TreeSet set = new TreeSet(com);
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Jack",33));
        set.add(new User("Marry",33));
        set.add(new User("Jack",56));
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
public class User implements Comparable{
    private String name;
    private int age;
    public User() {
    }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        System.out.println("User的equals方法");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
    //按照姓名从大到小排序,年龄从小到大排列
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
//            return -this.name.compareTo(user.name);
            int compare = -this.name.compareTo(user.name);
            if (compare != 0){
                return compare;
            }else {
                return Integer.compare(this.age,user.age);
            }
        }else {
            throw new RuntimeException("输入的类型不匹配");
        }
    }
}


六、Map接口


一、Map实现类的结构

|----Map:双列数据,存储key-value的数据     ---类似于高中的函数:y = f(x)

        |----HashMap:作为Map的主要实现类:线程不安全的,效率高;可以存储null的key-value的数据

             |----LinkedHashMap:保证在遍历Map元素时,可以按照添加的顺序实现遍历

                    原因:在原来的HashMap的基础上,添加了一对指针,指向前一个和后一个元素。

                    对于频繁的遍历操作,此类执行效率比HashMap高。

        |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序

                    底层使用红黑树

        |----Hashtable:作为古老的实现类:线程安全的,效率低;不能存储null的key-value的数据

             |----Properties:常用来处理配置文件,key和value都是String类型


        HashMap的底层: 数据组+链表   (jdk7及之前)

                       数组+链表+红黑树   (jdk8)


d45f0416db4f3150400c19d07268b0e5_502ce92dfc724d88b7b5f6d6c09dee53.png


二、Map结构的理解

Map与Collection并列存在。用于保存具有映射关系的数据:key-value

Map 中的 key 和 value 都可以是任何引用类型的数据

Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法

常用String类作为Map的“键”

key 和 value 之间存在单向一对一关系,即通过指定的key总能找到 唯一的、确定的 value Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和 Properties。其中,HashMap是 Map 接口使用频率最高的实现类


1fafeb532ef69362ea590af7bf3f012d_4083f42199634282890f924b62bb305a.png


三、Map实现类之一:HashMap( 使用频率最高 的实现类 )


79258a88b5d345f1ea1ce9ba665748dc_2ea2316368aa4de086ec252dae78c9c6.png


① HashMap 的存储结构

JDK 7 及以前版本: HashMap 是 数组+链表结构 ( 即为链地址法 )

JDK 8 版本发布以后: HashMap 是 数组+链表+红黑树 实现。

演示图如下:


c756bb0de5ea31afcb51b77d6fbcb030_e22518f1c5704d78bf8dc9ff6175e3cb.png


********************************************************************************************


693871ce938abcabe9ab13aaaf84a1c4_ef33fab25c0c4849bd940a9b5383487a.png


②HashMap的底层实现原理


以jdk7为例说明:

HashMap map = new HashMap();

在实例化以后,底层创建了长度是16的一维数组Entry[]  table。

.....可能已经执行过多次put...

map.put(key1,value1);

首先,计算key1所在类的HashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到Entry数组中的存放位置。

如果此位置上的数据为空,key1-value1添加成功。    ----情况1

如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较当前key1和已经存在的一个或多个数据

的哈希值:

       如果key1的哈希值跟已经存在的数据的哈希值不同,此时key1-value1添加成功。   ----情况2

       如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key11所在类的equals()方法,比较:

               如果equals()返回false:此时key1-value1添加成功。    ----情况3

               如果equals()返回true:将使用value1替换value2.

补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。形成链表结构时,新添加的key-value对在链表的尾部(七上八下)

在不断的添加过程中,会涉及到扩容问题,当超出临界值(且存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将所有的数据复制过来


jdk8  相较于  jdk7在底层实现方面的不同:

1. new  HashMap():底层没有创建:底层没有创建一个长度为16的数组

2. jdk  8底层数组是:Node[],而非Entry[]

3. 首次调用put()方法时,底层创建长度为16的数组

4. jdk7底层结构:数组+链表。

  jdk8底层结构:数组+链表+红黑树

   当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时

    此时此索引位置上的所有数据改为红黑树存储。


③HashMap源码中的重要常量


DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16

DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75

threshold:扩容的临界值,=容量*填充因子:16*0.75 = 12

TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8

MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。64

④HashMap的扩容


当 HashMap 中的元素越来越多的时候, hash 冲突的几率也就越来越高,因为数组的 长度是固定的。所以为了提高查询的效率,就要对HashMap 的数组进行扩容,而在 HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算 其在新数组中的位置,并放进去,这就是resize 。

那么 HashMap 什么时候进行扩容呢?

当 HashMap 中的元素个数超过数组大小 loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的默认值 (DEFAULT_LOAD_FACTOR ) 为 0.75 ,这是一个折中的取值。也就是说,默认情况下,数组大小( DEFAULT_INITIAL_CAPACITY ) 为 16 ,那么当 HashMap 中元素个数 超过16*0.75=12 (这个值就是代码中的 threshold 值,也叫做临界值)的时候,就把 数组的大小扩展为 2*16=32 ,即扩大一倍,然后重新计算每个元素在数组中的位置, 而这是一个非常消耗性能的操作, 所以如果我们已经预知 HashMap 中元素的个数, 那么预设元素的个数能够有效的提高HashMap 的性能。


四、Map实现类之二:LinkedHashMap


LinkedHashMap 是 HashMap 的子类

在 HashMap 存储结构的基础上,使用了一对双向链表来记录添加

元素的顺序

与 LinkedHashSet 类似, LinkedHashMap 可以维护 Map 的迭代

顺序:迭代顺序与 Key-Value 对的插入顺序一致

HashMap 中的内部类: Node

static class Node< K , V > implements Map.Entry< K , V > {

final int hash ;

final K key ;

V value ;

Node< K , V > next ;

}

LinkedHashMap 中的内部类: Entry

static class Entry< K , V > extends HashMap.Node< K , V > {

Entry< K , V > before , after ;

Entry( int hash, K key, V value, Node< K , V > next) {

super (hash, key, value, next);

}

}


五、Map实现类之三:TreeMap


TreeMap 存储 Key-Value 对时,需要根据 key-value 对进行排序。

TreeMap 可以保证所有的 Key-Value 对处于 有序 状态。

TreeSet 底层使用 红黑树 结构存储数据

TreeMap 的 Key 的排序:

自然排序 : TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有

的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException

定制排序 :创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对

TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现

Comparable 接口

TreeMap 判断 两个 key 相等的标准 :两个 key 通过 compareTo() 方法或

者 compare() 方法返回 0 。


public class TreeMapTest {
    //向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
    //因为要按照key进行排序:自然排序、定制排序
    //自然排序
    @Test
    public void test1(){
        TreeMap map = new TreeMap();
        User u1 = new User("Tom",23);
        User u2 = new User("Jerry",32);
        User u3 = new User("Jack",20);
        User u4 = new User("Rose",18);
        map.put(u1,98);
        map.put(u2,89);
        map.put(u3,76);
        map.put(u4,100);
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Object o = iterator.next();
            Map.Entry entry = (Map.Entry) o;
            System.out.println(entry.getKey() + "---->" + entry.getValue());
        }
    }
    //定制排序
    @Test
    public void test2(){
        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User && o2 instanceof User){
                    User u1 = (User) o1;
                    User u2 = (User) o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }
                throw new RuntimeException("输入类型不匹配");
            }
        });
        User u1 = new User("Tom",23);
        User u2 = new User("Jerry",32);
        User u3 = new User("Jack",20);
        User u4 = new User("Rose",18);
        map.put(u1,98);
        map.put(u2,89);
        map.put(u3,76);
        map.put(u4,100);
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Object o = iterator.next();
            Map.Entry entry = (Map.Entry) o;
            System.out.println(entry.getKey() + "---->" + entry.getValue());
        }
    }
}
//********************************************************************
public class User implements Comparable{
    private String name;
    private int age;
    public User() {
    }
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        System.out.println("User的equals方法");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
    //按照姓名从大到小排序,年龄从小到大排列
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
//            return -this.name.compareTo(user.name);
            int compare = -this.name.compareTo(user.name);
            if (compare != 0){
                return compare;
            }else {
                return Integer.compare(this.age,user.age);
            }
        }else {
            throw new RuntimeException("输入的类型不匹配");
        }
    }
}


六、Map实现类之四:Hashtable


Hashtable 是个古老的 Map 实现类, JDK1.0 就提供了。不同于 HashMap ,

Hashtable 是线程安全的。

Hashtable 实现原理和 HashMap 相同,功能相同。底层都使用哈希表结构,查询

速度快,很多情况下可以互用。

与 HashMap 不同, Hashtable 不允许使用 null 作为 key 和 value

与 HashMap 一样, Hashtable 也不能保证其中 Key-Value 对的顺序

Hashtable 判断两个 key 相等、两个 value 相等的标准,与 HashMap 一致。


七、Map实现类之五:Properties


Properties 类是 Hashtable 的子类,该对象用于处理属性文件

由于属性文件里的 key 、 value 都是字符串类型,所以 Properties 里的 key

和 value 都是字符串类型

存取数据时,建议使用 setProperty(String key,String value) 方法和

getProperty(String key) 方法

Properties pros = new Properties();

pros .load( new FileInputStream( "jdbc.properties" ));

String user = pros .getProperty( "user" );

System. out .println( user )


八、Map接口中定义的方法:


添加、删除、修改操作:

Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中

void putAll(Map m):将m中的所有key-value对存放到当前map中

Object remove(Object key):移除指定key的key-value对,并返回value

void clear():清空当前map中的所有数据


元素查询的操作:

Object get(Object key):获取指定key对应的value

boolean containsKey(Object key):是否包含指定的key

boolean containsValue(Object value):是否包含指定的value

int size():返回map中key-value对的个数

boolean isEmpty():判断当前map是否为空

boolean equals(Object obj):判断当前map和参数对象obj是否相等


元视图操作的方法:

Set keySet():返回所有key构成的Set集合

Collection values():返回所有value构成的Collection集合

Set entrySet():返回所有key-value对构成的Set集合


总结:常用方法:

添加:put()

删除:remove()

修改:put()

查询:get()

长度:size()

遍历:keySet()/values()/ entrySet()


public class MapTest {
    /*
     *  元视图操作的方法:
     *  Set keySet():返回所有key构成的Set集合
     *  Collection values():返回所有value构成的Collection集合
     *  Set entrySet():返回所有key-value对构成的Set集合
     */
    @Test
    public void test5(){
        HashMap map = new HashMap();
        map.put("AA",123);
        map.put(45,1234);
        map.put("BB",56);
        //遍历所有的key集:keySet()
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        System.out.println();
        //遍历所有的value集:values()
        Collection values = map.values();
        for(Object obj : values){
            System.out.println(obj);
        }
        System.out.println();
        //遍历所有的key-value
        //entrySet():
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            //entrySet集合中的元素都是entry
            System.out.println(iterator1.next());
        }
    }
    /*
     *  元素查询的操作:
     *  Object get(Object key):获取指定key对应的value
     *  boolean containsKey(Object key):是否包含指定的key
     *  boolean containsValue(Object value):是否包含指定的value
     *  int size():返回map中key-value对的个数
     *  boolean isEmpty():判断当前map是否为空
     *  boolean equals(Object obj):判断当前map和参数对象obj是否相等
     */
    @Test
    public void test4(){
        HashMap map = new HashMap();
        map.put("AA",123);
        map.put(45,123);
        map.put("BB",123);
        // Object get(Object key)
        System.out.println(map.get(45));
        //containsKey(Object key)
        boolean isExist = map.containsKey("BB");
        System.out.println(isExist);
        isExist = map.containsValue(123);
        System.out.println(isExist);
        map.clear();
        System.out.println(map.isEmpty());
    }
    /*
     * 添加、删除、修改操作:
     *  Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
     *  void putAll(Map m):将m中的所有key-value对存放到当前map中
     *  Object remove(Object key):移除指定key的key-value对,并返回value
     *  void clear():清空当前map中的所有数据
     */
    @Test
    public void test3(){
        HashMap map = new HashMap();
        //添加
        map.put("AA",123);
        map.put(45,123);
        map.put("BB",123);
        //修改
        map.put("AA",87);
        System.out.println(map);
        HashMap map1 = new HashMap();
        map1.put("CC",123);
        map1.put("DD",123);
        map.putAll(map1);
        System.out.println(map);
        //remove(Object key)
        Object value = map.remove("CCC");
        System.out.println(value);
        System.out.println(map);
        //clear()
        map.clear();//与map = null不同
        System.out.println(map.size());
        System.out.println(map);
    }
}


目录
相关文章
|
17天前
|
安全 Java 大数据
|
16天前
|
安全 Java 开发者
【JAVA】哪些集合类是线程安全的
【JAVA】哪些集合类是线程安全的
|
16天前
|
Java
【JAVA】怎么确保一个集合不能被修改
【JAVA】怎么确保一个集合不能被修改
|
2天前
|
存储 安全 Java
Java一分钟之-集合框架进阶:Set接口与HashSet
【5月更文挑战第10天】本文介绍了Java集合框架中的`Set`接口和`HashSet`类。`Set`接口继承自`Collection`,特征是不允许重复元素,顺序不确定。`HashSet`是`Set`的实现,基于哈希表,提供快速添加、删除和查找操作,但无序且非线程安全。文章讨论了`HashSet`的特性、常见问题(如元素比较规则、非唯一性和线程安全性)以及如何避免这些问题,并提供了代码示例展示基本操作和自定义对象的使用。理解这些概念和注意事项能提升代码效率和可维护性。
9 0
|
2天前
|
存储 安全 算法
Java一分钟之-Java集合框架入门:List接口与ArrayList
【5月更文挑战第10天】本文介绍了Java集合框架中的`List`接口和`ArrayList`实现类。`List`是有序集合,支持元素重复并能按索引访问。核心方法包括添加、删除、获取和设置元素。`ArrayList`基于动态数组,提供高效随机访问和自动扩容,但非线程安全。文章讨论了三个常见问题:索引越界、遍历时修改集合和并发修改,并给出避免策略。通过示例代码展示了基本操作和安全遍历删除。理解并正确使用`List`和`ArrayList`能提升程序效率和稳定性。
7 0
|
4天前
|
存储 安全 算法
掌握Java并发编程:Lock、Condition与并发集合
掌握Java并发编程:Lock、Condition与并发集合
11 0
|
4天前
|
存储 安全 Java
深入理解Java集合框架
深入理解Java集合框架
9 0
|
9天前
|
存储 安全 Java
Java集合的分类有哪些?
Java中的集合就像一个容器,专门用来存储Java对象,这些对象可以是任意的数据类型,并且长度可变。这些集合类都位于java.util包中,在使用时一定要注意导包的问题,否则会出现异常。
36 10
|
12天前
|
安全 Java
循环的时候去删除集合中的元素 java.util.ConcurrentModificationException
循环的时候去删除集合中的元素 java.util.ConcurrentModificationException
|
14天前
|
Java
【专栏】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性
【4月更文挑战第28天】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性。本文介绍了 Streams 的基本概念,如从数据源创建 Stream,以及中间和终端操作。通过过滤、映射、归并、排序、分组等案例,展示了 Streams 的使用,包括并行 Streams 提高效率。学习 Streams 可以提升代码质量和效率,文章鼓励读者在实际开发中探索更多 Streams 功能。