文章目录
集合
- 可以动态保存任意多个对象,方便使用
- 提供了一系列方便的操作对象的方法:add、remove、set、get等
- 使用集合添加、删除新元素的代码更简洁
集合主要是两组(单列集合、双列集合) - Collection接口有两个重要的子接口List Set,他们的实现子类都是单列集合(在集合里面都是单个单个的元素)
- Map接口的实现子类是双列集合,存放的是Key-Value
package collection_;
import java.util.ArrayList;
import java.util.HashMap;
public class Collection_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
// 单列
arrayList.add("jack");
arrayList.add("tom");
HashMap hashMap = new HashMap();
// 双列 k-v
hashMap.put("No1", "北京");
hashMap.put("No2", "上海");
}
}
集合常用方法
package collection_;
import java.util.ArrayList;
import java.util.List;
public class CollectionMethod {
public static void main(String[] args) {
// ArrayList实现了List接口,创建一个ArrayList对象,使用它的接口来接收
List list = new ArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);
list.add(true);
// 注意list里面是对象
// list.add(10)执行的其实是list.add(new Integer(10))
System.out.println("list = " + list); // list = [jack, 10, true]
// remove删除指定元素 可以指定索引或者指定对象
list.remove(0);
list.remove("jack");
// contains查找元素是否存在
System.out.println(list.contains("jack"));
// size获取元素的个数
System.out.println(list.size());
//isEmpty判断是否为空
System.out.println(list.isEmpty());
// clear 清理
list.clear();
// addAll 添加多个元素
ArrayList list2 = new ArrayList();
list2.add("mango");
list2.add("kaka");
list.addAll(list2); // 可以在指定位置加入集合,也可以不指定
System.out.println("list = " + list);
// containsAll:查找多个元素是否都存在 传入的是集合
System.out.println(list.containsAll(list2));
// removeAll:删除多个元素 传入要删除元素的集合
list.removeAll(list2);
}
}
Collection接口遍历元素
方式1——使用Iterator(迭代器)
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator仅用于遍历集合,Iterator本身并不存放对象
注意:在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用iterator.next()会抛出NoSuchElementException异常
package collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
// 进行遍历
// 1. 先得到col对应的迭代器
Iterator iterator = col.iterator();
while (iterator.hasNext()){ // 判断是否还有数据
// 返回下一个元素,类型是Object
Object obj = iterator.next();
System.out.println(obj);
}
// 当退出while循环后,这时iterator迭代器指向最后的元素
// 如果再执行 iterator.next() 会抛出NoSuchElementException
// 如果希望再次遍历,需要重置迭代器
iterator = col.iterator(); //重置迭代器游标操作
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
方式2——for循环增强
增强for循环,可以代替iterator迭代器,特点:增强for就是简化的iterator,本质一样。只能用于遍历集合或数组
package collection_;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionFor {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
// 使用增强for,在Collection集合
// 增强for,底层仍然是迭代器
for (Object book : col) { // 把col里面的每一个元素给book,然后输出遍历
System.out.println("book = " + book);
}
// 增强for,也可以直接在数组使用
}
}
List
List接口基本介绍
package collection_.list;
import java.sql.Array;
import java.util.ArrayList;
import java.util.List;
public class List_ {
public static void main(String[] args) {
// 1. List集合类中元素有序(即添加顺序和取出顺序一致),且可以有重复元素
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("marry");
list.add("tom"); // 可重复
System.out.println("list:" + list); // list:[jack, tom, marry, tom]
// 2. List集合中的每个元素都有其对应的顺序索引,即支持索引
System.out.println(list.get(2)); // 索引从0开始
}
}
List接口常用方法
- add
- addAll
- get
- indexOf
- lastIndexOf
- remove
- set(设置index位置的元素为xx,相当于是替换)
- subList(返回从fromIndex到toIndex位置的子集合 左闭右开)
package collection_.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* 按价格排序,从低到高(使用冒泡法)
*/
public class ListExercise01 {
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Book("king", 23.5, "smith"));
list.add(new Book("vot", 2.5, "lisa"));
list.add(new Book("kak", 89.5, "marry"));
// 遍历
for (Object o :list) {
System.out.println(o);;
}
// 冒泡排序
sort(list);
System.out.println("============排序后===============");
for (Object o :list) {
System.out.println(o);;
}
}
public static void sort(List list) {
int listSize = list.size();
for (int i = 0; i < listSize - 1; i++) {
for (int j = 0; j < listSize - 1 - i; j++) {
// 取出对象Book
Book book1 = (Book) list.get(j); // list.get(j)得到的是Object,需要进行向下转型,用Book对象引用来接收,这样才能调用getPrice方法得到每本书的价格
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;
this.price = price;
this.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) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "name=" + name + '\t' +
" price=" + price + '\t' +
" author=" + author;
}
}
Vector和ArrayList的比较
* | 底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,效率高 | 如果是有参构造,每次扩容为1.5倍;如果是无参,第一次是10,从第二次开始按1.5倍扩容 |
Vector | 可变数组 | jdk1.0 | 安全,效率不高 | 如果是无参,默认10,满后,就按2倍扩容; 如果指定大小,则每次按2倍扩容 |
LinkedList底层结构
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
LinkedList的底层操作机制:
1)LinkedList底层维护了一个双向链表
2)LinkedList中维护了两个属性first和和last分别指向首节点和尾结点
3)每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
- 链表相关基础操作
package collection_.list;
public class LinkedList01 {
public static void main(String[] args) {
Node jack = new Node("jack");
Node tom = new Node("tom");
Node mango = new Node("mango");
// 连接三个节点,形成双向链表
jack.next = tom;
tom.next = mango;
tom.pre = jack;
mango.pre = tom;
Node first = jack; // 让first引用指向Jack
Node last = mango;
// 从头到尾遍历
while (true){
if (first == null){
break;
}
// 输出first信息
System.out.println(first); // 自动调用toString方法
first = first.next;
}
System.out.println("================");
// 链表的添加删除操作
Node smith = new Node("smith");
smith.next = mango;
smith.pre = tom;
tom.next = smith;
mango.pre = smith;
// 从尾到头遍历
while (true){
if (last == null){
break;
}
System.out.println(last);
last = last.pre;
}
}
}
// 定义一个Node类, Node 对象 表示双向链表的一个节点
class Node{
public Object item; // 真正存放数据
public Node next;
public Node pre;
public Node(Object name) {
this.item = name;
}
@Override
public String toString() {
return "Node name = " + item;
}
}
* | 底层结构 | 增删的效率 | 改查的效率 |
---|---|---|---|
ArrayList | 可变数组 | 较低, 数组扩容 | 较高 |
LinkedList | 双向链表 | 较高, 通过链表追加 | 较低 |
- 如果改查的操作比较多,选择ArrayList
- 如果增删的操作比较多,选择LinkedList
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
Set
-
set接口
1)无序(添加和取出的顺序不一致),没有索引
2)不允许重复元素,所以最多包含一个null -
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,因此常用方法和Collection接口一样 -
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
1)可以使用迭代器
2)增强for
3)不能使用索引的方式来获取
package collection_.set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetMethod {
public static void main(String[] args) {
// 以Set接口的实现类 HashSet 为例,说明Set接口的方法
// set接口的实现类的对象(一般说接口对象,指的就是实现了这个接口的类的对象),不能存放重复的对象
// set接口对象存放数据是无序的(添加和取出顺序不一致)
Set set = new HashSet();
set.add("john");
set.add("lucy");
set.add("john");
set.add("jack");
set.add(null);
set.add(null);
System.out.println(set);
// 遍历
// 方式1:使用迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println("obj = " + obj);
}
System.out.println("============");
// 方式2:增强for
for (Object o : set) {
System.out.println(o);
}
}
}
关于set的添加
package collection_.set_;
import java.util.HashSet;
public class HashSet01 {
public static void main(String[] args) {
HashSet set = new HashSet();
/**
* 1. 在执行add后,会返回一个boolean值,如果添加成功,返回true;否则返回false
* 2. 可以通过remove指定删除哪个对象
*/
System.out.println(set.add("john"));
System.out.println(set.add("lucy"));
System.out.println(set.add("john")); // 此时返回false,因为前面已经添加过john了
System.out.println(set.add("jack"));
System.out.println(set.add("Rose"));
set.remove("john");
System.out.println("set = " + set);
System.out.println("================");
set = new HashSet();
set.add("lucy");
set.add("lucy");
set.add(new Dog("tom"));
set.add(new Dog("tom"));
// 这两个Dog都能过add成功,虽然这两个名字一样,但是他们是不同的对象
System.out.println("set = " + set);
System.out.println("==============");
set.add(new String("hsp"));
set.add(new String("hsp")); // 这里只加入了一个hsp
System.out.println(set);
System.out.println("=================");
System.out.println(new String("mango").hashCode());
System.out.println(new String("mango").hashCode());
}
}
class Dog{
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
HashSet底层是HashMap,HashMap底层是数组+链表+红黑树
package collection_.set_;
public class HashSetStructure {
public static void main(String[] args) {
// HashSet底层是HashMap,HashMap底层是数组+链表+红黑树
// 模拟一个HashMap的底层结构
// 1. 创建一个数组,数组的类型是Node[]
// 也有人直接把Node[] 数组称为表
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;
System.out.println("table = " + table);
}
}
class Node{ // 结点,存储数据,可以指向下一个结点,从而形成链表
Object item;
Node next;
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
HashSet添加元素底层:
- HashSet底层是HashMap
- 添加一个元素时,先得到hash值 - 会转成- 索引值
- 找到存储数据表table,看这个索引位置是否已经有存放的元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同则放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_THEEIFY_CAPACITY(默认64),就会进行树化(红黑树)
HashSet源码解析
package collection_.set_;
import java.util.HashSet;
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");
hashSet.add("php");
hashSet.add("java");
System.out.println("set = " + hashSet);
/**
* HashSet源码
* 1. 执行HashSet() 构造器
* public HashSet() {
* map = new HashMap<>();
* }
* 2. 执行add()
* public boolean add(E e) { // e = "java"
* return map.put(e, PRESENT)==null;
* }
* // 其中,PRESENT的定义为:private static final Object PRESENT = new Object();
* 3. 执行put() 该算法会执行 hash(key),得到key对应的hash值 算法 (h = key.hashCode()) ^ (h >>> 16) (也就是说计算得到的值并不是等于hashcode,进行了处理,避免冲突)
* public V put(K key, V value) { // key = “java” value = PRESENT (静态共享的,所以在后面,变化的只有key)
* return putVal(hash(key), key, value, false, true);
* }
* 4. 执行putVal
* final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
* boolean evict) {
* Node<K,V>[] tab; Node<K,V> p; int n, i; // 定义了辅助变量
* // table 就是HashMap的一个数组,类型是Node[]
* // if语句表示如果当前table是null,或者大小=0,就进行第一次扩容,到16个空间
* if ((tab = table) == null || (n = tab.length) == 0)
* n = (tab = resize()).length;
*
* // (1)根据key,得到hash,去计算该key应该存放到table表的哪个索引位置
* // 并把tab中这个位置的对象赋给p
* // (2)判断p是否为null,如果p为null,表示还没有存放元素,就创建一个Node(key = "java", value = PRESENT),执行 tab[i] = newNode(hash, key, value, null)
* // 这里n是table容量,比如第一次是16,那么这里n就是16,计算key存放的位置,i = (n - 1) & hash,hash是key对应的hash,看tab中i的位置是不是已经存放了元素 ,因此当相同key存入的时候,i是一样的,因此后面相同的key执行的是else语句
* // 哈希函数:相同内容的hash值一定相同,不同内容的hash值可能相同,又叫哈希碰撞
* if ((p = tab[i = (n - 1) & hash]) == null) // 注意&这里是位运算符,不是逻辑运算符
* // 注意,这里把key对应的hash也传入,是因为后面会进行比较,看他们的hash是不是一样的 这里的null表示的就是该节点后面没有节点
* tab[i] = newNode(hash, key, value, null); // 注意,key是真正存放的值,value是PRESENT,不变的
* else {
* // 开发技巧:在需要局部变量(辅助变量)的时候再创建
* Node<K,V> e; K k;
* // 如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
* // 并且满足下面两个条件之一: 就不能加入
* // (1)准备加入的key和p指向的Node节点的key是同一对象 (2)p指向的Node节点的key的equals()和准备加入的key比较后相同(这个equals()方法不能简单的理解为判断内容一样,因为每个类都可以有自己的equals()方法,程序员可以自己定)
* (也就是不仅要判断hash是不是一样,还要判断元素值,目的是判断是相同元素还是哈希碰撞导致的hash一样)
* if (p.hash == hash &&
* ((k = p.key) == key || (key != null && key.equals(k))))
* e = p; // 没有创建新节点加入,e指向已存在的
*
* // 再判断p是不是一棵红黑树
* // 如果是一棵红黑树,就执行putTreeVal,来进行添加
* else if (p instanceof TreeNode)
* e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
* else { // 如果table对应索引位置,已经是一个链表,就使用for循环
* // (1)依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
* // 注意在把元素添加到链表后 ,立即判断该链表是否已经达到8个节点(TREEIFY_THRESHOLD),如果到达8个,就调用treeifyBin() 对当前这个链表进行树化(转成红黑树)
* // 注意,在转成红黑树时,要进行判断,判断条件如下:
* if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // MIN_TREEIFY_CAPACITY默认64
* resize();
* // 如果上面条件成立,就先对table表扩容
* // 只要上面条件不成立时,才进行转成红黑树
* // (2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
* for (int binCount = 0; ; ++binCount) {
* if ((e = p.next) == null) {
* p.next = newNode(hash, key, value, null);
* if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
* treeifyBin(tab, hash);
* break;
* }
* if (e.hash == hash &&
* ((k = e.key) == key || (key != null && key.equals(k))))
* break;
* p = e;
* }
* }
* if (e != null) { // existing mapping for key
* V oldValue = e.value;
* if (!onlyIfAbsent || oldValue == null)
* e.value = value;
* afterNodeAccess(e);
* return oldValue;
* }
* }
* ++modCount;
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict);
* return null;
* }
*/
}
}
- HashSet练习
package collection_.set_;
import java.util.HashSet;
import java.util.Objects;
/**
* 1. 创建3个Employee对象放入HashSet中
* 2. 当name和age的值相同时,认为是相同员工,不能添加到HashSet集合中
*/
public class HashSetExercise {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(new Employee("john", 20));
set.add(new Employee("smith", 10));
set.add(new Employee("smith", 10));
for (Object o : set) {
System.out.println(o);
}
}
}
class Employee{
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}