1. 体系关系

Map

  • Hashtable
  • Properties
  • TreeMap
  • HashMap
  • LinkedHashMap

Collection

  • List
  • Vector
  • ArrayList
  • LinkedList
  • Set
  • HashSet
  • LinkedHashSet
  • TreeSet

2. Hashtable

1)存放的元素是键值对:即K-V

  1. hashtable的键和值都不能为null,否则会抛出NullPointerException
  2. hashTable使用方法基本上和HashMap一样
  3. hashTable是线程安全的(synchronized), hashMap是线程不安全的
Hashtable table = new Hashtable();//ok
        table.put("john", 100); //ok
        //table.put(null, 100); //异常 NullPointerException
        //table.put("john", null);//异常 NullPointerException
        table.put("lucy", 100);//ok
        table.put("lic", 100);//ok
        table.put("lic", 88);//替换
        table.put("hello1", 1);
        table.put("hello2", 1);
        table.put("hello3", 1);
        table.put("hello4", 1);
        table.put("hello5", 1);
        table.put("hello6", 1);
        System.out.println(table);

        //简单说明一下Hashtable的底层
        //1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
        //2. 临界值 threshold 8 = 11 * 0.75
        
        //3. 扩容: 按照自己的扩容机制来进行即可.
        //4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry

        //5. 当 if (count >= threshold) 满足时,就进行扩容
        //5. 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.

HashMap

  • 1.2 版本
  • 不安全 线程安全
  • 高 效率
  • 可以 允null键 null值

Hashtable

  • 1.0
  • 安全
  • 较低
  • 不可以

3. ProPerties

  1. Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形
    式来保存数据。
    2.他的使用特点和Hashtable类似
  2. Properties还可以用于从 xxx.properties文件中,加载数据到Properties类对象,
    并进行读取和修改
    4.说明:工作后xxx.properties文件通常作为配置文件,这个知识点在IO流举例,有兴
    趣可先看文章

Java读取Properties

键和值都是字符串类型。

load
Properties pro = new Properties();

FileInputStream in = new FileInputStream("a.properties");

pro.load(in);

in.close();
store

OutputStream out, String comments

将Properties类对象的属性列表 保存到输出流中

FileOutputStream oFile = new FileOutputStream(file,"a.properties");

pro.store(oFile, "Comment");

oFile.close();

如果comments不为空,保存后的属性文件第一行会是#comments,表示注释信息;如果为空则没有注释信息。

注释信息后面是属性文件的当前保存时间信息。

(3)getProperty/setProperty

这两个方法是分别是获取和设置属性信息。

3.代码实例

属性文件a.properties如下:

name=root
pass=liu
key=value

读取a.properties属性列表,与生成属性文件b.properties。代码如下:

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Properties;

public class PropertyTest {
    public static void main(String[] args) {
        Properties prop = new Properties();
        try{
            //读取属性文件a.properties
            InputStream in = new BufferedInputStream (new FileInputStream("a.properties"));
            //将Properties类对象的属性列表 保存到输出流中
            prop.load(in);

            //加载属性列表
            Iterator<String> it=prop.stringPropertyNames().iterator();

            //遍历,key
            while(it.hasNext()){
                String key=it.next();
                //使用key 得到value
                System.out.println(key+":"+prop.getProperty(key));
            }
            in.close();

            ///保存属性到b.properties文件
            FileOutputStream oFile = new FileOutputStream("b.properties", true);//true表示追加打开
            //设置新的属性
            prop.setProperty("phone", "10086");
            //进行 存储到输出流,并且设置文件头
            prop.store(oFile, "The New properties file");
            oFile.close();
        }
        catch(Exception e){
            System.out.println(e);
        }
    }
}
  • a.properties 文件,要放在idea的最最外层。
  • 目录:\代码\chapter08。如打开的 为 \代码\ 那就要放在这个目录下
b.properties
#The New properties file
#Wed Dec 07 22:30:20 CST 2022
phone=10086
name=root1
key=value
pass=liu

增删改查

//老韩解读
        //1. Properties 继承  Hashtable
        //2. 可以通过 k-v 存放数据,当然key 和 value 不能为 null
        //增加
        Properties p = new Properties();
        //p.put(null, "abc");//抛出 空指针异常
        //p.put("abc", null); //抛出 空指针异常
        p.put("john", 100);//k-v
        p.put("lucy", 100);
        p.put("lic", 100);
        p.put("lic", 88);//如果有相同的key , value被替换

        //使用set设置
        p.setProperty("zhang", "三");
        p.put("wang", "五");

        System.out.println("properties=" + p);

        //通过k 获取对应值
        System.out.println(p.get("lic"));//88

        //都可以得到
        System.out.println(p.getProperty("zhang"));
        System.out.println(p.get("zhang"));
        //无法得到。得到的为null,默认返回 abc
        System.out.println(p.getProperty("john","abc"));
        System.out.println(p.getProperty("wang"));


        //删除
        p.remove("lic");
        System.out.println("properties=" + p);

        //修改
        p.put("john", "约翰");
        System.out.println("properties=" + p);

Properties 中 get 传入Object 的 key 返回 Object 类型的 value

Properties 中 getProperty 传入String 类型 key 返回 String 类型的 value

  • getProperty 无法获得 非String的值。
  • setProperty 和 getProperty 就是专为 String key 和 String Value 而准备的。
  • put 和 get 方法 适用所有的。

4. 开发中选择

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:

1)先判断存储的类型(一组对象[单列]或一组键值对[双列)

2)一组对象[单列]: Collection接口
允许重复:List

  • 增删多:LinkedList[底层维护了一个双向链表]
  • 改查多:ArrayList [底层维护Object类型的可变数组]

不允许重复:Set

  • 无序: HashSet[底层是HashMap,维护了一个哈希表即(数组+链表+红黑树)
  • 排序:TreeSet[老韩举例说明]
  • 插入和取出顺序一致:LinkedHashSet,维护数组+双向链表

3)一组键值对[双列]:Map

  • 键无序: HashMap [底层是:哈希表jdk7:数组+链表,jdk8:数组+链表+红黑树]
  • 键排序:TreeMap[老韩举例说明]
  • 键插入和取出顺序一致:LinkedHashMap
  • 读取文件Properties

5. Set

Collection

  • List
  • ArrayList
  • LinkedList
  • Vector
  • Set
  • HashSet
  • TreeSet
  • LinkedHashSet

TreeSet 传递比较器

public TreeSet(Comparator<? super E> comparator) {
            this(new TreeMap<>(comparator));
        }

		//底层进入了 TreeMap
        //1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator
        private final Comparator<? super K> comparator;

        public TreeMap(Comparator<? super K> comparator) {
            this.comparator = comparator;
        }
//当我们使用无参构造器,创建TreeSet时,仍然是无序的 (这里应该错了,默认为 自然排序)

        //使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类) 并指定排序规则

测试的代码

// TreeSet treeSet = new TreeSet();
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //下面 调用String的 compareTo方法进行字符串大小比较
                //return ((String) o2).compareTo((String) o1);
                
                //按照长度大小排序
                return ((String) o1).length() - ((String) o2).length();
            }
        });

        //添加数据.
        treeSet.add("jack");
        treeSet.add("zzz");
        treeSet.add("tom");//3
        treeSet.add("sp");
        treeSet.add("a");
        treeSet.add("abc");//3,如果 按照长度大小比较。已经有3个长度了,这里不会进程插入。重要

        System.out.println("treeSet=" + treeSet);

TreeSet Put方法

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

    public V put(K key, V value) {

             //2. 在 调用 treeSet.add("tom"), 在底层会执行到
             if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
                do {
                    parent = t;
                    //动态绑定到我们的匿名内部类(对象)compare
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        //t的左节点,赋值为t。在树里一直 循环。直到 t.left为null的时候
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else //如果相等,即返回0,这个Key就没有加入。这里很重要。
                        return t.setValue(value);
                } while (t != null);
            }
    }

6. TreeMap

  • 从 TreeSet添加方法可知,底层是 TreeMap
private static final Object PRESENT = new Object();    

	public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            //首次添加走到这里
            compare(key, key); // type (and possibly null) check
			
            //使用 Entry 存储
            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) {
            上面的代码 已经复制
        }
    }
     
     //TreeMap的内部泪,模拟的树
     static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
     }
//使用默认的构造器,创建TreeMap, 是无序的(也没有排序)。错了,默认是 字符的自然排序

		//TreeMap treeMap = new TreeMap();

        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //按照传入的 k(String) 的大小进行排序
                //按照K(String) 的长度大小排序
                //return ((String) o2).compareTo((String) o1);
                return ((String) o2).length() - ((String) o1).length();
            }
        });

        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("kristina", "克瑞斯提诺");
        treeMap.put("smith", "斯密斯");
        treeMap.put("hsp", "韩顺平");//加入不了。但是会把 tom的值 换成 韩顺平。return t.setValue(value);

        System.out.println("treemap=" + treeMap);

        /*
            老韩解读源码:
            1. 构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator
             public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
            
            2. 调用put方法
            2.1 第一次添加, 把k-v 封装到 Entry对象,放入root
            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;
            }
            
            2.2 以后添加
            private final Comparator<? super K> comparator;
            
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do { //遍历所有的key , 给当前key找到适当位置
                    parent = t;
                    cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else  //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加
                        return t.setValue(value);
                } while (t != null);
            }
         */

7. Collections

Collections工具类介绍

  1. Collections是一个操作Set、List和Map等集合的工具类
  2. Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
    排序操作:(均为static方法)
  3. reverse(List):反转 List中元素的顺序
  4. shuffle(List):对List 集合元素进行随机排序
  5. sort(List):根据元素的自然顺序对指定List 集合元素按升序排序
  6. sort(List, Comparator):根据指定的Comparator产生的顺序对 List集合元素进行
    排序
  7. swap(List, int, int):将指定 list集合中的i处元素和j处元素进行交换

frequency(Collection<?>, Object)

  • 看对象,在集合中 出现的次数。
frequency
英
/ˈfriːkwənsi/

n.
出现次数;频繁;频率
//创建ArrayList 集合,用于测试.
        List list = new ArrayList();
        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");
        list.add("tom");


//        reverse(List):反转 List 中元素的顺序
        Collections.reverse(list);
        System.out.println("list=" + list);
//        shuffle(List):对 List 集合元素进行随机排序
//        for (int i = 0; i < 5; i++) {
//            Collections.shuffle(list);
//            System.out.println("list=" + list);
//        }

//        sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
        Collections.sort(list);
        System.out.println("自然排序后");
        System.out.println("list=" + list);
//        sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
        //我们希望按照 字符串的长度大小排序
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //可以加入校验代码.
                return ((String) o2).length() - ((String) o1).length();
            }
        });
        System.out.println("字符串长度大小排序=" + list);
//        swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

        //比如
        Collections.swap(list, 0, 1);
        System.out.println("交换后的情况");
        System.out.println("list=" + list);




        //Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
        System.out.println("自然顺序最大元素=" + Collections.max(list));
        //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
        //比如,我们要返回长度最大的元素
        Object maxObject = Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        });

		//使用 表达式
        String max = Collections.max(list, (String s1, String s2) ->
                s1.length() - s2.length()
        );

        System.out.println("长度最大的元素=" + maxObject);


        //Object min(Collection)
        //Object min(Collection,Comparator)
        //上面的两个方法,参考max即可

        //int frequency(Collection,Object):返回指定集合中指定元素的出现次数
        System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));

        //void copy(List dest,List src):将src中的内容复制到dest中

        ArrayList dest = new ArrayList();//ArrayList(list.size()) 这样依然不行。
        //为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样。最好大小要一样。
        for(int i = 0; i < list.size(); i++) {
            dest.add("");
        }
        //拷贝
        Collections.copy(dest, list);
        System.out.println("dest=" + dest);

        //boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
        //如果list中,有tom 就替换成 汤姆
        Collections.replaceAll(list, "tom", "汤姆");
        System.out.println("list替换后=" + list);

8. 家庭作业

public class Homework06 {
    public static void main(String[] args) {
        HashSet set = new HashSet();//ok
        Person p1 = new Person(1001,"AA");//ok
        Person p2 = new Person(1002,"BB");//ok
        set.add(p1);//ok
        set.add(p2);//ok
        
        //p1的name更改后。放入 set的位置 还是原来的。
        p1.name = "CC";
        //使用最新的p1做 hash,位置 肯定不是原来的了,移除失败。
        set.remove(p1);
        //所以:会输出两个对象
        System.out.println(set);//2
        
        //这个对象的 hash后,并没有相同的,所以能加入。
        set.add(new Person(1001,"CC"));
        //变成了3个对象
        System.out.println(set);//3
        
        //hash后,和 最早的p1的位置一样,但是:和 p1的内容 不一样,所以 可以加入。
        set.add(new Person(1001,"AA"));
        System.out.println(set);//4

    }
}

class Person {
    public String name;
    public int id;

    public Person(int id, String name) {
        this.name = name;
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id &&
                Objects.equals(name, person.name);
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
}

ArrayList 和 Vector比较

ArrayList

  • 可变数组
  • jdk1.2
  • 不安全,效率高
  • 如果使用有参构造器1.5倍,
  • 如果是无参构造器 第一次扩容10
  • 从第二次开始按照1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如原来是10, 10 + 10/2 ,变成15,所以 扩容1.5倍

Vector

  • 可变数组
  • jdk1.0
  • 安全,效率不高
  • 如果是无参,默认10,满后,按照2倍扩容
  • 如果是指定大小创建Vector,则每次按照2倍扩容.
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
		//没指定,就是 旧值+旧值
  • HashMap的扩容,依然是2倍
newThr = oldThr << 1; // double threshold

HashSet和TreeSet

试分析HashSet和TreeSet分别如何实现去重的
(1) HashSet的去重机制: hashCode()+ equals() ,底层先通过存入对象,进行运算得到一个hash值,通过hash值得到对应的索引,如果发现table索引所在的位置,没有数据,就直接存放,如果有数据,就进行equals比较[遍历比较],如果比较后,不相同,就加入,否则就不加入.(2) TreeSet的去重机制:如果你传入了一个Comparator匿名对象,就使用实现的compare去重,如果方法返回0,就认为是相同的元素/数据,就不添加,如果你没有传入一个Comparator匿名对象,则以你添加的对象实现的Compareable接口的compareTo去重.

Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            //如果没有传递
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
            	//则把String转成  Comparable,因为String是实现这个接口的。
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                //就是调用String的 compareTo方法了。
                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);
        }

TreeSet要重写Comparable

public class Homework05 {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        //分析源码
        //add 方法,因为 TreeSet() 构造器没有传入Comparator接口的匿名内部类
        //所以在底层 Comparable<? super K> k = (Comparable<? super K>) key;
        //即 把 Perosn转成 Comparable类型
        treeSet.add(new Person2());//ClassCastException.
        treeSet.add(new Person2());//返回0,在加入,无法加 进去

        System.out.println(treeSet);
    }
}

class Person2 implements Comparable {

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

ArrayList方法

1.add:添加单个元素

2.remove:删除指定元素

3.contains:查找元素是否存在

4.size:获取元素个数

5.isEmpty:判断是否为空

6.clear:清空

7.addAll:添加多个元素

8.containsAll:查找多个元素是否都存在

9.removeAll:删除多个元素