1.Java集合概述
- Java集合的功能:
- 用于存储数量不等的对象;
- 实现常用的数据结构;
- 保存具有关联性的数据(map);
- 集合类可以解决数组长度不可变导致的数据存储问题;
- 所有的集合类都位于java.util包下;
- 集合类中只能保存对象,值类型进入集合会自动装箱;
- Collection和Map是集合类的根接口;
- Set和List接口是Collection的两个子接口,分别代表无序集合和有序集合;Set中的元素不能重复;
- Map由key-value这种键值对数据组成,key是唯一的;
- 访问List可以根据索引,访问map可以根据key,但是访问set只能根据值本身访问;
- 最常用的实现类:HashSet, TreeSet, ArrayList, ArraryDeque, LinkedList, HashMap, TreeMap;
2.Java11增强的Collection和Iterator接口
1.概述
- Collection接口中的方法:
- boolean add(Object obj):向容器中添加数据;
- boolean addAll(Collection c):把集合c中所有元素添加到指定的集合中;
- void clear():清除集合里的所有元素,将集合的长度变为0;
- boolean contains(Object obj):
- boolean containsAll(Collection c):
- boolean isEmpty();
- Iterator iterator():返回一个iterator对象,用于遍历集合;
- boolean remove(Object obj):删除集合中指定的元素obj,只删除第一个符合条件的元素;
- boolean removeAll(Collection c)
- int size()
- Object[] toArray():将集合转换为数组;
- 用ArrayList实现Collection接口;
import java.util.*;
public class Collection
{
public static void main(String[] args)
{
var c = new ArrayList();
// 添加元素
c.add("元素1"); //不启用泛型的情况下都是Object
// 支持自动装箱
c.add(6);
System.out.println("容器c的大小为:" + c.size());
// 移除元素,参数是元素的索引
c.remove(1);
System.out.println("容器c的大小为:" + c.size());
System.out.println("c集合是否包含\"元素1\"字符串?"
+ c.contains("元素1"));
c.add("一本书");
System.out.println("c中的元素" + c);
var books = new HashSet();
books.add("一本书");
books.add("Java");
System.out.println("c中是否包含book中的元素"
+ c.containsAll(books));
// 从c中删除book中的元素
c.removeAll(books);
System.out.println("c中的元素" + c);
// 清空c
c.clear();
System.out.println("c中的元素" + c);
//让books中只含有c中的元素
books.retainAll(c);
System.out.println("books中有" + books);
}
}
2.使用lambda表达式遍历集合
-
把lambda表达式就想成是:参数->{方法体}的一种形式;
import java.util.*;
public class CollectionEach
{
public static void main(String[] args)
{
var books = new HashSet();
books.add("������Java EE��ҵӦ��ʵս");
books.add("���Java����");
books.add("���Android����");
// Collection的forEach方法遍历容器
books.forEach(obj -> System.out.println("��������Ԫ�أ�" + obj));
}
}
3.使用iterator遍历集合元素
-
Iterator接口定义了4个方法:
-
boolean hasNext():如果集合里含有元素,则返回true;
-
Object next():返回集合里的下一个元素;
-
void remove():删除集合里上一次next()方法返回的元素;
-
void forEachRemaining(Cosumer action):这是java8为Iterator新增的默认方法,可以用lambda表达式遍历集合元素
import java.util.*;
public class IteratorTest
{
public static void main(String[] args)
{
// 新建一个hashSet;
var books = new HashSet();
books.add("a");
books.add("b");
books.add("n");
// 在容器基础上建立一个迭代器;
var it = books.iterator();
while (it.hasNext())
{
// 从容器中拿出元素,并转换为String类型;
var book = (String) it.next();
System.out.println(book);
if (book.equals("a"))
{
// 从集合中删除next出来的元素
it.remove();
}
// 对元素进行赋值
book = "f"; // ��
}
System.out.println(books);
}
}
- 想要用Iterator就必须要有一个容器;
4.使用lambda表达式遍历Iterator(299)
5.使用foreach循环遍历集合元素
-
程序示例:
import java.util.*;
public class ForeachTest
{
public static void main(String[] args)
{
var books = new HashSet();
books.add(new String("a"));
books.add(new String("b"));
books.add(new String("c"));
for (var obj : books)
{
var book = (String) obj;
System.out.println(book);
if (book.equals("a"))
{
books.remove(book);
}
}
System.out.println(books);
}
}
7.使用Predicate操作集合(300)
6.使用Stream操作集合(301)
3.Set集合
1.HashSet(300)
- HashSet采用Hash算法来存储集合中的元素;
- HashSet的特点:
- 不能保证元素的排列顺序,顺序可能与添加顺序不同;
- HashSet不是线程同步的;
- 集合元素的值可以是null;
- 当向HashSet集合中存入一个元素时,HashSet对调用该对象的hashCode方法计算对象的哈希值,根据哈希值决定该对象的存储位置;
- 如果需要把某个类的对象放入HashSet中,要重写这个类的HashCode和equal方法,保证在equal返回true的同时,HashCode返回true;否则会导致重复存储同样数据的问题,与Set的规范冲突;
//想要把不同对象存入HashSet,必须将HashCode和equal都进行重写
import java.util.*;
class A
{
public boolean equals(Object obj)
{
return true;
}
}
class B
{
public int hashCode()
{
return 1;
}
}
class C
{
public int hashCode()
{
return 2;
}
public boolean equals(Object obj)
{
return true;
}
}
public class HashSetTest
{
public static void main(String[] args)
{
var books = new HashSet();
books.add(new A()); //没有重写hashcode,保留两个
books.add(new A());
books.add(new B()); //没有重写equal方法,保留两个
books.add(new B());
books.add(new C()); //equal和hashcode都进行了重写,Set只保留一个
books.add(new C());
System.out.println(books);
}
}
- HashSet中每个能存储元素的“槽位”通常称为“bucket”;
- 重写HashCode方法的基本规则
- 同一个对象调用HashCode方法,返回同样的值;
- 当两个对象调用equal方法返回true,HashCode方法应该返回相同的值;
- 对象中用作equals方法比较标准的实例变量,都应该用于计算HashCode值;
2.TreeSet
- TreeSet是SortedSet接口的实现类,TreeSet可以保证集合元素处于排序状态;
- TreeSet的常规方法(308);
import java.util.*;
public class TreeSetTest
{
public static void main(String[] args)
{
var nums = new TreeSet();
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
System.out.println(nums);
// 输出第一个元素
System.out.println(nums.first()); // 输出-9
System.out.println(nums.last()); // 输出10
// 返回小于4的子集
System.out.println(nums.headSet(4));
// 返回大于5的子集
System.out.println(nums.tailSet(5));
// 返回大于-3,小于4的子集
System.out.println(nums.subSet(-3, 4));
}
}
- TreeSet支持自然排序和定制排序两种策略(309)
3.LinkedHashSet
- LinkedHashSet是HashSet的子类,LinkedHashSet使用链表维护元素的存储次序,当遍历LinkedHashSet时,链表将以添加顺序进行访问;
import java.util.*;
public class LinkedHashSetTest
{
public static void main(String[] args)
{
var books = new LinkedHashSet();
books.add("1号");
books.add("2号");
System.out.println(books);
books.remove("1号");
books.add("3号");
System.out.println(books);
}
}
4.EnumSet
-
EnumSet是专门为枚举类设计的集合类;
-
EnumSet集合不允许加入null;
-
EnumSet集合的创建方法(315);
import java.util.*;
enum Season
{
SPRING,SUMMER,FALL,WINTER
}
public class EnumSetTest
{
public static void main(String[] args)
{
// 创建EnumSet,集合元素是枚举类的全部值
var es1 = EnumSet.allOf(Season.class);
System.out.println(es1); // 输出[SPRING, SUMMER, FALL, WINTER]
// 创建一个空的EnumSet,指定其集合元素是Season类的枚举值
var es2 = EnumSet.noneOf(Season.class);
System.out.println(es2); // 输出[]
// 添加枚举值
es2.add(Season.WINTER);
es2.add(Season.SPRING);
System.out.println(es2);
//创建一个带有部分元素的EnumSet
var es3 = EnumSet.of(Season.SUMMER, Season.WINTER);
System.out.println(es3);
var es4 = EnumSet.range(Season.SUMMER, Season.WINTER);
System.out.println(es4);
var es5 = EnumSet.complementOf(es4);
System.out.println(es5); // ���[SPRING]
}
}
5.Set的性能分析
-
HashSet优于TreeSet;
-
LinkedHashSet在查询上优于HashSet;插入删除上HashSet优于LinkedHashSet;
-
所有的Set都是线程不安全的;
4.List集合
1.改进的List接口和ListIterator接口
- List集合有序,且允许数据重复;
- List提供的根据索引来操作数据的方法(316)
import java.util.*;
public class ListTest
{
public static void main(String[] args)
{
var books = new ArrayList();
// List的常用方法
books.add("a");
books.add("b");
books.add("n");
System.out.println(books);
// 插入新的字符串
books.add(1, new String("ab"));
//遍历元素
for (var i = 0; i < books.size(); i++)
{
System.out.println(books.get(i));
}
// 移除元素
books.remove(2);
System.out.println(books);
// 输出字符串所在位置
System.out.println(books.indexOf(new String("a"))); // ��
//重置某个位置的元素
books.set(1, "vnbg");
System.out.println(books);
// [1,2]之间的全部字符
System.out.println(books.subList(1, 2));
}
}
- List提供了sort()和replaceAll()方法来对List进行排序:
import java.util.*;
public class ListTest3
{
public static void main(String[] args)
{
var books = new ArrayList();
// 添加元素
books.add("7");
books.add("8");
books.add("16");
books.add("11");
// 使用目标类型为Comparator的Lambda表达式对List集合排序
books.sort((o1, o2) -> ((String) o1).length() - ((String) o2).length());
System.out.println(books);
// 使用目标类型为UnaryOprator的Lambda表达式来替换集合中的所有元素
books.replaceAll(ele -> ((String) ele).length());
System.out.println(books); //输出[7, 8, 11, 16]
}
}
- List提供了listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口:
- boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素;
- Object previous():返回该迭代器的上一个元素;
- void add(Object obj):在指定位置插入一个元素
- ListIterator的用法;
import java.util.*;
public class ListIteratorTest
{
public static void main(String[] args)
{
String[] books = {
"abc","cbd"
};
var bookList = new ArrayList();
for (var i = 0; i < books.length; i++)
{
bookList.add(books[i]);
}
var lit = bookList.listIterator();
// 正向遍历
while (lit.hasNext())
{
System.out.println(lit.next());
lit.add("---分隔符-----");
}
System.out.println("=====下面开始反向迭代=====");
// 从后向前遍历
while (lit.hasPrevious())
{
System.out.println(lit.previous());
}
}
}
2.ArrayList和Vector实现类
- ArrayList和Vector类都是基于数组实现的List类,他们内部封装了一个动态数据Object[],数组会随着数据的增加而扩大;
- Vector有很多缺点,尽量避免使用Vector;
- 可以用initialCapacity(num)方法指定ArrayList的初始长度;也可以用ensureCapacity(int minCapacity)来一次性增加数组大小;
5.Queue集合
- Queue集合的作用是模拟队列;队列是先进先出类型数据结构;
- Queue中定义的方法:
- void add(Object o):将元素加入队列尾部;
- Object element():获取队列头部元素,不删除;
- boolean offer(Object o):将指定元素加入此队列的尾部;当使用容量有限的队列时,这个方法效率更好;
- Object peek():获取队列头部的元素,但是不删除元素;
- Object poll():获取头部元素并删除该元素;
- Object remove():获取头部元素并删除该元素;
1.PriorityQueue实现类
- PriorityQueue保存队列元素的顺序是按大小排列的;
import java.util.*;
public class PriorityQueueTest
{
public static void main(String[] args)
{
var pq = new PriorityQueue();
// 下面代码依次向pq中加入四个元素
pq.offer(6);
pq.offer(-3);
pq.offer(20);
pq.offer(18);
// 输出pq队列,并不是按元素的加入顺序排列
System.out.println(pq); // 输出[-3, 6, 20, 18]
// 访问队列第一个元素,其实就是队列中最小的元素:-3
System.out.println(pq.poll());
}
}
- PriorityQueue不允许添加null元素;
2.Deque接口和ArrayDeque实现类
- Deque接口是Queue接口的子接口,代表一个双端队列,Deque接口的方法(322);
- Deque可以实现栈的功能,并且建议采用Deque作为实现栈的方式,因为Stack太老了;
import java.util.*;
public class ArrayDequeStack
{
public static void main(String[] args)
{
var stack = new ArrayDeque();
// 依次将三个元素push入"栈"
stack.push("疯狂Java讲义");
stack.push("轻量级Java EE企业应用实战");
stack.push("疯狂Android讲义");
// 输出:[疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]
System.out.println(stack);
// 访问第一个元素,但并不将其pop出"栈",输出:疯狂Android讲义
System.out.println(stack.peek());
// 依然输出:[疯狂Android讲义, 疯狂Java讲义, 轻量级Java EE企业应用实战]
System.out.println(stack);
// pop出第一个元素,输出:疯狂Android讲义
System.out.println(stack.pop());
// 输出:[轻量级Java EE企业应用实战, 疯狂Java讲义]
System.out.println(stack);
}
}
- Deque作为队列:
import java.util.*;
public class ArrayDequeQueue
{
public static void main(String[] args)
{
var queue = new ArrayDeque();
// 依次将三个元素加入队列
queue.offer("疯狂Java讲义");
queue.offer("轻量级Java EE企业应用实战");
queue.offer("疯狂Android讲义");
// 输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
System.out.println(queue);
// 访问队列头部的元素,但并不将其poll出队列"栈",输出:疯狂Java讲义
System.out.println(queue.peek());
// 依然输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
System.out.println(queue);
// poll出第一个元素,输出:疯狂Java讲义
System.out.println(queue.poll());
// 输出:[轻量级Java EE企业应用实战, 疯狂Android讲义]
System.out.println(queue);
}
}
3.性能分析
- 强调搜索功能,就用ArrayList;强调插入和删除功能,就用linkedList;
- 遍历ArrayList,建议用get方法;遍历LinkedList,建议用Iterator迭代器;
- 如果多个线程需要同时访问List,可以用Collections将集合封装成线程安全的集合;
6.增强的Map集合
1.Java8为Map新增的方法
- Map用于保存具有映射关系的数据(key-value),Map的key是唯一的,value可以重复;
- Map也被称为是字典,Map中定义的方法如下:
- void clear():删除该Map对象中的所有key-value对;
- boolean containsKey(Object key):查询Map中是否包含对应的key;
- boolean containsValue(Object value):查询Map中是否包含对应的value;
- Set entrySet():返回Map中包含的key-value对所组成的Set集合;
- Object get(Object key):返回指定key所对应的value值,如果不存在,返回null;
- boolean isEmpty():判断Map是否为空;
- set KeySet():返回该Map中所有key组成的Set集合;
- Object putAll(Map m):将指定的Map复制到本Map中;
- Object remove(Object key):删除指定key所对应的key-value对,返回被删除key所关联的value;
- boolean remove(Object key, Object value):删除指定key,value对应的key-value对,返回布尔值;
- int size():返回该Map对应的key-value对数;
- Map内包含一个内部类Entry,该类封装了一个key-value对:
- Object getKey():返回Entry中的key值;
- Object getValue():返回Entry中的value值;
- Object setValue(V value):设置该Entry中包含的value值,并返回新设置的value值;
import java.util.*;
public class MapTest
{
public static void main(String[] args)
{
var map = new HashMap();
// 成对放入多个key-value对
map.put("疯狂Java讲义", 109);
map.put("疯狂iOS讲义", 10);
map.put("疯狂Ajax讲义", 79);
// 多次放入的key-value对中value可以重复
map.put("轻量级Java EE企业应用实战", 99);
// 放入重复的key时,新的value会覆盖原有的value
// 如果新的value覆盖了原有的value,该方法返回被覆盖的value
System.out.println(map.put("疯狂iOS讲义", 99)); // 输出10
System.out.println(map); // 输出的Map集合包含4个key-value对
// 判断是否包含指定key
System.out.println("是否包含值为 疯狂iOS讲义 的key:"
+ map.containsKey("疯狂iOS讲义")); // 输出true
// 判断是否包含指定value
System.out.println("是否包含值为 99 的value:"
+ map.containsValue(99)); // 输出true
// 获取Map集合的所有key组成的集合,通过遍历key来实现遍历所有key-value对
for (var key : map.keySet())
{
// map.get(key)方法获取指定key对应的value
System.out.println(key + "-->" + map.get(key));
}
map.remove("疯狂Ajax讲义"); // 根据key来删除key-value对。
System.out.println(map); // 输出结果不再包含 疯狂Ajax讲义=79 的key-value对
}
}
- Java8为Map提供了一些新的方法,见327页;
import java.util.*;
public class MapTest2
{
public static void main(String[] args)
{
var map = new HashMap();
// 成对放入多个key-value对
map.put("疯狂Java讲义", 109);
map.put("疯狂iOS讲义", 99);
map.put("疯狂Ajax讲义", 79);
// 尝试替换key为"疯狂XML讲义"的value,由于原Map中没有对应的key,
// 因此对Map没有改变,不会添加新的key-value对
map.replace("疯狂XML讲义", 66);
System.out.println(map);
// 使用原value与参数计算出来的结果覆盖原有的value
map.merge("疯狂iOS讲义", 10,
(oldVal, param) -> (Integer) oldVal + (Integer) param);
System.out.println(map); // "疯狂iOS讲义"的value增大了10
// 当key为"Java"对应的value为null(或不存在时),使用计算的结果作为新value
map.computeIfAbsent("Java", key -> ((String) key).length());
System.out.println(map); // map中添加了 Java=4 这组key-value对
// 当key为"Java"对应的value存在时,使用计算的结果作为新value
map.computeIfPresent("Java",
(key, value) -> (Integer) value * (Integer) value);
System.out.println(map); // map中 Java=4 变成 Java=16
}
}
2.改进的HashMap和Hashtable实现类(329)
3.LinkedHashMap实现类(331)
4.使用Properties读写属性文件
-
Properties类是Hashtable的子类,该对象在处理属性文件非常方便,它可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中;
-
Properties相当于一个key, value都是String类型的Map;
-
Properties可以把key-value对以XML文件的形式保存起来,也可以从XML文件中加载key-value对;
-
Properties中的方法(332)
import java.util.*;
import java.io.*;
public class PropertiesTest
{
public static void main(String[] args)
throws Exception
{
var props = new Properties();
// 向Properties中增加属性
props.setProperty("username", "yeeku");
props.setProperty("password", "123456");
// 将Properties中的key-value对保存到a.ini文件中
props.store(new FileOutputStream("a.ini"),
"comment line"); // ①
// 新建一个Properties对象
var props2 = new Properties();
// 向Properties中增加属性
props2.setProperty("gender", "male");
// 将a.ini文件中的key-value对追加到props2中
props2.load(new FileInputStream("a.ini")); // ②
System.out.println(props2);
}
}
5.SortedMap接口和TreeMap实现类(333)
6.WeakHashMap实现类
- 对于一般的HashMap,只要HashMap对象不被销毁,该HashMap的所有key所引用的对象就不会被垃圾回收;但是WeakHashMap中的key所引用的对象没有被其他引用的话,JVM会回收这些对象,WeakHashMap也会自动删除这些键值对;
import java.util.*;
/**
* Description:
* 网站: <a href="http://www.crazyit.org">疯狂Java联盟</a><br>
* Copyright (C), 2001-2020, Yeeku.H.Lee<br>
* This program is protected by copyright laws.<br>
* Program Name:<br>
* Date:<br>
* @author Yeeku.H.Lee kongyeeku@163.com
* @version 5.0
*/
public class WeakHashMapTest
{
public static void main(String[] args)
{
var whm = new WeakHashMap();
// 将WeakHashMap中添加三个key-value对,
// 三个key都是匿名字符串对象(没有其他引用)
whm.put(new String("语文"), new String("良好"));
whm.put(new String("数学"), new String("及格"));
whm.put(new String("英文"), new String("中等"));
//将 WeakHashMap中添加一个key-value对,
// 该key是一个系统缓存的字符串对象。
whm.put("java", new String("中等")); // ①
// 输出whm对象,将看到4个key-value对。
System.out.println(whm);
// 通知系统立即进行垃圾回收
System.gc();
System.runFinalization();
// 通常情况下,将只看到一个key-value对。
System.out.println(whm);
}
}
7.IdentityHashMap实现类(336)
8.Map的性能分析
- HashMap比Hashtable快;
- TreeMap很慢,但是TreeMap的优点是有序;
- HashMap是最优选;
7.HashSet和HasMap的性能选项(339)
8.操作集合的工具类:Collections
1.排序操作
- Collections类提供了对Set, List, Map等集合的操作方法;
- Collections提供的排序方法:
- void reverse(List list):反转指定list集合中元素的顺序;
- void shuffle(List list):对List集合进行随机排序(洗牌);
- void sort(List list):根据元素的自然顺序对指定的List集合的元素按升序排序;
- void sort(List list, Comparator c):根据Comparator产生的顺序对List进行排序;
- void swap(List list, int i, int j):将指定集合中的i元素和j元素进行替换;
- void rotate(List list, int distance);
2.查找/替换操作(342)
3.同步控制
- Collections提供了synchronizedXxx()方法,可以将指定的集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题;
import java.util.*;
public class SynchronizedTest
{
public static void main(String[] args)
{
// 下面程序创建了四个线程安全的集合对象
var c = Collections
.synchronizedCollection(new ArrayList());
var list = Collections.synchronizedList(new ArrayList());
var s = Collections.synchronizedSet(new HashSet());
var m = Collections.synchronizedMap(new HashMap());
}
}
4.设置不可变集合(343)
5.Java9新增不可变集合(343)