文章目录
- 一、数组封装
- 1.需求
- 2.编码实现
- 3.测试
- 二、集合
- 1.概述
- 2.继承体系
- 3.Collection
- 3.1.常用方法
- 3.2.使用方式
- 3.3.Iterator
- 4.forEach
- 5.List
- 5.1.ArrayList
- 5.2.LinkedList
- 5.2.1.个人理解
- 5.2.2.基本使用
- 5.2.3.底层实现
- 5.2.3.1.节点类Node
- 5.2.3.2.LinkedList
- 5.2.3.3.添加-add
- 5.2.3.4.获取-get
- 5.2.3.5.删除原理
一、数组封装
1.需求
数组没有提供方法,操作比较繁琐,所以适当进行封装,提高开发效率、降低使用难度
2.编码实现
public class Array {
private Object[] elements=new Object[10];
//元素个数
private int size=0;
//根据索引获取数据
public Object get(int index){
return elements[index];
}
//根据索引修改数据
public void set(int index,Object element){
elements[index]=element;
}
//获取数组元素个数
public int size(){
return size;
}
//添加数据
public void add(Object element){
//判断原数组是否有空间
if (size==elements.length) {
//新数组扩容为原数组的二倍
Object[] newArr=new Object[elements.length*2];
//将原数组数据拷贝到新数组
System.arraycopy(elements, 0, newArr, 0, size);
elements=newArr;
}
elements[size]=element;
size++;
}
//删除数据
public void remove(int index){
for (int i = index; i < size; i++) {
elements[i]=elements[i+1];
}
size--;
}
}
3.测试
public class Test {
public static void main(String[] args) {
Array array=new Array();
//尾部添加
array.add(6);
array.add(66);
array.add(666);
array.add(6666);
array.add(66666);
//查询
System.out.println(array.get(2));
//修改
array.set(3, 6);
//删除
array.remove(4);
for (int i = 0; i < array.size(); i++) {
System.out.println(array.get(i));
}
}
}
二、集合
1.概述
Java 集合是使程序能够存储和操纵元素 不固定的一组数据。所有 Java 集合类都位于 java.util 包中
既然已经有了数组来存放多个元素,为什么还有提供 Java 集合工具类
- 我们通过对比数组和 Java 集合工具类来解释 Java 集合工具类的必要性
数组 | 集合 |
长度固定 | 长度不固定 |
存放任意类型 | 不能存放基本数据类型,只能存放对象的引用 |
**注意:**如果集合中存放基本类型,一定要将其"装箱"成对应的"基本类型包装类"
2.继承体系
Collection 是集合,两个直接子接口是 List 和 Set
List 特性:有序 可重复,保证数据的添加顺序和取出顺序一致
Set 特性:无序 不可重复,不能保证数据的添加和取出顺序一致
3.Collection
3.1.常用方法
3.2.使用方式
import java.util.ArrayList;
import java.util.Collection;
public class Collection_01 {
public static void main(String[] args) {
Collection c=new ArrayList();
//添加
c.add("a");
c.add("b");
c.add("n");
//删除,根据内容删除
c.remove("a");
//Collection 中没有提供查询和修改
//已有元素个数
System.out.println(c.size());
System.out.println("b"+c.contains("b"));
//清空
c.clear();
//判断是否为空(是否个数为0)
System.out.println("空"+c.isEmpty());
c.add("a");
c.add("b");
c.add("n");
// c.add(1);
// c.add(66);
// c.add(false);
// c.add(new Object());
//转为数组并返回
Object[] array=c.toArray();
//增强for循环
// for(数据类型 变量 :集合/数组){循环体}
for (Object o : array) {
System.out.println(o);
}
}
}
3.3.Iterator
- Interator 迭代器,屏蔽了不同数据结构的底层结构差异性,提供了遍历的统一方式
- 迭代器为遍历提供了三个方法
- boolean hasNext() :判断是否还有元素,有就返回 true
- next() : 返回当前指向的元素,并让光标指向下一个元素
- remove() : 删除当前指向的元素,当使用迭代器时,想要删除数据,只能使用迭代器的删除,(一般在外部用其他方法删除后再进行迭代)
- 只要迭代器生成,那么集合中就不能添加或删除元素,如果需要添加或删除,则需要重新生成迭代器
public class Collection_02 {
public static void main(String[] args) {
Collection c=new ArrayList();
//添加
c.add("a");
c.add("b");
c.add("n");
//c.remove("n"); 想要删除数据一般在外部删除
//创建迭代器
Iterator it=c.iterator();
while (it.hasNext()) {
Object object=it.next();
System.out.println(object);
//迭代器中的删除,一般不会使用
if (object.equals("n")) {
it.remove();
}
}
System.out.println(c);
//迭代器使用完后,光标不会自动复原,还会停留在上一次移动后的位置
//想要再次使用 只能重新生成
it=c.iterator();
while (it.hasNext()) {
Object object=it.next();
System.out.println(object);
}
}
}
4.forEach
- 增强 for 循环,是 Iterator 的简写方式,但是功能不太全
- 使用增强 for 循环时,也不能对集合进行添加和删除
- 语法:for( 数据类型 变量 :集合 / 数组){ 循环体 }
Collection c=new ArrayList();
c.add("a");
c.add("b");
c.add("n");
// for(数据类型 变量 :集合/数组){循环体}
for (Object o : array) {
// o 是集合中的每个元素,相当于每次都会给 o 赋值,类似o[i]
System.out.println(o);
}
5.List
- List 有序可重复
- 有序:添加和取出顺序一致,队列操作
- 可重复:可以存储重复数据
5.1.ArrayList
- ArrayList 底层是数组,查询和修改效率极高,但是随机添加和删除效率较低
- 默认加载长度是 10 ,扩容后是原来的 1.5 倍, 非线程安全,效率极高
- 当第一次添加数据的时候,再创建数组,我们如果只是 new 了 ArrayList()的对象时,是并不会初始化底层数组的
java.util 中的源代码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//这里就是扩容为原来的1.5倍 oldCapacity / 0.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
例:
public class Collection_03_List {
public static void main(String[] args) {
List list=new ArrayList();
//添加到尾部
list.add("a");
list.add("b");
list.add("c");
list.add(6);
System.out.println(list);
//添加到指定位置
list.add(1, 66);
System.out.println(list);
//根据内容删除
list.remove("a");
System.out.println(list);
//根据下标删除
list.remove(2);
System.out.println(list);
//如果我们要根据内容删除,且该内容是数字
//那么必须将该值转换为对应的包装类,否则会被当做下标
list.remove(Integer.valueOf(6));// 6 就是想要删的内容,不是下标
System.out.println(list);
//根据下标修改
list.set(1, 666);
System.out.println(list);
//根据索引进行查询 ( 索引即下标,下标即索引 )
System.out.println(list.get(1));
//个数
System.out.println(list.size());
//判断是否包含
System.out.println(list.contains(666));
//判断是否为空 ( 个数是否为0 )
System.out.println(list.isEmpty());
//清空
list.clear();
//获取对应元素的索引下标,如果找不到则返回-1
System.out.println("下标"+list.indexOf(Integer.valueOf(666)));
//遍历
list.add("a");
list.add("b");
list.add("c");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
for (Object o : list) {
System.out.print(o+" ");
}
}
}
5.2.LinkedList
5.2.1.个人理解
个人理解:可以把链表看成曲别针,多个曲别针两端互相连接成为一条链,假如只有一个曲别针,那么它的"头部"就是这条链的"头部",同时"尾部"也是这条链的"尾部",如果向头部添加新的曲别针,那么新的曲别针的"尾部"就会连接(指向)旧曲别针的"头部",新曲别针的"头部"就会变成这条链的"头部",同理,如果向尾部部添加新的曲别针,那么新的曲别针的"头部"就会连接(指向)旧曲别针的"尾部",新曲别针的"尾部"就会变成这条链的"尾部"
5.2.2.基本使用
- LinkedList 底层是双向链表,随机添加和删除效率极高,但是查询和更改效率极低
- 双向:互相可以找到,可以向下找,也可以向上找
- 链表中存储的都是节点对象,每一个双向链表中的节点都包含三个属性:保存的值,下一个节点的引用,上一个节点的引用
- 基本用法和 ArrayList 类似,只不过 LinkedList 多了一些用法 (++)
public class Collection_04_LinkedList {
public static void main(String[] args) {
LinkedList list=new LinkedList();
//添加到尾部
list.add("a");
list.add("b");
list.add("c");
list.add(6);
list.addLast(9); ++
list.offerLast(99); ++
System.out.println(list);
//添加到头部
list.push(8); ++
list.addFirst(88); ++
list.offerFirst(888); ++
//添加到指定位置
list.add(1, 66);
System.out.println(list);
//根据内容删除
list.remove("a");
System.out.println(list);
//根据下标删除
list.remove(2);
System.out.println(list);
//如果我们要根据内容删除,且该内容是数字
//那么必须将该值转换为对应的包装类,否则会被当做下标
list.remove(Integer.valueOf(6));// 6 就是想要删的内容,不是下标
System.out.println(list);
//根据下标修改
list.set(1, 666);
System.out.println(list);
//根据索引进行查询 ( 索引即下标,下标即索引 )
System.out.println(list.get(1));
System.out.println("首"+list.getFirst()); ++
System.out.println("尾(#`O′)"+list.getLast()); ++
//获取第一个元素,并删除该元素,如果没有该元素(个数为0)则报错java.util.NoSuchElementException
System.out.println(list.pop()); ++
//获取第一个元素,并删除该元素,如果没有该元素(个数为0)则返回null
System.out.println(list.poll()); ++
System.out.println(list);
//个数
System.out.println(list.size());
//判断是否包含
System.out.println(list.contains(666));
//判断是否为空 ( 个数是否为0 )
System.out.println(list.isEmpty());
//清空
list.clear();
//获取对应元素的索引下标,如果找不到则返回-1
System.out.println("下标"+list.indexOf(Integer.valueOf(666)));
//遍历
list.add("a");
list.add("b");
list.add("c");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
for (Object o : list) {
System.out.print(o+" ");
}
}
}
5.2.3.底层实现
java.util …
5.2.3.1.节点类Node
private static class Node<E> {//是 LinkedList 中的静态内部类
E item;//我们要保存的元素 比如 add(6) 这里就保存6
Node<E> next;//下一个节点的对象地址
Node<E> prev;//上一个节点的对象地址
//构造方法
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
5.2.3.2.LinkedList
transient int size = 0;// 已添加元素个数
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;//首节点
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;//尾节点
5.2.3.3.添加-add
public boolean add(E e) {
linkLast(e);
return true;//添加成功则返回true
}
private void linkFirst(E e) {//头部添加
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
void linkLast(E e) {//尾部添加
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
上面代码看不懂就看图,图看不懂可以看看上面我的个人理解,好好想一下
5.2.3.4.获取-get
public E get(int index) {
checkElementIndex(index);//校验下标是否合法,不合法则抛出下标越界异常
return node(index).item;//下标合法则获取数据并返回
}
//校验函数
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
//获取数据
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
链表中是没有下标的,但是提供了下标操作的方法,但是并不是下标,本质上是帮我们进行遍历查找,也是通过 next 一个一个找的
5.2.3.5.删除原理
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item; //节点x处的元素
final Node<E> next = x.next;//节点x的下一个节点
final Node<E> prev = x.prev;//节点x的上一个节点
//如果x的上一个节点为空,说明x是首节点
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
//如果x的下一个节点为空,说明x是尾节点
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;//把节点保存的元素删除
size--; //个数
modCount++;
return element;
}