Collection集合(非常重要的一章,涉及到List、Set、Map,HashMap的底层源码分析,面试基本必问)

集合

1、集合的理解和好处

(1)数组
  • 长度开始时必须指定,而且一旦指定,不能更改
  • 保存的必须为同一类型的元素
  • 使用数组进行增加/删除元素比较麻烦
(2)集合
  • 可以动态的保存多个对象,使用比较方便
  • 提供了一系列方便的操作对象的方法,add,remove,set,get
  • 使用集合添加,删除新元素比较简洁,方便

2、集合的框架体系

  • 第一类:单列集合(List,Set)

在这里插入图片描述

  • 第二类:双列集合(Map),存放的是键值对形式

在这里插入图片描述

3、COLLECTION接口和常用方法

(1)COLLECTION接口特点
  • collection实现子类可以存放多个元素,每个元素可以是Object
  • 有些Collection实现类,可以存放重复的元素,有些不可以
  • 有些Collection实现类,有些是有序的(List),有些不是有序(Set)
  • Collection接口没有直接的实现子类,是需要通过它的子接口Set和List实现的
(2)COLLECTION接口方法-以ArrayList为例
public static void main(String[] args) {
    List list = new ArrayList();
    list.add("滴滴");
    list.add(true);
    list.add(10);   //存放的是对象,Integer(10)
    list.remove(1); //删除对应下标元素
    list.remove(true);  //删除指定元素
    list.contains("jack");
    list.size();
    list.isEmpty();
    list.addAll(new ArrayList<>(Arrays.asList("shu","sho","sho")));
}
(3)COLLECTION 接口遍历元素方式

第一种方式:Iterator(迭代器)

Java迭代器(Iterator)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口。

它提供了一种统一的方式来访问集合中的元素,而不需要了解底层集合的具体实现细节。Iterator本身并不存放对象。

迭代器接口定义了几个方法,最常用的是以下三个:

  • next() - 返回迭代器的下一个元素,并将迭代器的指针移到下一个位置。
  • hasNext() - 用于判断集合中是否还有下一个元素可以访问。
  • remove() - 从集合中删除迭代器最后访问的元素(可选操作)。

第二种方式:增强for循环

两种方式举例:

public static void main(String[] args) {
    // 创建集合
    ArrayList<String> sites = new ArrayList<String>();
    sites.add("Google");
    sites.add("Runoob");
    sites.add("Taobao");
    sites.add("Zhihu");
    //创建迭代器,进行遍历,相当于一个指针
    Iterator iterator = sites.iterator();
    while(iterator.hasNext()){
        String str = (String)iterator.next();
        System.out.println(str);
        if(str.equals("Google")){   //如果是google,删除当前元素
            iterator.remove();
        }
    }
    //进行下次遍历的时候要对迭代器进行重置
    iterator = sites.iterator();
    //另外一种遍历方式,增强for循环
    for(String str : sites){
        System.out.println(str);
    }
}

4、LIST接口和常用方法

(1)List接口介绍
  • List集合中元素有序,可以重复
  • List集合中每个元素都有对应的顺序索引
  • List容器中元素都对应一个整数型的序号记录其位置,可以根据序号取元素
  • 常用的接口实现类有ArrayList,LinkedList和Vector
(2)List常用方法

可以参考COLLECTION的方法介绍,基本一致,具体的建议看官方文档。

public static void main(String[] args) {
    List list = new ArrayList();
    list.add("张三丰");
    list.add("潘小青");
    list.add(1,"邹昊飞");  //在index = 1的地方插入
    list.set(1,"明星");       //在指定index插入设置元素
    list.get(1);            //获取下标为1的元素
    list.remove(1);    //删除下标为1的元素
}

5、ArrayList底层结构与源码分析

(1)ArrayList的注意事项
  • 允许所有类型的元素,包括空值null,并且可以多个
  • ArrayList是由数组实现数据存储的
  • ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高),在多线程的情况下,不建议使用ArrayList
(2)ArrayList的底层结构
  • ArrayList中维护了一个Object类型的数组elementData
    • transient Object[] elementData;
  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍
  • 如果使用的是指定大小的构造器,则初始elementData为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

核心源代码:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);		//1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

6、Vector底层结构与源码分析

(1)定义说明
  • Vector底层也是一个对象数组,protected Object[] elementData;
  • Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
  • 在开发中,需要线程同步安全时,考虑使用Vector
(2) 底层结构
  • 当创建Vector对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为2倍
  • 如果使用的是指定大小的构造器,则初始elementData为指定大小,如果需要扩容,则直接扩容elementData为2倍。

源代码:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //扩充为两倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?		//三元运算符
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
(3) Vector与ArrayList的对比
底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,效率高有参1.5倍
无参
1.第一次10
2.第二次1.5倍
Vector可变数组jdk1.0安全,效率不高有参2倍
无参
1.第一次10
2.第二次2倍

7、LinkedList底层结构

(1) 定义说明
  • LinkedList底层维护了一个双向链表
  • LinkedList中维护了两个属性first和last,分别指向首节点和尾结点
  • LinkedList删除和添加元素的效率 较高
(2) 源码分析
  • 新增节点,直接在链表尾部新增节点。更改指针指向。

    • void linkLast(E e) {
          final Node<E> l = last;
          final Node<E> newNode = new Node<>(l, e, null);
          last = newNode;
          if (l == null)		//为空和不为空的指向不一样;为空的话,first节点先指向第一个节点
              first = newNode;
          else				//不为空,新节点就需要接上前面那个链表
              l.next = newNode;
          size++;
          modCount++;
      }
      
  • 删除节点,在移除prev链和next链的时候分别判断是否是第一个节点和最后一个节点,分开判断和执行,涉及链表的断链和接链可以学习一下。

    • unlink(Node<E> x) {
          // assert x != null;
          //先去这个节点的三个值
          final E element = x.item;
          final Node<E> next = x.next;
          final Node<E> prev = x.prev;
      	//先处理prev,判断是否x前面是否为空,x是不是第一个节点
          if (prev == null) {
              first = next;
          } else {
              prev.next = next;
              x.prev = null;
          }
      	//然后处理next,判断x后面是否为空,x是不是最后一个节点
          if (next == null) {
              last = prev;
          } else {
              next.prev = prev;
              x.next = null;
          }
      
          x.item = null;
          size--;
          modCount++;
          return element;
      }
      

8、ArrayList与LinkedList对比

(1)ArrayList与LinkedList对比
底层结构增删的效率改查的效率
ArrayList可变数组较低,涉及扩容问题较高,直接索引
LinkedList双向链表较高,直接断链接链较低,遍历链表
(2)如何选择ArrayList和LinkedList
  • 涉及改查的操作多,选择ArrayList

  • 涉及增删的操作比较多,选择LinkedList

  • 一般来说,在程序中,80%-90%都是查询,大部分情况选择ArrayList

  • 根据具体业务灵活选择

9、Set接口和常用方法

(1)接口基本介绍
  • 无序(添加和取出的顺序不一致),没有索引
  • 不允许重复元素,所以最多包含一个null(自动去重)
  • 继承了COLLECTION接口,方法与COLLECTION接口一致
(2) Set接口的遍历方式
  • 可以使用迭代器
  • 增强for循环
  • 不能使用索引的方式来获取

示例:

//遍历方式
//第一种方式,迭代器的方式
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
//第二种方式,采用增强for循环
for(Object o : set){
    System.out.println("o:"+o);
}

10、Set接口实现类-HashSet

(1)接口基本介绍
  • 继承了Set接口

  • HashSet底层实际上是HashMap,参考源码

    • public HashSet() {
          map = new HashMap<>();
      }
      
  • 可以存放null,但是只能有一个null(不能有重复元素)

  • HashSet不保证元素是有序的,取决于hash,再确定索引

(2)底层结构以及源码分析(hash()+equals())
  • HashSet的底层是HashMap
  • 添加一个元素时,先得到hash值,代码会转为索引
  • 找到存储数据表table,看这个索引位置是否已经存放的有元素
  • 如果没有,直接加入
  • 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
  • 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小 >=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

源码分析:

//第一步:转为HashMap,所以HashSet的本质就是HashMap
public HashSet() {
        map = new HashMap<>();
    }

//第二步:进入put, PRESENT可以认为是一个常量,所有的HashSet的value都是一样的
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
//第三步:进入putVal,也就是整个的核心代码
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

//第四步:核心,将值插入到HashMap中,涉及扩容以及转为红黑树问题
//刚开始的结构是数组+链表
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为空或者大小为0,第一次扩容到16个空间大小
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
    
    	//如果当前key的hash值不在tab里面,直接创建新节点加入
        if ((p = tab[i = (n - 1) & hash]) == null)//根据hash值与(n-1)进行与操作得到下标索引值
            tab[i] = newNode(hash, key, value, null);
    
    	//如果当前key的hash值计算得到的索引不在当前表中,就继续判断
        else {
            Node<K,V> e; K k;		//辅助变量
            //判断该key是否已经在HashMap中
            //满足两个条件之一:就不能加入
            //1、准备加入的key值与当前Node的key值相同
            //2、key不为空并且p节点的key的equals()与准备加入的key相同
            //其中的equals函数是根据需要可以重写的,所以具体问题具体分析
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            
            //如果第一个key不相同,就判断当前节点是否是一个红黑树,如果是,则在红黑树上面进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            
            //如果不在红黑树上面,那就在链表中添加节点,遍历当前节点所在的链表
            else {
                //for循环比较该链表,并用bincount计数,判断是否需要转为红黑树
                for (int binCount = 0; ; ++binCount) {
                    
                    //如果后续没有找到,就直接创建一个新的节点插入到后面
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果长度大于等于8的时候,就需要考虑是否需要转换为红黑树了
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;		//for循环无限循环,得通过break来跳出
                    }
                    
                    //在链表中找到了,无需插入,直接break
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;		//for循环无限循环,得通过break来跳出
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;		//这里是hashMap的对传进来相同的key中不同的value进行的更新
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
    	//每次加入一个节点都需要++size,然后再来判断是否需要扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
(3)HashSet的扩容机制
  1. HashSet的底层是HashMap,第一次添加是,table数组扩容到16,临界值(threhold)是*加载因子(loadFactor)是0.75 = 12,
  2. 如果table数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24,依此类推
  3. 在Java8中,如果一条链表的元素个数到达TREEIfy——THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树),否则仍然采用数组的扩容机制

11、SET接口实现类-LinkedHashSet

(1)接口基本介绍
  • LinkeHashSet 是HashSet的子类
  • linkedHashSet 底层是一个LinkedHashMap,底层维护了一个数组+双向链表(有head和tail指针)
  • LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时采用链表维护元素插入的时候的相对位置,这可以使得元素是以插入顺序保存的。
  • LinkedHashSet不允许添加重复元素
(2)底层源码分析

本身数组是HashMap$Node[]类型,存放的元素数据是LinkedHashMap$Entry类型,二者有继承关系

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;		//继承了HashMapNode,变成了双向链表
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

其余的插入操作、扩容操作和判断元素是否相同的操作与HashMap一致。

12、Set接口实现类—TreeSet

(1)TreeSet注意事项
  1. TreeSet接口继承了Set接口
  2. 底层是TreeMap,TreeMap的底层是红黑树
  3. 使用无参构造时,仍然是无序的
  4. 使用有参构造,需要自己传入比较器(匿名内部类来实现制定规则的排序)

举例:

//无参构造器无序,需要重新定义一个比较器(匿名内部类)
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
//                return ((String)o1).compareTo((String)o2);
                //自定义比较函数,如果比较函数中两个值相同,就默认这两个元素相同,不会加入到Set当中
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        treeSet.add("wwc");     //底层是TreeMap
        treeSet.add("liujie");
        treeSet.add("luoxiang");
        treeSet.add("hanjie");  //如果是比较长度的话,这个与liujie长度相同,不会加入到treeSet中
        System.out.println(treeSet);

13、Map接口和常用方法

(1)Map接口的特点(Jdk1.8)
  • Map与COLLECTION并列存在。用于保存具有映射关系的数据Key—Value
  • Map中的Key和Value可以是任何引用类型的数据,会封装在HashMap$Node对象中
  • Map中的Key不允许重复,原因和HashSet一样
  • Map中的value可以重复
  • Map的key可以为null,value也可以为null,注意key为null时,只能有一个,value为null时,可以有多个
  • 常用String类型作为Map的key
  • Key和Value存在单向一对一的关系,即可以通过指定的key,总能找到对应的value
  • 一对k-v是放在一个HashMap $ Node中的,因为Node实现了Entry接口,有些书上也说一对k-v就是一个Entry

举例:

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("no1","jack");  //key唯一,相同则覆盖
    map.put("no1","jerry");
    map.put(null,null);     //都可以为null
    map.put("no2",null);    //value可以重复,也可以多个null
    System.out.println(map.get("no1")); //通过get方法获取
    System.out.println(map);    //顺序不是加入的顺序
}
(2)Map中Node与Entry的关系辨析

Map底层用一个table以数组+链表+红黑书组织HashMap$Node,为了方便管理把Node封装为entry,再将entry放到EntrySet中

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. k-v 最后是HashMap$Node node = newNode(hash, key, value, null)

  2. k-v为了方便程序员的遍历,还会创建EntrySet集合,集合存放的元素是Entry,一个Entry对象就有key和value

    • Set<Map.Entry<K, V>> entrySet();  //entryset结构
      
  3. entrySet中,定义的类型是Map.Entry,但实际上存放的还是HashMap $ Node(Node实现了Map.Entry的接口)

  4. 当把HashMap $ Node对象存放到entrySet就方便我们的遍历(Map本身并没有迭代器),因为Map.Entry提供了重要的方法:K getKey()和V getValue()

举例:

Map map = new HashMap();
map.put("no1",123);
//结构:Set<Map.Entry<K, V>> entrySet();
Set set = map.entrySet();
System.out.println(set.getClass());         //HashMap$EntrySet
for(Object entry :set){
    System.out.println(entry.getClass());   //HashMap$Node
    //向下转型Object->Map.Entry
    Map.Entry entry1 = (Map.Entry)entry;
    System.out.println("entry1.getKey(): "+entry1.getKey() +"   entry1.getValue():  " + entry1.getValue());
}
(3)Map的常用方法
Map map = new HashMap();
map.put("no1","jack");  //添加
map.put("no2","jerry");  //添加
String  str = (String)map.get("no1"); //获取
System.out.println(str);
map.size();                 //map大小
map.remove("no1");      //删除
System.out.println(map.isEmpty());  //判断是否为空
map.clear();            //清空
System.out.println(map.containsKey("no2")); //判断这个键是否存在
(4)Map接口的遍历方法

稍微比list,set麻烦,但是基本原理一致

//keyset,获取所有的键
//values,获取所有的值
//entryset,获取所有的键值对
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");

//第一组:取出所有的key,然后通过get方法获取value,遍历方式为增强for循环和迭代器
Set keyset = map.keySet();
//1、增强for循环
for(Object key : keyset){
    System.out.println(key+"-"+map.get(key));
}
//2、迭代器
Iterator iterator = keyset.iterator();
while(iterator.hasNext()){
    Object key = iterator.next();
    System.out.println(key+"-"+map.get(key));
}

//第二组:把所有的values取出来,遍历所有的values,但是无法获取key
Collection values = map.values();
//1、增强for循环
for(Object value : keyset){
    System.out.println(value);
}
//2、迭代器
iterator = values.iterator();
while(iterator.hasNext()){
    Object value = iterator.next();
    System.out.println(value);
}

//第三组:使用EntrySet,然后使用entry的getKey和getValue方法
Set entrySet = map.entrySet();
//1、增强for循环
for(Object entry : entrySet){
    Map.Entry mentry = (Map.Entry)entry;        //向下转型
    System.out.println(mentry.getKey()+"-"+mentry.getValue());
}
//2、迭代器
iterator = entrySet.iterator();
while(iterator.hasNext()){
    Map.Entry mentry = (Map.Entry)iterator.next();  //向下转型
    System.out.println(mentry.getKey()+"-"+mentry.getValue());
}

14、Map接口实现类-HashMap

(1)HashMap基本介绍
  1. HashMap是以key-val对来存储数据的
  2. HashMap是Map接口使用频率最高的实现类
  3. 如果添加相同的key,则会覆盖原来的key-val,等同于修改
  4. 与HashSet一样,不保证映射的顺序
  5. HashMap没有实现同步,线程不安全,方法没有做同步互斥的操作,没有synchronized
(2)HashMap底层源码分析

扩容机制:(与HashSet一样)

  1. 第一次添加是,table数组扩容到16,临界值(threhold)是*加载因子(loadFactor)是0.75 = 12,
  2. 如果table数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24,依此类推
  3. 在Java8中,如果一条链表的元素个数到达TREEIfy——THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树),否则仍然采用数组的扩容机制

源码分析参考HashSet

15、Map接口实现类-HashTable

(1)接口基本介绍
  1. 存放的是键值对:即 K-V
  2. hashtable的键和值都不能为null,否则会抛出NullPointerException
  3. hashTable使用方法基本与HashMap一致
  4. HashTable是线程安全的(synchronized),hashMap是线程不安全的

示例:

        Hashtable hashtable = new Hashtable();
        hashtable.put("no1",1);     //添加
//        hashtable.put("no1",null);  //空指针异常NullPointerException
//        hashtable.put(null,1);      //空指针异常NullPointerException
        hashtable.put("no1",2);     //替换
        hashtable.remove("no1");  //删除
        System.out.println(hashtable);
(2)源码分析
  • 底层是数组+链表,不会转化成红黑树

    • //数组
      Entry<?,?> tab[] = table;
      
      //单个节点结构为链表
      private static class Entry<K,V> implements Map.Entry<K,V> {
          final int hash;		
          final K key;
          V value;
          Entry<K,V> next;
      
  • 初始化容量为11,有一个临界值threhold = 当前容量 * loadFactor(0.75),超过临界值便会扩容,扩容机制为原来的两倍+1

    • int newCapacity = (oldCapacity << 1) + 1;
      
(3) HashMap与HashTable对比
版本线程安全(同步)效率允许null
HashMap1.2不安全可以
Hashtable1.0安全不可以

16、Map接口实现类TreeMap

(1)接口基本介绍
  1. TreeMap接口继承了Map接口
  2. TreeMap的底层是红黑树
  3. 使用无参构造时,仍然是无序的
  4. 使用有参构造,需要自己传入比较器(匿名内部类来实现制定规则的排序)
(2)源码分析
public V put(K key, V value) {	//添加元素的底层源码
    Entry<K,V> t = root;
    if (t == null) {			//当前为空的话,直接创新节点,加入到红黑树当中
        compare(key, key); // type (and possibly null) check
		//第一次加入数据也会进行比较,但是比较的话,是传入相同的值,排除非空的情况
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    int cmp;
    Entry<K,V> parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;	//如果自定义比较器不为空的话,也就是构造器传入了一个比较器的情况
    if (cpr != null) {
        do {					//遍历红黑树结构,看是否找到节点,通过比较器进行判断
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);	//找到了的话,直接覆盖原来的值,并返回
        } while (t != null);
    }
    else {			//没有传入比较器的情况,调用之前设置的比较器
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }			
    //如果都没有找到,就需要在红黑树中,插入一个节点
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}

17、Map接口实现类-Properties

(1)基本介绍
  1. Properties类继承自hashtable类并且实现了Map接口也是一种使用键值对的形式来保存数据
  2. 使用方法与HashTable类似
  3. Properties可以用于从xxx.porperties文件之中,加载数据到Properties类对象,并进行读取和修改。xxx.properties文件通常作为配置文件。
(2)常用方法
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的key , value 被替换
System.out.println("properties=" + properties);
properties.remove("lic");       //删除
System.out.println(properties.get("lucy"));     //查找

18、总结,如何选择集合实现类

选择什么集合实现类,主要取决于业务操作特点。

主要参考以下几点:

  • 先判断存储类型(单列/双列)(一组对象/键值对形式)
    • 一组对象[单列]:Collection接口
      • 允许重复:List
        • 增删多:LinkedList[底层是双向链表]
        • 改查多:ArrayList[底层是Object类型的可变数组]
      • 不允许重复:Set
        • 无序:hashSet[底层是HashMap,维护了一个哈希表[数组+链表+红黑树]]
        • 排序:TreeSet
        • 插入和取出顺序一致:LinkedHashSet,[数组+双向链表]
    • 一组键值对[双列]:Map
      • 键无序:HashMap[底层是哈希表:jdk7:数组+链表,jdk8:数组+链表+红黑树]
      • 键排序:TreeMap
      • 键插入和取出顺序一致:LinkedHashMap
      • 读取文件 Properties

19、COLLECTION工具类

(1)工具类基本介绍
  • Collections是一个集成了Set,List,Map的工具类
  • Collections中提供了一系列静态的方法对集合进行排序、查询、修改等操作
(2)排序操作(均为static方法,针对List进行排序)

方法举例:

/创建ArrayList 集合,用于测试.
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");

//1、sort(list)针对元素的顺序比较后进行排序
Collections.sort(list);
//2、sort(list, Comparator)添加指定的比较器进行排序
Collections.sort(list, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        //这里指定顺序进行排序
        return ((String)o1).length() - ((String)o2).length();
    }
});
//3、reverse(List)反转list比较
Collections.reverse(list);
//4、shuffle(List)随机排序
Collections.shuffle(list);
//5、swap(list, int, int)交换元素位置
Collections.swap(list, 1, 3);
System.out.println(list);
(3)其他方法
//6、max(Collection)返回最大元素,可以添加比较器,自行设定规则,来确定最大
System.out.println(Collections.max(list));
Collections.max(list, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        //这里指定长度进行排序
        return ((String)o1).length() - ((String)o2).length();
    }
});
//7、min参考max,方法一致
//8、frequency(Collection, Object)统计元素出现次数
System.out.println(Collections.frequency(list,"tom"));
//9、copy(List dest, List src)将src中的内容复制到dest中
List list2 = new ArrayList();
for (int i = 0; i < 10; i++) {
    list2.add(i);
}
Collections.copy(list2, list);   //list2的实际元素要比list多,不然会越界
System.out.println(list2);
//10、replaceAll(list, oldVal, newVal)将新值替换所有旧值
Collections.replaceAll(list,"tom","merry");
System.out.println(list);
  • 20
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值