1 数据结构
- 数据结构是计算机存储、组织数据的方式
- 数据结构分类
2 线性表
- 具有 n 个相同类型元素的有限序列( n ≥ 0 )
- a1是首节点(首元素),an是尾节点(尾元素)
- a1是a2的前驱,a2是a1的后继
3 数组
- 顺序存储的线性表,所有元素的内存地址是连续的
- 数组无法动态修改容量
- 实际开发中,我们更希望数组的容量可以动态改变,称为动态数组
4 动态数组的设计
-
成员变量设计
-
添加元素add(E element)
-
删除数组remove(int index)
-
添加元素add(int index, E element)
-
动态扩容
-
对象数组
-
动态缩容
- 如果内存使用比较紧张,动态数组有比较多的剩余空间,可以考虑进行缩容操
- 比如剩余空间占总容量的一半时,就进行缩容
- 如果扩容倍数、缩容时机设计不得当,有可能会导致复杂度震荡
- 比如添加一个元素就扩容,删除一个元素又缩容
- 为防止缩容,扩容倍数与缩容倍数相乘要避免等于1
- 在remove方法最后添加
private void trim() {
// 30
int oldCapacity = elements.length;
// 15
int newCapacity = oldCapacity >> 1;
if (size > (newCapacity) || oldCapacity <= DEFAULT_CAPACITY) return;
// 剩余空间还很多
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println(oldCapacity + "缩容为" + newCapacity);
}
5 动态数组实现
- ArrayList
package com.mj;
@SuppressWarnings("unchecked")
public class ArrayList<E> {
/**
* 元素的数量
*/
private int size;
/**
* 所有的元素
*/
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
private static final int ELEMENT_NOT_FOUND = -1;
public ArrayList(int capaticy) {
capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
// 存放的Object对象的地址,没法直接放对象,因为不同对象成员变量不同,大小不同,无法确定分配多大的内存空间用于存储,但地址的大小是固定的,为8个字节
// 同时如果真的存放是对象本身的话,会导致还没真正创建需要存放的对象,就分配出去了一块非常大的空间
elements = (E[]) new Object[capaticy];
}
public ArrayList() {
this(DEFAULT_CAPACITY);
}
/**
* 清除所有元素
*/
public void clear() {
// 如果只写size=0进行清空,会导致如果该数组中存放的对象的引用,所指向的那些对象,无法被清理掉,还占用着内存
// 也不要直接elements = null,这样会导致数组也被清空,以后想继续使用,还需要重新创建内部的数组对象,没有必要,因为该数组对象可以重复利用
// 也就是说,可以循环利用的留下,不能循环利用的删除
// 可以通过重写数组中存放的对象所在类的finalize方法,来确定该对象是否被回收, 如果该对象被正确回收,该finalize方法会被调用
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
/**
* 元素的数量
*
* @return
*/
public int size() {
return size;
}
/**
* 是否为空
*
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 是否包含某个元素
*
* @param element
* @return
*/
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
/**
* 添加元素到尾部
*
* @param element
*/
public void add(E element) {
add(size, element);
}
/**
* 获取index位置的元素
*
* @param index
* @return
*/
public E get(int index) {
rangeCheck(index);
return elements[index];
}
/**
* 设置index位置的元素
*
* @param index
* @param element
* @return 原来的元素ֵ
*/
public E set(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
/**
* 在index位置插入一个元素
*
* @param index
* @param element
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
// 动态扩容代码,需要确保数组中,能够容纳下size+1个元素,未添加前,有size个元素,添加后有size+1个元素,如果这比elements的length大,说明容量不够了
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = element;
size++;
}
/**
* 删除index位置的元素
*
* @param index
* @return
*/
public E remove(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
// 将未删除前的最后一个位置上的元素清空,不然新数组中的最后一个元素,和该元素指向同一块内存
// 导致即使将新数组中的最后一个元素remove后,其指向的对象不被清理(因为被原数组的最后一个元素也指着)
elements[--size] = null;
return old;
}
/**
* 查看元素的索引
*
* @param element
* @return
*/
public int indexOf(E element) {
// 因为ArrayList允许传入空值,因此传入的节点可能是空的,但null.equals会抛异常,所以对于空值需要特殊处理
if (element == null) { // 1
for (int i = 0; i < size; i++) {
if (elements[i] == null)
return i;
}
} else {
for (int i = 0; i < size; i++) {
// 此处不要直接使用==进行比较,应该自定义equals进行比较
if (element.equals(elements[i]))
return i; // n
}
}
return ELEMENT_NOT_FOUND;
}
// public int indexOf2(E element) {
// for (int i = 0; i < size; i++) {
// if (valEquals(element, elements[i])) return i; // 2n
// }
// return ELEMENT_NOT_FOUND;
// }
//
// private boolean valEquals(Object v1, Object v2) {
// return v1 == null ? v2 == null : v1.equals(v2);
// }
/**
* 保证要有capacity的容量
*
* @param capacity
*/
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity)
return;
// 新容量为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println(oldCapacity + "扩容为" + newCapacity);
}
private void outOfBounds(int index) {
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
private void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
private void rangeCheckForAdd(int index) {
// 插入时,是允许index与size相等的,表示向最后一个位置插入元素,而删除、获取、修改时,都不允许等于
if (index < 0 || index > size) {
outOfBounds(index);
}
}
@Override
public String toString() {
// size=3, [99, 88, 77]
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
for (int i = 0; i < size; i++) {
// 只要不是第0个元素,就在其前面拼接上一个,
if (i != 0) {
string.append(", ");
}
string.append(elements[i]);
// if (i != size - 1) {
// string.append(", ");
// }
}
string.append("]");
return string.toString();
}
}
- Asserts:测试工具类
package com.mj;
public class Asserts {
public static void test(boolean value) {
try {
if (!value) throw new Exception("测试未通过");
} catch (Exception e) {
e.printStackTrace();
}
}
}