集合
一、简介
动态保存任意多个对象
集合两组:单列集合,双列集合
Collection接口有两个重要的子接口 List Set,实现的子类都是单列集合
二、Collection 接口和常用方法
- Collection实现子类可以存放多个元素,每个元素可以是Object
- Collection的实现类,有些可以存放重复的元素,有些不可以
- Collection的实现类,有些事有序的(List),有些不是有序的(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
接口方法,以ArrayList举例
public class Collection_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
// add 添加单个元素
list.add("fff");
list.add(true);
list.add(14.23);
System.out.println(list); // [fff, true, 14.23]
// remove 删除指定元素
list.remove(0); // 删除第一个元素
System.out.println(list); // [true, 14.23]
list.remove(true); // 删除指定元素
System.out.println(list); // [14.23]
// contains 查找元素是否存在
list.add("fff");
list.add(true);
System.out.println(list); // [14.23, fff, true]
System.out.println(list.contains(14.23)); // true
// size 获取元素个数
System.out.println(list.size()); // 3
// isEmpty 判断是否为空
System.out.println(list.isEmpty()); // false
// clear 清空
list.clear();
System.out.println(list); // []
// addAll 添加多个元素
ArrayList list1 = new ArrayList();
list1.add("ggg");
list1.add("1223");
list.addAll(list1);
System.out.println(list); // [ggg, 1223]
// containsAll 查找多个元素是否都存在
System.out.println(list.containsAll(list1)); // true
// removeAll 删除多个元素
list.add(1111);
list.removeAll(list1);
System.out.println(list); // [1111]
}
}
三、Collection接口遍历元素方式
- Iterator迭代器遍历
Iterator对象称为迭代器,用于遍历集合中的元素
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
Iterator仅用于遍历集合,本身不存放对象
- 执行原理
Iterator iterator = collection.iterator(); // 得到一个集合的迭代器
// hasNext() 判断是否还有下一个元素
while(iterator.hasNext()){
//next() 后移 将后移以后集合位置上的元素返回
}
System.out.println(iterator.next());
public class CollectionIterator {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add(new Book("三国" ,"罗贯中",10));
collection.add(new Book("aaa","a",52));
collection.add(new Book("tt","as",25.2));
// 得到collection对应的迭代器
Iterator iterator = collection.iterator();
// 使用while循环遍历
while(iterator.hasNext()){// 判断是否还有数据
// 返回下一个元素,类型是Object
Object obj = iterator.next();
System.out.println(obj);
}
// 当退出while循环后,iterator迭代器,指向最后的元素
// 如果再次遍历,重置迭代器
iterator = collection.iterator();
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
- 增强for循环
for (Object obj:collection){
System.out.println(obj);
}
四、List接口和常用方法
List接口Collection接口的子接口
- List集合类中元素有序,即添加顺序和取出顺序一致,且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
- 常用的类有ArrayList、LinkedList和Vector
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
list.add("ccc");
list.add("cxx");
System.out.println(list); // [aaa, ccc, cxx]
System.out.println(list.get(1)); // ccc 索引从0开始
}
}
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("苹果");
list.add("鸡蛋");
list.add("aaa");
// void add(int index, Object ele):在 index 位置插入 ele 元素
list.add(1,"abc"); // [苹果, abc, 鸡蛋, aaa] 在位置1加入abc
// boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list1 = new ArrayList();
list1.add("ooo");
list1.add(222);
list.addAll(list1);
System.out.println(list); // [苹果, abc, 鸡蛋, aaa, ooo, 222]
// Object get(int index):获取 index 位置的元素
System.out.println(list.get(2)); // 鸡蛋
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("ooo")); // 4
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add("ooo");
System.out.println(list.lastIndexOf("ooo")); // 6
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(0);
System.out.println(list); // [abc, 鸡蛋, aaa, ooo, 222, ooo]
// Object set(int index, Object ele):设置指定 index 位置的元素为 ele
list.set(2,345);
System.out.println(list); // [abc, 鸡蛋, 345, ooo, 222, ooo]
// List subList(int fromIndex, int toIndex):返回从 fromIndex <= < toIndex 位置的子集合
List returnList = list.subList(1,3);
System.out.println(returnList); // [鸡蛋, 345]
}
}
- List的三种遍历方式
使用iterator
Iterator iterator = list.iterator();
while(iterator.hasNext()){
Object o = iterator.next();
}
使用增强for
```java for (Object o:list) {
}
3. 使用普通for
```java
for (int i = 0; i < list.size(); i++) {
Object object = list.get(i);
System.out.println(object);
}
五、ArrayList底层
null也可以加入ArrayList
ArrayList是由数组来实现数据存储的
ArrayList基本等同于Vector,但ArrayList是线程不安全的
ArrayList中维护了一个Object类型的数组elementData
当创建ArrayList对象时,如果使用无参构造器,初始elementData容量为0,第1次添加,扩容为10,再次扩容,扩容为elementDate的1.5倍
如果使用的是指定大小的构造器,初始elementDate容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
六、Vector
底层是对象数组
线程安全的,方法右synchronized
扩容:如果是无参,默认10,满了按2倍;如果指定大小,每次按2倍扩容
七、LinkedList
底层实现了双向链表和双端队列,可以添加任意元素,可以重复,包括null
线程不安全,没有实现同步
底层维护了一个双向链表,first和last分别指向首节点和尾节点
每个节点(Node)维护了prev,next,item三个属性,prev指向前一个节点,next指向后一个节点
添加删除效率比较高
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(222);
linkedList.add("sds");
System.out.println(linkedList); // [1, 222, sds]
linkedList.remove();// 删除第一个结点
linkedList.set(1,777); // 修改
System.out.println(linkedList); // [222, 777]
Object o = linkedList.get(1);
System.out.println(o); // 777
}
}
改查多选ArrayList
增删多选LinkedList
八、Set
无序,没有索引
不允许重复元素,最多包含一个null
遍历可以使用迭代器和增强for,不能用索引,即不能普通的循环
public class SetMethod {
public static void main(String[] args) {
Set set = new HashSet();
set.add("dfg");
set.add("ewq");
set.add("dqwd");
set.add("dfg");
set.add(111);
set.add(null);
set.add(null);
System.out.println(set); // [null, dfg, dqwd, ewq, 111]不能重复且无序
// 添加和取出的顺序不一致,虽然不是添加的顺序,但是是固定的
// 遍历
// 1. 迭代器
Iterator i = set.iterator();
while (i.hasNext()){
Object obj = i.next();
System.out.println(obj);
}
// 2. 增强for
for (Object o: set) {
System.out.println(o);
}
}
}
HashSet
实现Set接口,底层是HashMap
public HashSet() {
map = new HashMap<>();
}
可以添加null,但仅一个
不保证元素有序,hash后再确定索引的结果
public class HashSet_ {
public static void main(String[] args) {
Set set = new HashSet();
// add会返回一个boolean值,添加成功,返回true,否则返回false
System.out.println(set.add("dfg")); //true
System.out.println(set.add("ewq")); //true
System.out.println(set.add("dqwd")); //true
System.out.println(set.add("dfg")); // false
System.out.println(set.add(111)); //true
// remove指定删除某个对象
set.remove("dfg");
System.out.println(set);
}
}
public class HashSet01 {
public static void main(String[] args) {
Set set = new HashSet();
set.add("dfg");
set.add("dfg"); // 加不进
set.add(new Dog("t"));
set.add(new Dog("t")); // 加进去了 两个Dog对象
System.out.println(set); // [dfg, Dog{name='t'}, Dog{name='t'}]
set.add(new String("ttt"));
set.add(new String("ttt")); // 加不进去
System.out.println(set); // [dfg, ttt, Dog{name='t'}, Dog{name='t'}]
}
}
class Dog{
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
- 底层
HashSet的底层是HashMap,HashMap的底层是数组+链表+红黑树 - 扩容
第一次添加时,table数组扩容到16(临界值*加载因子=16x0.75=12),达到12时扩容到16x2=32,此时临界值32x0.75=24(加入一个结点就算一个)
添加一个元素时,得到hash值,转成索引值
找到存储表table的这个索引是否已经有元素
没有元素,直接添加
如果有,调用equals比较,相同,不能添加,不同,添加到最后
一条链表的元素个数达到TREEIFY_THRESHOLD(默认8),table大小>=MIN_TREEIFY_CAPACITY(默认64)会进行数化,红黑树
public class HashSet02 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("ml",45));
hashSet.add(new Employee("kl",21));
hashSet.add(new Employee("ml",45));
System.out.println(hashSet); //[Employee{name='ml', age=45}, Employee{name='kl', age=21}, Employee{name='ml', age=45}]
// 要求name和age的值相同,视为相同的Employee,即让它们hash值相同
// equals() and hashCode()后 [Employee{name='kl', age=21}, Employee{name='ml', age=45}]
}
}
class Employee{
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
// ALT+INSERT 选equals() and hashCode()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
LinkedHashSet
LinkedHashSet时HashSet的子类,底层是一个LinkedHashMap,底层维护了一个数组+双向链表
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使元素以插入顺序保存
不允许添加重复元素
九、Map 接口和常用方法
- 特点
双列key-value
k、v时任何引用类型的数据,会封装到HashMap$Node对象中
不允许重复
k、v可以为null,但只能有一个k为null,可以有多个v为空
通过k能找到对应的value
一对k-v放在一个HashMap$Node中Node实现了Entry接口,为了方便遍历(Entry提供了getKey(),getValue()方法),创建EntrySet集合,集合存放的是Entry,一个Entry对象有k,v EntrySet> 即transient Set > entrySet;
entrySet中定义的类型是Map.Entry,实际存放的是HashMapNode`实现了Map.Entry接口 常用方法
public class Map01 {
public static void main(String[] args) {
Map map = new HashMap();
map.put("we","121");
map.put("nfwkje",554);
map.put("vfs","efs");
map.put("cs","csf");
map.put("cs",new Book1("书",9)); // 替换
map.put("er","gerg");
map.put(null,"hi");
map.put("fwqw",null);
System.out.println(map); // {cs=Book1{name='书', num=9}, null=hi, vfs=efs, fwqw=null, nfwkje=554, er=gerg, we=121}
// remove 根据K删除
map.remove("vfs");
System.out.println(map); // {cs=Book1{name='书', num=9}, null=hi, fwqw=null, nfwkje=554, er=gerg, we=121}
System.out.println(map.get("cs")); // Book1{name='书', num=9}
// size 获取元素个数
System.out.println(map.size()); // 6
// isEmpty 判空
System.out.println(map.isEmpty()); // false
// 清空
// map.clear();
// containsKey 是否存在
System.out.println(map.containsKey("fwqw")); // true
}
}
class Book1{
private String name;
private int num;
public Book1(String name, int num) {
this.name = name;
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Book1{" +
"name='" + name + '\'' +
", num=" + num +
'}';
}
}
Map 遍历
// 遍历
public class Map02 {
public static void main(String[] args) {
Map map = new HashMap();
map.put("we","121");
map.put("nfwkje",554);
map.put("vfs","efs");
map.put("cs","csf");
map.put("cs",new Book1("书",9)); // 替换
map.put("er","gerg");
map.put(null,"hi");
map.put("fwqw",null);
// 1. 取出所有key,通过key得到value
Set keyset = map.keySet();
// 1.1 增强for
for (Object key:keyset) {
System.out.println(key+"-"+map.get(key));
}
// 1.2 迭代器
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key+"-"+map.get(key));
}
// 2. 把所有的values取出 可以使用所有的Collection遍历
Collection values = map.values();
// 2.1 增强for
for (Object value:values) {
System.out.println(value);
}
// 2.2 迭代器
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
}
// 3. 通过EntrySet获取k-v
Set set = map.entrySet();
// 3.1 增强for
for (Object entry: set) {
// 将entry转成Map.Entry
Map.Entry m =(Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
// 3.2 迭代器
Iterator iterator2 = set.iterator();
while (iterator2.hasNext()){
Object next = iterator2.next();
// 向下转型
Map.Entry m = (Map.Entry) next;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
HashMap(k,v)是一个Node,实现了Map.Entry
扩容机制和HashSet一样:- HashMap底层维护了Node类型的数组table,默认为null
- 创建对象时,加载因子初始化为0.75
- 添加k-v时,通过k的哈希值得到在table的索引,判断该索引处是否有元素,如果没有元素直接添加,如果有元素,继续判断该元素的k和准备加入的k是否相等,相等,直接替换v,不相等,判断是树结构还是链表结构,做出相应处理。添加时容量不够,扩容
- 第一次添加,table扩容至16,临界值为12
- 之后再扩容,table扩容至2倍32,临界值24
- 如果一条链表的元素个数超过EREEIFY_THRESHOLD(默认8),且table>=MIN_TREEIFY_CAPACITY(默认64),树化(红黑树)
HashTable
存放键值对 k-v
hashtable键和值都不能为null
使用方法基本上和HashMap一样
hashtable是线程安全的
扩容:>=临界值时,以*2+1的大小扩容
Properties
键值对保存数据
和HashTable类似
用于从xx.properties文件中加载数据到Properties类对象,进行读取和修改
public class Properties_ {
public static void main(String[] args) {
Properties properties = new Properties();
// k,v不能为null
properties.put("qww",100);
properties.put("lls",100);
properties.put("z",100);
properties.put("z",90); // 如果有相同的k,v值被替换
System.out.println(properties); // {qww=100, z=90, lls=100}
// 用k获取v
System.out.println(properties.get("z"));
// 删除
properties.remove("z");
System.out.println(properties); // {qww=100, lls=100}
// 修改
properties.put("lls",110);
}
}
十、TreeSet、TreeMap
- TreeSet
public class TreeSet_ {
public static void main(String[] args) {
// 无参构造器创建TreeSet时,是无序的
TreeSet treeSet = new TreeSet();
treeSet.add("jis");
treeSet.add("aaas");
treeSet.add("aa");
treeSet.add("cd");
treeSet.add("111");
System.out.println(treeSet); // [111, aa, aaas, cd, jis]
// 按照字符串大小排序
// 使用TreeSet提供一个构造器,传入一个比较器(匿名内部类) 并指定排序规则
/*
1. 构造器把传入的比较器对象赋给了TreeSet 的底层的 TreeMap 的属性 this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 调用add
if (cpr != null) {//cpr 是匿名内部类(对象)
do {
parent = t;
//动态绑定到匿名内部类(对象)
compare cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回 0,这个 Key 就没有加入
return t.setValue(value);
} while (t != null);
}
*/
TreeSet treeSet1 = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//使用String的compareTo方法进行字符串大小比较
return ((String)o2).compareTo((String) o1);
}
});
// 添加数据
treeSet1.add("jis");
treeSet1.add("aaas");
treeSet1.add("aa");
treeSet1.add("cd");
treeSet1.add("111");
System.out.println(treeSet1); // [jis, cd, aaas, aa, 111]
// 按照字符长度大小排序
TreeSet treeSet2 = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).length()-((String) o1).length();
}
});
treeSet2.add("jis");
treeSet2.add("aaas");
treeSet2.add("aa");
treeSet2.add("c");
treeSet2.add("111");
System.out.println(treeSet2); // [aaas, jis, aa, c] "111"没有被加入,因为规则是以长度大小排序,长度相同时被看作是同一个
}
}
- TreeMap
public class TreeMap_ {
public static void main(String[] args) {
// 使用默认的构造器创建,是无序的
// 按照传入k的字符串大小进行排序
/*
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); // 第一次添加也会调用compare,这样判断是不是null
root = new Entry<>(key, value, null);
size = 1; modCount++;
return null;
}
2.2 再次添加
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);
}
*/
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o2).compareTo((String) o1);
}
});
treeMap.put("de","fwefee0");
treeMap.put("vea",45);
treeMap.put("aihia","edas");
System.out.println(treeMap); // {vea=45, de=fwefee0, aihia=edas}
}
}
十一、Collections工具类
操作Set、List、Map等集合的工具类
提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
- 都是static方法
public class Collections_ {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
list.add("ccc");
list.add("cxx");
list.add("kql");
System.out.println(list); // [aaa, ccc, cxx, kql]
// reverse 反转List中元素的顺序
Collections.reverse(list);
System.out.println(list); // [kql, cxx, ccc, aaa]
// shuffle 对集合元素进行随机排序
for (int i = 0;i<5;i++){
Collections.shuffle(list);
System.out.println(list);
}
// sort(List) 根据元素的自然顺序对指定集合元素按升序排序
Collections.sort(list);
System.out.println(list);
// sort(List, Comparator) 根据指定的顺序对集合元素排序
// 按字符串的大小排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1,Object o2){
return ((String)o2).compareTo((String)o1);
}
});
System.out.println(list); // [kql, cxx, ccc, aaa]
// swap(List,int,int) 将指定list集合中i处的元素和j处的元素进行交换
Collections.swap(list, 0, 1);
System.out.println(list); // [cxx, kql, ccc, aaa]
// Object max(Collection) 根据元素的自然排序,返回给定集合中的最大元素
System.out.println(Collections.max(list)); // kql
// Object max(Collection, Comparator) 根据Comparator指定的顺序,返回给定集合中的最大元素
Object max = Collections.max(list, new Comparator() {
@Override public int compare(Object o1, Object o2) {
return ((String)o2).compareTo((String)o1);
}
});
System.out.println(max); // aaa
// Object min(Collection)
// Object min(Collection,Comparator) 跟max类似
// int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println(Collections.frequency(list,"aaa")); // 1
//void copy(List dest,List src):将 src 中的内容复制到 dest 中
ArrayList dest = new ArrayList();
for (int i = 0;i < list.size();i++){
dest.add(""); // 要先给dest赋值
}
Collections.copy(dest,list);
System.out.println(dest); // [cxx, kql, ccc, aaa]
// boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
Collections.replaceAll(list,"aaa","xxx");
System.out.println(list); // [cxx, kql, ccc, xxx]
}
}