[数据结构] - 数组
1、Java数组介绍
在Java中,数组是用来存放同一种数据类型的集合,注意只能存放同一种数据类型(Object类型数组除外)。
-
在内存中,数组是一块连续的区域。 拿上面的看电影来说,这几个人在电影院必须坐在一起。
-
数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间。 比如看电影时,为了保证10个人能坐在一起,必须提前订好10个连续的位置。这样的好处就是能保证10个人可以在一起。但是这样的缺点是,如果来的人不够10个,那么剩下的位置就浪费了。如果临时有多来了个人,那么10个就不够用了,这时可能需要将第11个位置上的人挪走,或者是他们11个人重新去找一个11连坐的位置,效率都很低。如果没有找到符合要求的作为,那么就没法坐了。
-
插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移。删除数据时,这个数据后面的数据都要往前移动。 比如原来去了5个人,然后后来又去了一个人要坐在第三个位置上,那么第三个到第五个都要往后移动一个位子,将第三个位置留给新来的人。 当这个人走了的时候,因为他们要连在一起的,所以他后面几个人要往前移动一个位置,把这个空位补上。
-
随机读取效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给地址的数据。
-
并且不利于扩展,数组定义的空间不够时要重新定义数组。一种解决办法是封装一次数组,数组元素超过75%时候就动态扩容,新申请一个1.5倍的数组,把原来的数组全部拷贝过去。
①、数组的声明
第一种方式:
数据类型 [] 数组名称 = new 数据类型[数组长度];
这里 [] 可以放在数组名称的前面,也可以放在数组名称的后面,我们推荐放在数组名称的前面,这样看上去 数据类型 [] 表示的很明显是一个数组类型,而放在数组名称后面,则不是那么直观。
第二种方式:
数据类型 [] 数组名称 = {数组元素1,数组元素2,......}
这种方式声明数组的同时直接给定了数组的元素,数组的大小由给定的数组元素个数决定。
//声明数组1,声明一个长度为3,只能存放int类型的数据
int [] myArray = new int[3];
//声明数组2,声明一个数组元素为 1,2,3的int类型数组
int [] myArray2 = {1,2,3};
②、访问数组元素以及给数组元素赋值
数组是存在下标索引的,通过下标可以获取指定位置的元素,数组小标是从0开始的,也就是说下标0对应的就是数组中第1个元素,可以很方便的对数组中的元素进行存取操作。
前面数组的声明第二种方式,我们在声明数组的同时,也进行了初始化赋值。
//声明数组,声明一个长度为3,只能存放int类型的数据
int [] myArray = new int[3];
//给myArray第一个元素赋值1
myArray[0] = 1;
//访问myArray的第一个元素
System.out.println(myArray[0]);
上面的myArray 数组,我们只能赋值三个元素,也就是下标从0到2,如果你访问 myArray[3] ,那么会报数组下标越界异常。
③、数组遍历
数组有个 length 属性,是记录数组的长度的,我们可以利用length属性来遍历数组。
//声明数组2,声明一个数组元素为 1,2,3的int类型数组
int [] myArray2 = {1,2,3};
for(int i = 0 ; i < myArray2.length ; i++){
System.out.println(myArray2[i]);
}
2、简单封装一个动态数组
package me.liangtian.array;
public class Array<E> {
/**
* 存放数据的数组
*/
private E[] data;
/**
* 数组中现有数据量
*/
private int size;
public Array(int capacity) {
data = (E[])new Object[capacity];
size = 0;
}
/**
* 默认数组长度10
*/
public Array() {
new Array<>(10);
}
/**
* 得到数组长度
*/
public int getCapacity() {
return data.length;
}
/**
* 已有数组大小
*/
public int getSize() {
return size;
}
/**
* 判断数组是否为空
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 索引index处添加一个元素
* 1.判断索引是否有效
* 2.判断数组是否已经满了,若满了那么扩容
* 3.从后往前,index处的元素后移一个位置
* 4.index索引处元素赋值
* 5.size++
*/
public void add(int index, E e) {
if(index < 0 || index > size) {
throw new IllegalArgumentException("add failed, index must between 0 and size");
}
if(index == size) {
resize( 2 * data.length);
}
for (int i = size -1; i >= index; i--) {
data[i+1] = data[i];
}
data[index] = e;
size++;
}
/**
* 在尾部添加新元素
*/
public void addLast(E e) {
add(size, e);
}
/**
* 在头部添加新元素
*/
public void addFirst(E e) {
add(0, e);
}
/**
* 是否包含否个元素
*/
public boolean contains(E e) {
for (E e1 : data) {
if(e1.equals(e)) {
return true;
}
}
return false;
}
/**
* 动态扩容
* 1.新建一个容器,大小为指定大小newCapacity
* 2.将之前的容器的元素按原有顺序放到新的容器中
* 3.将原指针指向新容器
*/
private void resize(int newCapacity) {
E[] newDate = (E[]) new Object[newCapacity];
for(int i = 0; i < size; i++) {
newDate[i] = data[i];
}
data = newDate;
}
/**
* 查找元素E在数组中的索引
* 1.遍历所有的数组,若匹配到(equals非==)那么返回索引,否则返回-1
*/
private int find(E e) {
for (int i = 0; i < data.length; i++) {
if(data[i].equals(e)) {
return i;
}
}
return -1;
}
/**
* 删除元素并返回删除之前的位置
* 若不存在则返回-1
*/
public int removeElement(E e) {
int index = find(e);
if(index != -1) {
remove(index);
}
return index;
}
/**
* 根据索引删除元素,并返回删除元素
* 1.判断索引是否有效
* 2.将索引处的元素保存到单独一个变量,用于返回
* 3.将所有元素左移,索引从小到大
* 4.将size索引处的元素清空,并且size-1
* 5.均摊算法复杂度。为防止算法复杂度振荡,只有size <= data.leng/4 那么重新调整数组大小为原大小二分之一
* 注意数组容器大小不能为0
*/
private E remove(int index) {
if(index < 0 || index >= size) {
throw new IllegalArgumentException("index must between 0 and size -1");
}
E removeData = data[index];
for(int i = index + 1; i < size; i++) {
data[i-1] = data[i];
}
data[index] = null;
size--;
if(size <= data.length/4 && data.length /2 != 0) {
resize(data.length /2);
}
return removeData;
}
}