1、容器概述
1.1 容器的结构
Collection是容器的父接口,它有两个子接口Set和List,Set是无序不可重复的,List则是有序可重复的。List下面有两个常用的实现类ArrayList和LinkedList,他们的区别是:ArrayList的底层实现是数组,所以里面的元素有索引,查询快,增删改慢,LinkedList的底层实现是链表,所以查询慢,增删改快。
1.2 Collection接口中定义的方法
boolean add(Object element);
boolean remove(Object element);
boolean contains(Object element);
int size();
boolean isEmpty();
void clear();
Iterator iterator();
boolean containsAll(Collection c);
boolean addAll(Collection c);
boolean removeAll(Collection c);
boolean retainAll(Collection c); //交集
Object[] toArray();
1.3 List接口
List是有序的Collection,此接口的用户可以成对列表中的每个元素的插入位置进行精确的定位,用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表当中的元素。
List与set不同,列表通常允许重复的元素,更精确的讲列表通常允许满足e1.equals(e2)条件的元素e1,e2并存,并且如果列表本身允许null元素的话,通常他们允许多个null元素。
List比Collection多了一些跟顺序有关的方法:
void add(Object element);
void add(int index,Object element);
Object get(int index);
Object set(int index,Object element);//修改某一位置的元素。
Object remove(int index);
int index(Object o);//返回某一元素的索引,如果没有该元素返回-1;
ArrayList:底层用数组实现的List,特点是:查询效率高 ,增删效率低,不安全。
LinkedList:底层是双向链表实现的List,特点是:查询效率低,增删效率高,线程不安全。
Vector:底层用数组实现的List,特点:线程安全。
1.4 SET接口
Set接口无序的Collection,并且里面的元素不允许重复。
HashSet:采用哈希算法实现的Set,HashSet的底层是用HashMap实现的,因此查询效率较高,由于采用hashCode算法直接确定元素的内存地址,增删效率也挺高的。
存入HashSet当中的元素要求实现重写hashCode和equals方法
HashSet的部分源码:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
}
从add方法可以看出,HashSet是把数据存放到Mpa的key当中,而map的value是一个常量,这也是解释了为啥HashSet的不能重复。
2、Map
Map有一个常用的子类实现:HashMap,HashMap的底层实现是 数组+链表;HashMap的键不能重复,可以为null。键如果重复,后面设置的值会覆盖前面的值。
图示如下:
Java中有规定:如果两个内容相同(equals)的对象,应该具有相同的hashcode。所以如果自定义的对象要重写equals方法,那么一定也要重写hasdCode方法。这也是保证集合的相关操作的原因。
2.1 HashMap和Hashtable的区别
存入HashMap当中的key不能重复,所以key对象要求实现重写hashCode和equals方法,值可以重复。
1、HashMap是线程不安全的,效率高,key可以有一个null,value可以有多个null;Hashtable是线程安全的,效率低,key和value都不能为null。
2、父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap
3、Iterator接口
所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现Iterator接口的对象
Iterator对象称作为迭代器,用以方便的对容器内元素的遍历操作,Iterator接口定义了如下方法:
boolean hashNext();//判断是否有元素没有被遍历
Object next();//返回游标当前位置的元素并将游标移动到下一个位置
void remove();//删除游标左边的元素,在执行完next之后该操作只能执行一次。
3.1 迭代器遍历List
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class Test01 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//通过索引遍历List
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//通过迭代器遍历List
for(Iterator iter2 = list.iterator();iter2.hasNext();){
String str = (String) iter2.next();
System.out.println(str);
iter2.remove();
//iter2.remove();//这里只能remove一次,重复remove会报错
}
System.out.println(list.size()+"******");
Set set = new HashSet();
set.add("高1");
set.add("高2");
set.add("高3");
//通过迭代器遍历Set
// Iterator iter = set.iterator();
// while(iter.hasNext()){
for(Iterator iter = set.iterator();iter.hasNext();){
String str = (String) iter.next();
System.out.println(str);
}
}
}
3.2 迭代器遍历map
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 测试Map的遍历方式
*/
public class Test02 {
public static void main(String[] args) {
Map map = new HashMap();
map.put("aa", "aaaa");
map.put("bb", "bbbb");
//遍历Map的第一种方式
Set keys = map.keySet();
for(Iterator iter = keys.iterator();iter.hasNext();){
String keyStr = (String) iter.next();
System.out.println(keyStr+"---"+map.get(keyStr));
}
//遍历Map的第二种方式
Set<Entry> set2 = map.entrySet();
for(Iterator iter = set2.iterator();iter.hasNext();){
Entry e = (Entry) iter.next();
System.out.println(e.getKey()+"---"+e.getValue());
}
}
}
4、HashMap的分拣存储思路
根据面向对象的方式,用HashMap实现分拣存储:
4.1 创建对象
public class Letter {
private String name; //单词
private int count; //次数
public Letter() {
}
public Letter(String name) {
this.name = name;
}
public Letter(String name, int count) {
super();
this.name = name;
this.count = count;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
4.2 HashMap分拣
public class MapDemo02 {
public static void main(String[] args) {
// 1、分割字符串
String[] arr ="this is a cat and that is a mice and where is the food ?".split(" ");
// 2、分拣存储
Map<String,Letter> map =new HashMap<String,Letter>();
for(String key:arr){
/*
//第一次查看是否 存在 袋子
if(!map.containsKey(key)){ //不存在
map.put(key, new Letter(key)); //准备好袋子
}
//获取袋子
Letter value =map.get(key);
value.setCount(value.getCount()+1);//装东西
*/
Letter value =map.get(key);
if(null==value){
value =new Letter();
map.put(key, value);
}
value.setCount(value.getCount()+1);//装东西
}
//3、查看每个单词出现的次数
for(String key:map.keySet()){
Letter value =map.get(key);
System.out.println(key+"-->"+value.getCount());
}
}
5、TreeSet和TreeMap
5.1 TreeSet
TreeSet是Set接口的子类,TreeSet的元素不可以重复,元素可以排序,添加数据时自动进行排序,TreeSet存储数据尽量不要修改。
实体类实现任何接口:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Person {
private final String name;//名称
private final int handsome;//帅气指数
public Person() {
name =null;
handsome =0;
}
public Person(String name, int handsome) {
super();
this.name = name;
this.handsome = handsome;
}
//get/set方法
@Override
public String toString() {
return "姓名:"+this.name+",帅气指数:"+this.handsome+"\n";
}
}
比较的时候使用匿名内部类实现接口:Comparator
import java.util.TreeSet;
/**
* 提供了 解耦的方式:业务排序类
*/
public class TreeSetDemo {
public static void main(String[] args) {
Person p1 =new Person("您",100);
Person p2 =new Person("刘德华",1000);
Person p3 =new Person("梁朝伟",1200);
Person p4 =new Person("老裴",50);
//依次存放到TreeSet容器中,使用排序的业务类(匿名内部类)
TreeSet<Person> persons =new TreeSet<Person>(
new java.util.Comparator<Person>(){
@Override
public int compare(Person o1, Person o2) {
return -(o1.getHandsome()-o2.getHandsome());
}
}
);
persons.add(p1);
//TreeSet 在添加数据时排序
persons.add(p2);
persons.add(p3);
persons.add(p4);
System.out.println(persons);
/*
//改变数据
p4.setHandsome(100);
p4.setName("您");
*/
//p4 与p1 内容重复
System.out.println(persons);
}
}
实体类Worker实现排序接口Comparable:
public class Worker implements java.lang.Comparable<Worker> {
//工种
private String type;
//工资
private double salary;
public Worker() {
}
public Worker(String type, double salary) {
super();
this.type = type;
this.salary = salary;
}
//get/set方法
/**
* 按工资升序
*/
@Override
public int compareTo(Worker o) {
return this.salary>o.salary?1:( this.salary==o.salary?0:-1);
}
@Override
public String toString() {
return "工种:"+this.type+",工资:"+this.salary+"\n";
}
}
import java.util.TreeSet;
/**
* 实体类实现Comparable 接口的应用
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
Worker w1 =new Worker("垃圾回收员",12000);
Worker w2 =new Worker("农民工",8000);
Worker w3 =new Worker("程序猿",5000);
TreeSet<Worker> employees =new TreeSet<Worker>();
employees.add(w1);
employees.add(w2);
employees.add(w3);
System.out.println(employees);
}
}
5.2 TreeMap
TreeMap是Map接口的子类,TreeMap要求键(key)是可以排序的,与TreeSet的原理一样。
import java.util.Set;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
Person p1 =new Person("您",100);
Person p2 =new Person("刘德华",1000);
Person p3 =new Person("梁朝伟",1200);
Person p4 =new Person("老裴",50);
TreeMap<Person,String> map =new TreeMap<Person,String>(new java.util.Comparator<Person>(){
@Override
public int compare(Person o1, Person o2) {
return -(o1.getHandsome()-o2.getHandsome());
}
} );
map.put(p1, "bjsxt");
map.put(p2, "bjsxt");
map.put(p3, "bjsxt");
map.put(p4, "bjsxt");
//查看键
Set<Person> persons =map.keySet();
System.out.println(persons);
}
}
package com.bjsxt.sort.col;
import java.util.TreeSet;
/**
* 实体类实现Comparable 接口的应用
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
Worker w1 =new Worker("垃圾回收员",12000);
Worker w2 =new Worker("农民工",8000);
Worker w3 =new Worker("程序猿",5000);
TreeSet<Worker> employees =new TreeSet<Worker>();
employees.add(w1);
employees.add(w2);
employees.add(w3);
System.out.println(employees);
}
}
6、Properties
Properties是Hashtable的子类,常用来读取资源配置文件,它要求key/value都只能是字符串。
下面是Properties的相关方法的测试:
6.1 存储与读取
import java.util.Properties;
/**
* Properties 资源配置文件的读写
* 1、key 与value 只能为字符串
* 2、存储与读取
* setProperty(String key, String value)
* getProperty(String key, String defaultValue)
*/
public class Demo01 {
public static void main(String[] args) {
//创建对象
Properties pro =new Properties();
//存储
pro.setProperty("driver", "oracle.jdbc.driver.OracleDriver");
//pro.setProperty("url", "jdbc:oracle:thin:@localhost:1521:orcl");
pro.setProperty("user", "scott");
pro.setProperty("pwd", "tiger");
//获取
String url =pro.getProperty("url","test");//如果没有取到url的值就会使用test这个默认值
System.out.println(url);
}
}
6.2 Properties写出数据到文件中
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
/**
* 使用Properties 输出到文件
* 资源配置文件:
* 1、读取后缀为 .properties 使用如下方法
* store(OutputStream out, String comments)
store(Writer writer, String comments)
2、读取后缀为 .xml 使用如下方法
storeToXML(OutputStream os, String comment) :UTF-8字符集
storeToXML(OutputStream os, String comment, String encoding)
*/
public class Demo02 {
public static void main(String[] args) throws FileNotFoundException, IOException {
//创建对象
Properties pro =new Properties();
//存储
pro.setProperty("driver", "oracle.jdbc.driver.OracleDriver");
pro.setProperty("url", "jdbc:oracle:thin:@localhost:1521:orcl");
pro.setProperty("user", "scott");
pro.setProperty("pwd", "tiger");
//存储到e:/others 绝对路径 盘符: //db配置 这个参数是注释作用
//pro.store(new FileOutputStream(new File("e:/others/db.properties")), "db配置");
//pro.storeToXML(new FileOutputStream(new File("e:/others/db.xml")), "db配置");
//使用相对路径 当前的工程
// pro.store(new FileOutputStream(new File("db.properties")), "db配置");
// pro.store(new FileOutputStream(new File("src/db.properties")), "db配置");
pro.store(new FileOutputStream(new File("src/com/bjsxt/others/pro/db.properties")), "db配置");
}
}
6.3 Properties根据资源文件加载配置文件
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
/**
* 使用Properties读取配置文件
* 资源配置文件:
* 使用相对与绝对路径读取
* load(InputStream inStream)
load(Reader reader)
loadFromXML(InputStream in)
*/
public class Demo03 {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pro=new Properties();
//读取 绝对路径
//pro.load(new FileReader("e:/others/db.properties"));
//读取 相对路径
pro.load(new FileReader("src/com/bjsxt/others/pro/db.properties"));
System.out.println(pro.getProperty("user", "bjsxt"));
}
}
6.4 Properties根据类路径加载配置文件(重点)
import java.io.IOException;
import java.util.Properties;
/**
* 使用类相对路径读取配置文件
* bin 这里相对的是bin目录,也就是classes文件的根目录
*/
public class Demo04 {
public static void main(String[] args) throws IOException {
Properties pro =new Properties();
//类相对路径的 / 斜杠表示是 bin 目录
//pro.load(Demo04.class.getResourceAsStream("/com/bjsxt/others/pro/db.properties"));
//使用类加载器加载配置文件:"" 这里的空字符表示 bin 目录,所以它不是以/开头的路径
pro.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/bjsxt/others/pro/db.properties"));
System.out.println(pro.getProperty("user", "bjsxt"));
}
}
7、引用的分类:(强,软,弱,虚)
引用分类:强、软、弱、虚
强引用:GC运行时,不会被垃圾回收
软引用:GC运行时可能会回收,在JVM内存不够的时候
弱引用只要运行垃圾回收就会被回收
虚引用:类似于无引用,主要用于追踪对象被回收的状态,不能单独使用,必须与引用队列
import java.lang.ref.WeakReference;
/**
* 引用分类:强、软、弱、虚
强引用:GC运行时,不会被垃圾回收
软引用:GC运行时可能会回收,在JVM内存不够的时候
弱引用只要运行垃圾回收就会被回收
虚引用:类似于无引用,主要用于追踪对象被回收的状态,不能单独使用,必须与引用队列ReferenceQueue一起使用
* 这里只演示 强引用与弱引用
* @author Administrator
*
*/
public class RefDemo {
public static void main(String[] args) {
//该字符串存在堆中
String str =new String("bjsxt is very good");//弱引用
//弱引用 管理 对象
WeakReference<String> wr =new WeakReference<String>(str);
System.out.println("gc运行前:"+wr.get());//bjsxt is very good
//断开引用
str =null;
//通知回收
System.gc();
System.runFinalization();
//对象被回收
System.out.println("gc运行后:"+wr.get()); //null
//gc运行前:bjsxt is very good
//gc运行后:null
}
public static void testStrong(){ //强引用
//字符串常量池 共享(不能回收)
String str ="bjsxt is very good";
//弱引用 管理 对象
WeakReference<String> wr =new WeakReference<String>(str);
System.out.println("gc运行前:"+wr.get());//bjsxt is very good
//断开引用
str =null;
//通知回收
System.gc();
System.runFinalization();
System.out.println("gc运行后:"+wr.get());//bjsxt is very good
//会发现垃圾回收后,这个wr这个引用还在,没有被回收
//gc运行前:bjsxt is very good
//gc运行后:bjsxt is very good
}
}
8、WeakHashMap
WeakHashMap的特点:如果它的key是弱引用,那么垃圾回收一运行就会被立马回收。
它的使用场景:当这个Map当中的数据量非常大的时候,像之前的HashMap等会长期占用内存,而WeakHashMap则可以及时释放内存。
import java.util.WeakHashMap;
/**
* WeakHashMap 键为弱类型,gc运行立即回收
*/
public class WeakHashMapDemo {
public static void main(String[] args) {
WeakHashMap<String,String> map =new WeakHashMap<String,String>();
//测试数据
//常量池对象,不会回收
map.put("abc", "a");
map.put("d", "test");
//gc运行 已被回收
map.put(new String("bjsxt"), "c");
map.put(new String("dsf"), "d");
//通知回收
System.gc();
System.runFinalization();
System.out.println(map.size());//结果是2,因为后面两个是弱引用,会被回收
}
}
9、IdentityHashMap
import java.util.IdentityHashMap;
/**
* IdentityHashMap 键(key)只比较地址去重
*/
public class IdentityHashMapDemo {
public static void main(String[] args) {
IdentityHashMap<String ,String> map =new IdentityHashMap<String,String>();
//常量池中的"a"
map.put("a", "a1");
map.put("a", "a2");
System.out.println(map.size());//这里的结果是:1,因为a是常量池中的,是同一个对象,地址也就相同
map.put(new String("a"), "a3");
map.put(new String("a"), "a4");
System.out.println(map.size());
}
}
10、EnumMap
import java.util.EnumMap;
/**
* EnumMap:枚举map,要求键为枚举
*/
public class EnumMapDemo {
public static void main(String[] args) {
EnumMap<Season,String> map =new EnumMap<Season,String>(Season.class);
//存放值
map.put(Season.SPRING, "春困");
map.put(Season.SUMMER, "夏无力");
map.put(Season.AUTUMN, "秋乏");
map.put(Season.WINTER, "冬眠");
System.out.println(map.size());
}
}
//季节
enum Season{
SPRING,SUMMER,AUTUMN,WINTER
}
11、同步容器与只读控制
11.1 同步容器
我们之前讲的List,Set和Map中大部分容器都是线程不安全的,如果我们要使用线程安全的容器怎么办呢?一种方式是后面要讲到的同步方法,还有就是Collections类中给我们提供了把线程不安全的容器转为线程安全的容器的方法:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 使用Collections管理同步 容器
* synchronizedList()
synchronizedSet()
synchronizedMap()
*/
public class Demo01 {
public static void main(String[] args) {
List<String> list =new ArrayList<String>();
list.add("a");
list.add("b");
//list可以同步
List<String> synList =Collections.synchronizedList(list);
System.out.println("线程安全的list制作完毕");
}
}
11.2 只读设置
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 只读设置
* 1、emptyXxx() 把集合变成空的不可变的集合
* emptyList()
* emptyMap()
* emptySet()
2、singletonXxx() 返回一个元素不可变的集合
singleton(T o)
singletonList(T o)
singletonMap(K key, V value)
3、unmodifiableXxx() 返回不可变容器
unmodifiableList(List<? extends T> list)
unmodifiableSet(Set<? extends T> s)
unmodifiableMap(Map<? extends K,? extends V> m)
*/
public class Demo02 {
/**
* @param args
*/
public static void main(String[] args) {
Map<String,String> map =new HashMap<String,String>();
map.put("test", "test");
map.put("bjsxt", "bjsxt");
//只读控制,返回的map2相当于是原来的map的一个快照,不能修改,但是之前的map是可以修改的,如果修改了map,那么map2也会改变
Map<String,String> map2 =Collections.unmodifiableMap(map);
//map2.put("a", "a"); //不能操作,改变这个容器会报错
System.out.println(map2.size());
//一个元素的容器测试
List<String> list =Collections.singletonList(new String());
list.add("test");
//list.add("bjsxt"); //只能包含一个元素的容器,否则会抛一个异常:不被支持的操作
}
public static Set<String> oper(Set<String> set){
if(null==set){
return Collections.EMPTY_SET; //外部获取避免NullPointerException
}
//操作
return set;
}
}