第十章 集合框架 ① 笔记

1. 内容回顾

1.1. 课前测试
1.2 上节内容
在这里插入图片描述

2. 本章重点

2.1 java集合框架的常用接口及其特征
2.2 List和ArrayList的使用
2.3 Set和HashSet的使用
2.4 Map和HashMap的使用
2.5 集合泛型的使用

3. 具体内容

3.1 集合框架

3.1.1 概念

Java集合框架(Java Collections Framework简称JCF)是为表示和操作集合,而规定的一种统一的标准的体系结构。集合框架包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
集合就是用于存储对象的容器。 只要是对象类型就可以存进集合框架中。集合的长度是可变的。 泛型集合中不可以存储基本数据类型的值。
在这里插入图片描述

3.1.2 集合和数组的区别

数组和集合相比,数组的缺点是它长度是固定的,没有办法动态扩展。
而集合存储数据时是没有长度限制的,是可以动态扩展的。集合容器因为内部的数据结构不同,有多种不同的容器对象。这些容器对象不断的向上抽取,就形成了集合框架。

3.2 Collection 接口

Collection接口是单值集合的顶层接口,它的继承关系如下。
在这里插入图片描述
在这里插入图片描述

public class Test1 {
	public static void main(String[] args) {
		// 定义Collection接口变量,指向集合实现类对象ArrayList
		// ArrayList->List->Collection
		Collection c1 = new ArrayList();
		// 向Collection集合中添加元素
		// 1.默认可以存储任何类型的数据
		// 2.Collection允许数据重复,并且可以存null值
		// 3.Collection 没有办法根据索引获取单个元素
		c1.add(100);
	}
}

3.3 List接口及其实现类

3.3.1 特点

 List集合是有序集合: 数据的添加和存储次序一致
 List集合可以存储重复的数据
 List集合中的数据可以通过下标访问

在这里插入图片描述
在这里插入图片描述

3.3.2 ArrayList实现类

特点:
 实现了List接口
 可以动态扩容(我们只管存,长度不够,底层会自动的扩容)
 通过下标可以快速访问数据
 查找快,插入删除慢
 ArrayList底层是数组,对数组做了封装
 可以存储任意类型的数据,包括null
 数据按照存储次序排列
 数据可以重复
 多线程访问时不安全
ArrayList的创建和使用

//List:1.数据有序 2.可以重复 3.有索引
public static void main(String[] args) {
	//创建list集合
	//1.数据有序 2.可以重复 3.有索引
	List list = new ArrayList();
	list.add("bbb");
	list.add("aaa");
	list.add("aaa");
	list.add(123);
	list.add(234.234);
	list.add(true);
	list.add(new Student("张三","男",20));
	//打印输出
	System.out.println(list);
	//输出长度
	System.out.println(list.size());
	//输出下标为2的元素
	System.out.println(list.get(2));
	//根据数值删除数据
	//list.remove("bbb");
	//System.out.println(list);
	//list.remove(234.234);
	//System.out.println(list);
	//传入整数时,默认按索引删除
	//list.remove(new Integer(123));
	//list.remove(Integer.valueOf(123));
	//System.out.println(list);
	//list.remove(new Student("张三","男",20));
	//System.out.println(list);
	//批量删除
//	 List list2 = new ArrayList();
//	 list2.add("bbb");
//	 list2.add("aaa");
//	list2.add(Integer.valueOf(123));
//	list.removeAll(list2);
//	System.out.println(list);
 	//是否包含相同元素
	//System.out.println(list.contains("xxx"));
	//集合遍历
	//方式1:索引遍历
	for (int i=0;i<list.size();i++){
	Object o = list.get(i);
	System.out.println(o);
	}
	System.out.println("======================");
	//方式2:for each
	for(Object o : list){
	System.out.println(o);
	}
}

删除自定义对象时,通过equals实现相同内容对象的删除

public class Student {
	private String name;
	private String sex;
	private int age;
	
	public Student() {
	}
	
	public Student(String name, String sex, int age) {
		this.name = name;
		this.sex = sex;
		this.age = age;
	}
	
	//重写equals方法,实现按内容判断相等性
	@Override //s1.equals(s2)
	public boolean equals(Object obj) {
	System.out.println("=======调用了 student equals========:"+obj);
		//1.判断地址是否相等
		if(this==obj){
			return true;
		}
		//2.判断目标类型和当前对象的类型是否一致
		if (!(obj instanceof Student)){
			//类型不一致,直接返回false
			return false;
		}
		//3.做类型转换,比较内容
		Student s = (Student) obj;
		if(this.name.equals(s.name) && this.sex.equals(s.sex) && this.age==s.age){
			return true;
		}
		return false;
	}
	public String getName() {
	return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student{" +
			"name='" + name + '\'' +
			", sex='" + sex + '\'' +
			", age=" + age +
			'}';
	}
}

遍历集合: for+索引遍历

// 遍历方式1: for + 索引
for(int i=0;i<list.size();i++){
	// list中默认装进去之后都变为Object类型的数据
	Object o = list.get(i);
	System.out.println(o);
}

遍历集合: for Each遍历

// 遍历方式2: for Each
for (Object o : list){
	System.out.println(o);
}
3.3.3 ArrayList底层源码

对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对
数组的操作。
1) ArrayList无参构造: ArrayList()

public ArrayList() {
	//elementData 是ArrayList 底层实际存储数据的对象数组
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

transient Object[] elementData; // non-private to simplify nested class acce
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

说明:new ArrayList的时候,本质就是创建了一个空的Object类型的数组

2) ArrayList有参构造: ArrayList(int initialCapacity)

// initialCapacity:初始容量
public ArrayList(int initialCapacity) {
	if (initialCapacity > 0) {
		// 初始容量大于0,则按初始容量创建数组
		this.elementData = new Object[initialCapacity];
	} else if (initialCapacity == 0) {
		// 初始容量是0,则赋值一个空数组
		this.elementData = EMPTY_ELEMENTDATA;
	} else {
		// 初始容量小于0,则报错
		throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
	}
}

3) 添加元素: add(E e) 扩容原理

public boolean add(E e) {
// 扩展数组容量
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将数据存储到数组中,并且元素个数加 1
elementData[size++] = e;
return true;
}
// 扩容具体实现1
private void ensureCapacityInternal(int minCapacity) {
// 判断当前ArrayList底层是否是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 如果要扩容的容量小于10,则直接按10个长度扩容
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 判断当前数组长度如果小于要扩容的长度,则执行grow
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 第一次最小容量是10
private void grow(int minCapacity) {
// overflow-conscious code
// 初始长度0
int oldCapacity = elementData.length;
// 新容量0
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
// 新容量变为10
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//Arrays.copyOf:进行数组扩容,扩容到指定长度
elementData = Arrays.copyOf(elementData, newCapacity);
}
小结: ArrayList添加元素的时候,会进行数组扩容。每次扩容1.5倍
第一次扩容到10,第二次扩容到15

4) 删除元素: remove(Object o)

public boolean remove(Object o) {
//判断要删除的数据是否空
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 数据不为空的删除
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
//通过系统复制的方式,删除元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}

5) 清空集合: clear()

// 将数组每个元素设置空,长度为0
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}

6) 查找集合中某个元素的位置: indexOf(Object o)

// 查找某个元素的位置
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

7) 返回集合元素长度:size()

public int size() {
// 记录了元素的个数
return size;
}
3.3.4 LinkedList和Vector

LinkedList: 具有List的特征,底层以链表结构实现,可以进行头尾元素的添加删除
Vetor: 与ArrayList功能一致,在多线程环境下,比ArrayList安全。性能比较低。

public class Test {
public static void main(String[] args) {
// LinkedList : 具有List特征的链表集合,底层实现是链表
LinkedList ll = new LinkedList();
ll.add("ddd");
ll.add("ccc");
ll.add("aaa");
ll.add("bbb");
System.out.println(ll);
System.out.println(ll.size());
System.out.println(ll.get(0));
// 除了具有List的基本特征,还具有链表特征
ll.addFirst("xxx"); // 在头部添加数据
ll.addLast("1000"); // 在尾部添加数据
System.out.println(ll);
//ll.removeFirst();
//ll.removeLast();
Vector v = new Vector();
v.add("aaa");
v.add("ccc");
v.add("vvv");
System.out.println(v);
}
}

3.4 Set接口及其实现类

在这里插入图片描述

3.4.1 Set接口特点

 Set接口是无序的(HashSet无序。TreeSet有逻辑次序。LinkeHashSet遵循添加次序)
 Set接口中的数据不允许重复
 Set接口无法通过下标访问数据
 查找慢,插入删除快
(底层数据结构是:jdk1.8之前:哈希表、链表。jdk1.8之后:哈希表、链表和红黑树)
 Set集合使用equals()和hashCode()方法实现元素去重

3.4.2 HashSet实现类

HashSet特点:
 HashSet是Set接口的实现类
 线程不安全

public class Test2 {
public static void main(String[] args) {
// hash : 哈希,散列
// tree : 树,有次序(数据的逻辑次序)
Student s1 = new Student("张三","男",18);
Student s2 = new Student("李四","女",18);
Student s3 = new Student("王五","男",16);
// 1. 是否有序:无序(没有按数据的添加次序呈现) 2. 是否允许重复:不允许重复
Set set = new HashSet();
set.add("aaa");
set.add("aaa");
set.add(100);
set.add(2.34);
set.add(s1);
set.add(true);
set.add(s2);
set.add(100);
//System.out.println(set);
// 打印长度
System.out.println(set.size());
// 删除数据
set.remove(100);
System.out.println(set);
// 遍历
// forEach
for(Object o : set){
System.out.println(o);
}
// 迭代器
System.out.println("===============");
Iterator it = set.iterator(); // 通过集合对象的iterator方法得到迭代器对
while (it.hasNext()){ // 测试集合是否有元素
System.out.println(it.next()); //获取元素并输出
}
}
}

HashSet避免对象重复的规则:
1)如果对象的hashCode值不同,则不用判断equals方法,就直接存到HashSet中。
2)如果对象的hashCode值相同,需要用equals方法进行比较,如果结果为true,则视为相同元素,不存储。如果结果为false,视为不同元素,进行存储。
在这里插入图片描述
注意:如果对象元素要存储到HashSet中,必须覆盖hashCode方法和equals方法。才能保证从对象中的内容的角度保证唯一。

public class Student {
private String name;
private String sex;
private int age;
public Student() {
}
public Student(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写系统的hashCode方法
@Override
public int hashCode() {
System.out.println("调用了我们自己定义的hashcode方法");
//我们自己定义,将对象三个属性都用上,返回一个这个数值,作为底层索引
return this.name.hashCode()+this.sex.hashCode()+age;
}
@Override
public boolean equals(Object obj) {
System.out.println("=======调用了equals方法=======");
if(this==obj){
return true;
}
if(!(obj instanceof Student)){
return false;
}
Student s = (Student) obj;
if(this.name.equals(s.name)&&this.sex.equals(s.sex)&&this.age==s.age
return true;
}
return false;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
public class Test4 {
public static void main(String[] args) {
Student s1 = new Student("张三","男",20);
Student s2 = new Student("张三","男",20);
Student s3 = new Student("李四","男",20);
// Object中的HashCode默认返回的是对象的内存地址
//System.out.println(s1.hashCode());
//System.out.println(s2.hashCode());
Set s = new HashSet();
s.add(s1);
s.add(s2);
s.add(s3);
System.out.println(s);
// hash运算(默认调用数据的hashCode()方法实现)结果是相同的前提下,才会进行相
// hash运算结果不同,是无法去重的
/*String s = "sdf";
String ss = "sdf";
System.out.println(s.hashCode());
System.out.println(ss.hashCode());
*/
}
}
3.4.3 迭代器Iterator

迭代器(iterator)有时又称游标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表或数组)上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。
HashSet类中没有提供根据集合索引获取索引对应的值的⽅法,
因此遍历HashSet时需要使⽤Iterator迭代器。Iterator的主要⽅法如下
在这里插入图片描述

public static void main(String[] args) {
// LinkedHashSet:链表结构的Set集合
// 1.不重复 2.能保证添加的次序(添加和输出次序一致)
Set set = new LinkedHashSet();
set.add("c");
set.add("a");
set.add("d");
set.add("b");
//System.out.println(set);
// 迭代器遍历集合
Iterator it = set.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
3.4.4 TreeSet

TreeSet 基于TreeMap 实现。TreeSet可以实现有序集合,但是有序性需要通过比较器实
现。
TreeSet特点:
 有序
 不重复
 添加、删除、判断元素存在性效率比较高
 线程不安全
TreeSet对元素进行排序的方式:

  1. 如果是基本数据类型和String类型,无需其它操作,可以直接进行排序。
  2. 对象类型元素排序,需要实现Comparable接口,并覆盖其compareTo方法。
  3. 自己定义实现了Comparator接口的排序类,并将其传给TreeSet,实现自定义的排序
    规则。
    实例1:对象类实现Comparable接口进行排序
// 让学生实现Comparable,变为可比较的对象
public class Student implements Comparable {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
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 "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Object o) {
System.out.println("===调用Student 的 compareTo===");
// 判断要比较的目标类型和当前类型是否一致
if(!(o instanceof Student)){
return -1;
}
// 目标数据属于当前类型
Student s = (Student) o;
if(this.age>s.age){
return 1;
}else if(this.age<s.age){
return -1;
}else{
return this.name.compareTo(s.name);
}
}
}
public class Test {
public static void main(String[] args) {
//TreeSet: 1. 不重复 2. 可以保证数据的逻辑次序(我们要自己提供排序规则)
/*
String s = "abc";
String s2 = "bd";
// 大于0 小于0 等于0
System.out.println(s.compareTo(s2));
Set set = new TreeSet();
set.add("b");
set.add("c");
set.add("a");
set.add("d");
System.out.println(set);
System.out.println("=================");
*/
//Comparable c = new Student("xxx",18);
Set set2 = new TreeSet();
set2.add(new Student("张三",20));
set2.add(new Student("李四",17));
set2.add(new Student("王五",22));
set2.add(new Student("赵六",18));
//System.out.println(set2);
for(Object o : set2){
System.out.println(o);
}
// Class(类)Cast(转换)Exception(异常): 类型转换异常
}
}

实例2:自定义排序类实现Comparator接口进行排序

public class Car {
private String brand;
private int speed;
public Car(String brand, int speed) {
this.brand = brand;
this.speed = speed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", speed=" + speed +
'}';
}
}
public class MyComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
System.out.println("=====调用了compare方法=============");
// 先判断两个要比较的数据是否是相同类型的
if((o1 instanceof Car) && (o2 instanceof Car)){
Car c1 = (Car)o1;
Car c2 = (Car)o2;
if(c1.getSpeed()> c2.getSpeed()){
// c1速度大于c2 返回1
return 1;
}else if(c1.getSpeed()<c2.getSpeed()){
// c1速度小于c2 返回-1
return -1;
}else{
// 如果速度相等,则安排汽车的名字比较(按字符编码比较)
return c1.getBrand().compareTo(c2.getBrand());
}
}else{
return -1;
}
}
}
public class Test {
public static void main(String[] args) {
// TreeSet : 1.保证数据逻辑有序 2.不重复
// 实现有序的两种方式
// a. 自定义类型实现Comparable接口,实现里面的compareTo方法
// b. 自定义一个比较器对象实现Comparator接口,实现里面的commpare方法
/*Set set = new TreeSet();
set.add("a");
set.add("c");
set.add("d");
set.add("b");
set.add("a");
System.out.println(set);*/
// 在TreeSet构造中,传入自己定义的比较器
Set set = new TreeSet(new MyComparator());
set.add(new Car("宝马",200));
set.add(new Car("奔驰",180));
set.add(new Car("法拉利",210));
for(Object o : set){
System.out.println(o);
}
}
}
3.4.5 LinkedHashSet

LinkedHashSet是一种有序的Set集合,其元素的存入和取出顺序是相同的。

public class Test {
public static void main(String[] args) {
// LinkedHashSet:链表结构的Set集合
// 1.不重复 2.能保证添加的次序(添加和输出次序一致)
Set set = new LinkedHashSet();
set.add("c");
set.add("a");
set.add("d");
set.add("b");
System.out.println(set);
}
}

3.5 Map接口及其实现类

Map接口特点:
 以键值对方式存储数据(Collection是单值集合)
 键不能重复,键重复时,后面的数据会覆盖前面的数据
 HashMap的鍵和值可以存储null。Hashtable不能存儲null值。
 键值对数据无序
在这里插入图片描述
在这里插入图片描述

3.5.1 HashMap实现类

HashMap实现了Map接口,拥有Map接口的基本特点。HashMap线程不安全,效率高。
HashMap的底层是由哈希表、链表加红黑树构成的。

public class Test {
public static void main(String[] args) {
//Map: 1.键不允许重复 2.无序
Map map = new HashMap();
map.put("aaa",100);
map.put("aaa",400); // 键相同时,后面覆盖前面的
map.put("ccc",100);
map.put("ccc",200);
map.put("bbb",300);
map.put("张三",new Student("张三",20));
map.put(333,2.234);
map.put(null,2322);
map.put("xxx",null);
System.out.println(map);
/*System.out.println(map.size()); //长度
System.out.println(map.get("张三")); //根据键获取值
System.out.println(map.containsKey(333)); //是否包含333这个键
System.out.println(map.containsValue(2322)); //是否包含某个值*/
// 获取Map中所有的键,得到一个由键组成的Set集合
/*
Set set = map.keySet();
System.out.println(set);
// 遍历键的集合
for(Object key : set){
// 输出所有键
// map.get(key):获取对应键的值
System.out.println(key+" "+map.get(key));
}*/
// 遍历map
for(Object obj : map.keySet()){
System.out.println(obj+" "+map.get(obj));
}
}
}
public static void main(String[] args) {
//遍历Map的两种方式
Map map = new HashMap();
//存储数据
map.put("aaa",100);
map.put("bbb",200);
map.put("aaa",300);//键重复时,后面的数据覆盖前面的
map.put(20,"xxxx");
map.put("ccc",200);
map.put("张三",new People("张三",20));
System.out.println(map);
//方式一:遍历键的集合
Set keys = map.keySet();
for (Object k : keys){
System.out.println(k+" ---> "+map.get(k));
}
System.out.println("==================");
//方式二:遍历键值对集合
Set kvSet = map.entrySet();
//遍历键值对对象
for (Object kv : kvSet){
//Map.Entry:Map接口的子接口,表示一个荐椎对对象
Map.Entry e = (Map.Entry)kv;
System.out.println(e.getKey()+" ==== "+e.getValue());
}
}
3.5.2 HashMap底层原理

HashMap的put()和get()的实现

1) map.put(key,value)实现原理

第一步:首先将k,v封装到Node对象当中(节点)。
第二步:它的底层会调用K的hashCode()方法得出hash值。
第三步:通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equals。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
在这里插入图片描述
在这里插入图片描述

2) map.get(key) 实现原理

第一步:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

3)map的扩容原理

扩容:
a.当数组元素个数大于阈值(长度*0.75)时,数组扩容
b.当某个节点的链表超过8,并且数组长度小于64,此时不将链表转为树,而是进行扩容
c.当单个节点链表长度大于8,并且数组长度大于64,此时将链表转为红黑树
HashMap底層原理参考:
http://www.hollischuang.com/archives/2091
https://blog.csdn.net/lkforce/article/details/89521318

3.5.3 Hashtable

Hashtable也实现了Map接口,但是与HashMap相比有如下特点:
 以键值对方式存储数据
 键不能重复
 数据无序
键和值都不能存储null
 线程安全,效率低

Hashtable hashtable =new Hashtable();
hashtable.put("aaa",100);
//hashtable.put("aaa",null);
hashtable.put(null,100);
hashtable.put("xxx",100);
System.out.println(hashtable);

3.6 泛型集合

泛型:对要管理的数据进行类型限定,方便数据的处理。
为什么实用泛型:
在往集合中存储数据的时候缺乏类型检查,不安全
泛型:类型检查机制;
好处:省略了装箱和拆箱,效率高;类型安全。
语法:

List<T>:E类型约束
Set<T> : E类型约束
Map<K,V>:K,V类型约束

实例:

public class Test2 {
public static void main(String[] args) {
// 泛型: 限定数据类型
// 不使用泛型的时候,存储的时候很方便,但是使用的时候很麻烦
// 使用泛型之后,那么集合只能存储特定类型的数据,别的类型是无法存储的
// 泛型类型必须是对象类型
List<Student> list = new ArrayList<Student>();
//list.add(100); //限定泛型之后,其他类型不能存储
//list.add("abc");
list.add(new Student(1001,"张三",20));
list.add(new Student(1002,"李四",18));
list.add(new Student(1003,"王五",19));
Student stu = list.get(0); // 获取数据的时候,是明确的类型
System.out.println(stu);
// 遍历的时候,直接使用特定类型遍历
for (Student s : list){
System.out.println(s.getNum()+" "+s.getName());
}
/*
List list = new ArrayList();
list.add(100);
list.add("abc");
list.add(new Student(1001,"张三",20));
for (Object o: list){
Student student = (Student)o;
System.out.println(o);
}*/
}
}

public class Test3 {
public static void main(String[] args) {
// Set泛型,只存储字符串数据
Set<String> set = new HashSet<String>();
//set.add(100); // 报错
set.add("100");
set.add("aaa");
set.add("ccc");
set.add("bbb");
for(String s : set){
System.out.println(s);
}
System.out.println("================");
Student s1 = new Student(1001,"张三",20);
Student s2 = new Student(1002,"李四",18);
Student s3 = new Student(1003,"王五",22);
Student s4 = new Student(1004,"赵六",12);
Student s5 = new Student(1005,"孙琪",19);
Map<Integer,Student> map =new HashMap<>();
map.put(s1.getNum(),s1);
map.put(s2.getNum(),s2);
map.put(s3.getNum(),s3);
map.put(s4.getNum(),s4);
map.put(s5.getNum(),s5);
for(Integer k : map.keySet()){
System.out.println(k+" "+map.get(k));
}
}
}

3.7 自定义链表

链表是最简单的动态数据结构,数据存储在节点(Node)中,其节点的数据结构如下:

class Node{
Object obj;//数据存储的地方
Node next;//也是一个节点,他指向当前节点的下一个节点
}

我们可以把链表理解成为一个火车,每个链表,其实就是一节车厢,数据存储在车厢中中,而每个火车节都有一个指针,连接着下一个火车节。
链表有一个优点:
真正的动态数据结构,无需关心创建的空间是否过大,不需要像数据一样担心容量的问题。
缺点:
不能像数组那样,给一个索引就能查找到指定的值。

1.单向链表

单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next
指向null。
在这里插入图片描述

2.单向循环链表

单向循环链表和单向列表的不同是,最后一个节点的next不是指向null,而是指向head节点,形成一个“环”。
在这里插入图片描述

3.双向链表

从名字就可以看出,双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。
在这里插入图片描述

4.双向循环链表

双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。而LinkedList就是基于双向循环链表设计的。
在这里插入图片描述

// 节点类型
//public class Node<T> {
// private T data;
// private Node<T> next;
//}
// 链表中的节点类
public class Node {
//字符串数据
private String data;
// 下个节点的变量
private Node next;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
public class MyLink {
// 链表中的头部节点
private Node head;
// 节点个数
private int size;
public MyLink() {
this.head = new Node();
}
// 添加方法
public void add(String data){
// 1.创建一个节点
Node newNode = new Node();
// 2.设置节点
newNode.setData(data);
// 3.找到链表中的最后一个节点
Node n = head;
while (n.getNext()!=null){ //判断当前节点是否有下个节点
// 如果有下个节点,让n指向到下个节点
n = n.getNext();
}
// 4.让最后一个节点指向新节点
// while循环结束时,就找到了最后一个节点
n.setNext(newNode);
// 长度加1
size++;
}
// 遍历
public void display(){
// 从头节点的下个节点开始,如果节点不为空,则输出节点数据
Node node = head.getNext();
while (node!=null){
// 当前节点不为空,输出数据
System.out.println(node.getData());
node = node.getNext();
}
}
// 删除
//修改
public Node getHead() {
return head;
}
public void setHead(Node head) {
this.head = head;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
}
public class Test {
public static void main(String[] args) {
//ArrayList a = new ArrayList();
// 创建自定义链表对象
MyLink ml = new MyLink();
ml.add("aaa");
ml.add("ccc");
ml.add("dddd");
ml.add("eee");
ml.display();
System.out.println(ml.getSize());
}
}

4. 本章总结

4.1 java集合框架的常用接口及其特征
4.2 List和ArrayList的使用
4.3 Set和HashSet的使用
4.4 Map和HashMap的使用
4.5 泛型的使用

4.6 集合使用总结:

  1. 需要数据唯一时,使用Set集合
     需要保持规则顺序,此时用TreeSet
     不需要指定顺序,此时用HashSet
     需要保证代码添加数据的次序,此时用LinkedHashSet
  2. 不需要唯一时,使用List集合
     需要频繁增删操作时,使用LinkedList
     不需要频繁增删操作,需要做大量查询操作时,使用ArrayList
  3. 如果以键值对方式存储数据时,使用HashMap
  4. 通过类名记住集合框架类的结构和所属体系
    List: ArrayList LinkedList
    Set : HashSet TreeSet
    后缀名就是该集合所属的体系。 前缀名就是该集合所属的数据结构。
    看到array:想到的就是数组,查询速度快。因为有角标是连续的内存地址。
    看到link:想到的就是链表,增删动作的速度快,而且要想到
    add get remove+first/last的 方法
    看到hash:想到的就是哈希表,那么就具有唯一性,
    而且要想到元素需要覆盖hashcode方法 和equals方法
    看到tree:想到的就是二叉树,接下来想到的就是排序,然后就是两个接口
    Comparable接口 实现compareTo(To)
    Comparator接口 实现compare() 方法

5. 课后作业

  1. 构建一个学生集合,定义方法实现添加、修改、删除、查找、遍历等功能
  2. 将Person对象放入ArrayList中,必须有重复的对象,转换到HashSet中(不能有重复的对象),之后将集合中的Person对象导入到HashMap,key使用Person的名字,并遍历并展示出来。
  3. List和Map数据转存

在这里插入图片描述
4) 自己实现链表结构,完成增删改查
5) for循环, for each, iterator 迭代器 有什么区别

// An highlighted block
var foo = 'bar';
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值