申明:
内容来源于老师的课堂笔记,这只是我的整理以及一些额外补充。
十、集合
了解点
CRUD:Create、Retrieve、Update、Delete
1、集合的接口
集合的父接口(除Map外):Collection
掌握点
1、集合的概念
1)数组的缺点
①只能存放单一类型的数据
②数组中提供对数据操作的CRUD方法有限
③数组中存放的数据是有序、可重复的,不能满足无序不可重复的数据存储
2)集合的分类:
①List:有序、可重复
②Set:无序、不可重复
③Map:以键值对(key-value)的形式存储,键不可重复、值可以重复
2、List接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable {
}
基本方法:
List<Object> list = new ArrayList<Object>();
Object obj = new Object();
Collection<Object> c = new ArrayList<Object>();
list.add(obj);
list.add(0, obj);
list.add("obj");
list.add(9);
list.addAll(c);
list.set(0, new Object());
list.clear();
list.contains(obj);
list.containsAll(c);
list.indexOf(obj);
list.isEmpty();
list.size();
list.toArray();//Returns an array containing all of the elements in this list in propersequence (from first to last element).
集合的遍历:
①For循环
②Iterator迭代器(代码参考自:Java Iterator(迭代器) | 菜鸟教程 (runoob.com))
// 引入 ArrayList 和 Iterator 类
import java.util.ArrayList;
import java.util.Iterator;
public class RunoobTest {
public static void main(String[] args) {
// 创建集合
ArrayList<String> sites = new ArrayList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
sites.add("Zhihu");
// 获取迭代器
Iterator<String> it = sites.iterator();
// 输出集合中的所有元素
while(it.hasNext()) {
System.out.println(it.next());
it.remove();//输出后删除
}
}
}
③For...each(是Java5的新特性之一)
for(元素类型t 元素变量x : 遍历对象obj){
引用了x的java语句;
}
集合存放的是对象的引用地址,对于基本类型存放的是对应的包装类(包装类才有引用地址)
1)List排序
①对基本类型和String类型排序(使用sort方法)
以Integer为例子,升序排序:
//Integer集合,正序排序
List<Integer> list = new ArrayList<Integer>(Arrays.asList(10, 3, 6, 1, 4, 5, 9));
Collections.sort(list);
System.out.println("集合正序排序:");
for (Integer num : list) {
System.out.println(num);
}
//倒叙排序
Comparator<Integer> reverseComparator = Collections.reverseOrder();
Collections.sort(list, reverseComparator);
System.out.println("集合倒叙排序:");
for (Integer num : list) {
System.out.println(num);
}
以上两段排序代码来自:Java—Sort排序_java排序_v2hoping的博客-CSDN博客
sort()可以对八种基本类型以及String类型进行排序,因为他们都各自实现了Comparable接口,重写了compareTo方法
②对自定义对象类型排序-Comparable接口-compareTo()
当前需要被排序的类型要实现Comparable接口,并重写compareTo方法自定义排序规则,自定义也可以实现多级排序
引用自:Java-Comparable类(重写compareTo方法)_重写comparable_你上你也行的博客-CSDN博客
public class ComparableRectangle implements Comparable<ComparableRectangle> { double area; double width; double height; public ComparableRectangle(double width,double height){ this.width=width; this.height=height; } public double getArea(){ return height*height; } @Override public int compareTo(ComparableRectangle o) { if (this.getArea()<o.getArea()){ return -1; }else if (this.getArea()==o.getArea()){ return 0; }else{ return 1; } } public static void main(String[] args) { ComparableRectangle c1=new ComparableRectangle(10,11); ComparableRectangle c2=new ComparableRectangle(12,13); System.out.println("比较结果:"+c1.compareTo(c2)); } }
③独立创建排序规则-Comparator接口-compare()
给指定排序的类独立创建排序规则,提升内聚性,降低耦合度。
和compareTo()的写法相差无几,compare()有两个形参。
默认按自然顺序升序(-1,0,1)排序,降序则反向返回(1,0,-1)即可。
使用.sort()调用
引用自:[Java] Comparator接口/compare方法的介绍与使用_Pilipilip233的博客-CSDN博客
@Test public void test4(){ Product[] arr = new Product[4]; arr[0] = new Product("A",59); arr[1] = new Product("B",12); arr[2] = new Product("C",44); arr[3] = new Product("D",102); Arrays.sort(arr, new Comparator<Product>() { @Override public int compare(Product o1, Product o2) { if (o1.getName().equals(o2.getName())){ return -Double.compare(o1.getPrice(),o2.getPrice()); }else { return o1.getName().compareTo(o2.getName()); } } }); // 未实现Comparable接口会抛异常 System.out.println(Arrays.toString(arr)); }
2)List源码分析-ArrayList
*源码查看方法:在类名上按F3,选择添加jdk下的src.zip
(1)Java8的源码
①创建ArrayList()对象
在创建对象前就已经把对象数组初始化了,创建对象时可以直接赋值
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
其中this.element定义在
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
transient是一个之间没有接触过的关键词,去深入地了解了一下
以下内容来自:Java transient 关键字 | 菜鸟教程 (runoob.com)
1. transient的作用及使用方法
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
2. transient 使用小结
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
然后发现自己对序列化的概念也不太熟悉
以下内容来自:java序列化,看这篇就够了 - 9龙 - 博客园 (cnblogs.com)
一、序列化的含义、意义及使用场景
- 序列化:将对象写入到IO流中
- 反序列化:从IO流中恢复对象
- 意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
- 使用场景:所有可在网络上传输的对象都必须是可序列化的,比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。通常建议:程序创建的每个JavaBean类都实现Serializeable接口。
二、序列化实现的方式
如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现Serializable接口或者Externalizable接口之一。
1、Serializable
1.1 普通序列化
Serializable接口是一个标记接口,不用实现任何方法。一旦实现了此接口,该类的对象就是可序列化的。
- 序列化步骤:
步骤一:创建一个ObjectOutputStream输出流;
步骤二:调用ObjectOutputStream对象的writeObject输出可序列化对象。
public class Person implements Serializable { private String name; private int age; //我不提供无参构造器 public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class WriteObject { public static void main(String[] args) { try (//创建一个ObjectOutputStream输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) { //将对象序列化到文件s Person person = new Person("9龙", 23); oos.writeObject(person); } catch (Exception e) { e.printStackTrace(); } } }
- 反序列化步骤:
步骤一:创建一个ObjectInputStream输入流;
步骤二:调用ObjectInputStream对象的readObject()得到序列化的对象。
我们将上面序列化到person.txt的person对象反序列化回来
public class Person implements Serializable { private String name; private int age; //我不提供无参构造器 public Person(String name, int age) { System.out.println("反序列化,你调用我了吗?"); this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class ReadObject { public static void main(String[] args) { try (//创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"))) { Person brady = (Person) ois.readObject(); System.out.println(brady); } catch (Exception e) { e.printStackTrace(); } } } //输出结果 //Person{name='9龙', age=23}
②Add()
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
ArrayList的扩容机制 ↑
③get(index)
先判断下标是否越界,再返回元素
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
④Add(int,element)
先检查是否越界,再扩容,然后将index的位置空出来,将要插入的元素放在index
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
⑤Set(int,element)
先将旧元素取出,把set的元素加入,返回旧元素
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
⑥Remove(int index) / Remove(Object obj)
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
(2)Java7和Java8的源码区别
关于创建ArrayList对象
Java7中的ArrayList对象的创建(底层object[]的创建)相当于单例模式中的懒汉式
Java8中的ArrayList对象的创建(底层object[]的创建)相当于单例模式中的饿汉式
3)List的其他实现类
(1)LinkedList类
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
使用Node节点存储数据(Node为内部类)
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
linkLast()和linkFirst()
/**
* Links e as first element.
*/
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++;
}
/**
* Links e as last element.
*/
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++;
}
元素查找:
在链表中查找,先判断当前位置是否为size的一半。
若小于一半,从前往后找,找next
若大于一半,从后往前找,找prev
/**
* Returns the (non-null) Node at the specified element index.
*/
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;
}
}
ArrayList和LinkedList的比较:
ArrayList 数组实现 增删慢,查询快
LinkedList 链表实现 增删快,查询慢
(2)Vector类
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
①创建对象
/**
* Constructs an empty vector with the specified initial capacity and
* capacity increment.
*
* @param initialCapacity the initial capacity of the vector
* @param capacityIncrement the amount by which the capacity is
* increased when the vector overflows
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
/**
* Constructs an empty vector with the specified initial capacity and
* with its capacity increment equal to zero.
*
* @param initialCapacity the initial capacity of the vector
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
②add()
扩容机制同样通过grow()
实现了synchronized线程同步,线程安全的
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
ArrayList 和 Vector 比较
ArrayList 数组实现 轻量级,速度快,线程不安全
Vector 数组实现 重量级,速度慢,线程安全
4)List接口面试题
1、ArrayList和LinkedList、ArrayList和Vector的异同点(底层实现机制、扩容机制、线程安全)
2、尝试写一个MyList接口,来实现对数据的CRUD等基本操作
3、Set接口
public interface Set<E> extends Collection<E>
Set是无序且不可重复的
Set最常用的实现类是HashSet
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
常用方法以及遍历方法:
Set s = new HashSet();
s.add(new Object());
s.remove(new Object());
s.clear();
s.isEmpty();
for(Object obj:s) {
System.out.println(obj);
}
遍历Set只能使用迭代器和for...each,因为Set是无序的。
- Set元素的存储 / 去重原理
实际上使用HashMap来存储数据
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
①Set的不可重复性:对于基本类型而言是值不重复;对于对象而言,是引用地址不重复。对于对象而言,一般是根据业务逻辑属性是否相等判断,所以需要重写equals方法。即,set中不存在任意两个元素e1.equals(e2)==true,null值也只能存在一个。
②Set的无序性:无序但不随机,set输出的元素顺序是固定的,固定但不确定。Set中每添加一个元素,存入的位置是根据当前元素的哈希值(引用地址)决定的,因哈希值固定,固位置固定。
③Set元素的存储过程
A、当添加一个元素时,会根据元素的哈希散列算法计算出一个元素存储的位置【不同哈希值通过同一个散列算法得到的位置有可能是一样的】(哈希冲突)
B、当计算出的位置上如果没有元素时,这个元素就是不重复的,直接储存
C、当计算出的位置上有元素时,则需要通过equals和这个位置上的所有元素进行比较,如果重复,则直接丢弃,如果不重复,则把元素链接到此位置上七上八下。
- 哈希散列算法
- 采用哈希表(散列)原理,hash算法的意义在于提供了一种快速存取数据的方法,它用一种算法建立键与值之间的对应关系(每个值只能有一个键,但是一个键能对应多个值),这样可以在数组等数据结构中快速存取数据。
- 简单的说就是将任意内容的输入转换成相同长度输出的加密方式
1)Set的其他实现类
(1)LinkedHashSet:LinkedHashSet底层也是数组存储,只是把数据通过链表进行连接
(2)TreeSet:是SortedSet接口的实现类,可以实现对数据的排序(对象排序需要实现排序规则)
2)Set面试题
4、Map接口
key-value存储数据,key不可重复,value可重复
public interface Map<K,V>
Map基本使用:
常用方法:
Map<String,Object> map =new HashMap<String,Object>();
map.put("key", "value");
Object obj = map.get("key");
map.containsKey("key");
map.keySet();//返回一个key的集合(set
map.remove("key");
map.replace("key", "value_");
map.values();//返回一个值的集合Collection
Map的遍历:
以下内容参考自:Map的5种遍历方式 - BORS - 博客园 (cnblogs.com)
1.entrySet遍历
for (Map.Entry<String, String> entry : map.entrySet()) { String k = entry.getKey(); String v = entry.getValue(); System.out.println(k + " : " + v); }
2.在for循环中遍历key或者value,一般适用于只需要map中的key或者value时使用,在性能上比使用entrySet较好
//key for (String key : map.keySet()) { System.out.println(key); } //value for (String value : map.values()) { System.out.println(value); }
3.通过Iterator遍历
Iterator<Map.Entry<String, String>> entries = map.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<String, String> entry = entries.next(); String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + " : " + value); }
4.通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作
for (String key : map.keySet()) { String value = map.get(key); System.out.println(key + " : " + value); }
5.java8 Lambda, 性能低于entrySet,所以更推荐用entrySet的方式
map.forEach((k, v) -> { System.out.println(k + " : " + v); });
1)Map其他实现类
2)Map的源码分析
3)HashMap的面试题
5、Collections工具类
6、Queue