Java中的集合
集合:能够动态存放多个数据的存储结构,Java提供的一个公共API,存在于java.util包中
Java整个集合的架构如下:
主要有两个分支:
-
Collection 每次只能存入一个值 具体的存储性质和不同的子接口有关
Set 存入的元素无序且不重复 常用实现类有:
-
HashSet 无序不重复的集合
-
LinkedHashSet 有序不重复的集合 有序是通过链表方式实现
List 存入的元素有序且可以重复 常用实现类有:
- ArrayList 底层以数组方式实现 特点 读取效率高 但修改的效率较低 经常使用ArrayList
- LinkedList 底层以双向链表实现实现 特点 修改的效率高 但读取效率较低
-
Map 每次需要存入一个键值对
常用实现类 HashMap
Collection
Collection是所有单个存储元素的一个顶级类 常用方法:
•int size(); 返回此collection中的元素数。
•boolean isEmpty(); 判断此collection中是否包含元素。
•boolean contains(Object obj); 判断此collection是否包含指定的元素。
•boolean contains(Collection c); 判断此collection是否包含指定collection中的所有元素。
•boolean add(E element); 向此collection中添加元素。
•boolean addAll(Collection c);将指定collection中的所有元素添加到此collection中
•boolean remove(Object element); 从此collection中移除指定的元素。
•boolean removeAll(Collection c); 移除此collection中那些也包含在指定collection中的所有元 素。
•void clear(); 移除些collection中所有的元素。
•boolean retainAll(Collection c); 仅保留此collection中那些也包含在指定collection的元素。
•Iterator iterator(); 返回在此collection的元素上进行迭代的迭代器。
•T[] toArray(T[] arr); 把此collection转成数组。
Iterator
Iterator 是Collection的父级接口 内部提供了对于Collection类型的集合的遍历方式
常用方法:
- hasNext() 查看当前指针的下一个位置是否有元素 如果有返回true 否则返回false
- next() 移动指针到下一个元素 并获取该元素 返回给调用者
- remove() 移除当前遍历的指针
自从JDK5开始,所有Iterator方式实现的变量都可以使用 forEach方式来进行遍历
Set接口
Set接口是Collection的一个子接口类型,它的特点是无序且不重复
Set集合与数学中“集合”的概念相对应
如何判定Set中的元素是否重复:
- 判定该对象的hashCode()方法返回的值是否相等 如果相等即判定当前两个对象相等 直接跳过该对象的存储步骤 如果不等则直接存入元表中
- 当hashCode() 返回的值相等时 再利用对象的equals()方法来判定当前两个对象是否相等 如果相等则跳过该存储不走 如果不等 则会将该存储的模块转换为链表形式进行存储 在JDK8后如果链表的长度达到8 则会将链表转换成一个红黑树结构进行存储 但此种方式很少会触发
根据对象的哈希码值计算出它的存储索引,在散列表的相应位置(表元)上的元素间进行少量的比较操作就可以找出它。
Set系的集合存、取、删对象都有很高的效率。
对于要存放到Set集合中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法以实现对 象相等规则
常用子接口:
- HashSet 不保存元素的存入顺序
底层是使用Hash表的形式进行存储数据的,所以读取效率较高,直接根据hash值读取对应的数据
- LinkedHashSet 底层是链表 保证存入顺序 但依然不能重复
需要有序且不重复时使用 但效率略低
List接口
实现List接口的集合类中的元素是有序的,且允许重复。
List集合中的元素都对应一个整数型的序号记载其在集合中的位置,可以根据序号存取集合中的元素。
JDK API所提供的List集合类常用的有:
- ArrayList
- LinkedList
常用的方法:
public Object get(int index) 返回列表中的元素数
public Object add(int index, Object element); 在列表的指定位置插入指定元素.将当前处于该位置的
元素(如果有的话)和所有后续元素向右移动
public Object set(int index, Object element) ; 用指定元素替换列表中指定位置的元素
public Object remove(int index) 移除列表中指定位置的元素
public ListIterator listIterator() 返回此列表元素的列表迭代器
remove(Object obj) 如果obj存在则直接移除
remove(int index) 移除指定位置处的元素 并将元素返回给调用者
ArrayList
使用数组结构实现的List集合
优点:
- 对于使用索引取出元素有较好的效率
- 它使用索引来快速定位对象
缺点:
- 元素做删除或插入速度较慢
- 因为使用了数组,需要移动后面的元素以调整索引顺序、
LinkedList
LinkedList是使用双向链表实现的集合。
LinkedList新增了一些插入、删除的方法。
优点:
- 对频繁的插入或删除元素有较高的效率
- 适合实现栈(Stack)和队列(Queue)
缺点:
- 读取数据只能某一方向依次遍历查找 效率较低
Map
在集合中,Map和Collection是一个并列的关系,它内部职能存储键值对(key-value)。且key的值不能重 复且是无序的,value无要求。
JDK API中Map接口的实现类常用的有:
-
HashMap 内部使用哈希表对 “键-值”映射对 进行散列存放。
1.使用频率最高的一个集合。
-
LinkedHashMap 是HashMap的子类
1.使用哈希表存放映射对
2.用链表记录映射对的插入顺序。
Map实现类中存储的“键-值”映射对是通过键来唯一标识,Map底层的“键”是用Set来存放的。 所以,存入Map中的映射对的“键”对应的类必须重写hashcode()和equals()方法。常用String作为Map 的“键”。
Map接口常用方法:
•Object put(Object key, Object value); //将指定的“键-值”对存入Map中
•Object get(Object key); //返回指定键所映射的值
•Object remove(Object key); //根据指定的键把此“键-值”对从Map中移除。
•boolean containsKey(Object key); //判断此Map是否包含指定键的“键-值”对。
•boolean containsValue(Object value); //判断是否包含指定值的“键-值”对。
•boolean isEmpty(); //判断此Map中是否有元素。方法名 方法介绍
public E push(E item) 把元素压入堆栈顶部
public E pop() 移除堆栈顶部的对象,并作为此方法的值返回该对象
public E peek() 查看堆栈顶部的对象,但不从堆栈中移除它。
public boolean empty() 测试堆栈是否为空。
public int search(Object o) 返回对象在堆栈中的位置,以1为基数。
•int size(); //获得些Map中“键-值”对的数量。
•void clear(); //清空Map中的所有“键-值”对。
•Set keySet(); //返回此Map中包含的键的Set集。
•Collection values(); //返回此Map中包含的值的Collection集。
•Set<Map.Entry<K,V>> entrySet(); //返回此Map中包含的“键-值”对的Set集
遗留类
Vector
旧版的ArrayList,它大多数操作跟ArrayList相同,区别之处在于Vector是线程同步的。
它有一枚举方式可以类似Iterator进行遍历访问,也可以使用forEach或者Enumcation(JDK1.0用来遍历 集合,后面不推荐使用可以用Iterator替代)
Stack
•Stack类是Vector的子类,它是以后进先出(LIFO)方式存储元素的栈。线程同步的。
Stack类中增加了5个方法对Vector类进行了扩展
方法名 | 方法介绍 |
---|---|
public E push(E item) | 把元素压入堆栈顶部 |
public E pop() | 移除堆栈顶部的对象,并作为此方法的值返回该对象 |
public E peek() | 查看堆栈顶部的对象,但不从堆栈中移除它。 |
public boolean empty() | 测试堆栈是否为空。 |
public int search(Object o) | 返回对象在堆栈中的位置,以1为基数。 |
Hashtable
旧版的HashMap,JDK1.0中创建的,在JDK2.0中使用HashMap替换Hashtable,本身具有线程同步的 功能。
使用方式和HashMap大致一样。
Properties
Hashtable的一个子类,用于获取当前项目中的配置文件中的信息,该类没有泛型 所有的key-value均 为String类型
Properties类表示了一个持久的属性集。Properties可保存在流中或从流中加载。属性集中每个键及其 对应值都是一个字符串。
不建议使用 put 和 putAll 这类存放元素方法,应该使用 setProperty(String key, String value)方法,因 为存放的“键-值”对都是字符串。类似取值也应该使用getProperty(String key)。
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class PropertiesTest {
public static void main(String[] args) {
InputStream is = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("config.properties");
Properties prop = new Properties();
try {
prop.load(is);
} catch (IOException e) { e.printStackTrace(); }
String name = prop.getProperty("name");
String pwd = prop.getProperty("pwd");
System.out.println(name + ", " + pwd);
}
}
排序接口
TreeSet & TreeMap
在集合中存在一个自动排序的存储集合,底层使用红黑树进行数据存储。
只要存入该集合中的数据都会按照排序规则进行默认排列
对于数据类型的包装类 以及 String类型 默认都实现了排序接口 会按照字典顺序进行数据排列
如果希望元素放置在TreeSet或者TreeMap中 则该对象必须实现比较接口Comparable 否则在存放时会 抛出异常
Comparable
比较接口,所有需要实现排序规则的对象都应该实现该接口,表示该类时一个可比较类。
实现Comparable时需要实现其CompareTo(Object o) 方法
按照升序进行判定规则:
a.age > b.age 需返回 正整数
a.age < b.age 需返回 负整数
a.age == b.age 需返回 0
按照降序排列则只需将正整数和负整数的位置交换即可
优点:无序另外设置排序规则,在可排序的方法中可以直接进行排序
缺点:在一个项目中如果实现Comparable 则该对象默认只能有一种排序方式
public class Student implements Comparable{
private int id; //编号
private String name; //姓名
private double score; //考试得分
public Student(){}
public Student(int id, String name, double score) {
this.id = id; this.name = name; this.score = score;
}
//省略所有属性的getter和setter方法
//实现compareTo方法,按成绩升序排序
public int compareTo(Object o) {
Student other = (Student)o;
if(this.score > other.score){ return 1;
}else if(this.score < other.score){ return -1;
}else{ return 0; }
}
public boolean equals(Object obj) {…}
public int hashCode() { …}
}
Comparator
比较器接口,可以自定义一个类来实现Comparator接口,并实现其compare(Object o1,Object o2)方 法
比较规则和Comparable中的规则一致。
优点:在一个项目中可以按照不同的需求定义不同的比较器,在具体需求出使用精准的比较规则来进行 数据排序
缺点:在使用时需要手动告知排序接口排序规则(设置参数),如果仅使用一次则见识使用匿名内部类 或Lambda表达式来实现
当一个对象即实现了Comparable接口 用有Comparator类型的排序类 则如果显示调用Comparator的 排序类 会覆盖Comparable的实现规则 否则直接使用Comparable的默认排序
/** 学生考试得分比较器 */
class StudentScoreComparator implements Comparator<Student>{
public int compare(Student o1, Student o2) {
if(o1.getScore() > o2.getScore()){ return 1;
}else if(o1.getScore() < o2.getScore()){ return -1;
}else{ return 0; }
}
}
/** 学生姓名比较器 */
class StudentNameComparator implements Comparator<Student>{
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
}
//使用不同比较器的排序集合
Set<Student> set = new TreeSet<Student>(new StudentScoreComparator());
Set<Student> set2 = new TreeSet<Student>(new StudentNameComparator());
Collections
针对于集合专门设计的工具类,提供了集合常用的功能,方便开发者提升开发效率。
常用方法:
void sort(List list) 根据元素的自然顺序 对指定List列表按升序进行排序。List列表中的所有元素都必
须实现 Comparable 接口。
void shuffle(List list) 对List列表内的元素进行随机排列
void reverse(List list) 对List列表内的元素进行反转
void copy(List dest, List src) 将src列表内的元素复制到dest列表中
List synchronizedList(List list) 返回指定列表支持的同步(线程安全的)列表
集合类的线程安全
集合类大多数默认都没有考虑线程安全问题,程序必须自行实现同步以确保共享数据在多线程下存取不 会出错:
使用同步锁
- synchronized(list){ list.add(…); }
用java.uitl.Collections的synchronizedXxx()方法来返回一个同步化的容器对象
•List list = Collections.synchronizedList(new ArrayList());
这种方式在迭代时仍要用synchronized修饰
List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
Iterator i = list.iterator(); • while (i.hasNext()) {
foo(i.next());
}
}
从JDK5.0开始,提供了一个concurrent包,专门提供了一些在并发操作中好用的对象。跟集合相关的常 用对象有:
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
以上的对象在同步性和效率性上进行了平衡,只在修改元素时加锁,在读取元素时不上锁,提高读取效 率,适合使用于并发性强且不长修改的数据。