一、复习部分
1. 动态数组基于普通数最大的区别就在于具备动态扩容的能力(解决普通数组定长问题)
2. 在索引位置插入元素的基本操作的实现思路
A. 例子:
B. 思路:
a. 判断index的越界问题
b. 从索引为1之后的元素向后搬移一位。为了防止元素的覆盖,应该从前向后依次搬移
c. 然后将腾出来的位置赋值即可。
3. 删除索引位置节点元素的基本操作的实现思路
A. 例子:
B. 思路:从待删除的下一个位置开始从前向后依次向前覆盖一位。
注意:for循环的i的范围应该是[index,size-2]或者是[index,size-1],前者是相当于最后一位没有被size的位置覆盖,后者是被size的位置覆盖
for (int i = index; i <= size - 2; i++) { elementData[i] = elementData[i + 1]; }
二、自己实现的动态数组MyArray子类
1. 基本操作的重难点方法实现部分(书接上回)
(1)关于removeAllValue的实现思路
A. 一种带bug的实现方式
出错原因:
B. 基于上述的改进:当i不是待删除元素(当elementData[i] != val的时候)就可以 i++了。因此改成一个while循环。但是内部循环有可能导致i == size, 因此条件需要附加一个 i != size
注意1:为什么需要while循环
注意2:为什么要多添加一个条件
二、作业讲解
1. 例题1:
解析:
A. 基本数据类型不是包装类的简写形式,不能用包装类替代基本数据类型
B. long和double都是64bit的存储空间,这是正确的
C. 默认的整数数据类是int,但是默认的浮点数据类型是double
D. 基本数据类型声明的变量中没有静态方法
2. 例题2:27. 移除元素 - 力扣(Leetcode)
A. 思路
(1)双指针法:fir和sec初始都指向第一个元素。fir最终指向值不为val的元素区间的最后一个元素的后一个位置,而sec用来扫描集合。
(2)定义nums[0, fir - 1]是值都不为val的区间。sec依次从左到右循环遍历数组,当sec指向的值不为val,则添加到nums[fir],然后fir++;相等的时候,则直接sec++即可。
(3)由于fir最终指向值不为val的元素区间的最后一个元素的后一个位置,因此返回新数组的长度即为fir
B. 代码
C. 自己实现的代码
3. 例题3:26. 删除有序数组中的重复项 - 力扣(Leetcode)
A. 思路:
(1)由于空间复杂度要求O(1),因此不能采用映射的方法,只能原地删除
(2)如何知道哪个元素是重复的,双引用法。因为要判断元素是否是重复的就需要比较,此时就需要双引用
(3)移动逻辑:
fir和sec指针初始指向第一个元素。fir不动,sec 依次++,依次对比data[fir] 和data[sec]的值,相等则直接sec++;不相等则说明遇到不重复元素,fir++,然后使得data[fir] = data[sec]
怎么理解:[0, fir]区间内存储不重复元素。当data[fir] ==data[sec],说明sec还是指向的是重复的元素,因此直接sec++即可;当data[fir] !=data[sec]说明遇到不重复元素了,因此fir ++ 然后赋值即可
(4)当sec移动到数组末尾的时候,fir恰好指向不重复元素的最后一个元素。因此返回的有效元素个数 = fir + 1;
B. 代码
三、JDK中动态数组的实现java.util.ArrayList
1. 如何在IDEA中打开源码
(1)方法1:
代码中的类或者方法,直接使用ctrl + 鼠标左键即可进入该类/方法。如果是针对覆写的方法,则优先进入接口的方法方法定义,如果想要进入子类具体覆写的方法,则使用ctrl + alt + 鼠标左键,如果有多个子类覆写了相同的方法则可以选择查看哪个子类的覆写方法
(2)方法2: 使用IDEA的快速搜索功能:任意位置双击shift。
A. 方法:勾选右上角的Include non-project items.如果不勾选就只会找当前项目内出现的内容。如果勾选了则优先在当前项目中寻找内容,找不到则在依赖的库(JDK)中寻找
B. 注意1:如何显示类内部的结构
C. 找到实现某个接口的所有子类,点击左边绿色的箭头
2. ArrayList的定义,继承和实现的接口
3. List下关于动态数组的实现有两个子类:ArrayList和Vector都是基于动态数组的实现
(1)区别:
ArrayList是异步操作,线程不安全,效率高
Vector是同步操作(对整个数组进行加锁操作),线程安全,效率比较低
(2)实际应用场景中,Vector基本不使用,要使用线程安全的动态数组,则使用juc包下的CopyOnWriteArrayList(更加细化的读写锁机制来保证线程安全)
4. ArrayList的使用
(1)ArrayList的构造方法
A. 构造方法
B. 使用实例
(2)ArrayList覆写的常用基本操作方法
A. 三种插入操作
a. 方法定义:
b. 使用实例
B. 查询中的get和set方法
a. 方法定义
b. 使用实例
C. 查询中的contains方法和indexOf以及lastindexOf方法
a. 方法定义
b. 使用实例
C. 删除方法
a. 方法定义
b. 使用实例(String类型ArrayList为例)
c. 当ArrayList此时保存的是整形,则会造成歧义,编译器默认调用按索引查询的方法,若想使用按值删除的方法,则特殊处理(先通过IndexOf方法找到值为o的元素的下标,然后再通过按索引删除的方法删除即可实现按值删除),实例如下
D. clear();//清楚所有内容,实例如下
E. 产生子数组:subList
a. 方法定义(注意:这里的截取范围是左闭右开的,为[fromIndex, toIndex); 产生了新的子数组)
b. 使用实例
c. 注意(重中之重):l1和l2共用elementData数组
(3)ArrayList的遍历
遍历:按照一定的规则将集合中的所有元素全部访问一遍,做到不重复,不遗漏,这种操作称为集合的遍历
注:打印输出只是遍历的一种体现而已,遍历不止打印输出
A. 遍历方式1:普通for循环+get方法遍历
B. 遍历方式2:for_each循环遍历(实现Iterable接口的子类都可以使用for_each循环)
C. 遍历方式3:迭代器方式
D. 三种方式代码和测试如下:
E. 如何选择使用遍历方式(重中之重):若不牵扯修改集合的内容,用哪个都可以;若牵扯修改集合的内容,则只能使用迭代器方式
a. 报错例子
b. 原理:上述异常称为集合并发修改异常!集合的fail-fast机制:在并发运行的时候有可能多个用户操作同一个数组,因此在遍历一个集合的同时如果修改可能造成其他用户取得的数据是错误的。
c. 解决方法:遍历的时候尽量不修改,若非要修改则使用迭代器方式遍历修改。
注意(重中之重):补充的一个方法:size()方法