集合
数组的不足之处:
1.长度开始必须指定,而且一旦指定,不可修改
2.保存必须为同一类型的元素
3.使用数组进行增加元素比较麻烦
集合
1 可以动态保存任意多个对象,使用比较方便。
2 提供一系列方便的操作对象的方法:add remove set get等
3 使用集合添加 删除元素操作简单
集合框架
单列集合框架
双列集合
collection 接口
public interface Collection<E> extends Iterable<E>
特点:
1.collection实现子类可以存放多个元素,每个元素可以是Object。
2.有些collection的实现类 可以存放重复的元素,有些不可以。
3.有些collection的实现类 有些是有序的(list)有些不是有序的(set)
4.collection接口没事直接的实现子类,是通过他的子接口 List 和Set 来实现的
常用方法:
ArrayList为例
List list = new ArrayList();
list.add("hello");//list(new String("hello))
list.add(10);//list(new Integer(10))
list.add(false);
list.remove(0);
list.remove("hello");
list.contains("hello"); //查找元素是否存在
list.size(); //元素个数
list.isEmpty(); //元素是否为空
list.clear();//清空元素
ArrayList list1 = new ArrayList();
list1.add("一身转战三千里");
list1.add("一剑曾当百万师");
list.addAll(list1); //添加多个元素
list.containsAll(list1); //判断多个元素存在
list.removeAll(list1);//删除多个元素
list.indexOf("hello");//元素在集合中第一次出现的位置
list.lastIndexOf("hello"); //元素在集合中最后一次出现的位置
list.set(1,"he");//指定index的元素替换
list.subList(0,2);//返回子集合 0 <= index <2
collection接口遍历元素的方式1–使用Iterator
1.Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
2.所有实现Collection接口的集合类都有iterator()方法,用于返回一个实现了Iterator接口的对象,即返回一个迭代器
3.Iterator仅用于遍历集合,本身不存放对象。
Iterator的执行原理
调用iterator.next()必须调用iterator.hasNext()
Iterator iterator = list.iterator();
//------->IDEA快捷键 itit回车
while(iterator.hasNext()){
System.out.println(iterator.next()); //指针下移 将下移以后集合位置上的元素返回
}
//while 退出后 迭代器指向最后一个元素
//需要再次遍历则需要重置迭代器 iterator = list.iterator();
collection接口遍历元素的方式2–使用for循环增强
简化版的iterator,本质一样,只能用来遍历集合或者数组。
基本语法:
for(元素类型 元素名:集合或者数组名){
访问元素
}
for (Object o : list) {
System.out.println(o);
}
List接口
List是Collection接口的子接口
1.List集合类的元素是有序的(添加顺序和去除的顺序是一致的),且可以重复
2.List集合中的每个元素都有其对应的顺序索引
3.List容器中的元素都对应一个整数型的序列号记载其在容器中的位置,可以根据序列号存取容器中的元素。
4.常用List的接口实现类,ArrayList 、 LinkedList 、Vector
List随机练习,Book按照价格排序遍历
@SuppressWarnings("all")
public static void main(String[] args) {
List list = new ArrayList<Book>();
list.add(new Book("罪与罚",30.3,"陀思妥耶夫斯基"));
list.add(new Book("复活",40.4,"列夫托尔斯泰"));
list.add(new Book("圆圈正义",20.7,"法外狂徒"));
list.add(new Book("全国富婆通讯录",10000.0,"海贼王"));
// list.sort(new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// Book o11 = (Book) o1;
// Book o21 = (Book) o2;
// return (int) (o11.getPrice()-o21.getPrice());
// }
// });
// Iterator iterator = list.iterator();
// while (iterator.hasNext()) {
// Object next = iterator.next();
// System.out.println(next);
// }
System.out.println("==================================");
sort02(list);
for (Object o : list) {
System.out.println(o);
}
}
//冒个泡
@SuppressWarnings("all")
public static void sort02(List list){
for (int i = 0; i < list.size()-1; i++) {
for (int j = 0; j < list.size()-1-i; j++){
Book book1 =(Book) list.get(j);
Book book2 =(Book) list.get(j+1);
if(book1.getPrice()> book2.getPrice()){
list.set(j,book2);
list.set(j+1,book1);
}
}
}
}
}
class Book{
private String name;
private Double Price;
private String Author;
public Book(String name, Double price, String author) {
this.name = name;
Price = price;
Author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return Price;
}
public void setPrice(Double price) {
Price = price;
}
public String getAuthor() {
return Author;
}
public void setAuthor(String author) {
Author = author;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", Price=" + Price +
", Author='" + Author + '\'' +
'}';
}
}
ArrayList的注意事项
1.可以放入多个null
2.是由数组来实现数据存储的
3.ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高),多线程不建议使用ArrayList
ArrayList底层结构和源码分析
1.ArrayList中维护了一个Object类型的elementData
transient Object[] elementData; // 表示该属性不会被序列化
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.当创建ArrayList对象时,如果使用的是无参构造器,则 初始 elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容为elementData的1.5倍
3.如果使用的指定大小的构造器,则初始 elementData容量为指定大小,如果需要再次扩容,则为elementData的1.5倍
分析:
1. 使用无参构造器 new ArrayList();
Object[] elementData ={};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.list.add
public boolean add(E e) {
ensureCapacityInternal(size + 1); //先确定是否要扩容
elementData[size++] = e; //然后执行赋值
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity); //第一扩容 DEFAULT_CAPACITY=10
}
return minCapacity;//第二次进来
}
private void ensureExplicitCapacity(int minCapacity) { // minCapacity =10
modCount++; //记录当前集合被修改的次数
// overflow-conscious code
if (minCapacity - elementData.length > 0) //当前elementData大小不够就grow扩容 第一次10-0>0 所以扩容 第二次 2-10 < 0 不扩容
grow(minCapacity); // 底层扩容
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //第一次为0
int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍扩容 第一次这里为0
if (newCapacity - minCapacity < 0) //0-10 < 0
newCapacity = minCapacity; //newCapacity =10
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); //elementData 有10个大小 里面全为null
}
如果debug看list数据不全的话 去掉下面的勾选
vector
1.Vector底层也是一个对象数组 protected Object[] elementData;
2.vector 线程同步
无参构造,默认是10,满后按照2倍扩容 指定大小直接2倍扩容
public Vector() {
this(10);
}
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0) //1-10
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? //capacityIncrement =0
capacityIncrement : oldCapacity); //2倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList
1.实现了双向链表和双端队列的特点
2.可以添加任何元素,元素可以重复 可以是null
3.线程不安全
示意图:
LinkedList的元素添加和删除不是通过数组完成的,添加删除效率高
模拟简单的双向链表
public class homework01 {
public static void main(String[] args) {
Node node1 = new Node("悟空");
Node node2 = new Node("八戒");
Node node3 = new Node("沙僧");
//node1-->node2-->node3
node1.next = node2;
node2.next = node3;
//node3-->node2-->node1
node3.pre = node2;
node2.pre = node1;
Node first = node1; //头结点
Node last = node3; //尾结点
//队伍添加一个师傅
Node node = new Node("唐僧");
first = node;
node.next = node1;
node1.pre = node;
//队伍删除一头猪
node1.next = node3;
node3.pre = node1;
//遍历 从头到尾
while (true){
if(first == null){
break;
}else {
System.out.println(first);
first = first.next;
}
}
//遍历 从尾到头
while (true){
if(last == null){
break;
}else {
System.out.println(last);
last = last.pre;
}
}
}
}
//双向链表的一个对象
class Node{
public Object item; //存放数据
public Node next; //下一个
public Node pre; //前一个
public Node(Object item) {
this.item = item;
}
@Override
public String toString() {
return "item=" + item ;
}
}
LinkedList源码分析
public LinkedList() {
}
此时linkedlist属性 first = null last = null
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last; // l = last = null
final Node<E> newNode = new Node<>(l, e1, null);
last = newNode; //last->newNode
if (l == null)
first = newNode; //first-->newNode
else
l.next = newNode;
size++;
modCount++;
} //first-->Node1(null, e1, null)<--last
//第二次进来
void linkLast(E e) {
final Node<E> l = last; //l-->node1
final Node<E> newNode = new Node<>(l, e2, null); //nede2(node1,e2,null)
last = newNode;//last->node2
if (l == null) //l= node1
first = newNode;
else
l.next = newNode; //node1(null,e1,node2)
size++;
modCount++;
}
//first(null,e1,node2)->(node1,e2,null) ->last
LinkedList.remove(); //删除第一个
public E removeFirst() {
final Node<E> f = first; //
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item; //first.item
final Node<E> next = f.next; //指向第二个节点
f.item = null; //第一个节点 item =null
f.next = null; // 第一个节点 next = null 一二断了一半
first = next; //first 指向第二节点
if (next == null)
last = null; //只有一个节点 空链表
else //有2+个
next.prev = null; //第二个节点的prev = null 一二节点断了另一半
size--;
modCount++;
return element;
}
/**
* Unlinks non-null last node l.
*/
private E unlinkLast(Node<E> l) { //last
// assert l == last && l != null;
final E element = l.item; //last.item
final Node<E> prev = l.prev; //prev 倒数第二个
l.item = null;
l.prev = null; // help GC //最后一个的prev指向倒数第二个断了
last = prev; //last指向倒数第二个
if (prev == null)
first = null;
else
prev.next = null;//倒数第二个next指向最后一个断了
size--;
modCount++;
return element;
}
Arraylist 与LinkedList比较
ArrayList 底层 为可变数组,增删用的是数组的扩容效率低,改查的效率高
LinkedList 双向链表 增删效率高,改查的效率低。
两者之间的选择
1.改查较多 用ArrayList
2.增删较多 用Linkedlist
3.一般程序大多数是查询,大部分情况下用Array list
4.混合使用效率高
5.都是线程不安全的
set接口
set接口基本介绍
1 无序(添加顺序和取出顺序不一致,但是添加完的顺序是不会变的),没有索引
2 不允许重复元素 所以最多包含一个null
3 常用的 HashSet TreeSet
遍历
增强for 迭代器
HashSet
能存放一个null 不能存放重复元素 ,不保证存放和去除顺序一致
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>(); //底层是个hashMap
}
HashSet使用示例
new class(); new String()有区别
Set set = new HashSet();
System.out.println(set.add("唐僧")); //T
System.out.println(set.add("孙悟空")); //T
System.out.println(set.add("唐僧")); //F
set = new HashSet();
set.add("hello");
set.add("hello");
System.out.println(set);//[hello]
set.add(new Person("唐僧"));
set.add(new Person("唐僧")); //person重写equals方法则可以只添加一个
System.out.println(set); //[homework.Person@4554617c, hello, homework.Person@1b6d3586]
/*
class Person{
public String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
*/
set.add(new String("world"));
set.add(new String("world"));
System.out.println(set); // [world, homework.Person@4554617c, hello, homework.Person@1b6d3586]
//hashSet添加元素到同一链表上
//重写类的hashcode()方法 return 固定值
HashSet底层
HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)
数组链表模拟
public static void main(String[] args) {
Node[] table = new Node[16];
Node john = new Node("john", null);
table[2] = john;
Node jack = new Node("jack", null);
john.next = jack; //jack挂载到john
Node rose = new Node("rose", null);
jack.next = rose;
Node lucy = new Node("lucy", null);
table[3] = lucy;
}
class Node{
Object item;
Node next;
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
HashSet底层添加元素
1.HashSet底层是HashMap
2.添加一个元素时,先得到hash值 --会转成—索引值
3.找到存储数据表table,看看索引位置是否有存放元素
4.没有直接写入
5 如果有,调用equals比较,如果相同,就放弃添加,如果不同,则添加到最后
6 在java8中,一条链表的元素个数超过8个 并且table>=64个 就会进化成红黑树
(链表元素超过8,table没到64会数组扩容)
HashSet 源码
Set set = new HashSet();
set.add("唐僧"); //T
set.add("孙悟空"); //T
set.add("唐僧"); //F
public boolean add(E e) { //e ="唐僧"
return map.put(e, PRESENT)==null; //private static final Object PRESENT = new Object();
}
public V put(K key, V value) { // value = PRESENT
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//重点
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //辅助变量
//transient Node<K,V>[] table; = null
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //执行完resize() tab就16
if ((p = tab[i = (n - 1) & hash]) == null) //根据key得到hash值去计算存放table的索引位置,这个位置的对象赋给P
tab[i] = newNode(hash, key, value, null); //为空 没存放过元素直接newNode
else {
Node<K,V> e; K k;
if (p.hash == hash && //当前索引位置的链表的第一个元素hash值和准备添加的key的hash值一样
((k = p.key) == key || (key != null && key.equals(k)))) //准备加入的key和p指向的node节点的key(此时都是“唐僧”) 或者p指向节点node的key的equals和加入的key比较
//此时“唐僧”可能(k = p.key) == key 相同了
//或者 new class出来 == 不符合,但是如果重写class的equals方法可以保证key.equals(k)
e = p;
else if (p instanceof TreeNode) //加入不满足 然后判断p是不是红黑树 是则putTreeVal添加
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { //当前位置存放的是链表
for (int binCount = 0; ; ++binCount) { //死循环比较
if ((e = p.next) == null) { //依次和链表的没一个元素比较后,都不相同,则加到链表最后
p.next = newNode(hash, key, value, null);
//元素添加到链表的最后 判断是否达到8个
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st TREEIFY_THRESHOLD =8
treeifyBin(tab, hash); //当前的链表数化 table.length<64 不会马上数化 会扩容
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //比较链表中有相同退出
p = e; //p->p.next 向后移
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue; //存在相同的就返回oldValue
}
}
++modCount;
if (++size > threshold) //>12 扩容 只要是添加元素就算size+1 不管是不是添加链表
resize();
afterNodeInsertion(evict); //hashMap子类实现的方法
return null; //成功添加返回null
}
//========================
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //第一次进来 table = null
int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldCap = 0
int oldThr = threshold; //oldThr =0
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; //newCap =16
//newThr = (int)0.75*16 临界值 16个空间用了12个就开始扩容了
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab; //table开辟16个空间
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
hashset示例
public static void main(String[] args) {
HashSet hashSet = new HashSet();
for (int i = 0; i < 12; i++) { //第一次table=16,链表元素第9个 table=32 10 table=64 11 树化
hashSet.add(new A(i));
}
}
class A{
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 1;
}
}
LinkedHashSet
1.LinkedHashSet 是HashSet的子类
2.LinkedHashSet 底层是一个LinkedHashMap 底层维护的是数组+双向链表
3.根据hashcode值决定元素存储的位置,同时使用链表维护元素的次序,使得元素看起来是以插入顺序保存的
4.不能添加相同元素
说明
1.LinkedHashSet中维护了一个hash表和双向链表(有head 和tail)
2.每一个节点都有before和after属性,这样可以形成双向链表
3.添加一个元素时,先求hash值,再求索引确定元素在table的位置,然后添加的元素加入到双向链表中,添加原理和HashSet一样
//添加元素示例
tail.next = newElement;
newElement.prev = tail;
tail = newElement
4.遍历 由于双向链表 保证插入的顺序和遍历顺序一致
内部结构示意图
LinkedHashSet hashSet = new LinkedHashSet();
hashSet.add(1);
hashSet.add(2);
hashSet.add(3);
Entry 是Node的子类 LinkedHashMap中
TreeSet
底层还是TreeMap
Set treeSet = new TreeSet(new Comparator() { //TreeMap comparator
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).compareTo((String) o2);
}
});
treeSet.add("hello");
treeSet.add("world");
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; //自定义的comparator
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value); //comparator return 0 不会添加
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}