Java集合

数组存储的特点:

  • 一旦初始化,其长度也就确定了
  • 数组一旦定义好,其元素类型也就确定了。只能操作指定类型的数据

数组存储的弊端:

  • 一旦初始化,长度不可修改
  • 数组提供的方法非常有限
  • 数组只能存储有序、可重复的数据

Collection接口

Collection接口常用方法

Collection是单列数据,区别于Map【key-value键值对】【双列数据】

如果添加的元素有自定义类,需要重写其equals方法

    @Test
    public void test1(){
        Collection coll = new ArrayList();

        //1. add(Object e)
        coll.add("AA");
        coll.add(LocalDateTime.now());

        //2. size()
        System.out.println(coll.size());//4
        //2.1 addALL(Collection coll)
        Collection coll1 = new ArrayList();
        coll1.add("BB");
        coll1.add(456);
        coll.addAll(coll1);
        System.out.println(coll.size());//6

        //3. isEmpty(),判断当前集合是否为空
        System.out.println(coll.isEmpty());//false

        //4. clear 清空集合元素
        coll.clear();

        //5. contains(Obj obj)判断当前集合中是否包含obj
        coll.add("123");
        coll.add(new String("Tom"));

        boolean contains = coll.contains("123");
        System.out.println(contains);//true
        //注意,以下String对象是内容相同,虽然是两个对象,但仍然是true
        //调用equals()方法判断,故如果是自定义类,需要重写equals方法
        System.out.println(coll.contains(new String("Tom")));//true

        //5.1 containsAll(Collection coll1)
        //判断形参coll1中所有元素是否都存在于当前集合中
        Collection coll2 = Arrays.asList(123,456);//List对象
        System.out.println(coll.containsAll(coll2));//false
    }
    @Test
    public void test2(){
        //6. remove(Obj obj),从当前集合中删除指定元素,返回boolean值
        //也调用equals方法
        Collection coll = new ArrayList();
        coll.add(111);
        coll.add(222);
        coll.add(123);
        coll.add("456");
        coll.add(new String("Tom"));
        coll.add(789);
        System.out.println(coll.remove(111));//true

        //6.1 removeAll(Collection coll1),从当前集合中移除coll1中所有的元素
        Collection coll1 =Arrays.asList(new String("Tom"),789);
        System.out.println("removeALL:"+coll.removeAll(coll1));//true
        System.out.println("coll:"+coll);//[222, 123, 456]

        //7. retainAll(Collection coll2),
        //获取当前集合和coll2集合的交集,并返回给当前集合
        Collection coll2 =Arrays.asList(123,222);
        coll.retainAll(coll2);
        System.out.println("coll:"+coll);//coll:[222, 123]

        //8.equals()
        //逐个相比,即使内容一样,如果位置不一样,也是false
        //因为List是有序的,如果是set则不比有序,结果为true
        System.out.println(coll.equals(coll2));//false
    }
    @Test
    public void test3() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add("456");
        coll.add(new String("Tom"));
        coll.add(789);

        //9.集合 ----> 数组,toArray()
        Object[] arr = coll.toArray();
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        //数组 ----> 集合,调用Arrays的静态方法asList
        List<Object> list = Arrays.asList(arr);
        System.out.println(list);//[123, 456, Tom, 789]

        /*
        注意:
        如果直接传递数组,基本类型数组将被视为一个对象而不会被解析成数组
        但是如果直接传递参数将能正常解析。
        因此传递基本类型数组时强烈建议转为其封装类对象的数组
        */
        //情况1:只要传递的基本类型的数组,生成List的元素个数均为1
        List arr1 = Arrays.asList(new int[]{123, 456});
        System.out.println(arr1.size());//1
        //情况2:传递对象数组,元素个数正确。
        List arr2 = Arrays.asList(new Integer[]{123, 456});
        System.out.println(arr2.size());//2
        //情况3:传递参数,元素个数正确。
        List arr1 = Arrays.asList(123, 456);
        System.out.println(arr1.size());//2
    }
        //10.iterator,返回Iterator接口实例,用于遍历集合元素
        //见本文Iterator

Iterator 遍历

集合-collection接口的遍历操作,使用迭代器Iterator接口。

Collection接口继承了java.lang.Iteratble接口,该接口有一个Iterator()方法,用以返回一个实现了Iterator接口的对象。

  • 迭代器对象内部的方法hasNext()next()

  • 集合对象每次调用iterator()方法,都得到一个全新的迭代器对象

    • 默认游标都在集合的第一个元素之前
  • 迭代器对象内部定义了remove(),可以删除当前元素。

    • remove()方法不同于直接调用集合的remove()方法

    • 如果还未调用next()方法,或者上次调用next()方法后已经调用remove()方法,再调用remove()都会报IllegalStateExcepton

 	
	@Test
    public void iteratorTest(){
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add("456");
        coll.add(new String("Tom"));
        coll.add(789);
		
        Iterator iterator1 = coll.iterator();
        //迭代器遍历集合元素 
        while (iterator.hasNext()){
            System.out.println(iterator1.next());
        }
        //删除集合中的“Tom”数据
        Iterator iterator2 = coll.iterator();
        while (iterator2.hasNext()){
            Object obj = iterator2.next();
            if ("Tom".equals(obj)){
                iterator2.remove();
            }
        }
    }

foreach遍历

JDK5.0提供了foreach循环迭代访问Collection或者数组

遍历操作不需要获取Collection或者数组的长度,无需使用索引访问元素。

底层调用的是Iterator接口完成操作。

    @Test
    public void test(){
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add("456");
        coll.add(new String("Tom"));
        coll.add(789);

        //集合:for(集合中元素的类型 局部变量名:集合对象)
        for (Object obj:coll){
            System.out.println(obj);
        }
        //数组:for(数组中元素的类型 局部变量名:数组对象)
        int[] arr = new int[]{1,2,3,4,5};
        for (int i:arr){
            System.out.println(i);
        }
    }

1、List接口

鉴于Java中数组用来存储数据的局限性,通常用List替代数组。

List集合类中元素有序、且可重复,集合中每个元素都有其对应的索引。

相对静态数组而言可以扩展长度,称作”动态数组“。

向List中添加的自定义数据,其所在的类一定要重写equals()

实现类—ArrayList

ArrayList源码分析:

jdk 7的情况下
ArrayList list = new ArrayList();//底层创建了长度为10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次添加导致底层elementData数组容量不够,则扩容。
//默认情况下,扩容为原本容量的1.5倍,同时将原本数组复制到新数组中。
//如果扩容后大小仍不够,则直接以需求的大小作为容量。

故,事先应该估计容量,使用带参的ArrayList构造器。

jdk 8的变化
ArrayList list = new ArrayList();//底层Object[]数组elementData,初始化为{},并没有创建数组
list.add(123);//第一次调用add()时,底层才创建了长度为10的数组,并将123添加到elementData
...
后续添加和扩容与jdk7无异

实现类—LinkedList

双向链表

LinkedList源码分析:

LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node,创建了Node对象

实现类—Vector(根本不用)

Vector源码分析:底层创建了长度为10的Object[]数组,扩容时默认为两倍扩容

List接口额外定义的方法

List三种遍历方式

iterator

foreach

根据索引遍历

    @Test
    public void test(){
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(new String("Person"));

        //1. void add()(int index,Object obj) 在索引index处插入obj元素
        list.add(1,"456");
        System.out.println(list);

        //2. void addAll(int index,Collection coll) 从索引index开始将coll的所有元素插入
        List coll = Arrays.asList("AA", "BB", "CC");
        list.addAll(2,coll);//将coll中的元素逐个插入
        System.out.println(list); //[123, 456, AA, BB, CC, Person]
        list.add(2,coll); //将整个coll作为一个对象插入
        System.out.println(list); //[123, 456, [AA, BB, CC], AA, BB, CC, Person]

        //3. int indexOf(Object obj) 返回obj对象在集合中首次出现的位置。若不存在,则返回-1
        System.out.println("index:"+list.indexOf(123));//index:0

        //4. int lastIndexOf(Object obj) 返回obj对象在集合中最后一次出现的位置。若不存在,则返回-1
        System.out.println("index:"+list.lastIndexOf(123));//index:0

        //5. Object remove(int index) 移除指定index位置的元素,并返回此元素
        //注意,和collection接口中remove方法构成重载。【remove(Obj obj),从当前集合中删除指定元素,返回boolean值】
        System.out.println("remove:"+list.remove(0));//remove:123

        //6. void set(int index,Object obj) 设置index位置上的元素为obj
        list.set(0,789);
        System.out.println(list);//[789, [AA, BB, CC], AA, BB, CC, Person]

        //7. List subList(int fromIndex, int toIndex)
        //返回从fromIndex到toIndex,左闭右开区间的子集
        List listTemp=list.subList(0,2);
        System.out.println(listTemp);//[789, [AA, BB, CC]]
        
        //8. get(int index) 获得某个索引处的值
        System.out.println(list.get(0));
    }

2、Set接口

元素无序、不可重复的集合。

Set接口没有新的方法,即与Collection接口的方法一致.

  • 无序性:根据哈希值来添加存储,而不是根据索引顺序添加

  • 不可重复性:保证添加的元素,按照equals方法判断时,不能返回true

实现类-HashSet

向HashSet中添加的数据,其所在的类一定要重写hashCode()equals()

添加元素的过程:

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

  • 我们向HashSet中添加元素a

  • 首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,通过某种算法得到索引位置。

  • 判断数组此位置上是否有元素

    • 如果没有其他元素,则元素a添加成功
    • 如果有元素b(或者以链表形式存在的多个元素),则比较元素a和元素b的hash值:
      • 如果hash值不相同,则元素a添加成功
      • 如果hash值相同,则调用元素a的equals方法,与元素b相比较:
        • equals方法返回true,则元素a与元素b相同,元素a添加失败
        • equals方法返回false,则元素a与元素b不同,元素a添加成功
    @Test
    public void test(){
        HashSet set = new HashSet();
        set.add(123);
        set.add("ABC");
        set.add("789");
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

实现类-LinkedHashSet

LinkedHashSetHashSet的子类。

HashSet的基础上,LinkedHashSet提供了双向链表,存储的元素多了前节点和后节点,从而实现有顺序。

实现类-TreeSet

TreeSetSortedSet接口的实现类,底层使用红黑树结构存储数据。

TreeSet两种排序方法:自然排序和定制排序,默认自然排序。添加元素时自动排序。

向TreeSet中添加的数据,其所在的类一定要继承Comparable或者Comparator。

TreeSet方法通过排序方法实现去重。

  • 如果自定义类里comparaTo方法只定义了姓名的比较,没有定义年龄的比较,则当两个对象姓名一样年龄不一样时,会进行去重。
  • 如果姓名和年龄都定义了比较,那么则不会去重

TreeSet中添加的对象,要求是相同类的对象。

    @Test
    public void test() {
        TreeSet set = new TreeSet();
        set.add("123");
        set.add("ABC");
        set.add("789");
        Iterator iterator = set.iterator();
        //输出的顺序是比较大小后的结果
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
自然排序

在自然排序中,比较两个对象是否相同的标准为:compareTo返回0。不再是equals方法

//需要Person继承Comparable接口,重写compareTo方法 
//如在new TreeSet()中不加参数,则默认自然排序,即调用compareTo方法
	@Test
    public void test() {
        TreeSet set = new TreeSet();
        set.add(new Person("Tim",13));
        set.add(new Person("Jerry",23));
        set.add(new Person("amy",32));
        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 Person && o2 instanceof Person){
				Person p1 = (Person)o1;
                Person p2 = (Person)o2;
                return p1.name.compareTo(p2.name)
        	}
        	//return 0;
            throw new RunTimeException("传入数据类型不一致");
        }
    }
    //加了参数,按参数进行排序
    TreeSet set = new TreeSet(com);
    
    set.add(new Person("Tim",13));
    set.add(new Person("Jerry",23));
    set.add(new Person("amy",32));
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

Map接口

保存具有映射关系"key-value"的集合

Map结构

  • Map中的key:无序的、不可重复的,故使用Set存储所有的key
    • HashMap:key所在的类要重写equals()hashCode()方法
    • TreeMap:向TreeMap中添加的数据,其所在的类一定要继承Comparable或者Comparator。
  • Map中的value:无序的、可重复的,故使用Collection存储所有的value
    • value所在的类要重写equals()方法

一个键值对:key-value构成了一个Entry对象

  • Map中的Entry:无序的、不可重复的,故使用Set存储所有的Entry

Map接口的方法

//添加、删除、修改操∶
Object put(Object key,0bject value)∶将指定key-value添加到(或修改)当前map对象中 
void putAll(Map m)∶将m中的所有key-value对存放到当前map 中 
Object remove(Object key)∶移除指定key的key-value对,并返回value 
void clear()∶,清空当前imap 中的所有数据元素查询的操∶
    
//元素查询
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()∶ 返回所有entry对象key-value对构成的Set集合

    //转为Set和Collection后,可以用iterator进行遍历
    //Entry类有getKey()和getValue()
//遍历所有的key-value
//方式一,使用entrySet()得到所有entry对象,再逐个获取值
	@Test
    public void test(){
        LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
        map.put(123,"AA");
        map.put(456,"BB");
        map.put(789,"CC");
        
        Set entrySet = map.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()){
            Object obj = iterator.next();
            //entrySet集合中的元素都是entry
            Map.Entry entry = (Map.Entry)obj;
            System.out.println(entry.getKey()+"----->"+entry.getValue());
        }
    }

//方式二,使用keySet()得到所有key,再逐个获取key对应的value

实现类—HashMap

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

实现类—LinkedHashMap

保证在遍历map元素时,可以按照添加的顺序实现遍历。

@Test
public void test(){
    LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
    map.put(123,"AA");
    map.put(456,"BB");
    map.put(789,"CC");
    System.out.println(map);
}

实现类—TreeMap

TreeMapSortedSet接口的实现类,底层使用红黑树结构存储数据。

TreeMap两种排序方法:自然排序和定制排序,默认自然排序。添加元素时自动排序。

//自然排序,实现Map中自定义类继承Comparable接口,重写里的compareTo方法
//Todo
//定制排序
    @Test
    public void test2(){
        TreeMap treeMap = new TreeMap<>(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Person && o2 instanceof Person){
                    Person p1 = (Person)o1;
                    Person p2 = (Person)o2;
                    return Integer.compare(p1.getAge(),p2.getAge());
                }
                throw new RuntimeException("输入类型不匹配");
            }
        });
    }

实现类—Hashtable(根本不用)

作为古老的实现类;

线程安全,效率低;

不可以存储key为null或者value为null的数据

实现类—Properties

Hashtable的子类

//properties文件
//不能为了好看加空格
name=Tom
password=abc123
public class HashMapTest {
    public static void main(String[] args)  {
        Properties pros = new Properties();
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("jdbc.properties");
            
            //加载流对应文件
            pros.load(fis);
            
            String name = pros.getProperty("name");
            String password = pros.getProperty("password");
            System.out.println("name="+name+",password="+password);
            //name=Tom,password=abc123
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类

Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

排序操作∶(均为static方法)

reverse(List)∶ 反转 List 中元素的顺序

shuffle(List)∶ 对 List 集合元素进行随机排序	//例:Collections.shuffle(l1);

sort(List)∶ 根据元素的自然顺序对指定 List 集合元素按升序排序

sort(List,Comparator)∶根据指定的 Comparator 产生的顺序对 List 集合元素进行排序

swap(List,intint)∶ 将指定 list 集合中的i处元素和 j 处元素进行交换
	@Test
    public void test5(){
        ArrayList coll = new ArrayList();
        coll.add(123);
        coll.add("456");
        coll.add(789);

        //copy()的使用
        /* 不能如下的方式使用copy,
        List dest = new ArrayList();
        Collections.copy(dest,coll);
        
        因为此时dest的底层数据size为0,调用copy(),由于小于源列表,会报异常
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");
         */
        //如下,此时新建Object类型数组,默认值为null,然后转成list
        List dest =  Arrays.asList(new Object[coll.size()]);
        System.out.println(dest.size());
        Collections.copy(dest,coll);
        System.out.println(dest);
    }

查找、替换

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中

boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值

同步控制

Collections类提供了多个synchronizedXxx()方法,

该方法可使将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题

//例如,返回的list就是线程安全
List list = Collections.synchronizedList(l1)

题目

ArrayListLinkedListVector三者的异同

相同:三个类都实现了List接口,都是存储有序、可重复的数据

不同:

  • ArrayList:List接口的主要实现类;线程不安全,效率高;底层使用Object[ ]存储。
  • LinkedList:对于频繁的插入、删除操作,使用此类比ArrayList高;底层使用双层链表存储。
  • Vector:List接口的古老实现类;线程安全,效率低;底层使用Object[ ]存储。

HashSetLinkedHashSetTreeSet三者的异同

  • HashSet:作为Set接口的主要实现类;线程不安全;可以存储Null值
    • LinkedHashSet:作为HashSet的子类;遍历内部数据时,可以按照添加的顺序遍历;对于频繁的遍历操作,LinkedHashSet高于HashSet
  • TreeSet:可以按照添加元素的指定属性,进行排序

Map中各类的异同

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

    • LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。

      ​ 原因:在原有HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。

      ​ 对于频繁的遍历,效率高于HashMap

  • TreeMap:可以按照添加的key-value进行排序,实现排序遍历。

    ​ 此时考虑key的自然排序或者定制排序。

  • HashTable:作为古老的实现类;线程安全,效率低;不可以存储key为null或者value为null的数据

    • Properties:常用来处理配置文件。key和value都是Stirng类型

List 的小题

区分List中remove(int index)remove(Object obj)

//list输出什么?
public void testListRemove() {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        updateList(list);
        System.out.println(list);//[1,2]
    }

private static void updateList(List list) {
        list.remove(2);//此处把2看为索引,结果------->[1,2]
    	list.remove(new Integer(2));//此处视为对象2,结果------>[1,3]
    }

HashMap的底层实现原理

以JDK-7为例说明

HashMap map = new HashMap();

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

【可能经过了多次put】

map.put(key1,value1);

首先,调用key1所在类的hashCode()计算key1的哈希值。

然后,使用该哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。

  • 如果此位置上的数据为空,此时的key1-value1添加成功
  • 如果此位置上的数据不为空【此位置存在一个或多个数据(以链表形式存在)】,需要比较key1和已经存在数据的哈希值
    • 如果key1的哈希值和已存在数据的哈希值都不相同,此时key1-value1添加成功
    • 如果key1的哈希值和某个数据的哈希值相同,则继续比较,调用key1所在类的equals()方法
      • 如果equals()返回false,此时的key1-value1添加成功
      • 如果equals()返回true,则使用value1替换此相同key的value值【即修改value值】

当超出临界值(且要存放的位置非空时),扩容。

默认扩容方式:扩容为原来的两倍,并将原本的数据复制过来

相较JDK-7,JDK-8的区别

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

  • JDK-8底层的数组是Node[],而非Entry[]

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

  • JDK-7底层结构只有数组+链表。JDK-8底层结构:数组+链表+红黑树

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

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

  • JDK-7是头插法,JDK-8是尾插法

HashMap底层参数

  • DEFAULT_INITTAL_CAPACITY∶HashMap的默认容量,16

  • DEFAULT_LOAD_FACTOR∶ HashMap的默认加载因子∶ 0.75

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

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

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

  • table∶存储元素的数组,总是2的n次幂 entrySet∶存储具体元素的集

  • size∶HashMap中存储的键值对的数量

  • modCount∶ HashMap扩容和结构改变的次数

  • threshold∶扩容的临界值,=容量*填充因子

  • loadFactor∶ 填充因子

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值