集合/容器
1.集合的概念
当我们需要保存一组类型相同的元素时,可以通过使用数组来存储,数组就是一个容器。但是数组有一个缺点:数组一旦定义,长度将不能再变化。
在实践中,我们需要保存一些变长的数据集合,需要保存一些能够动态增长长度的容器来存储数据,同时我们需要对数据的保存的逻辑可能各种各样,于是就有了各种各样的数据结构。Java中对于各种数据结构的实现(存储),就是我们用到的集合。
2.使用集合的好处
- 集合类的长度是可变的。
- 不同的数据存储,操作方式不同。
3. Java的集合体系
Java的集合框架是由很多接口,抽象类,具体类组成的,都位于java.util
包中。
ArrayList
:底层是数组实现。
LinkedList
:底层是链表实现。
Vector
:底层也是数组实现,线程安全。
HashSet
:底层是hash表实现。
TreeSet
:底层是红黑树实现。
4.集合与数组的区别
都是用来存储数据的容器,两者区别如下:
5. Collection接口
Collection
接口:定义了存取一组对象的方法,其子接口Set
和List
分别定义了存储方式。
Set
中的数据对象没有顺序且不可以重复。List
中的数据对象有顺序且可以重复。
在Collection中定义了一些集合中的共有方法:(代码示例如下)
public static void main(String[] args) {
/*
集合中默认可以存储任意数据类型,建议使用泛型,存储同一种类型的数据
*/
Collection<String> c = new ArrayList<String>();
c.add("abd");
c.add("ab");
c.add("c");
c.add("d");
// c.remove("a");//移除指定元素
// System.out.println(c.remove("a"));//移除指定元素,集合中包含指定元素返回true
// System.out.println(c.remove("f"));//移除指定元素,集合中不包含指定元素返回false
//有条件的删除操作
// c.removeIf(new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.startsWith("a");//删除以“a”开头的元素(删除的过滤条件)
// }
// });
// c.clear();// 清空集合
// System.out.println(c.isEmpty());//判断集合是否为空
// System.out.println(c.contains("a"));//判断集合中是否包含指定元素 ,包含返回true
// System.out.println(c.contains("f"));//不包含返回false
//流的方式遍历集合
c.stream().forEach((a)-> System.out.println(a));
System.out.println(c);
}
6. List接口及实现类
List继承了Collection接口,有三个实现的类:
(1). ArrayList
ArrayList
底层是数组实现,长度可变,在内存中分配连续的空间。查询快,增删慢,遍历元素和随机访问元素的效率比较高。创建之初,在底层常见一个默认长度的数组,当数组内容添加满了之后,再继续添加时,会扩容一个新数组,为原来的1.5倍。
**缺点:**扩容后,元素不能存满,造成空间浪费。
(方法及代码示例如下)
public static void main(String[] args) {
// List<String> list =new ArrayList<>();
/*
调用默认无参的构造方法,创建ArrayList对象时,并没有实际的创建数组
第一次添加元素时创建,默认长度为10
*/
ArrayList<String> alist = new ArrayList<String>();
alist.add("a");//给集合中添加元素
alist.add("b");
alist.add("c");
alist.add("d");
alist.add("e");
alist.add("e");
alist.add("e");//当add添加元素时,数组满了后,会扩容,为原来的1.5倍,返回一个新数组
alist.add(3,"A");//向指定位置添加
// System.out.println(alist.remove(4));//根据索引删除
// System.out.println(alist.remove("e"));//根据内容删除,删除指定元素(第一次出现的位置)
// System.out.println(alist.get(2));//获得指定索引的元素
// System.out.println(alist.set(2,"C"));//替换指定位置上的元素
// System.out.println(alist.indexOf("a"));//获取指定元素的下标
// System.out.println(alist.lastIndexOf("e"));//从后往前,获取指定元素的下标
// List<String> newlist = alist.subList(2,5);//获取指定范围的元素集合
// System.out.println(list);
System.out.println(alist);
/*
调用有参的构造方法,创建ArrayList对象时,创建一个指定长度的数组。
this.elementData = new Object[initialCapacity];
ArrayList 最大容量 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
*/
ArrayList<String> alist1 = new ArrayList<String>(10);
}
public static void main(String[] args) {
ArrayList<String> alist = new ArrayList<String>();
alist.add("a");
alist.add("d");
alist.add("c");
alist.add("b");
alist.add("a");
// 排序
alist.sort(new Comparator<String>() {
//使用匿名内部类,定义排序规则
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
//使用 流 的形式 遍历集合中的元素
alist.forEach((a)-> System.out.println(a));
System.out.println(alist);
public static void main(String[] args) {
ArrayList<String> alist = new ArrayList<String>();
alist.add("a");
alist.add("b");
alist.add("c");
alist.add("d");
ArrayList<String> alist1 = new ArrayList<String>();
alist1.add("a");
alist1.add("b");
alist1.add("c");
alist1.add("e");
// alist.addAll(alist1);//把指定的集合中的元素添加到此集合中
// System.out.println(alist.containsAll(alist1));//此集合中是否包含指定集合中的元素,包含返回true
// System.out.println(alist.contains("h"));//判断此集合中是否包含指定元素 包含返回true
// System.out.println(alist.removeAll(alist1));//删除两个集合中相同的元素,有返回true
System.out.println(alist.retainAll(alist1));//保留两个集合中相同的元素,有变化返回true
System.out.println(alist);
(2). LinkedList
LinkedList
:底层是链表实现,采用链表存储方式。查询慢,增删快,插入、删除元素时效率比较高。查找元素时,从第一个节点往后查找,增加、删除元素时其他元素的位置不动,只需要改变指针域的值即可。
(方法及代码示例如下)
public static void main(String[] args) {
LinkedList<String> llist = new LinkedList<>();
//LinkedList里面还提供了许多关于链表头尾操作的方法,这些方法用于模拟队列结构,栈结构操作.
llist.addLast("a");
llist.addLast("b");
llist.addLast("c");
llist.addLast("d");
llist.addLast("e");
System.out.println(llist.size());
// llist.clear(); //清空集合中的元素
System.out.println(llist.pollFirst());//检索并删除此列表的第一个元素,如果此列表为空,则返回 null
System.out.println(llist.peekFirst());//检索但不删除此列表的第一个元素,如果此列表为空,则返回 null
// System.out.println(llist.getFirst());//返回此列表中的第一个元素。
// System.out.println(llist.removeFirst());//从此列表中删除并返回第一个元素
System.out.println(llist);
llist.addFirst("1");//在该列表开头插入指定的元素 ,无返回值
llist.offerFirst("1");//在此列表的前面插入指定的元素.返回值是boolean类型
llist.offerLast("w");//在该列表的末尾插入指定的元素
System.out.println(llist.offerFirst("1"));
System.out.println(llist);
}
(3). Vector
Vector
:数组列表,添加同步锁,线程安全的。
public static void main(String[] args) {
/*
Vector 底层也是数组实现
new Vector<>(); 在创建Vector对象时,就已经创建底层存储数组,长度默认为10
Vector 中的方法都是线程安全
*/
Vector<String> v = new Vector<>();
v.add("a");
v.add("a");
v.add("b");
v.add("c");
v.add("a");
System.out.println(v); // 输出元素可以重复 [a, a, b, c, a]
}
7. List接口集合迭代
遍历集合元素的方法:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("d");
list.add("d");
list.add("d");
// list接口实现类的遍历 方式1 使用流的形式遍历集合中的元素
// list.stream().forEach((a)-> System.out.println(a));
// 遍历方式 2 for循环遍历
/* for (int i = 0; i < list.size(); i++) {
// System.out.println(list.get(i));
//for循环时,可以从集合中删除元素,但是要注意下标与集合的的内容就会不对应
if(list.get(i).equals("d")){
list.remove(i);
//i--;
}
}
System.out.println(list); */
//遍历方式3 增强for循环
for(String t : list){
// System.out.println(t);
/*
理论上,增强for循环中不允许删除元素
如果必须要删除元素,则只能删除一个元素,并立即break终止循环
*/
list.remove("d");
break;
// if (t.equals("d")){
// list.remove(t);
// break;
// }
}
System.out.println(list);
}
迭代器遍历(Iterator):通过集合返回迭代器
(方法及代码示例如下)
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("d");
list.add("d");
list.add("d");
//迭代器 通过iterator() 方法 返回一个 Iterator对象
// 迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){//hasNext() 判断集合中还有没有元素
String s = it.next();
//System.out.println(s);
if (s.equals("d")){
//remove() 删除元素,删除后 里面的游标(指针、计数器)会往回退一下
it.remove();// 使用迭代器中提供的方法
}
}
System.out.println(list);
}
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("d");
list.add("d");
list.add("d");
//迭代器 通过iterator() 方法 返回一个 Iterator对象
/*
迭代器1 从前向后遍历
ListIterator() 只适合于List接口实现类
遍历时还可以添加元素,设置替换元素,nextIndex()元素位置
*/
/* ListIterator<String> listit = list.listIterator();
while (listit.hasNext()){ //有没有上一个元素
//System.out.println(listit.next()+":"+listit.nextIndex()); //获取元素及对应位序
listit.next();
// listit.add("x"); // 添加元素
// listit.set("s"); // 修改元素(全部)
// listit.remove(); //删除元素
}
System.out.println(list);*/
// 迭代器2 从后向前遍历
ListIterator<String> listit = list.listIterator(list.size());
System.out.println(listit.hasPrevious());
while (listit.hasPrevious()){ //有没有上一个元素
System.out.println(listit.previous());
}
System.out.println(list);
}
8.Set接口及实现类
Set
接口继承了Collection
接口。
Set中所存储的元素是不可重复的,但是是无序的,Set中的元素没有索引
Set接口有两个类:HashSet
、TreeSet
(1)HashSet
HashSet
类中的元素不能重复,即彼此调用equals方法比较,都返回false。
底层数据结构是哈希表+链表。
注:哈希表依赖于哈希值存储
public static void main(String[] args) {
/*
Set 不能存储重复元素
HashSet 无序存储(不是按照添加顺序排列)
*/
HashSet<String> h = new HashSet<>();
h.add("a");
h.add("a");
h.add("b");
h.add("c");
h.add("x");
h.add("w");
h.add("p");
h.add("通话");
h.add("重地");
h.add("H"); // add 了九次,但集合的size为8,因为有一个重复的a没有存进去
System.out.println(h); //[p, a, b, 通话, 重地, c, w, x, H]
System.out.println(h.size()); // b, 通话, 重地 宏观上三者在哈希表的第二位(从0开始) 微观上这三个通过链表联系
/* 几个问题:
HashSet是如何判断内容重复
1. 每次添加内容是,会用内容的hash值来判断是否相同,但是hash值这种方法不安全,
可能会出现内容不同,hash值相同(块)
2. 当出现内容不同,hash值相同(hash碰撞,冲突)时,使用equals方法比较,比较
的是内容是否相同(安全)
(双保险:保证效率,又保证了安全)
3.hash值怎么来?
调用hashCode()方法
两种情况: 1. 类中已经重写hashCode(),例如String类,根据内容来计算hash值
2. 类中没有重写hashCode(),调用Object类中hashCode().(可以想成
对象在内存中的地址)。
*/
}
(2)TreeSet
TreeSet
:可以给集合中的元素进行指定方式的排序(可以按照元素的自然顺序排序)。存储的对象必须实现Comparable
接口。
TreeSet
底层数据结构是二叉树。(红黑树是一种自平衡的二叉树)
public static void main(String[] args) {
/*
Set 不能存储重复元素
TreeSet 可以按照元素的自然顺序排序
底层是红黑树
*/
TreeSet<String> tset = new TreeSet<>();
tset.add("b");
tset.add("a");
tset.add("d");
tset.add("c");
tset.add("c");
System.out.println(tset);// [a, b, c, d]
}
(3)Set遍历方式
public static void main(String[] args) {
TreeSet<String> tset = new TreeSet<String>();
tset.add("b");
tset.add("a");
tset.add("d");
tset.add("c");
tset.add("x");
// Set 遍历方式1
/*
tset.stream().forEach((a)-> System.out.println(a));
*/
// 增强for循环2
/* for (String t : tset) {
System.out.println(t);
}*/
// 迭代器遍历3
Iterator<String> it = tset.iterator();
while (it.hasNext()){
String e = it.next();
System.out.println(e);
/*
a
b
c
d
x
*/
}
}
9.Collections的一些方法
public static void main(String[] args) {
/*
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"a","s","m","f"); // 将多个元素插入到指定集合中去
System.out.println(list); //[a, s, m, f]
Collections.sort(list); // 排序
System.out.println(list);//[a, f, m, s]
System.out.println(Collections.binarySearch(list,"b")); //返回指定元素在集合中的 位序, >0集合中存在该元素,<0 b不存在
*/
ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("c");
list1.add("d");
list1.add("f");
ArrayList<String> list2 = new ArrayList<>();
list2.add("x");
list2.add("y");
list2.add("z");
Collections.copy(list1,list2); // 把list2 中的元素复制到list1中相应的位置上,list1中与list2中对应位置的元素会被覆盖
// 要求目标集合的size不能小于源集合的size
System.out.println(list1); //[x, y, z, f]
Collections.fill(list2,"g");// 用指定元素替换掉集合中所有的元素
System.out.println(list2); //[g, g, g]
System.out.println(Collections.max(list1)); //z 根据其元素的 自然顺序返回给定集合的最大元素。
Collections.replaceAll(list1,"f","F"); // 将列表中一个指定值的所有出现替换为另一个。
System.out.println(list1); //[x, y, z, F]
Collections.reverse(list1);// 逆序输出
System.out.println(list1);// [F, z, y, x]
Collections.swap(list1,0,2);// 交换集合中指定两个位置的元素
System.out.println(list1);//[y, z, F, x]
}
10.Map接口及其实现类
Map集合采取键值对存储,键不能重复,值可以重复,键唯一。
Map接口有两个类:HashMAp
、TreeMap
(1)HashMap
HashMap
是键值对存储,键不重复,如果有重复的键,后面重复键的值会把前面重复键的值覆盖掉,允许存储一个键为null的键值对。
HashMap
中元素的key值不能重复,即彼此调用equals方法,返回为false。排列顺序是不固定的。
public static void main(String[] args) {
/*
Map集合
键 值对 存储。
键不能重复,键唯一,值可以重复。
HashMap 键是无序的(不是按照添加顺序排列)
如果有重复的键,后面的值会把前面的覆盖掉
可以存储一个为 null 的键
*/
HashMap<String,String> hm = new HashMap<>();
hm.put("a","a"); // 通过 put() 进行添加
hm.put("a","b"); //键不能重复 如果有重复的键,后面的值会把前面的覆盖掉
hm.put("c","C");
hm.put("d","d");
hm.put(null,"n");// 可以存储一个为 null 的键
hm.put(null,"u");
System.out.println(hm);
// hm.remove(null);// 移除指定键值的键值对
// hm.clear(); //清空集合
System.out.println(hm.containsKey("e")); // 判断是否包含指定 键 的 键值对
System.out.println(hm.containsValue("d"));// 判断是否包含指定 值 的 键值对
System.out.println(hm.isEmpty()); // 判断集合是否非空
System.out.println(hm.size()); //4 返回集合长度
System.out.println(hm.get("c")); // 获取指定键 的值
System.out.println(hm);
(2)HashMap的遍历方式
public static void main(String[] args) {
HashMap<String,String> hm = new HashMap<>();
hm.put("a","a");
hm.put("b","b");
hm.put("c","c");
hm.put("p","p");
System.out.println(hm); //{p=p, a=a, b=b, c=c}
/*
Map 集合 遍历
*/
/*
① forEach()
*/
// hm.forEach((k,v)-> System.out.println(k+":"+v));
/*
② .keySet(); 先获取 hm 中所有的键,存储到Set集合中
再对Set集合进行遍历,通过每次拿到的key去获得对应的值
*/
/* Set<String> keyset = hm.keySet();
for (String key : keyset) {
System.out.println(key + ":" + hm.get(key));
}*/
/*
③ .entrySet(); 将Map集合中 键值对封装到一个 Entry 对象中
*/
Set<Map.Entry<String, String>> entryset = hm.entrySet();
//增强for循环
/* for (Map.Entry entey : entryset) {
System.out.println(entey.getKey() + ":" + entey.getValue());
}*/
// 迭代器
Iterator<Map.Entry<String,String>> it = entryset.iterator();
while (it.hasNext()){
Map.Entry<String,String> entry = it.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
/*
p:p
a:a
b:b
c:c
*/
}
(3)TreeMap
TreeMap
是键值对存储,键不重复,可以根据键的自然顺序排序。
TreeMap
中所有的元素都保持着某种固定的顺序,如果需要得到一个有序的Map就应该使用TreeMap
,key值所在类必须实现Comparable接口。
public static void main(String[] args) {
/*
TreeMap
键值对 键不能重复
可以根据键的自然顺序排序 ,指定的键的类型的类必须实现Comparable接口(排序时使用)
*/
TreeMap<Integer,String> tm = new TreeMap<>();
tm.put(1,"a");
tm.put(1,"b");
tm.put(3,"d");
tm.put(2,"c");
// tm.put(null,"n"); // 不允许存储为null的键 ,会报错
System.out.println(tm); //{1=b, 2=c, 3=d}
}
(4)HashTable
public static void main(String[] args) {
/*
Hashtable
无序的, 初始容量为11 线程安全
不允许存储为null的键
*/
Hashtable<Integer,String> ht = new Hashtable<>();
ht.put(1,"a");
ht.put(1,"b");
ht.put(3,"d");
ht.put(2,"c");
// ht.put(null,"u"); // 不允许存储为null的键 ,会报错
System.out.println(ht);// {3=d, 2=c, 1=b}
}