我们要学习的第一个数据结构就是数组,数组中很多值得挖掘。
数组基础
把数据码成一排进行存放
数组中索引从0开始,Java语法中要求数组存放同一类型的元素,可以通过中括号下标的方式取到元素。
这样可以看到Main中有的方法。
package cn.mtianyan;public class Main { public static void main(String[] args) { // 必须传入长度 int[] arr = new int[10]; for (int i = 0; i < arr.length; i++) { arr[i] = i; } System.out.println("========="); int[] scores = new int[]{100,99,96}; for (int i = 0; i < scores.length; i++) { System.out.println(scores[i]); } System.out.println("========="); for (int score : scores) { System.out.println(score); } System.out.println("========="); scores[0] = 92; for (int score : scores) { System.out.println(score); } } }
运行结果:
数组可以这样遍历,是因为它实现了可遍历接口。
Java为我们提供的数组,其中非常重要的一部分就是它的索引。索引可以有语义,也可以没有语义,2可以代表学号,但也可以将其视为没有语义。
数组最大的优点: 快速查询,scores[2]直接插到2号索引的。因此数组最好应用于"索引有语义"的情况,查学号为2的学生成绩。
但并非所有有语意的索弓都适用于数组,如身份证号: 1101031985121 66666;依次为索引,这得开辟很大的内存空间,空间浪费。
数组也可以处理“索引没有语意”的情况。我们在这一章,主要处理“索引没有语意”的情况数组的使用。
索引没有语意,如何表示没有元素? capacity 和 size的区别: 数组size是3,capacity是8。
如何添加元素(超过size如何处理)?如何删除元素(挪位)? Java中的数组并没有这些方法,我们需要基于java的数组,二 次封装属于我们自己的数组类。
我们自己做的是动态数组(内部依然使用java数组实现),Java的是静态数组。
增删改查,有的数据结构不一定四个齐全。capacity是数组最多可以装多少元素,和实际能装多少元素是没有关系的。
package cn.mtianyan;public class Array { private int[] data; private int size; /** * 带容量参数构造函数 * * @param capacity 数组容量 */ public Array(int capacity) { data = new int[capacity]; size = 0; } /** * 默认构造函数 */ public Array() { this(10); } /** * 静态数组入参构造函数 * * @param data 传入静态数组 */ public Array(int[] data) { this.data = data; } /** * 获取数组元素个数 * * @return size 数组元素个数 */ public int getSize() { return size; } /** * 获取数组的容量 * * @return capacity 获取容量 */ public int getCapacity(){ return data.length; } /** * 判断数组是否为空 * * @return 是否为空 */ public boolean isEmpty(){ return size == 0; } }
向数组中添加元素
向数组末尾添加元素
size指向data中第一个为空位置。因此添加元素时只需要添加到arr[size],并size++即可。(注意添加元素判满)
/** * 向所有元素末尾添加一个新元素。 * * @param e 添加的元素 */ public void addLast(int e){// if (isFull()){// throw new IllegalArgumentException("AddLast failed. Array is Full");// }else {// data[size] =e; // data[size++] =e;// size++;// } add(size,e); } /** * 向索引0号位置添加元素 * * @param e 添加的元素 */ public void addFirst(int e){ add(0,e); } /** * 在index位置插入一个新元素e * * @param index 插入位置索引 * @param e 插入元素 */ public void add(int index,int e){ if (isFull()) throw new IllegalArgumentException("AddLast failed. Array is Full"); // 大于size就不是紧密排列了 if (index<0 || index>size){ throw new IllegalArgumentException("AddLast failed. Required index<0 or index>size "); } else { // 从哪开始挪呢: 从size-1这个元素(size本身是指向空的),挪到哪个呢,index位置的这个元素也是要挪的。 for (int i=size-1; i>=index; i--){ data[i+1] = data[i]; // 后一个等于前一个,从数组最后一个元素开始。 // 极端值验证: size 1 index 0;(i=0;i>=0;i--)会执行一次data[1]=data[0].正确。 } data[index] = e; size++; } }
77插入,后面的元素都要向后挪动。
这里注意必须从100这里,向后挪,否则会造成值覆盖。
在数组中查询元素和修改元素
/** * 打印数组信息及遍历元素。 * * @return 数组信息和元素遍历结果 */ @Override public String toString() { StringBuilder res = new StringBuilder(); res.append(String.format("Array: size = %d, capacity = %d\n",size,data.length)); res.append('['); for (int i = 0; i < size; i++) { res.append(data[i]); if (i !=size-1) // 最后一个元素不要加, res.append(", "); } res.append(']'); return res.toString(); }
package cn.mtianyan;public class ArrayTest { public static void main(String[] args) { Array array = new Array(20); for (int i = 0; i < 10; i++) { array.addLast(i); } System.out.println(array); } }
运行结果:
array.add(1,100); System.out.println(array);
运行结果:
array.addFirst(-1); System.out.println(array);
运行结果:
取出某一个元素。
/** * 传入索引,获取该位置元素 * * @param index 要获取的元素索引 * @return 返回该位置数组元素 */ public int get(int index){ if (index<0 || index>=size){ throw new IllegalArgumentException("Get failed. Required index<0 or index>=size "); }else { return data[index]; } }
通过get方法用户无法使用到数组后半截没有使用到的空间,通过封装保证。
System.out.println(array.get(array.getSize()-1));
运行结果:
/** * 传入索引和元素值,将该位置元素设置为传入值 * @param index 索引 * @param e 传入值 */ public void set(int index,int e){ if (index<0 || index>=size){ throw new IllegalArgumentException("Set failed. Required index<0 or index>=size "); }else { data[index] = e; } }
array.set(0,-99); System.out.println(array);
运行结果:
数组中的包含,搜索和删除元素
如果要删除索引为1的元素,从88开始,后面都要往前挪,挪size-index次。只需要size--,对于最后一个位置的元素不用做其他操作,因为用户也访问不到。
/** * 查找数组中是否有元素e * * @param e * @return 包含 true; 不包含 false */ public boolean contains(int e){ for (int i = 0; i < size; i++) { if (data[i] == e){ return true; } } return false; } /** * 查找数组中元素,返回其索引(第一个遇到) * * @param e 元素 * @return 不存在 -1; 存在 i */ public int find(int e){ for (int i = 0; i < size; i++) { if (data[i] == e){ return i; } } return -1; } public int[] findAll(int e){ int[] tempArray=new int[size]; int index = 0; for (int i = 0; i < size; i++) { if (data[i] == e){ tempArray[index] = i; index++; } } int[] indexArray=new int[index]; for (int i = 0; i < index; i++) { indexArray[i] = tempArray[i]; } return indexArray; }
注意findAll中使用tempArray临时对象的作用: 同时将int数组,与它的size一起传递了出来。
System.out.println("===============加入重复元素后数组如下==================="); array.add(3,3); array.add(array.getSize()-1,9); System.out.println(array); System.out.println("================包含 寻找测试==========================="); System.out.println(array.contains(-99)); System.out.println(array.contains(-100)); System.out.println("3的index: "+array.find(3)); int [] tmpArr = array.findAll(3); for (int i : tmpArr) { System.out.print(i+" "); } System.out.println();
运行结果:
/** * 删除数组元素,返回删除的元素值 * * @param index 索引 * @return 该索引位置元素值 */ public int remove(int index){ // 判空,空数组 removeFirst index=0,size=0,index>=size异常。空数组 removeLast index=-1 index<0异常; if (index<0 || index>=size){ throw new IllegalArgumentException("Remove failed. Required index<0 or index>=size "); }else { int ret = data[index]; for (int i=index+1;i < size;i++){ // 从哪个元素开始挪,从index位置的后一个元素开始,挪到哪个元素结束,挪到size-1(因此没等号) data[i-1] = data[i]; // 前一个等于后一个 } size--; return ret; } } /** * 删除第一个(索引0)元素 * * @return 删除的元素值 */ public int removeFirst(){ return remove(0); } /** * 删除最后一个(索引size-1)元素 * * @return 删除的元素值 */ public int removeLast(){ return remove(size-1); } /** * 删除数组中某一个元素值(删除数组中第一个找到的) * * @param e 元素值 * @return 删除是否成功 */ public boolean removeElement(int e){ int index = find(e); if (index != -1){ remove(index); return true; } return false; } /** * 删除数组中包含的所有该元素值 * @param e 元素值 * @return 删除成功与否 */ public boolean removeAllElement(int e){ int[] indexArray = findAll(e); if (indexArray.length != 0){ for (int i = 0; i < indexArray.length; i++) { remove(indexArray[i]-i); // 此处注意-i的巧妙 } return true; } return false; }
重难点代码在于remove(indexArray[i]-i);此处很可能会被忽略,造成异常。因为数组每删除一个,原本获取到的临近后一个元素的index值应该-1;临近后两个则应该-2;以此类推。
System.out.println("================开始删除测试========================"); System.out.println(array); array.remove(3); // 删除一个3 System.out.println(array); array.removeElement(1); // 删除1 System.out.println(array); System.out.println("=====删除index3 后 删除元素1如上====");
运行结果:
array.removeAllElement(9); System.out.println(array); System.out.println("=====删除所有9====="); array.addFirst(-99); array.removeAllElement(-99); System.out.println(array); System.out.println("=====首位添加-99,后删除所有-99 结果如上=====");
array.add(4,99); array.add(5,99); array.addFirst(99); array.addLast(99); array.add(7,99); System.out.println(array); System.out.println("=====上面为删除99前,下面为删除99后====="); array.removeAllElement(99); System.out.println(array);
使用泛型
我们上面实现的数组目前只能存放int类型,但是我们需要的是可以承载多种类型,甚至自定义对象的容器。Java泛型可以解决这个问题。
使用泛型: 让我们的数据结构可以放置“任何”数据类型,不可以是基本数据类型,只能是类对象。
java中的八种基本类型: boolean , byte, char , short , int , long , float , double;
每个基本数据类型都有对应的包装类: Boolean , Byte , Char , Short, Int , Long , Float , Double。自动的拆包,解包。
public class Array<Element>public class Array<E>
为类名后面添加泛型类型标识,此处的类型标识可以自定义。
private E[] data;
这里无法直接通过E实例化。只能通过间接的Object再转换。
data = (E[]) new Object[capacity];
/** * 静态数组入参构造函数 * * @param data 传入静态数组 */ public Array(E[] data) { this.data = data; }
public void add(int index,E e){public void addLast(E e){public void addFirst(E e){
public boolean contains(int E){ for (int i = 0; i < size; i++) { if (data[i].equals(E)){ return true; } } return false; }
这里注意,两个对象之间的比较要使用equals方法,将所有的与成员数组类型相关的都改了。
public E remove(int index){ // 判空,空数组 removeFirst index=0,size=0,index>=size异常。空数组 removeLast index=-1 index<0异常; if (index<0 || index>=size){ throw new IllegalArgumentException("Remove failed. Required index<0 or index>=size "); }else { E ret = data[index]; for (int i=index+1;i < size;i++){ // 从哪个元素开始挪,从index位置的后一个元素开始,挪到哪个元素结束,挪到size-1(因此没等号) data[i-1] = data[i]; // 前一个等于后一个 } size--; data[size] = null; return ret; } }
data[size]还指着一个值不过用户访问不到而已,新的元素添加会自动覆盖。使用泛型后,data数组中存放的是类对象的引用, size-- 后data[size]依然指向一个引用,引用就涉及到空间释放的问题,Java有自动垃圾回收机制,但如果 data[size]仍存放引用,就不会被自动垃圾回收。data[size] = null;(不必须,可以被新对象覆盖)
如果不置为null,它会被称为loitering Objects,没有用但垃圾回收机制不回收。 loitering Objects != memory leak 为了程序优化,手动去除更好。
Main中改动
Array<Integer> array = new Array(20); Array<Integer> array = new Array<>(20);
即使不改,也是可以正常运行的。但是我们最好加上更清楚的看出类型,jdk1.7之后不用再后面再重复一次。
package cn.mtianyan;public class Student { private String name; private int score; public Student(String studentName, int studentScore){ name = studentName; score = studentScore; } @Override public String toString(){ return String.format("Student(name: %s, score: %d)", name, score); } public static void main(String[] args) { Array<Student> arr = new Array(); arr.addLast(new Student("mtianyan1", 100)); arr.addLast(new Student("mtianyan2", 66)); arr.addLast(new Student("mtianyan3", 88)); System.out.println(arr); } }
运行结果:
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Student)) return false; Student student = (Student) o; return score == student.score && (name.equals(student.name)); } @Override public int hashCode() { return Objects.hash(name,score); }
作者:天涯明月笙
链接:https://www.jianshu.com/p/291d5c9e2591
共同学习,写下你的评论
评论加载中...
作者其他优质文章