文章目录
集合
集合与数组的相似点:
- 都可以存储多个对象。对外作为一个整体存在
数组的缺点:
- 长度必须在初始化是指定,且固定不变
- 数组采用连续存储空间,删除和添加效率低下
- 数组缺乏封装,操作繁琐
集合架构:
- Collection接口存储一组不唯一,无序的对象
- List接口存储一组不唯一,有序(索引顺序)的对象
- Set接口存储一组唯一,无序的对象
- Map接口存储一组键值对象,提供Key到value的映射(Key 唯一 无序 )(value 不唯一 无序)
List集合
List集合的主要实现类有ArrayList和LinkedList,分别是数据结构中顺序表和链表的实现。另外还包括栈和队列的实现类:Deque和Queue。
List特点:有序 不唯一(可重复)
ArrayList
ArrayList:
- 在内存中分配连续的空间,实现了长度可变的数组
- 优点:遍历元素和随机访问元素的效率比较高
- 缺点:添加和删除需大量移动元素,效率低,按照内容查询效率
ArrayList的使用:
public class ArrList1 {
public static void main(String[] args) {
//定义一个ArrayList集合
//<泛型>中只能放一个对象,不能放基本类型,所以放int的包装类
ArrayList<Integer> list=new ArrayList<>();
list.add(123); //往集合里存入数据,jdk5以后提供自动装箱
list.add(234);
list.add(456);
list.add(new Integer(886));
//从集合里取出单个数据(ArrayList底层是用数组实现,所以用下标取值)
Integer integer = list.get(3);
System.out.println(integer);
//返回集合的长度
System.out.println(list.size());
//遍历集合方式一
for (int i = 0; i <list.size() ; i++) {
System.out.print(list.get(i)+"\t");
}
//遍历集合方式二
for (Integer arrlist: list) {
System.out.println(arrlist);
}
}
}
ArrayList更多的使用方法:
public class ArrList2 {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//可以在指定下标位置添加元素,【下标,元素】
list.add(1,23);
ArrayList<Integer> list2=new ArrayList<>();
list2.add(888);
list2.add(999);
//把List2中的元素全部存储到List中
list.addAll(list2);
//修改指定下表元素
list.set(0,11);
//删除指定下表元素
list.remove(1);
//删除指定内容元素
list.remove(new Integer(3));
//删除整个数组
list.removeAll(list2);
//清空集合
//list.clear();
//判断集合是否为空
boolean empty = list.isEmpty();
System.out.println(empty);
//判断集合是否包含指定元素
boolean contains = list.contains(555);
System.out.println(contains);
System.out.println("集合输出:"+list.toString());
}
}
理解ArrayList的底层源码:
/*
ArrayList底层源码解析:
当 ArrayList<Integer> list=new ArrayList<>(); 时
底层发生了什么?
*/
//1. 进入ArrayList方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 2. 进入 elementData
transient Object[] elementData;
// 3. 进入DEFAULTCAPACITY_EMPTY_ELEMENTDATA
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
所以 Object[] elementData={};
底层创建一个空数组,长度为0,如果用户在集合中增加内容,那么这时候数组直接进行扩容
那么底层的数组是如何进行扩容的?
*/
//1. 进入add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//2. 进入ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 3. 进入calculateCapacity方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
4. DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};
DEFAULT_CAPACITY=10;
//5. 进入ensureExplicitCapacity方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 6. 进入grow方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
//7. 进入hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/* 如果我们第一次在数组中添加数据,这个时候底层会给数组长度为10。
我们后续添加的内容如果超出了长度会进行二次扩容,增加50%,即新数组为旧数组的1.5倍。
如果增加了50%不够用,那么会直接使用新内容的长度作为新数组的长度,如果新增内容超过数组最大长度, 抛出异常
*/
//底层是如何用get方法获取数据的呢?
1.进入get方法:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
2.进入rangeCheck(index)方法:
private void rangeCheck(int index) {
if (index >= size) //给定的下标超过数组长度,也就是数组越界,抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3.进入elementData(index)方法:
E elementData(int index) {
return (E) elementData[index]; //直接返回数组下标对应的值
}
ArrayList在JDK1.7和1.8中的区别
JDK1.7中,使用无参构造方法创建ArrayList对象时,默认底层数组长度是10。
JDK1.8中,使用无参构造方法创建ArrayList对象时,默认底层数组长度是0;第一次添加元素,容量不足就要扩容,这样是为了避免创建集合不添加数据而造成的资源浪费。
LinkedList
LinkedList:
- 采用双链表存储的方式
- 缺点:插入、删除元素效率比较高(但是前提也是必须先低效率查询才可。如果插入删除发生在头尾可以减少查询次数)
LinkedList的使用:
public class LinkedList1 {
public static void main(String[] args) {
LinkedList<Integer> list=new LinkedList<>();
list.add(123);
list.add(456);
list.add(789);
list.addFirst(333); //在第一个位置添加元素
list.addLast(888); //在最后一个位置添加元素
list.add(1,911); //在中间指定位置添加元素
list.remove(0);//删除指定元素
list.removeFirst();//删除第一个元素
list.removeLast();//删除最后一个元素
list.getFirst();//获取第一个元素
list.getLast();//获取最后一个元素
System.out.println(list.toString());
}
}
理解LinkedList的底层源码:
//LinkedList底层源码:
//进入LinkedList<>
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
// 1.进入add方法
public boolean add(E e) {
linkLast(e);
return true;
}
// 2.进入 linkLast方法
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++;
}
ArrayList与LinkedList的区别
问题1:将ArrayList替换成LinkedList之后,不变的是什么?
- 运算结果没有变
- 执行的功能代码没有变
问题2:将ArrayList替换成LinkedList之后,变化的是什么?
-
底层的结构变了
ArrayList:数组 LinkedList:双向链表
-
具体的执行过程变化了 list.add(2,99)
ArrayList:大量的后移元素
LinkedList:不需要大量的移动元素,修改节点的指向即可
问题3:到底是使用ArrayList还是LinkedList
- 根据使用场合而定
- 大量的根据索引查询的操作,大量的遍历操作(按照索引0–n-1逐个查询一般),建议使用ArrayList
- 如果存在较多的添加、删除操作,建议使用LinkedList
问题4:LinkedList增加了哪些方法
- 增加了对添加、删除、获取首尾元素的方法
- addFirst()、addLast()、removeFirst()、removeLast()、getFirst()、getLast().
Java中栈和队列的实现类
public static void main(String[] args) {
//栈 特点:先进后出
Stack<String> stack=new Stack<>();
//入栈
stack.push("ABC");
stack.push("SDF");
stack.push("VBN");
//栈顶
System.out.println(stack.peek());
//出栈
stack.pop();
//单端队列 特点:先进先出
Queue<String> queue=new LinkedList<>();
//入队
queue.offer("01");
queue.offer("02");
queue.offer("03");
System.out.println(queue.peek()); //栈顶
System.out.println(queue.poll());//出队
//双端队列
Deque<String> deque=new LinkedList<>();
//入队
deque.push("11");
deque.push("22");
deque.push("33");
deque.push("44");
//出队
deque.pop();
}