赞
踩
目录
2.7修改索引为index位置的元素为新值,返回修改前的元素值
2.8删除线性表中索引为index的元素,返回删除前的元素值
满足以下两个要求的数据结构称为线性表:
(1)保存在线性表中的元素都是相同类型的。
(2)元素之间线性排列,元素和元素之间逻辑上是连续的,索引为0的元素一定是逻辑上在索引为1的元素之前。
线性表有一个最大的特点,就是有明确的索引下标的概念。
线性表示一个比较抽象的感念,具体实现有很多,数组,链表,栈,队列,字符串都是具体的线性表。Java接口优先原则,无论具体是那种线性表,操作盒方法应该对外都是统一的,接口定义了所有实现子类所共同应当具备的行为和规范。
JDK中线性表的接口称为List,无论是数组实现还是链表实现,操作线性表的方法都已经在List接口中定义好了。
基本数组特点:保存的单个相同类型的元素,一旦声明一个原始数组,无论采用哪种实例化方式,最终数组一旦定义,长度固定。
由于原始数组存在定长问题,因此,需要将原始数组做扩充,将原始数组封装到自定义的类中,让他具备可扩展干的能力=>对使用者来说,用户无需关心数组的长度问题,提供给用户的数组是一个动态数组,用户只需要使用提供的数组进行增删改查即可,无需关心数组的越界问题。
本质:将原始的数组封装到类中,对用户淡化数组长度的概念,当数组长度不够时,类的内部自己进行扩容操作,对用户透明。
动态数组 = 基本数组封装到类中 + 对外提供一系列的方便进行增删改查的方法。
无论是数组还是链表都属于线性表的一种,所以只要是线性表的子类,都应该具备基础的增删改查方法。
定义线性表的规范:一个类能称之为是线性表的子类,应该具备类似的行为(都是保存单个元素的集合,集合中元素的类型是相同的,元素之间逻辑上连续)
定义线性表接口Seqlist。定义接口带来的好处:就是可以以非常低的成本来更换具体的子类。
- public interface SeqList {
- void add(int val);
- // 在索引为index的位置插入新元素
- void add(int index,int val);
- // 查询线性表中是否包含指定元素val
- boolean contains(int val);
- // 返回索引为index的元素值
- int get(int index);
- // 修改索引为index位置的元素为新值,返回修改前的元素值
- int set(int index,int newVal);
- // 删除线性表中索引为index的元素,返回删除前的元素值
- int removeByIndex(int index);
- // 删除第一个值为val的元素
- void removeByValueOnce(int val);
- // 在线性表中删除所有值为val的元素
- void removeAllValue(int val);
- }
首先需要设置一个内部数组用来存储输入的数组,然后设置一个size用来记录add方法中插入的位置。再设置一个默认变量,在无参构造中使用。
创建两个构造方法,如果没有输入明确的数组长度,默认产生对象时,初始化长度为10。另一个构造方法则输入了数组长度,创建一个相应长度的数组。
- public class MyArray implements SeqList {
- private int[] elementData;
- private int size;
- private static final int DEFAULT_CAPACITY = 10;
- public MyArray() {
- this(DEFAULT_CAPACITY);
- }
- public MyArray(int capacity){
- this.elementData = new int[capacity];
- }
- }
首先需要排除非法情况,就是数组长度为0时,需要抛出异常。
size的位置恰好就是当前数组中尾部插入元素的位置,完全插入后size就可以记录当前线性表中的实际元素个数。
如果整个数组都被填满了,需要让数组在下一次插入前进行扩容,扩容后的就是根据原数组的内容拷贝过来,长度变为原来的1.5倍,超过原数组长度的内容。改变了原数组的引用,指向了扩容后的数组。
在扩容这里有一个隐藏风险,当原先数组长度为奇数个的时候,扩容失败,如果oldLength == 1,1 + 1 / 2 = 1 + 0 = 1,扩容没有成功,所以需要将newLength赋值为oldLength+((oldLength+1)>>1)。
- public void add(int val) {
- if (elementData.length == 0) {
- throw new IllegalArgumentException("arr length cannot be zero!");
- }
- this.elementData[size] = val;
- size ++;
- if (size == elementData.length) {
- grow();
- }
- }
- private void grow(){
- int oldLength = this.elementData.length;
- int newLength = oldLength+((oldLength+1)>>1);
- this.elementData = Arrays.copyOf(elementData,newLength);
- }
将数组输出成字符串形式,需要借用StringBuilder中的append方法,将每个数组中的元素按顺序添加,然后输出字符串形式。
- public String toString(){
- StringBuilder s = new StringBuilder("[");
- for (int i = 0; i < size; i++) {
- s.append(this.elementData[i]);
- if(i<size-1){
- s.append(",");
- }
- }
- s.append("]");
- return s.toString();
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- }
首先需要判断index的合法性,小于index和大于size都不合法。
如果index==size就是尾插法,可以直接调用add方法。
排除过后就可以将指定元素插入到索引位置,然后将本来在该位置的元素向后移一位,后面的每一个元素都向后移动一位,最后需要判断是否需要扩容为了方便下一步的add。
- public void add(int index, int val) {
- if (index < 0 || index > size) {
- throw new IllegalArgumentException("add index illegal!");
- }
- if (index == size) {
- add(val);
- return;
- }
- if(index==this.elementData.length){
- grow();
- }
- size++;
- for(int i = index;i<size;i++){
- int tmp = this.elementData[i];
- this.elementData[i] = val;
- val = tmp;
- }
-
- if (size == elementData.length) {
- grow();
- }
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- list.add(3,7);
- System.out.println(list.toString());
- list.add(0,7);
- System.out.println(list.toString());
- list.add(6,7);
- System.out.println(list.toString());
- }
查找整个数组,当遇到指定元素则返回true,如果不包含指定元素则返回false。
- public boolean contains(int val) {
- for(int i = 0;i<size;i++){
- if(this.elementData[i]==val){
- return true;
- }
- }
- return false;
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- System.out.println(list.contains(9));
- System.out.println(list.contains(8));
- }
首先需要判断index是否合法,创建了rangeCheck方法,排除了index < 0和index >= size的情况。
然后直接返回当前位置的元素。
- public int get(int index) {
- if (!rangeCheck(index)) {
- throw new IllegalArgumentException("get index illegal!");
- }
- return elementData[index];
- }
- private boolean rangeCheck(int index) {
- if (index < 0 || index >= size) {
- return false;
- }
- return true;
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- System.out.println(list.get(0));
- }
同样首先判断index是否合法。
然后将索引位置的元素改成新的值,返回旧的值。
- public int set(int index, int newVal) {
- if (!rangeCheck(index)) {
- throw new IllegalArgumentException("get index illegal!");
- }
- int temp = this.elementData[index];
- this.elementData[index] = newVal;
- return temp;
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- System.out.println(list.set(3,7));
- System.out.println(list.toString());
- }
首先先判断index是否合法。
然后将index位置后的每一个元素都向前移动一位,然后把size的数值减一,返回原本在索引位置上的值。
- public int removeByIndex(int index) {
- if (!rangeCheck(index)) {
- throw new IllegalArgumentException("get index illegal!");
- }
- int tmp = this.elementData[index];
- for(int i = index;i<size-1;i++){
- this.elementData[i] = this.elementData[i+1];
- }
- size--;
- return tmp;
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- System.out.println(list.removeByIndex(3));
- System.out.println(list.toString());
- }
直接遍历整个数组,当第一次遇到值为val的元素就调用removeByIndex方法,然后返回。
- public void removeByValueOnce(int val) {
- for(int i = 0;i<size;i++){
- if(this.elementData[i]== val){
- removeByIndex(i);
- return ;
- }
- }
- System.err.println("当前集合中不存在元素" + val);
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(9);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- list.removeByValueOnce(9);
- System.out.println(list.toString());
- list.removeByValueOnce(2);
- System.out.println(list.toString());
- }
直接遍历整个数组,当第一次遇到值为val的元素就调用removeByIndex方法,然后不返回,继续遍历。
这个时候有一个特殊情况,调用removeByIndex时可能补上来的元素值也是val,所以判断数组当前位置值是否为val需要用while循环。
- public void removeAllValue(int val) {
- boolean isRemoved = false;
- for(int i = 0;i<size;i++){
- while(this.elementData[i]== val){
- isRemoved = true;
- removeByIndex(i);
- }
- }
- if (!isRemoved) {
- System.err.println("当前集合中不存在元素" + val);
- }
- }
- public static void main(String[] args) {
- SeqList list = new MyArray(4);
- list.add(1);
- list.add(9);
- list.add(5);
- list.add(9);
- System.out.println(list.toString());
- list.removeAllValue(9);
- System.out.println(list.toString());
- list.removeAllValue(2);
- System.out.println(list.toString());
- }
RandomAccess:实现了整个接口就具备了随机访问的能力,给一个索引下标可以立即O(1)取得该位置的元素。
Cloneable:可复制的能力。
List:线性表接口。
Seriallizable:序列化接口,ArrayList这个类就可以方便的转为字节数组进行输入输出。
List下关于动态数组的实现有两个子类,ArrayList和Vector都是基于动态数组的实现,ArrayList式异步操作,线程不安全,效率高。Vector是同步操作(对整个数组进行加锁操作)线程安全,效率比较低。
ArrayList() 构造一个初始容量为 10 的空列表。 |
ArrayList(Collection<? extends E> c) 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。 |
ArrayList(int initialCapacity) 构造一个具有指定初始容量的空列表。 |
前两种构造方法都没有传入数值,l3传入了数值,但是也产生了新的对象,改变l3,link不会发生改变。
- public static void main(String[] args) {
- List<Integer> l1 = new ArrayList<>();
- List<Integer> l2 = new ArrayList<>(5);
- List<Integer> link = new LinkedList<>();
- link.add(1);
- link.add(3);
- link.add(5);
- List<Integer> l3 = new ArrayList<>(link);
- System.out.println(l1);
- System.out.println(l2);
- System.out.println(l3);
- }
(1)添加
boolean | add(E e) 将指定的元素添加到此列表的尾部。 |
void | add(int index, E element) 将指定的元素插入此列表中的指定位置。 |
boolean | addAll(Collection<? extends E> c) 按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。 |
在第三个方法中将link的所有元素添加到l1的尾部,但是对l1进行操作,并不会影响到list。
- public static void main(String[] args) {
- List<Integer> l1 = new ArrayList<>();
- // 尾插
- l1.add(1);
- l1.add(3);
- l1.add(5);
- l1.add(1,10);
- List<Integer> link = new LinkedList<>();
- link.add(2);
- link.add(4);
- link.add(6);
- l1.addAll(link);
- System.out.println(l1);
- }
(2)删除
E | remove(int index) 移除此列表中指定位置上的元素。 |
boolean | remove(Object o) 移除此列表中首次出现的指定元素(如果存在)。 |
void | clear() 移除此列表中的所有元素。 |
当调用List集合保存的就是整型,调用remove方法默认会调用按照索引删除。
如果想用移除此列表中首次出现的指定元素,需要先获取值对应的索引,然后调用remove。
而clear清空则调用了就把集合中所有元素都删除。
- public static void main(String[] args) {
- List<Integer> l1 = new ArrayList<>();
- l1.add(1);
- l1.add(3);
- l1.add(5);
- l1.add(7);
- System.out.println(l1.remove(1));
- System.out.println(l1);
- System.out.println(l1.remove(l1.indexOf(7)));
- System.out.println(l1);
- l1.clear();
- System.out.println(l1);
- }
(3)截取
List<E> | subList(int fromindex, int toIndex) 截取部分list。 |
截取的区间是左闭右开的,并且截取是产生新的集合,并不影响被截取的原集合。
- public static void main(String[] args) {
- List<Integer> l1 = new ArrayList<>();
- l1.add(1);
- l1.add(3);
- l1.add(5);
- l1.add(7);
- System.out.println(l1);
- List<Integer> ret = l1.subList(1,3);
- System.out.println(ret);
- System.out.println(l1);
- }
(4)指定元素有关的方法。
boolean | contains(Object o) 如果此列表中包含指定的元素,则返回 true。 |
E | get(int index) 返回此列表中指定位置上的元素。 |
int | indexOf(Object o) 返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。 |
int | lastIndexOf(Object o) 返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。 |
E | set(int index, E element) 用指定的元素替代此列表中指定位置上的元素。 |
- public static void main(String[] args) {
- List<Integer> l1 = new ArrayList<>();
- l1.add(1);
- l1.add(3);
- l1.add(5);
- l1.add(7);
- l1.add(7);
- System.out.println(l1.contains(7));
- System.out.println(l1.get(2));
- System.out.println(l1.indexOf(3));
- System.out.println(l1.lastIndexOf(7));
- System.out.println(l1.set(2,9));
- System.out.println(l1);
- }
什么是遍历:就是按照一定的规则将集合中的所有元素全部访问一遍,做到不重复,不遗漏。
(1)普通for循环+get方法进行遍历。
(2)for-each循环(lterable接口的子类都可使用for-each循环)。
(3)使用集合的迭代器。
迭代器是专门为了遍历一个集合产生的,对于List集合的子类,有一个接口;terator接口,从前向后遍历,hasNext()=> 判断集合是否还有元素没有遍历,next()=> 取出当前要遍历的元素。
- public static void main(String[] args) {
- List<Integer> list = new ArrayList<>();
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(7);
- for (int i = 0; i < l1.size(); i++) {
- System.out.print(l1.get(i)+" ");
- }
- System.out.println();
- for (int i :l1) {
- System.out.print(i+" ");
- }
- System.out.println();
- Iterator<Integer> iterator = list.listIterator();
- while (iterator.hasNext()) {
- System.out.print(iterator.next() + " ");
- }
- }
这三种遍历方式,如果只是普通遍历的话,不牵扯到集合内容的修改时,用哪一种都行。但是在遍历的过程中,修改了集合的内容需要使用的迭代器方式。
- public static void main(String[] args) {
- List<Integer> list = new ArrayList<>();
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(7);
- for (int i :list) {
- System.out.print(i+" ");
- }
- System.out.println();
- }
如果没有使用迭代器就会报错,这个错误叫集合的并发修改异常。假设这个集合是由多个用户同时访问的,其中一个用户修改了集合的内容,那么其他用户就看看到不一样的结果。
以下为采用迭代器的方法。
- public static void main(String[] args) {
- List<Integer> list = new ArrayList<>();
- list.add(1);
- list.add(3);
- list.add(5);
- list.add(7);
- Iterator<Integer> iterator = list.listIterator();
- while (iterator.hasNext()) {
- int num = iterator.next();
- if (num == 3) {
- iterator.remove();
- }
- System.out.println(iterator.next());
- }
ArrayList的无参构造并没有初始化elementData。只有在第一次add时才会真正将数组进行实例化。是懒加载。懒加载就是产生对象时,内部保存的元素的集合并不初始化,只有在第一次真正添加元素之前在进行内部空间的初始化,尽可能的避免空间浪费。
Vector的无参构造就默认初始化了长度为10的Object数组。
ArrayList中size是有效元素个数,默认为零。在add方法中首先要判断size加一判断是否越界,确保空间够用再进行尾插操作。
若此时data为空,返回默认长度10和size+1的最大值,若数组不为空直接返回size+1整个长度,也就是说如果数组还没有初始化,第一次调用add时,就将数组长度赋值为10。
当前有效元素的个数首先要+1,然后和data的长度进行比较,在添加元素之前先扩容。
在grow中也是将新数组长度变为1.5倍。第一个if循环中如果data还没初始化,newCap为默认长度10,同时还有一种情况,当data长度已经接近整形最大值还要翻倍处理,只要新增一个长度元素即可。
而Vector的grow方法前面不太一样,capacityIncrement为自定义扩容参数,并且扩容为原数组的两倍。
动态数组仍然是数组,因此支持随机访问的能力,在O(1)时间内根据索引取得对应索引的元素。
不能简单的说,动态数组的插入和删除效率较低,只有在数组的任意位置的插入和删除(尤其是数组头部)需要进行元素的搬移。默认的尾插其实效率特别高,甚至比链表还快。
扩容需要开辟新的空间,旧的空间需要JVM来进行垃圾回收,此时需要频繁添加数据频繁导致扩容触发,空间的申请和释放都是不小的性能消耗。因此在频繁的进行元素插入和删除时,考虑用链表进行。
一般的扩容都是扩容为原数组的1.5倍或者两倍,假设当前元素100个,为了多放一个元素需要多申请50个或者100个单位,会造成空间的浪费。
所以若需要频繁进行元素的插入和删除,此时选择链表最优,若需要频繁按照索引查询元素,使用数组更优。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。