Java
day14——2023.8.24
iterator()方法
iterator()方法,来自于Collection接口 的 父接口 Iterable 接口
Iterable接口其实就定义了Iterator iterator();这个抽象方法,其实就是用来指定将来接口中迭代元素的规则
ArrayList 实现了List接口,List接口,继承自Collection接口,Collection接口中,存在iterator()方法
ArrayList 类中,必定重写了 iterator()方法,因为iterator()是父接口的抽象方法
ArrayList中的方法如下 :
返回值是 new Itr(), 说明Itr是 Iterator接口的自实现类,那么既然Itr是实现类,必定会重写Iterator接口中的抽象方法 hasNext() ,next()
通过查看源码,发现 Itr类,是ArrayList中的一个内部类
分析这个内部类的源码:
有一个属性 cursor ,用来记录每次返回的下一个元素的索引
重写的hasNext()方法,用来判断 cursor是否返回到最后了,和list的size相同,就结束
重写的next()方法,用来将指定的元素返回,每次返回后,将cursor指向下一个元素
public E next() {
checkForComodification();
int i = cursor;
Object[] elementData = ArrayList.this.elementData;
cursor = i + 1;
return (E) elementData[lastRet = i];
}
ArrayList总结
1,ArrayList底层是动态扩容的数组,使用无参构造创建ArrayList的时候,其实创建的是一个空数组,当放入第一个元素开始,扩容为默认容量10
2,当原来的容量放满之后,放入下一个元素的时候,数组开始扩容,扩容到原来容量的1.5倍,每次扩容都会进行底层的数组复制的操作
3,频繁的扩容,会降低ArrayList的效率 ,所以,如果知道要存入多少个元素,那么在声明的时候就直接指定容量,可以加大ArrayList的效率
4,ArrayList 是有序的,指的是存取的顺序有序,可以存放 null值,可以存放重复的元素
5,ArrayList是线程不安全的
Vector和ArrayList的区别
Vector是JDK1.2的类,是一个比较老旧的集合
Vector的底层也是数组,和ArrayList的区别是 :
Vector是线程同步的(安全的),它类中的方法上都加了synchronized来修饰,所以它的效率低一点
Vector的扩容,每次扩容2倍
LinkedList的使用
LinkedList底层是双向链表
既然是双向链表,优点是往集合中插入和删除元素较快,查找指定元素效率低一些
LinkedList还实现了Deque接口,所以LinkedList中,也有操作队列、栈的方法
LinkedList既然是链表的底层,那么必然存在Node 节点 ,在这里声明了一个内部类Node用来表示节点:
LinkedList常用的方法
public class LinkedListDemo {
public static void main(String[] args) {
//创建LinkedList对象,泛型是String
/* LinkedList<String> linkedList = new LinkedList<>();
//调用普通的list中的方法
linkedList.add("hello");
linkedList.add("world");
linkedList.add("java");
linkedList.add("mysql");
linkedList.add(4,"spring");
System.out.println(linkedList);
String s = linkedList.get(3);
System.out.println(s);
linkedList.remove(1);
System.out.println(linkedList);
//遍历
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
System.out.println("--------------");
for (String s1 : linkedList) {
System.out.println(s1);
}
System.out.println("--------------");
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
*/
//栈结构相关的方法
//E pop()
//从此列表表示的堆栈中弹出一个元素。
//void push(E e)
//将元素推送到由此列表表示的堆栈上。
/* LinkedList<String> list1 = new LinkedList<>();
list1.push("hello");
list1.push("world");
list1.push("java");
list1.push("mysql");
System.out.println(list1);
String pop = list1.pop();
System.out.println(pop); //mysql
System.out.println(list1);
*/
//队列结构相关方法
//void addFirst(E e)
//在该列表开头插入指定的元素。
//void addLast(E e)
//将指定的元素追加到此列表的末尾。
//E getFirst()
//返回此列表中的第一个元素。
//E getLast()
//返回此列表中的最后一个元素。
//E pollFirst()
//检索并删除此列表的第一个元素,如果此列表为空,则返回 null 。
//E pollLast()
//检索并删除此列表的最后一个元素,如果此列表为空,则返回 null 。
//E removeFirst()
//从此列表中删除并返回第一个元素。
//E removeLast()
//从此列表中删除并返回最后一个元素。
LinkedList<String> linkedList1 = new LinkedList<>();
linkedList1.addFirst("1");
linkedList1.addFirst("2");
linkedList1.addFirst("3");
linkedList1.addLast("4");
linkedList1.addLast("5");
linkedList1.addLast("6");
System.out.println(linkedList1);
System.out.println(linkedList1.getFirst()); //3
System.out.println(linkedList1.getLast());//6
System.out.println(linkedList1);
String s1 = linkedList1.pollFirst();
System.out.println(s1);
String s2 = linkedList1.pollLast();
System.out.println(s2);
System.out.println(linkedList1);
linkedList1.removeFirst();
linkedList1.removeLast();
System.out.println(linkedList1);
}
}
LinkedList源码解析
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
{
//记录当前双向链表的长度
transient int size = 0;
//记录当前双向链表的头节点
transient Node first;
//记录当前双向链表的尾节点
transient Node last;
构造方法
LinkedList的构造方法有两个:
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll©;
}
普通方法
add方法
add方法实际上就是往链表最后添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast(E)方法可以在当前双向链表的尾节点之后添加一个新的节点,并且调整last属性的指向位置
void linkLast(E e) {
//使用一个临时变量来记录操作前的last属性信息
final Node<E> l = last;
//创建一个新的节点,item属性值为e,新节点的前置对象指向原来的尾节点,后置节点为null
final Node<E> newNode = new Node<>(l, e, null);
//因为要在双向链表的尾节点添加新的节点,将last属性中的信息重新设置
last = newNode;
//条件成立,说明双向链表没有任何节点
if (l == null)
//将first节点指向新的节点,first和last都同时指向同一个节点
first = newNode;
else
//不成立,双向链表至少有一个节点,将原来的尾节点的后置节点指向新的尾节点
l.next = newNode;
//双向链表长度 + 1
size++;
//linkedList集合的操作次数 + 1
modCount++;
}
addLast方法,同add方法
public void addLast(E e) {
linkLast(e);
}
addFirst方法
public void addFirst(E e) {
linkFirst(e);
}
/**
* Links e as first element.
*/
private void linkFirst(E e) {
//使用一个临时的变量记录操作前first属性的信息
final Node<E> f = first;
//创建一个数据信息为e的新节点,该节点前置节点引用为null,后置节点引用指向原先的头节点
final Node<E> newNode = new Node<>(null, e, f);
//因为要在双向链表头部添加新的节点,将first属性中的信息重新设置
first = newNode;
//条件成立,说明双向链表没有任何节点
if (f == null)
//将last节点也指向新的节点,这样first和last节点属性同时指向同一个节点
last = newNode;
else
//不成立,说明双向链表至少有一个节点,只需要把原来的头节点的前置节点引用指向新的头节点
f.prev = newNode;
//双向链表长度 + 1
size++;
//linkedList集合的操作次数 + 1
modCount++;
}
remove方法
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
实际调用的是unlink()方法
E unlink(Node<E> x) {
// assert x != null;
//定义一个element变量,记录当前节点中的数据对象,以便方法最后返回
final E element = x.item;
//创建一个next节点,记录当前节点中的后置节点引用,可能为null
final Node<E> next = x.next;
//创建一个prev节点,记录当前节点中的前置节点引用,可能为null
final Node<E> prev = x.prev;
//如果条件成立,说明被移除的x节点是双向链表的头节点
if (prev == null) {
//将x的后置节点设置为新的头节点
first = next;
} else {
//将x的前置节点中的后置节点设置为移除的x节点的后置节点
prev.next = next;
//将移除的x节点的前置节点设置为null
x.prev = null;
}
//如果条件成立,说明被移除的x节点是双向链表的尾节点
if (next == null) {
//将移除的x的节点的前置节点设置为新的尾节点
last = prev;
} else {
//将x的后置节点中的前置节点设置为移除x节点的前置节点
next.prev = prev;
//将移除的x节点的后置节点设置为null
x.next = null;
}
//将移除的x节点中的数据对象设置为null
x.item = null;
//双向链表长度 - 1
size--;
//LinkedList集合操作次数 + 1
modCount++;
return element;
}
get方法
可以看到get方法实现就两段代码:
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
我们进去看一下具体的实现是怎么样的:
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;
}
}
set方法
set方法和get方法其实差不多,根据下标来判断是从头遍历还是从尾遍历
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
List总结
常用的子类ArrayList,LinkedList,Vector,有序(存取有序),可以存放null,可以重复
ArrayList:
底层是动态扩容数组、初始容量默认是0,放入元素变为10,存放第11个元素开始扩容,扩容到原来的1.5倍,查询效率快,增删效率较慢,因为增删的时候,需要数组的拷贝(native方法,c/c++实现)
LinkedList:
底层是双向链表,查询效率较低,从头到尾遍历,增删效率较高
Vector :
底层也是数组,初始容量默认为10,每次扩容2倍,方法都是同步的,效率相对较慢
一般将来,查询多用ArrayList,增删多用LinkedList
其实增加元素的时候,如果一直是增加到末尾的话,使用ArrayList也很快,一直删除末尾元素,ArrayList也快
1,什么是自动装箱和自动拆箱
自动装箱:将基本类型的值,直接赋值给对应的包装类对象。
自动拆箱:将包装类型的对象,直接赋值给对应基本类型变量
2,常见的5个运行时异常
NullPointerException(空指针异常)
ArrayIndexOutOfBoundsException(数组越界异常)
ClassCastException(类转换异常)
IllegalArgumentException(非法参数异常)
ArithmeticException(算术异常)
3,throw和throws区别
throw声明在方法内
throws声明在方法体中
throw抛出一个具体的异常类型
throws声明一个方法可能抛出的所有异常信息,表示将来异常的一种可能性,但并不一定发生异常
throw需要用户自己捕获相关异常,然后再对其处理,最后将异常信息抛出
throws通常不需要显式的捕获异常,由系统自动将异常抛给上级方法
4,final、finally、finalize 三个的区别
final 是一个修饰符,用于修饰类、方法和变量,分别表示不可继承、不可重写和不可修改。
finally 是一个关键字,用于定义在异常处理中一定会被执行的代码块。finalize 是 Object 类的一个方法,用于对象的垃圾回收前的清理操作,
5,String和StringBuffer、StringBuilder的区别
String声明的字符不可变,可以看做是字符串常量,String的内容一旦改变,引用就改变了,可以看成是一个新的引用,所以,String类型做字符串拼接,比较耗时
StringBuffer称为字符串缓冲区,提供了一些方法,可以改变该缓冲区中的数据,但是对应的引用没有改变,相对来说StringBuffer效率更快
StringBuffer和StringBuilder ,他们都是字符串缓冲区,只不过StringBuffer的方法多了一个 synchronized关键字,它是线程安全的。多线程情况下,用StringBuffer,单线程就用StringBuilder