(好了 总结一句 一个是java写的 一个是c++写的 很好 就是这样 皮一手)
一、数组声明了它容纳的元素的类型,而集合不声明。
二、数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
三、数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
四、数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
Java 集合类型分为 Collection 和 Map,它们是 Java 集合的根接口,这两个接口又包含了一些子接口或实现类。图 1 和图 2 分别为 Collection 和 Map 的子接口及其实现类。
黄色块为集合的接口,蓝色块为集合的实现类
java集合类 分点做简单介绍 以及使用 介绍 优势以及劣势 以及 基本知识点
Collection 接口是 List、Set 和 Queue 接口的父接口,通常情况下不被直接使用。Collection
接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作。定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue
集合。
这里 我们介绍一下 Arraylist和LinkedList ,stack和vector 不做过多介绍 具体用法 和stl容器的差不多。
在Java中,必须导入 java.util.List 包才能使用List。
List<Integer> numbers = new LinkedList<>();
LinkedList<Integer> numbers = new LinkedList<>();
在这里,我们已经创建Vector,ArrayList和LinkedList类的对象。现在这些对象就可以使用List接口的功能。
List接口包括Collection接口的所有方法。 这是因为Collection是List的超级接口。
Collection接口中还提供了一些常用的List接口方法:
add() - 将元素添加到列表
addAll() - 将一个列表的所有元素添加到另一个
get() - 有助于从列表中随机访问元素
iterator() - 返回迭代器对象,该对象可用于顺序访问列表的元素
set() - 更改列表的元素
remove() - 从列表中删除一个元素
removeAll() - 从列表中删除所有元素
clear() - 从列表中删除所有元素(比removeAll()效率更高)
size() - 返回列表的长度
toArray() - 将列表转换为数组
contains() - 如果列表包含指定的元素,则返回true
list接口种的集合 通用!
package com.example.demo;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
public class working {
public static void main(String[] args) {
//使用LinkedList类创建列表
List<Integer> numbers = new LinkedList<>();
//将元素添加到列表
numbers.add(1);
numbers.add(2);
numbers.add(3);
System.out.println("List: " + numbers);
//从列表中访问元素
int number = numbers.get(2);
System.out.println("访问元素: " + number);
//使用indexOf()方法
int index = numbers.indexOf(2);
System.out.println("位置3的元素是 " + index);
//从列表中删除元素
int removedNumber = numbers.remove(1);
System.out.println("删除元素: " + removedNumber);
//将列表转为数组
Object[] array = numbers.toArray();
System.out.print("剩余数组元素为:");
for(int i=0;i<array.length;i++)
System.out.print(array[i]+" ");
//换行
System.out.println();
//从列表中查询某数
System.out.println("能不能从数组中查询到1 "+numbers.contains(1));
}
}
双向链表
让我们来看看底层源码
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++;
}
开头就说了 因为LinkedList是双向循环链表 所以它也可以当成队列,栈和双端队列来使用
首先我们先来说一下LinkedList关于队列操作的源码。
队列的基本方法
//定义
LinkedList<Integer> queue = new LinkedList<Integer>();
//添加元素
queue.add(1);
//删除队列头元素
queue.poll();
//获取队列头元素,不删除
queue.peek();
.add()//添加元素
public boolean add(E e) {
linkLast(e); //添加在队尾
return true;
}
.poll()//删除队列头元素
// 删除并返回第一个节点
// 若LinkedList的大小为0,则返回null
public E poll() {
if (size == 0)
return null;
return removeFirst();
}
.peek()//获取队列头元素,不删除
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peek() {
if (size == 0)
return null;
return getFirst();
}
栈的基本方法
//定义栈
LinkedList<Integer> stack = new LinkedList<Integer>();
//将元素插入到栈顶
stack.push(1)
//取出栈顶的元素并删除栈顶的元素
stack.pop()
//获取栈顶元素,不删除
stack.peek()
源码分析如下:
.push() //将元素插入到栈顶
// 将e插入到双向链表开头
public void push(E e) {
addFirst(e);
}
.pop() //取出栈顶的元素并删除栈顶的元素
// 删除并返回第一个节点
public E pop() {
return removeFirst();
}
.peek() //获取栈顶元素,不删除
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peekFirst() {
if (size == 0)
return null;
return getFirst();
}
双端队列的基本方法
//定义
LinkedList<Integer> deque = new LinkedList<Integer>();
deque.addFirst(); //在队列头部添加
deque.pollFirst(); //删除头部第一个元素(等价于poll())
deque.peekFirst(); //获取头部第一个元素(等价于peek())
deque.addLast(1); //在队列尾部添加(等价于add())
deque.pollLast(); //删除尾部第一个元素
deque.peekLast(); //获取尾部第一个元素
演示:
.addFirst() ; //在队列头部添加
// 将元素添加到LinkedList的起始位置
public void addFirst(E e) {
addBefore(e, header.next);
}
.pollFirst() ; //删除头部第一个元素(等价于poll())
// 删除并返回第一个节点
// 若LinkedList的大小为0,则返回null
public E pollFirst() {
if (size == 0)
return null;
return removeFirst();
}
.peekFirst() ; //获取头部第一个元素(等价于peek())
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peekFirst() {
if (size == 0)
return null;
return getFirst();
}
.addLast() ; //在队列尾部添加(等价于add())
// 将元素添加到LinkedList的结束位置
public void addLast(E e) {
addBefore(e, header);
}
.pollLast() ; // 删除并返回最后一个节点
// 删除并返回最后一个节点
// 若LinkedList的大小为0,则返回null
public E pollLast() {
if (size == 0)
return null;
return removeLast();
}
.peekLast() ; //获取尾部第一个元素
// 返回最后一个节点
// 若LinkedList的大小为0,则返回null
public E peekLast() {
if (size == 0)
return null;
return getLast();
}
底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
课后问题:
为什么LinkedList是线程不安全的?
package com.example.demo;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Vector;
public class working {
public static void main(String[] args) {
//使用ArrayList类创建列表
List<Integer> numbers = new ArrayList<>(); // <里面不能使用基本类型> 这个在泛型的时候 会说到 要用包装类
//将元素添加到列表
numbers.add(1);
numbers.add(2);
numbers.add(3);
System.out.println("List: " + numbers);
//从列表中访问元素
int number = numbers.get(2);
System.out.println("访问元素: " + number);
//从列表中删除元素
int removedNumber = numbers.remove(1);
System.out.println("删除元素: " + removedNumber);
//将列表转为数组
Object[] array = numbers.toArray();
System.out.print("剩余数组元素为:");
for(int i=0;i<array.length;i++)
System.out.print(array[i]+" ");
//换行
System.out.println();
//从列表中查询某数
System.out.println("能不能从数组中查询到1 "+numbers.contains(1));
}
}
名称是elementData,类型是Object[],所以ArrayList里面可以存放任意类型的元素。
我们都知道Arraylist是一个动态的数组(说白了 也就是 可以根据内容来确定数组的大小)
ArrayList在JDK1.8与JDK1.7底层区别
JDK1.7:ArrayList像 饿汉式 ,直接创建一个初始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
JDK1.8:ArrayList像
懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
可以,ArrayList存储的类型是object,null属于object类型。
private void printList() {
List<Integer> dataList = new ArrayList<>();
dataList.add(1);
dataList.add(null);
dataList.add(null);
for (Integer d : dataList) {
System.out.println(d);
}
System.out.println("------------------------");
for (Integer d : dataList) {
if (d != null) { // 需要这个判断吗?
System.out.println(d);
}
}
}
输出:
1
null
null
------------------------
1
Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
Array 大小是固定的,ArrayList 的大小是动态变化的。
ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
课后问题: 为什么说ArrayList是线程不安全的?
答案在以前写的Arraylist源码分析中:https://blog.csdn.net/qq_54729417/article/details/121066332
由于Queue是一个接口,因此我们无法提供它的直接实现。
为了使用Queue的功能,我们需要使用实现它的类:
在队列中,以先进先出的方式存储和访问元素。也就是说,从后面添加元素,从前面删除元素。
Queue接口的一些常用方法是:
相信大家都看到了 这里很多方法都和相似 那么 它们之间有什么区别呢?
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。
这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer()
返回的 false。
remove() 和 poll() 方法都是从队列中删除第一个元素(head)。remove() 的行为与 Collection接口的版本相似,
但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。
element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element()
抛出一个异常,而 peek() 返回 null。
在Java中,必须导入java.util.Set包才能使用Set。
//使用HashSet实现Set
Set<String> animals = new HashSet<>();
Set接口中还提供了Collection接口的一些常用方法:
Java Set接口允许我们执行基本的数学集合运算,例如并集,交集和子集。
package com.example.demo;
import java.util.*;
public class working {
public static void main(String[] args) {
//使用HashSet类创建集合
Set<Integer> set1 = new HashSet<>();
//将元素添加到set1
set1.add(2);
set1.add(3);
System.out.println("Set1: " + set1);
//使用HashSet类创建另一个集合
Set<Integer> set2 = new HashSet<>();
//添加元素
set2.add(1);
set2.add(2);
System.out.println("Set2: " + set2);
//两个集合的并集
set2.addAll(set1);
System.out.println("并集是: " + set2);
}
}
HashSet和HashMap都是默认初始容量是16(jdk1.7的),但是jdk1.8做了优化,初始容量为0,第一次存元素的时候才扩容为16,加载因子是0.75,扩容为原来的2倍。而带LinkedHashSet和LinkedHashMap是链表不存在扩容的,HashSet:底层是数组+链表的结构。
不能 为什么呢 ?
引用对象在存储的时候,会先根据hashcode值,存放到hash表的相应位置,如果该位置已经被占,就通过equals方法判断是否相同,相同就不存,不同就继续存放(并不会覆盖原来的)。
当然我们在用hashset存储对象时,重写hashCode和equals对象,就可以改变其原本的结果。
在Java中,Map元素存储在键/值对中。 键是与各个值相关联的唯一值。
Map集合不能包含重复的键。并且,每个键都与一个值相关联。
Map接口维护3个不同的集合:
因此,我们可以分别访问键,值和关联。
在Java中,我们必须导入java.util.Map包才能使用Map。导入包后,将按照以下方法创建map。
//使用HashMap类创建Map
Map<Key, Value> numbers = new HashMap<>();
在上面的代码中,我们创建了一个名为numbers的Map。我们已经使用HashMap类来实现Map接口。
这里,
Map接口包括Collection接口的所有方法。这是因为Collection是Map的超级接口。
除了Collection接口中可用的方法之外,Map接口还包括以下方法:
package com.example.demo;
import java.util.*;
public class working {
public static void main(String[] args) {
//使用HashMap类创建map
Map<String, Integer> numbers = new HashMap<>();
//将元素插入map集合
numbers.put("One", 1);
numbers.put("Two", 2);
System.out.println("Map: " + numbers);
//map的键
System.out.println("Keys: " + numbers.keySet());
//map的值
System.out.println("Values: " + numbers.values());
//map的条目
System.out.println("Entries: " + numbers.entrySet());
//从map集合中删除元素
int value = numbers.remove("Two");
System.out.println("被删除的值是: " + value);
}
}
HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。 HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。 如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。
HashMap采⽤Entry数组来存储key-value对,每⼀个键值对组成了⼀个Entry实体,Entry类实际上是⼀个单向的链表结构,它具有Next指针,可以连接下⼀个Entry实体。 只是在JDK1.8中,链表⻓度⼤于8的时候,链表会转成红⿊树!
由于我们的数组的值是限制死的,我们在对key值进行散列取到下标以后,放入到数组中时,难免出现两个key值不同,但是却放入到下标相同的格子中,此时我们就可以使用链表来对其进行链式的存放。
线性探测再散列
放入元素,如果发生冲突,就往后找没有元素的位置;
平方探测再散列
如果发生冲突,放到(冲突+1平方)的位置,如果还发生冲突,就放到(冲突-1平方)的位置;如果还有人就放到(冲突+2平方)的位置,以此类推,要是负数就倒序数。
优点
对于记录总数频繁可变的情况处理的较好;
结点是动态分配,不会造成内存的浪费;
删除记录比较方便,可是直接通过指针操作;
缺点
存储的记录是随机分布在内存中的,跳转访问时会带来额外的开销;
由于使用指针,记录不容易进行序列化操作;
List:元素有序,元素可重复,添加的元素放在最后(按照插入顺序保存元素)
ArrayList
数据结构为数组,访问快(可以直接通过下标访问),增删慢,未实现线程同步。
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法 并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
LinkedList
数据结构为链表,增删速度快,查询慢,未实现线程同步LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
Vector类
数据结构为数组,访问快(可以直接通过下标访问),增删慢,实现线程同步 Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出
ConcurrentModificationException。
Set:元素无序并且不允许重复元素
HashSet
数据结构为哈希表,元素无序、不重复,至多有一个null元素 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成。 特点如下 不能保证元素的排列顺序,顺序有可能发生变化 不是同步的 集合元素可以是null,但只能放入一个null当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
LinkedHashSet
数据结构是哈希表和链表,与HashSet相比访问更快,插入时性能稍微
LinkedHashSet继承自HashSet。同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
TreeSet
数据结构是二叉树(红黑树),元素可排序、不重复
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式:自然排序和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序 (1)TreeSet内的元素实现Comparable接口,重写该接口的compareTo(Object
obj)方法,以此确定排序。(元素必须实现该接口,否则程序会抛出异常)。
(2)当重写元素对应类的equals()方法时,应该保证该方法与compareTo(Objectobj)方法有一致的结果,即如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较结果应该也为0(即相等) 定制排序-- 自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现int compare(To1,To2)方法
Queue:元素有序,先进先出
ArrayDeque
数据结构为数组,双端队列,在队头队尾均可心插入或删除元素 实现了DeQueue接口。
DeQueue(Double-ended queue)继承了Queue接口,创建双向队列,灵活性更强,可以前向或后向迭代,
PriorityQueue
数据结构为优先级队列,元素不允许null,非同步
ArrayList 和Vector,底层都是Object数组,默认加载因子都是1(元素满了才扩展容量).默认容量都是10;但是ArrayList 在jdk1.8时默认为空,当添加元素时,才初始化为10个容量。ArrayList:新容量为原容量的1.5倍,Vector:新容量为原容量的2倍.
LinkedList:没有扩容机制,因为其底层是双向链表结构。不存在数组的扩容一说,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。
HashSet和HashMap都是默认初始容量是16(jdk1.7的),但是jdk1.8做了优化,初始容量为0,第一次存元素的时候才扩容为16,加载因子是0.75,扩容为原来的2倍。而带LinkedHashSet和LinkedHashMap是链表不存在扩容的,HashSet:底层是数组+链表的结构
后续添加