一、Java类集位于Java.util包下
二、Collection接口及其子接口List与Set。
1、Collection接口(JDK1.2)的继承实现结构
1)Collection继承了Iterable接口(JDK1.5)
可使用for-each循环(增强型for循环)
继承了该接口的核心方法Iterator<T> iterator()(返回值为Iterator类、集合遍历类)该方法在JDK1.5以前直接在Collection接口中直接定义的
2)Collection接口提供了两个最重要的方法
add();//给集合添加元素
iterator();//遍历集合:迭代器
2、子接口List(80%)(对Collection接口进行了扩充:get()由List接口提供)
最常用实现子类ArrayList的使用
List<String> list = new ArrayList<>();
list.add("3");
list.add("2");
list.add("5");
System.out.println(list.size());//3
System.out.println(list.get(2));//5
System.out.println(list.contains("3"));//true
System.out.println(list.contains(new String("3")));//true:String类覆写了equals()
System.out.println(list.remove(1));//2:remove()返回移除的值
//增强for循环遍历
for (String string : list) {
System.out.println(string);//3 5
}
List集合与简单Java类(POJO):对于remove()、contains()等方法需要equals():覆写equals()比较属性值
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class Test {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("123", 20));
list.add(new Person("122", 20));
list.add(new Person("125", 20));
System.out.println(list.size());//3
System.out.println(list.get(2));//Person [name=125, age=20]
//修改:返回被修改前的值
System.out.println(list.set(1, new Person("1", 12)));//Person [name=122, age=20]
System.out.println(list.contains(new Person("123", 10)));//false
//移除
System.out.println(list.remove(1));//Person [name=1, age=12]
//迭代器遍历
Iterator<Person> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
1)ArrayList与Vector的区别:
a、Vector是JDK1.0提供的;ArrayList是JDK1.2提供的;
b、 Vector采用同步处理,效率低,线程安全,
ArrayList采用异步处理,效率高,线程不安全;
c、ArrayList支持Iterator、ListIterator、foreach输出,
Vector支持Iterator、ListIterator、foreach、Enumeration输出。
使用时,优先考虑ArrayList。
(虽ArrayList是线程不安全的,在JDK1.2提供,可利用Collections.synchronizedList()将ArrayList包装为线程安全的List)
2)ArrayList与LinkedList的区别:
a、ArrayList底层采用数组实现,LinkedList底层采用双链表;
b、ArrayList适用于频繁查找元素的场景,
LinkedList适用于频繁修改元素的场景。
3)ArrayList与Vector(底层实现均为数组)的扩容策略
a、ArrayList:初始化时为空数组;
扩容策略:增加50%(即扩容后容量为当前容量的1.5倍)
源码实现:int newCapacity = oldCapacity + (oldCapacity >> 1);
b、Vector:初始化时容量大小为10;
扩容策略:增加一倍(即扩容后容量为当前容量的2倍),可自定义扩容:设置capacityIncrement,默认为0
源码实现:int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
动态扩容的本质:调用Arrays.copyOf(elementData, newCapacity)创建新数组,底层使用System提供的一个native静态方法arraycopy():浅复制(对对象引用的复制)
3、子接口Set(20%)(本质为没有value的Map)(对Collection父接口无扩充)
1)实现子类HashSet
无序存储,实际上就是HashMap:底层实现为hashMap
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
允许存储对象为null,且不能重复;
判断重复依据:hashCode()(返回对象地址的hash码)+equals()
HashSet的使用:
存储JDK提供的类对象
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A");//不可重复
set.add(null);//允许为null
for (String string : set) {//乱序
System.out.println(string);//null A B
}
存储自定义类对象
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class Test {
public static void main(String[] args) {
Set<Person> set = new HashSet<>();
set.add(new Person("张三", 20));
set.add(new Person("张三", 20));
set.add(new Person("张三", 18));
set.add(new Person("张四", 20));
set.add(null);
for (Person person : set) {
System.out.println(person);
}
/*null
Person [name=张三, age=18]
Person [name=张四, age=20]
Person [name=张三, age=20]
*/
}
}
2)实现子类TreeSet
有序存储(顺序自定义),实际上为TreeMap;
不允许存储对象为null,且不能重复;
自定义类对象使用TreeSet存储,必须覆写CompareTo接口
判断重复元素:实现CompareTo()接口(Java.lang包),覆写该接口的唯一方法compareTo(T o)方法
public int compareTo(T o);//返回值:大于0:当前对象 > 目标对象
等于0:当前对象 = 目标对象
小于0:当前对象 < 目标对象
TreeSet的使用:
存储JDK提供的类对象
Set<String> set = new TreeSet<>();
set.add("D");
set.add("B");
set.add("A");
set.add("A");//不可重复
//set.add(null);//不能为null
for (String string : set) {//有序
System.out.println(string);//A B D
}
存储自定义类对象
class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Person o) {
if(this.age > o.age) {
return 1;
}else if(this.age < o.age) {
return -1;
}else {
return this.name.compareTo(o.name);
}
}
}
public class Test {
public static void main(String[] args) {
//自定义类必须要实现Comparable接口
Set<Person> set = new TreeSet<>();
set.add(new Person("张三", 20));
set.add(new Person("张三", 10));
set.add(new Person("张三", 18));
set.add(new Person("张四", 20));
for (Person person : set) {
System.out.println(person);
}
/*Person [name=张三, age=10]
Person [name=张三, age=18]
Person [name=张三, age=20]
Person [name=张四, age=20]
*/
}
}
二、集合输出的四种形式
1、Iterator迭代器输出(只能从前往后遍历输出)
1)方法:
hasNext():判断是否有下个元素;
next():取得当前元素;
remove():删除当前元素
//迭代器遍历
Iterator<Person> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
2)迭代器的快速失败(fail-fast)行为
a、是什么?
在遍历一个集合的过程中,当集合数据被修改(并发修改)时,就会抛出java.util.ConcurrentModificationException异常,该异常的抛出就是迭代器的快速失败行为。
b、产生原因:对集合的并发修改
并发修改:当一个或多个线程正在遍历一个集合Collection,此时另一个线程修改了这个集合的内容(增删改)。
源码实现:
首先,在代码中存在一个变量modCount,该变量用来描述当前集合被修改(结构)的次数;
然后,在调用iterator取得迭代器时,存在一个变量expectedModCount,此时被赋值为当前的modCount;
当在迭代器内部发生modCount != expectedModCount时,迭代器就会发生快速失败行为,抛出异常。
c、会产生该异常的类集:ArrayList、Vector等
d、解决:使用fail-safe类集(CopyOnWriteList、ConCurrentHashMap)(线程安全集合:CopyOnWriteList与JDK1.7之前的ConCurrentHashMap使用Loak锁实现线程安全)
fail-safe机制的内容:任何对集合结构的修改都会在一个复制的集合上进行修改,由于集合根本就没变,所以不会发生并发错误
该机制存在的两个问题:
由于需要复制集合,所以会产生大量的无效对象,开销大;
无法保证读取的数据是目前原数据结构的数据。
2、双向迭代器ListIterator(List接口独有,可以从前往后遍历输出,也可从后往前遍历输出)
方法:
hasNext():判断是否有下个元素;
next():取得当前元素;
remove():删除当前元素;
hasPrevious():判断是否有上个元素;
previous():取得上个元素。
要想从后向前输出,必须先从前往后走一遍
3、枚举输出Enumeration(只有Vector类才有)
4、for-each输出
三、Map集合及其子类
1、Map的特点
一次保存两个对象,针对键值对(K=V)对象的处理
可通过key值找到value
2、常用方法:
put(K key, V value):向Map中追加数据
get(K key):根据key值取得对应的value,没找到返回null
keySet():返回值类型为Set(Key值不能重复)
values():返回值类型为Collection(value可重复)
3、Map集合的内部接口:Entry<K,V>(保存键值对对象)
目的:方便Map集合输出以及元素保存
entrySet():Set<Map.Entry<K,V>>:将Map集合变为Set集合
getKey():取得Key值
getValue():取得value值
代码实现:Map集合的标准输出
Map<Integer, String> map = new HashMap<>();
map.put(1, "123");
map.put(2, "234");
map.put(3, "345");
//Map的标准输出
//将Map->Set
//Entry:interface Entry<K,V>:Map的内部接口
Set<Map.Entry<Integer, String>> set = map.entrySet();
//取得迭代器输出
Iterator<Map.Entry<Integer, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Map.Entry<Integer, String> tmp = iterator.next();
System.out.println(tmp.getKey() + " = " +tmp.getValue());
}
4、子类HashMap底层数据结构的实现
k=v:k(Set,可为null,不能重复),v(Collection,可为null,可重复)
线程不安全
基于哈希表的实现
源码解析
1)内部实现:数组+链表
数组被分成一个一个的桶,通过hash值决定键值对在数组的位置;
hash值相同的键值对,以链表的形式存储;
当链表长度超过阈值(默认为8)-1时,将其树化。
static final int TREEIFY_THRESHOLD = 8;
2)构造方法
无参构造:初始化默认负载因子0.75f
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
3)putVal()源码
对数组大小的初始化(HashMap采用lazy-load懒加载:在初次使用(put())时对table初始化)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
putVal()部分代码:
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
a、可看出,当table为null时,会调用resize()进行初始化数组大小
b、具体键值对在哈希表中的位置取决于下方的运算,避免hash碰撞
i = (n - 1) & hash
4)resize()源码
初始化时,桶大小为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
门限值 = 负载因子*容量
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
门限值通常以倍数进行调整
newThr = oldThr << 1;
扩容后将老数组元素拷贝到新数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
5)负载因子和容量
预设容量大小需要满足:大于预估元素数量 / 负载因子,同时是2的幂次方
负载因子:不推荐改变
设置超过0.75会明显增加哈希冲突,降低HashMap的性能
设置太小,会导致频繁的扩容,影响访问性能
6)树化
JDK1.8引入:安全问题
当简直对对象冲突时,会放在同一个桶中以链表形式存储,链表过长,严重影响存取性能
当同一个桶中的元素个数 >= 7时,会调用treeifyBin尝试树化
static final int TREEIFY_THRESHOLD = 8;
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
当容量小于MIN_TREEIFY_CAPACITY(默认为64),会调用resize()进行扩容
否则(else if),才会树化
static final int MIN_TREEIFY_CAPACITY = 64;
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
当链表被树化后变成红黑树后,元素删除N次后,如果红黑树节点个数 < UNTREEIFY_THRESHOLD(默认6),在下一次resize()后,又会将红黑树变为链表
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit)
5、子类Hashtable (JDK1.0,第一个存储二元偶对象的类)
k,v均不能为null
线程安全:在增删改等方法上加锁synchronized实现,同Vector
HashMap与Hashtable的区别:
1)Hashtable是早期Java类库(JDK1.0)提供的一个哈希表实现,
同步;
不支持null键和值;
由于同步导致的性能开销,所以已经很少被推荐使用;
2)HashMap:JDK1.2
异步;
支持null键和值;
通常情况下,进行put和get操作,可以达到常数时间的性能,所以它是绝大部分利用键值对存取场景的首选。
6、子类TreeMap
7、子类ConcurrentHashMap
所在包:JUC:java.util.concurrent(所有与并发有关的类均在此包)
1)ConcurrentHashMap产生的原因?
JDK1.5以前效率较低;Collections提供的线程安全包装方法本质上和Hashtable、vector实现线程安全的核心思想一致;
线程安全包装方法在各种修改方法(add、remove、set等方法)中使用内键锁实现同步,没有真正意义下的改进,因此效率较低
2)JDK1.7的实现
分离锁:将内部结构分段(Segment),存放的HashEntry数组,hash相同的条目也按照链表存储,在进行并发操作时,只锁条目对应的Segment段,有效避免类似的Hashtable整体同步的问题,大大提高性能
HashEntry内部使用volatile的value字段来保证可见性
构造时,Segment数量由concurrentcyLevel决定,默认为16,必须为2的幂
3)JDK1.8的变化
总体结构上与HashMap非常相似,同样是大的桶数组,虽然内部还有Segment,但仅仅是为了保证序列化的兼容问题,不再有结构上的用处,因此不再使用Segment;
数据存储用volatile来保证可见性;
使用CAS(compare and swap)等操作,在特定场景进行无锁并发操作。
四、属性类Properties的5大常用操作方法
Properties(extends Hashtable)专门做属性文件处理
Java中的资源文件:*.properties,内容保存形式为:"key = value"(字符串形式)
1、设置属性:setProperty(String key, String value);
2、取得属性:getProperty(String key);
根据指定key取得value,没找到返回null
3、取得属性:getProperty(String key, String defaultValue);
根据指定key取得value,没找到返回默认值
4、保存属性到目标终端:store(OutptStream out, String comments);//注释
5、从文件读取属性:load(InputStream in);
代码操作:
Properties properties = new Properties();
properties.setProperty("creator", "zhang");
properties.setProperty("test", "ok");
File file = new File("pro.properties");
try {
properties.store(new FileOutputStream(file), "test");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//读取文件属性值
try {
properties.load(new FileInputStream(file));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(properties.getProperty("test"));
文件内容:
#test
#Sat Aug 25 18:57:19 CST 2018
creator=zhang
test=ok
5、(编程题)
现有自定义类Person,其中有age和name属性。
在主方法中使用Person作为key,String作为value进行保存。要求使用Iterator输出Map集合。
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name:" + name + ", age:" + age + "]";
}
}
public class Test {
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
map.put(new Person("zhang", 18), "编号1");
map.put(new Person("张三", 23), "编号2");
Set<Map.Entry<Person, String>> set = map.entrySet();
Iterator<Map.Entry<Person, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Map.Entry<Person, String> res = iterator.next();
System.out.println(res.getKey()+ " = " + res.getValue());
}
}
}