1. 体系关系
Map
- Hashtable
- Properties
- TreeMap
- HashMap
- LinkedHashMap
Collection
- List
- Vector
- ArrayList
- LinkedList
- Set
- HashSet
- LinkedHashSet
- TreeSet
- HashSet
2. Hashtable
1)存放的元素是键值对:即K-V
-
hashtable的键和值都不能为null,否则会抛出NullPointerException
-
hashTable使用方法基本上和HashMap一样
-
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
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形
式来保存数据。
2.他的使用特点和Hashtable类似 - Properties还可以用于从 xxx.properties文件中,加载数据到Properties类对象,
并进行读取和修改
4.说明:工作后xxx.properties文件通常作为配置文件,这个知识点在IO流举例,有兴
趣可先看文章
Java读取Properties
https://www.cnblogs.com/xudong-bupt/p/3758136.html
键和值都是字符串类型。
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工具类介绍
- Collections是一个操作Set、List和Map等集合的工具类
- Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
排序操作:(均为static方法) - reverse(List):反转 List中元素的顺序
- shuffle(List):对List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List 集合元素按升序排序
- sort(List, Comparator):根据指定的Comparator产生的顺序对 List集合元素进行
排序 - 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倍
- 如果是无参构造器 第一次扩容10
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:删除多个元素