※Java中的集合(数据结构)
--数组-线性表
--链表
--栈
--队列
--散列表
--二叉树
--映射关系(key-value)
List集合:
ArrayList:ArrayList是线程不安全的、物理地址上是连续的
--有序的、可重复的、线性结构的(线性表)
List的工作原理:底层是通过数组来实现的,当存储的数据到达一定的个数的时候,会扩充集合的容量
1、创建ArrayList是调用了无参构造器,默认初始化为空的对象数组
Object[] elementData={};
2、add添加数据的方法
ensureCapacityInternal(size + 1); 【确保容量是足够的】
--判断是否为空的数组、一开始的时候先初始化长度为10
--判断是否溢出,如果是则进行扩容,1.5倍的长度的扩容
通过数组工具类中的copyOf方法扩容
elementData[size++] = e;//赋值
return true;//返回true
1、创建ArrayList是调用了无参构造器,默认初始化为空的对象数组 Object[] elementData={}; 2、add添加数据的方法 ensureCapacityInternal(size + 1); 【确保容量是足够的】 --判断是否为空的数组、一开始的时候先初始化长度为10 --判断是否溢出,如果是则进行扩容,1.5倍的长度的扩容 通过数组工具类中的copyOf方法扩容 elementData[size++] = e;//赋值 return true;//返回true
//注意:所有的集合中存放的数据都是引用类型
--List集合的使用
/**
* @author Lantzrung
* @date 2022年7月25日
* @Description
*/
package com.day0725;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ListDemo1 {
private static Collection c;
public static void main(String[] args) {
// 数组:声明类型 [声明数组长度]
Object[] arr = new Object[5];
arr[0] = "abc";
arr[1] = 1;
// 注意:这里有警告是因为没有限定类型,<>是泛型的
// 注意:这里的1是包装类不是int而是Integer
// List【有序的、可重复】
// 注意:List在集合中存放的不是基本数据类型,而是Object引用类型
// 1、ArrayList数组默认存储数据是【10】【36】、
// Collection是最大的接口
// Collection/*父接口*/ c = new List();//因为List本身是一个接口,所以它们本身不能被实例化
// ctrl放到鼠标List
// 以下是List的部分源码
// public interface List<E> extends Collection<E> {
// // Query Operations
// /**
// * Returns the number of elements in this list. If this list contains
// * more than <tt>Integer.MAX_VALUE</tt> elements, returns
// * <tt>Integer.MAX_VALUE</tt>.
// *
// * @return the number of elements in this list
// */
// }
// Collection c = new ArrayList();
// 在List源码中 对着list按住ctrl键再点击Open Implementation可以查看实现类的接口 如常用的ArrayList<E>
// 定义一个容器可以存放多个数据,并且数据是可变的【集合】【功能方便、强大】
// List【有序的、可重复】 注意:在集合中存放的不是基本数据类型,而是Object引用
// 操作一:
List l = new ArrayList();
// 添加数据
l.add(1);// 注意:这里的1是包装类不是int而是Integer
l.add(2);//
l.add(3);
// 输出数据
System.out.println(l);// [1, 2, 3]
// 指定索引插入数据
l.add(1, "abc");
// 输出数据
System.out.println(l);// [1, abc, 2, 3]
// 操作二:
// 创建第二个对象
List l1 = new ArrayList<>();
// 添加数据
l1.add(1);// 不是int,而是Integer
l1.add(11);
l1.add(12);
l1.add(13);
// 将另一组的集合数据添加进来
l1.addAll(l);
System.out.println(l);// [1, abc, 2, 3]
System.out.println(l1);// [11, 12, 13, 1, abc, 2, 3]
// // 注意:要是反过来添加的话
// l.addAll(l1);
// System.out.println(l1);// [11, 12, 13]
// System.out.println(l);// [1, abc, 2, 3, 11, 12, 13]
// 操作三:
// 获取数据的索引位置
List l2 = new ArrayList<>();
// 添加数据
l2.add(1);
l2.add(2);
l2.add(3);
// 根据索引进行随机获取
System.out.println(l2.get(1));// 2
// 获取数据的索引 方式一: 获取的是该集合中最前面的索引位置
System.out.println(l2.indexOf(2));// 1
// 获取数据的索引 方式二:
System.out.println(l2.lastIndexOf(2));// 1 就是访问当前2的索引位置在哪里就是在1中
// 操作四:
// 添加索引值
l2.add(2);
System.out.println(l2.indexOf(2));// 1 获取的是该集合中最前面的索引位置
System.out.println(l2.lastIndexOf(2));// 3 就是访问当前集合中的最后一个引用出现的
//索引位置
// 替换数据 【根据索引位置进行替换】
l2.set(1, "B");
System.out.println(l2);// [1, B, 3, 2]
// 操作五:
List l3 = new ArrayList();
// 添加数据
l3.add("a");
l3.add("b");
l3.add("c");
l3.add("b");
// 根据索引进行随机获取
System.out.println(l3.get(1)); // b
// 获取数据的索引 方式一: 获取的是该集合中最前面的索引位置
System.out.println(l3.indexOf(2));// -1 要是找不到该数据的索引位置则为-1
System.out.println(l3.indexOf("b"));// 1
// 获取数据的索引 方式二:
System.out.println(l3.indexOf("b"));// 1
System.out.println(l3.lastIndexOf("b"));// 3
// 操作六:
// 删除数据 【根据对象删除、根据索引删除】
// 根据对象删除
l3.remove("a");
System.out.println(l3);// [b, c, b]
// 根据索引删除
// l3.remove(2);
// System.out.println(l3);//[a, b, b]
// 添加一个包装类的数据 然后删除该包装类
l3.add(1);
System.out.println(l3);// [b, c, b, 1]
l3.remove(new Integer(1));
System.out.println(l3);// [b, c, b]
// 删除该集合中的全部数据
l3.removeAll(l3);//根据集合的内容来进行删除
System.out.println(l3);//[]
// 操作七:
List l4 = new ArrayList();
// 添加数据
l4.add("a");
l4.add("b");
l4.add("c");
l4.add("b");
//替换数据
l4.set(1,"B");
System.out.println(l4);//[a, B, c, b]
// 截取集合数据[开始,结束]
List sublist = l4.subList(2, 4);
System.out.println(sublist);// [c, b]
// 以下后面会涉及到
// 替换所有值
// l4.replaceAll(operator);// lambda表达式
// 比较器进行排序
// list.sort(c);
// 使用stream形式来遍历list集合
// l4.stream.map();
// //--判断是否包含某个元素
// System.out.println(list.contains(2));
//
// //--判断集合是否为空
// System.out.println(list.isEmpty());
//
// //--转换为数组
// System.out.println(Arrays.toString(list.toArray()));
}
}
为什么不推荐在增强foreach循环时使用元素的方法删除或增加元素的方法?
在增强for循环中,集合遍历是通过Iterator进行的,但是元素的add和remove却是直接使用的集合类自己的方法。这就导致了Iterator在进行下一次遍历时(调用next()方法),会发现有一个元素在自己不知道的情况下被删除/添加了(Iterator认为可能是其他线程操作的),就会抛出一个异常,用来提示用户,可能发生了并发修改。
解决方法:
- 直接使用普通for循环进行操作
- 直接使用Iterator提供的方法进行操作
- 在删除要删除的第一个元素时立即结束循环
// 注意:关于Iterator的源码在那里呢?打开的教程如下qwq,和相应的继承链
// 在ArraysList中找以下源码
// public class ArrayList<E> extends AbstractList<E>
// implements List<E>, RandomAccess, Cloneable, java.io.Serializable
// 可以看到ArrayList<E>是继承于AbstractList<E>
// 实现接口是 List<E>
// 然后再打开List<E>的源码
// 查看以下源码可以看出List<E>继承的
// public interface List<E> extends Collection<E>
// 然后再打开Collection<E>的源码
// 可以看到Collection<E>是继承于Iterable<E>的
// public interface Collection<E> extends Iterable<E>
// 然后我们打开Iterable<E> 看到下面的源码就是最大的接口到这里已经无法打开了
// public interface Iterable<T>
// Collection<E>中的源码往下滑既可以发现我们要用的迭代器啦qwq
// Iterator<E> iterator();
-- List遍历代码
/**
* @author Lantzrung
* @date 2022年7月25日
* @Description
*/
package com.day0725;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class ListDemo2 {
public static void main(String[] args) {
List list = new ArrayList();
// 添加数据
list.add("a");
list.add("b");
list.add("c");
list.add("d");
// 1、for【while】
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 2、foreach
for (Object object : list) {
System.out.println(object);
}
// 3、使用迭代器进项迭代
Iterator it = list.iterator();// 迭代器
// 判断是否有下一个数据
while (it.hasNext()) {
// 获取下一个数据
System.out.println(it.next());
}
System.out.println("-----------------");
// 4、使用集合中的forEach方法 【labmda表达式】
list.forEach(object -> System.out.println(object));
System.out.println("----------------");
// 5、遍历,当=="c"时删除
// for (Object object : list) {
// String str = (String) object;
// if (str == "c") {
// list.remove(object);
// }
// }
// list.forEach(object -> System.out.println(object));
// // 6、在集合再加入一个c进行操作,可能会出现并发异常
// list.add("c");
// for (Object object : list) {
// String str = (String) object;
// if (str.equals("c")) {
// list.remove(object); //出现并发异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.day0725.ListDemo2.main(ListDemo2.java:76)
// }
// }
// System.out.println(list);
// 7、为了避免会出现并发异常可以使用迭代器进行删除
// 加入一个"c"元素
list.add("c");
System.out.println("删除前:"+list);//删除前:[a, b, c, d, c]
Iterator iterator = list.iterator();
// 判断是否有下一个数据
while (iterator.hasNext()) {
Object object = (Object) iterator.next();
//
if (object.equals("c")) {
list.remove(object);//而且list是不可改变的,还是会出现并发异常,所以要是使用迭代器来删除元素Exception in thread "main" java.util.ConcurrentModificationException
// iterator.remove(); //使用迭代器删除元素
}
}
System.out.println("删除后:"+list);//删除后:[a, b, d]
}
}
--ArrayList的继承链和实现接口
/**
* @author Lantzrung
* @date 2022年7月27日
* @Description
*/
package day0726;
public class Inheritance_Chain {
public static void main(String[] args) {
// 1、首先我们先打开ArrayList的源码 往上滑找到
// 这里可以看出ArrayList<E>是继承AbstractList<E>,然后实现于List<E>, RandomAccess, Cloneable, java.io.Serializable
// public class ArrayList<E> extends AbstractList<E>
// implements List<E>, RandomAccess, Cloneable, java.io.Serializable
// 2、然后我们再打开AbstractList<E> 发现
// AbstractList<E>是个抽象类,然后继承于AbstractCollection<E>,接口实现于 List<E>
// public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
// 3、然后再打开AbstractCollection<E>发现
// AbstractCollection<E>是个抽象类,接口实现于Collection<E>
// public abstract class AbstractCollection<E> implements Collection<E>
// 3.1、当然这里我们可以分支打开List<E> 发现
// List<E>接口继承于Collection<E>
// public interface List<E> extends Collection<E>
// 4、然后我们打开Collection<E> 发现Collection<E>接口继承于 Iterable<E>
// public interface Collection<E> extends Iterable<E>
// 5、最后我们打开Iterable<E> 发现它是最大的接口
// public interface Iterable<T>
}
}
--ArrayList的工作原理
public class Working_Principle {
public static void main(String[] args) {
// ArrayList()的工作原理:
// 1、先打开ArrayList的源码
// public ArrayList() {
// this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
// }
// 2、然后再打开elementData看到它是又一个Object[]的数组创建的引用变量
// transient Object[] elementData; // non-private to simplify nested class
// access
// 3、打开DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的数组
// 看到private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 4、然后我们在这里面ctrl+f查找add可以找到添加的方法
// 然后我们在里面看到有一个ensureCapacityInternal的方法打开
// public boolean add(E e) {
//9、首先这里先判断它长度够不够,要是不够就扩容 ,如果长度够了就进行计数
// ensureCapacityInternal(size + 1); // Increments modCount!!
// elementData[size++] = e;
// return true;
// }
// 5、创建时默认为空数组,当第一次添加数据时会初始化长度为10
// 这是是判断elementData是否为空 也是ArrayList默认初始化为10的方法过程
// private void ensureCapacityInternal(int minCapacity) {
// if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
// }
//
// ensureExplicitCapacity(minCapacity);// 默认初始化
// }
// 6、我们先打开
// private static final int DEFAULT_CAPACITY = 10;
// 如果为空那么DEFAULT_CAPACITY默认初始化为10 minCapacity最小值返回10
// 7、然后打开ensureExplicitCapacity,可以看到以下方法
// minCapacity的默认值为10 然后ensureExplicitCapacity是用来扩展的modCount++;
// 然后if判断10-0>0所以返回grow(minCapacity)
// private void ensureExplicitCapacity(int minCapacity) {
// modCount++;//10、这里的一个计数单位,它是用来存放有去做了多少个数据的保存
//
// // overflow-conscious code
// if (minCapacity - elementData.length > 0)
// grow(minCapacity);
// }
// 8、然后再打开grow源码查看
// 如果后面插入数据时,长度不够则会进行1.5倍的扩容【使用数组拷贝实现】
// private void grow(int minCapacity) {
// // overflow-conscious code
// int oldCapacity = elementData.length;
// int newCapacity = oldCapacity + (oldCapacity >> 1);
// 然后 1 + 0.5 这里的右移相当于除以2
// 新的长度等于旧的长度 = 1.5倍
// if (newCapacity - minCapacity < 0)
// newCapacity = minCapacity;
// if (newCapacity - MAX_ARRAY_SIZE > 0)
// newCapacity = hugeCapacity(minCapacity);
// // minCapacity is usually close to size, so this is a win:
// elementData = Arrays.copyOf(elementData, newCapacity); //这里是指1.5倍的
// 扩容
// 11、在ArrayList中查看size源码
// public int size() {
// return size;
// }
// private int size;//可以返回当前size的长度
}
}
// 总结: ArrayList本身就是一个数组 【默认初始化长度为10】 【每次扩展因子为1.5倍】 通过【Arrays.copyOf()的方法来进行扩容的】
--创建时默认为空数组,当第一次添加数据时会初始化长度为10
--如果后面插入数据时,长度不够则会进行1.5倍的扩容【使用数组拷贝实现】
// 1、 private void ensureCapacityInternal(int minCapacity) {
// if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
// }
// 来使得ArrayList【默认初始化长度为10】
// 2、int newCapacity = oldCapacity + (oldCapacity >> 1);是为扩展因子
// 3、Arrays.copyOf()是扩容方法