集合
集合的好处
- 可以动态保存任意多个对象
- 提供一系列方便操作对象的方法:add、remove、set、get等
- 使用集合添加,删除新元素的示意代码
集合主要分为两组:(单列集合,双列集合)
1. Collection 接口有两个重要的子接口:List 和 Set,它们实现的子类都是单列集合
List list = new ArrayList();
//add 添加单个元素
list.add("小明");
list.add(10);
//相当于list.add(new Integer(10));
list.add(true);
2. Map 接口的实现子类是双列集合,存放 key-value
Map map = new HashMap();
map.put("N01","北京");
map.put("N02","上海");
map.put("N03","深圳");
Collection
Collection框架图
collection中的方法
public static void main(String[] args) {
List list = new ArrayList();
//add 添加单个元素
list.add("小明");
list.add(10); //list.add(new Integer(10));
list.add(true);
System.out.println(list); //输出:[小明, 10, true]
//remove 删除指定元素
list.remove(0); //1.删除索引0,返回被删除的对象(小明)
System.out.println(list); //[10, true]
list.remove(true); //2.删除true(具体的值),则返回boolean值
System.out.println(list); //[10]
//contains:查找元素是否存在
System.out.println(list.contains("小明")); //false
System.out.println(list.contains(10));//true
//size:获取元素个数
System.out.println(list.size()); //1
//isEmpty:判断是否为空
System.out.println(list.isEmpty()); //false
//clear:清空
list.clear();
System.out.println(list); // []
//addAll:添加多个元素
List list1 = new ArrayList();
list1.add("花木兰");
list1.add("孙尚香");
list1.add("后羿");
list.addAll(list1);
System.out.println(list.equals(list1)); //true
System.out.println(list); //[花木兰, 孙尚香, 后羿]
//containsAll:查找多个元素是否存在
System.out.println(list.containsAll(list1)); //true
//removeAll:删除多个元素
list.add("武则天");
list.removeAll(list1);//删除了[花木兰, 孙尚香, 后羿]
System.out.println(list);//[武则天]
}
Collection接口遍历对象方式
- Iterator对象成为迭代器,主要用于遍历Collection 集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator仅用于遍历集合,Iterator本身并不存放对象
示例代码:
public class IteratorDemo {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 49.9));
col.add(new Book("红楼梦", "曹雪芹", 29.9));
col.add(new Book("西游记", "吴承恩", 69.1));
col.add(new Book("水浒传", "施耐庵", 99.8));
/*
1.遍历col集合
2.得到 col 对应的迭代器
*/
Iterator iterator = col.iterator();
//3.使用while循环遍历
while (iterator.hasNext()) { //判断是否还有数据
//返回下一个元素,类型是Object
Object obj = iterator.next();
System.out.println("obj="+obj);
}
//快捷键 itit 快捷生成 while
/* 当退出while循环后,这时的iterator迭代器,指向最后的元素
如果需要再次遍历,需要重置我们的迭代器
iterator = col.iterator();
*/
iterator = col.iterator();
}
}
//创建一个Book类
public 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循环就是简化版的iterator,本质一样,只能用于遍历集合和数组,可以代替iterator迭代器
示例代码:
public class ForDemo {
public static void main(String[] args) {
@SuppressWarnings("all")
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 49.9));
col.add(new Book("红楼梦", "曹雪芹", 29.9));
col.add(new Book("西游记", "吴承恩", 69.1));
col.add(new Book("水浒传", "施耐庵", 99.8));
//使用增强for 在 Collection集合
//增强for循环,底层仍然是迭代器
//快捷方式 I
for (Object book : col){
System.out.println("book=" + book);
}
//增强for 也可以直接在数组使用
int nums[] = {1,3,5,7,8,9};
for (int i : nums){
System.out.println(i);
}
}
}
List接口和常用方法
List接口基本介绍
List接口是Collection接口的子接口
1.List集合类中元素有序(即添加顺序和取出顺序一致)且可重复
2.List集合中的每个元素都有其对应的顺序索引,即支持索引
//List集合类中元素有序,且可重复
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("peter");
list.add("tom"); //可重复
System.out.println(list); //[jack, tom, peter, tom]
//List集合中的每个元素都有其对应的顺序索引,即支持索引 索引从0开始
System.out.println(list.get(1)); //tom
3.List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
4.JDK API中List接口实现的类有:ArrayList、LinkedList和Vector
List常用方法
public class ListMethod {
public static void main(String[] args) {
@SuppressWarnings("all")
List list1 = new ArrayList();
list1.add("雅典娜");
list1.add("夏洛特");
//void add(int index,Object ele):在index位置插入ele元素
list1.add(1,"孙尚香"); //在index为 1 的位置插入 孙尚香
System.out.println(list1); //[雅典娜, 孙尚香, 夏洛特]
//boolean addAll(int index,Collection eles):从index位置开始将eles中所有的元素添加进来
List list2 = new ArrayList();
list2.add("狗哥");
list2.add("墨菲特");
list1.addAll(1,list2); //在 孙尚香 之前插入
System.out.println(list1); //[雅典娜, 狗哥, 墨菲特, 孙尚香, 夏洛特]
//Object get(int index):获取指定index位置的元素
System.out.println(list1.get(2)); //墨菲特
//int indexOf(Object obj):返回obj在集合中出现的首次位置
List list3 = new ArrayList();
list3.add('g');
list3.add('c');
list3.add('a');
list3.add('c');
//获取字符 c 在 list3 中出现的首次位置
int index = list3.indexOf('c');
System.out.println(index); // 1
//int lastIndexOf(Object obj):返回obj在集合中出现的最后位置
System.out.println(list3.lastIndexOf('c')); //3
//Object remove(int index):移除指定index位置的元素,并返回此元素
System.out.println(list3.remove(0)); // g
System.out.println(list3); //[c, a, c]
//Object set(int index,Object ele):设置指定index位置的元素为ele,相当于是替换
list3.set(1,'c'); //将list3中索引为1的元素替换成'c'
System.out.println(list3); //[c, c, c]
//List subList(int fromIndex, int toIndex):返回从fromIndex(包含)到toIndex(不包含)位置的子集合
List list4 = new ArrayList();
list4.add(1);
list4.add(2);
list4.add(3);
list4.add(4);
list4.add(5);
List list5 = list4.subList(1,3);
System.out.println(list5); //返回从索引 1 到索引 3 位置的子集合 不包含 3
//输出 [2, 3]
}
}
测试题
使用List的实现类添加图书,并遍历,要求按价格低到高排序
示例代码
public class ListTest {
public static void main(String[] args) {
List list = new ArrayList();
//添加书籍数据
list.add(new Book("红楼梦","曹雪芹",19.9));
list.add(new Book("三国转","罗贯中",18.8));
list.add(new Book("西游记","吴承恩",54.1 ));
list.add(new Book("水浒传","施耐庵",99.6));
//增强for循环
System.out.println("=====排序前=====");
for (Object o :list) {
System.out.println(o);
}
sort(list); //调用静态排序方法
System.out.println("=====排序后=====");
for (Object o :list) {
System.out.println(o);
}
}
public static void sort(List list){
int length = list.size();
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - i - 1; j++) {
Book book1 = (Book)list.get(j);
Book book2 = (Book)list.get(j+1);
if (book1.getPrice() > book2.getPrice()){
list.set(j,book2);
list.set(j+1,book1);
}
}
}
}
}
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 "名称:"+name+" "+"价格:"+price+" "+"作者:"+author;
}
}
ArrayList底层结构和源码分析
- ArrayList中维护了一个Object类型的数组elementData transient Object[] elementData; // transient 表示瞬间,短暂的,表示该属性不会被序列号
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
Vector底层结构
Vector的基本介绍
- Vector类的定义和说明
- Vector底层也是一个对象数组,protected Object[] elementData;
- Vector 是线程同步的,即线程安全,Vector类的操作方法带有 synchronized
- 在开发中,需要线程同步安全时,考虑使用Vector
Vector和ArrayList的比较
LinkedList底层结构
LinkedList的全面说明
- LinkedList底层实现了双向链表和双端队列的特点
- 可以添加任意元素(元素可以重复),包括 null
- 线程不安全,没有实现同步
LinkedList的底层操作机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last,分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向最后一个节点,最终实现双向链表
- LinkedList元素的添加和删除,不是通过数组完成的,相对来说效率较高
示例代码:
//定义一个Node类 Node对象 表示双向链表的一个节点
class Node{
public Object item; //真正存放数据
public Node next; //指向下一个节点
public Node pre; //指向上一个节点
public Node(Object name){
this.item = name;
}
@Override
public String toString() {
return "Node name = " + item;
}
}
public class LinkedList {
public static void main(String[] args) {
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node smith = new Node("smith");
//连接三个节点 形成双向链表
// jack -> tom -> smith
jack.next = tom;
tom.next = smith;
// smith -> tom -> jack
smith.pre = tom;
tom.pre = jack;
//头部指向jack(首节点)
Node first = jack;
//尾部指向smith(尾节点)
Node last = smith;
//演示链表的添加对象/数据 要求:在tom和smith之间插入一个"hyk"
//创建Node节点
Node hyk = new Node("hyk");
hyk.next = smith; //hyk的下一个是smith
hyk.pre = tom; //hyk的上一个是tom
smith.pre = hyk; //smith的上一个是hyk
tom.next = hyk; //tom的下一个是hyk
//演示从头到尾进行遍历
while (true){
if (first == null){
break;
}
//输出first信息
System.out.println(first);
first = first.next; //first为jack,它的下一个是tom,由此类推
}
System.out.println("=================================");
//从尾到头进行遍历
while (true){
if (last == null){
break;
}
System.out.println(last);
last = last.pre;
}
}
}
ArrayList和LinkedList的比较
Set接口和常用方法
Set接口基本介绍
1. 无序,(添加和取出的顺序不一致),没有索引
2. 不允许有重复元素,所以最多包含一个null
3. JDK API中Set接口的实现类有:
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
1.可以使用迭代器
2.增强for循环
3.不能使用索引的方式来获取
public class SetMethod {
public static void main(String[] args) {
//以Set接口的实现类HashSet来创建一个新的实例
Set set = new HashSet();
//添加数据
set.add("jack");
set.add("peter");
set.add("jack");//重复的数据
set.add("tom");
set.add(null);
set.add(null);//重复的null
/*
1.Set接口实现类hashSet的对象,不能存放重复的元素
2.只能添加一个null,并且存放的数据是一致的,即添加
的顺序和取出的顺序不一致
3.取出的顺序虽然和添加的顺序不一致,但它是固定的
*/
System.out.println(set);
//输出:[null, tom, peter, jack]
//Set的遍历
// 方式一:使用迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.print(next+"\t");
//输出:null tom peter jack
}
System.out.println("");
//方式二:增强for循环
for (Object o : set) {
System.out.print(o+"\t");
//输出:null tom peter jack
}
}
}
HashSet的全面说明
1. HashSet实现了Set接口
2.HashSet实际上是HashMap,源码如下:
public HashSet(){
map = new HashMap<>();
}
3.可以存放null,但是只能有一个
4.HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即不保证存放元素的顺序和取出顺序一致)
5.不能有重复的元素/对象
public class HashSet_ {
public static void main(String[] args) {
HashSet set = new HashSet();
System.out.println(set.add("jack")); //t
System.out.println(set.add("tom")); //t
System.out.println(set.add("jack")); //f
System.out.println(set.add(null)); //t
set.remove("jack");
System.out.println("set="+set);
//set=[null, tom]
set = new HashSet();
System.out.println(set.add("lucy"));//t
System.out.println(set.add("lucy"));//f
set.add(new Dog("tom"));//t
set.add(new Dog("tom"));//t
System.out.println("set="+set);
//输出:set=[tom, tom, lucy]
set.add(new String("smith")); //成功
set.add(new String("smith")); //失败
set.add(new StringBuffer("smith")); //成功
set.add(new StringBuilder("smith")); //成功
System.out.println(set);
//输出:[smith, smith, tom, tom, lucy, smith]
}
}
class Dog{
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
HashSet底层机制说明
HashSet底层是HashMap,而HashMap的底层是(数组+链表+红黑树)
模拟简单的数组+链表结构
public class HashSetBottom {
public static void main(String[] args) {
//模拟一个HashSet的底层 (HashMap的底层结构)
//1.创建一个数组 数组的类型是Node[]
//2.Node[]数组也可以直接称为表
Node[] table = new Node[16];
System.out.println("table="+table);
//3.创建节点
Node john = new Node("john",null);
table[2] = john;
Node jack = new Node("jack", null);
john.next = jack; //将 jack 节点挂载到 john
Node rose = new Node("rose", null);
jack.next = rose; //将 rose 节点挂载到 jack
Node lucy = new Node("lucy", null);
table[3] = lucy; //把lucy放在table表索引为3的位置
System.out.println("table="+table);
}
}
class Node{ //节点 存储数据 可以指向下一个节点 从而形成链表
Object item; //存放数据
Node next; //指向下一个节点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
1.HashSet 底层是 HashMap
2.添加一个元素时,先得到hash值,会转成索引值
3.找到存储数据表table,看这个索引位置是否已经存放元素
4.如果没有,直接加入
5.如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
6.在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行数化(红黑树)
LinkedHashSet的全面说明
1.LinkedHashSet 是 HashSet 的子类
2.LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个 数组+双向链表
3.LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),使得元素看起来是以插入顺序保存的
4.LinkedHashSet 不允许添加重复元素
Map
Map框架图
Map接口和常用方法
Map接口实现类的特点
1.Map与Collection并列存在,用于保存具有映射关系的数据:Key-Value(双列元素)
2.Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
3.Map中的key不允许重复,原因和HashSet一样
4.Map中的value可以重复
5.Map中的key可以为null,value也可以为null,注意key为null只能有一个,value为null可以多个
6.常用String类作为Map的key
7.key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
public class MapDemo {
public static void main(String[] args) {
//1.Map与Collection并列存在,用于保存具有映射关系的数据:Key-Value(双列元素)
//2.Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
Map map = new HashMap();
map.put("n01","青蛙");//K-Y
map.put("n02","孙尚香");//K-Y
map.put("n03","貂蝉");//K-Y
System.out.println(map);
//输出:{n03=貂蝉, n02=孙尚香, n01=青蛙}
//3.Map中的key不允许重复,原因和HashSet一样
map.put("n01","牛马");
System.out.println(map);
//输出:{n03=貂蝉, n02=孙尚香, n01=牛马}
//当有相同的key 就等价于替换 青蛙 ——> 牛马
//4.Map中的value可以重复
map.put("n04","貂蝉");
System.out.println(map);
//输出:{n03=貂蝉, n02=孙尚香, n04=貂蝉, n01=牛马}
//5.Map中的key可以为null,value也可以为null,注意key为null只能有一个,value为null可以多个
map.put(null,"后羿");//不输出
map.put(null,"嫦娥"); //只输出一个 因为 key为null只能有一个
map.put("n04",null);
map.put("n05",null); //value为null可以多个
System.out.println(map);
//输出:{n03=貂蝉, null=嫦娥, n02=孙尚香, n05=null, n04=null, n01=牛马}
//6.常用String类作为Map的key
map.put(1,"孙策");
map.put(new Object(),"小乔");
map.put(2,"大桥");//其它类型也可以
System.out.println(map);
//{n03=貂蝉, null=嫦娥, n02=孙尚香, 1=孙策, n05=null, 2=大桥, n04=null, java.lang.Object@1b6d3586=小乔, n01=牛马}
//7.key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
System.out.println(map.get("n02")); //通过get方法传入key,会返回对应的value
//输出:孙尚香
}
}
8.Map存放数据的key-value示意图,一对k-v是放在HashMap$Node中的,因为Node实现了Entry接口(也可以说一对k-v就是一个Entry)
Map体系继承图
Map常用方法
public class MapMethod {
public static void main(String[] args) {
//演示Map接口常用方法
Map map = new HashMap();
map.put("邓超",new Book("勇气",10000));
map.put("邓超","孙俪"); //t
map.put("宋喆","马蓉");//t
map.put("王宝强","马蓉");//t
map.put("蔡徐坤",null);//t
map.put(null,"刘亦菲");//t
map.put("鹿晗","关晓彤");//t
System.out.println(map);
//{邓超=孙俪, 宋喆=马蓉, null=刘亦菲, 王宝强=马蓉, 蔡徐坤=null, 鹿晗=关晓彤}
//remove:根据键删除映射关系
map.remove(null);
System.out.println(map);
//输出:{邓超=孙俪, 宋喆=马蓉, 王宝强=马蓉, 蔡徐坤=null, 鹿晗=关晓彤}
//get:根据键获取值
Object val = map.get("鹿晗");
System.out.println(val); //关晓彤
//size:获取元素个数
System.out.println(map.size()); //5
//isEmpty:判断个数是否为0
System.out.println(map.isEmpty()); //false
//containsKey:查找键是否存在
System.out.println(map.containsKey("鹿晗")); //true
//clear:清除
map.clear();
System.out.println(map); //{}
}
}
class Book{
private String name;
private int number;
public Book(String name, int number) {
this.name = name;
this.number = number;
}
}
Map接口的遍历方法
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超","孙俪");
map.put("宋喆","马蓉");
map.put("王宝强","马蓉");
map.put("蔡徐坤",null);
map.put(null,"刘亦菲");
map.put("鹿晗","关晓彤");
//先取出所有的 Key,通过 Key 取出对应的 Value
Set keySet = map.keySet();
//1.增强for
System.out.println("============第一种方式============");
for (Object key : keySet) {
System.out.println(key+"-"+map.get(key));
}
//2.迭代器
System.out.println("============第二种方式============");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key+"-"+map.get(key));
}
//把所有的values取出
System.out.println("============取出所有的value 增强for============");
Collection values = map.values();
//可以使用所有的Collections使用的遍历方法
//1.增强for
for (Object value : values) {
System.out.println(value);
}
//2.迭代器
System.out.println("============取出所有的value 迭代器============");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
//通过Entry来获取 k-v
System.out.println("============使用EntrySet 的 for增强============");
Set entrySet = map.entrySet(); //EntrySet<Entry<K,V>>
//1.增强for
for (Object entry : entrySet) {
//将entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
//2.迭代器
System.out.println("============使用EntrySet 的 迭代器============");
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
Object next = iterator2.next();
Map.Entry m = (Map.Entry) next;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}