目录
List接口的实现类有很多,常见的有:
ArrayList:动态数组
Vector: 动态数组
LinkList: 双向链表
Stack:栈
自定义动态数组
package com.atguigu.list;
import java.util.Arrays;
import java.util.Iterator;
/*
MyArrayList模仿ArrayList动态数组类的实现。
MyArrayList是一个容器,是一个集合,用来存储对象的。
(1)元素的类型是什么?不确定,可以使用泛型
<E>:Element Type元素类型
(2)内部是动态数组,要有数组结构
暂且声明为Object[]
(3)声明一个实例变量来记录实际存储的元素个数
*/
public class MyArrayList<E> implements Iterable<E>{
// private E[] all = new E[5];//无法创建
private Object[] all = new Object[5];
private int total;
/**
* 添加元素
* @param e E:添加一个元素到当前集合中
*/
public void add(E e){
//考虑数组是否已满,需要扩容
grow();
/* all[total] = e;
total++;*/
all[total++] = e;
}
private void grow() {
//考虑数组是否已满,需要扩容
if (total >= all.length) {
// all = Arrays.copyOf(all, all.length+1);
// all = Arrays.copyOf(all, all.length*2);
all = Arrays.copyOf(all, all.length + (all.length >> 1));//扩容为原来1.5倍
}
}
/**
* 添加元素
* @param e E:添加一个元素到当前集合中
*/
public void add(int index, E e){
//检查下标
if(index<0 || index>total){
throw new IndexOutOfBoundsException(index +"越界");
}
//考虑数组是否已满,需要扩容
grow();
//把[index]位置的元素往后移动
/*
假设all.length = 10,total= 7, index = 3,正常下标[0,6]
all[6] -> all[7]
all[5] -> all[6]
all[4] -> all[5]
all[3] -> all[4]
移动元素的个数是4, total - index
*/
System.arraycopy(all, index, all, index+1, total-index);
all[index] = e;
total++;
}
/**
* 返回index位置的元素
* @param index int 下标
* @return E 元素
*/
public E get(int index){
//判断index合法性
checkIndex(index);
return (E) all[index];
}
private void checkIndex(int index) {
//判断index合法性
if(index<0 || index>=total){
throw new IndexOutOfBoundsException(index +"越界");
}
}
/**
* 查询obj在当前集合中的位置。
* @param obj Object
* @return int 如果有多个重复的,只返回第1个的下标。如果不存在返回-1;
*/
public int indexOf(Object obj){
/*
思路:遍历all数组,如果元素和obj一样,就返回下标,如果遍历完没有一样的,返回-1
*/
if(obj != null) {
for (int i = 0; i < total; i++) {
if (obj.equals(all[i])) {
return i;
}
}
}else{
for (int i = 0; i < total; i++) {
if (all[i] == obj) {//null == null是相等的
return i;
}
}
}
return -1;
}
/**
* 查询obj在当前集合中的位置。
* @param obj Object
* @return int 如果有多个重复的,只返回最后1个的下标。如果不存在返回-1;
*/
public int lastIndexOf(Object obj){
/*
思路:遍历all数组,如果元素和obj一样,就返回下标,如果遍历完没有一样的,返回-1
*/
if(obj != null) {
for (int i = total-1; i >=0; i--) {
if (obj.equals(all[i])) {
return i;
}
}
}else{
for (int i = total-1; i >=0; i--) {
if (all[i] == obj) {//null == null是相等的
return i;
}
}
}
return -1;
}
/**
* 判断obj是否在当前集合中
* @param obj Object 查询的目标对象
* @return boolean 存在就返回true,不存在返回false
*/
public boolean contains(Object obj){
return indexOf(obj) != -1;//如果存在,下标就不会是-1,
}
/**
* 修改index位置的元素
* @param index int 下标
* @param e E 目标对象
*/
public void set(int index, E e){
//判断index合法性
checkIndex(index);
all[index] = e;
}
/**
* 替换集合中obj元素为e
* @param obj Object 目标对象
* @param e E 新对象
*/
public void set(Object obj, E e){
/*
两种实现方案:一种替换所有找到的,第二种是替换第一个找到的
*/
//这里选择实现第二种
int index = indexOf(obj);
if(index != -1) {
set(index, e);
}
}
/**
* 删除index位置的元素
* @param index int 下标
*/
public void remove(int index){
/*
思路:先把index位置后面往前移动,再把最后一个位置置为null
*/
/*
假设all.length=10, total = 7, index = 3, 有效元素的范围[0,6]
all[4]->all[3]
all[5]->all[4]
all[6]->all[5]
一共往前复制了3个元素,total-index-1
*/
System.arraycopy(all, index+1, all, index, total-index-1);
// all[total-1] = null;
// total--;
all[--total]= null;
}
/**
* 删除目标对象
* @param obj Object 目标对象
*/
public void remove(Object obj){
/*
方案:(1)删除所有重复的obj(2)删除第一个找到的obj
*/
//这里选择方案二
int index = indexOf(obj);
if(index != -1) {
remove(index);
}
}
/**
* 把现在集合中的所有元素用数组返回
* @return Object[] 返回元素的数组
*/
public Object[] toArray(){
return Arrays.copyOf(all, total);
}
/**
* 获取元素个数的方法
* @return int
*/
public int size(){
return total;
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E>{
int cursor;
@Override
public boolean hasNext() {
return cursor < total;
}
@Override
public E next() {
return (E) all[cursor++];
}
}
/*
hasNext() 判断当前位置有没有元素
其实应该是currentIsPresent() current:当前位置,present:存在
next() 取出当前位置的元素,并把引用/下标指向下一个元素
其实应该是fetchAndMoveNext() fetch:获取,moveNext:移到下一个
*/
}
Vector和ArrayList
1.区别
LinkList
- 链表与动态数组区别
动态数组底层的物理结构是数组,因此根据索引访问效率非常高。但是末尾位置的插入和删除效率不高,因为涉及到移动元素。另外添加操作涉及到扩容问题,就会增加时空消耗。
链表底层的物理结构是链表,因此根据索引访问的效率不高,但是插入和删除不需要移动元素,只需要修改前后元素的指向关系即可,而且链表的添加不会涉及到扩容问题。
链表自己实现
package MyLinkList;
/*
*(1)链式结构,首先要有结点
*(2)双链表可以从前往后也可以从后往前遍历节点,所以需要记录第一个节点地址和最后一个节点地址
*/
import java.util.Iterator;
public class MyLinkList<E> implements Iterable<E>{
private Node first;
private Node last;
private int total;
public void add(E data) {
Node newNode = new Node(last, data, null);
if (last != null) {
last.next = newNode;
} else {
first = newNode;
}
last = newNode;
total++;
}
public int size() {
return total;
}
private Node findNode(Object obj) {
Node node = first;
if (obj == null) {
while (node != null) {
if (node.data == null) {
return node;
}
node = node.next;
}
} else {
while (node != null) {
if (obj.equals(node.data)) {
return node;
}
node = node.next;
}
}
return null;
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
Node node = first;
@Override
public boolean hasNext() {
return node != null;
}
@Override
public E next() {
E value = node.data;
node = node.next;
return value;
}
}
public void remove(Object obj) {
// 第一步找到目标节点
Node node = findNode(obj);
if (node == null) {
return;
}
if (node.prev == null) {
first = node.next;
} else {
node.prev.next = node.next;
}
if (node.next == null) {
last = node.prev;
} else {
node.next.prev = node.prev;
}
node.prev = null;
node.next = null;
node.data = null;
total--;
}
// 双链表的节点类型
private class Node {
Node prev;
E data;
Node next;
Node(Node prev, E data, Node next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
}
迭代器的hasNext和next方法什么意思?
- hasNext判断当前位置有没有元素,其实应该是currentIsPresent()
- next取出当前位置的元素,并把引用/下标指向下一个元素,其实应该是fetchAndMoveNext
栈
- 体现栈结构的操作方法:
a.peek()方法:查看栈顶元素,不弹出
b.pop()方法:弹出栈
c.push方法:压入栈
队列
LinkLIst实现了queue的接口,下面的队列的方法,LinkList是都可以使用的。
而且LinkedList还实现了Deque接口(双端队列)
Set集合
- collection集合总揽
List接口:可以重复、有序、根据索引操作等
Queue接口:先进先出
Deque接口:可以先进先出,可以后进先出,可以先进后出...
set接口:不允许元素重复,有一些是有序的,有一些是无序的。
底层的存储顺序和添加的顺序绝对不一致。 - set也是collection接口的子接口,但是没有扩展新的方法,所以用的都是collection接口的方法。
- Set实现类有:HashSet,TreeSet,LinkedHashSet
HashSet:无序,且不重复
TreeSet: 自动按照顺序排序,string是按照Unicode编码排序,指定排序,对于自己新增的类,必须实现comparator,
LinkedHashSet:不可重复,有序(按照添加顺序),底层不是按照添加顺序存储的,但是会有一个链表记录它们的添加顺序。
//先按照长度排序,长度相同的按照字母unicode排序
HashSet<String> set = new HashSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int lenResult = o1.length() - o2.length();
return lenResult == 0 ? o1.compareToIgnoreCase(o2) : lenResult;
}
});