什么是集合?有什么用?
数组其实就是一个集合,集合是一个容器,是一个载体可以容纳多个对象。
在实际开发中,假设连接数据库,数据库当中有十条记录,把这十条记录查出来,java将这十条数据封装成十个对象放到集合传到前端,遍历集合,将数据展示出来
集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,
集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
list.add(100); //自动装箱Integer
注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是“引用”。
在java中每一个不同的集合,底层会对应不同的数据结构
往不同的集合中
存储元素,等于将数据放到了不同的数据结构当中。什么是数据结构?数据存储的
结构就是数据结构。不同的数据结构,数据存储方式不同。例如:
数组、二叉树、链表、哈希表…
以上这些都是常见的数据结构。
你往集合c1中放数据,可能是放到数组上了。
你往集合c2中放数据,可能是放到二叉树上了。
.....
你使用不同的集合等同于使用了不同的数据结构。
java中已经将数据结构实现了,已经写好了这些常用的集合类,只需要掌握怎么用?在什么情况下选择哪一种合适的集合去使用即可。
new ArrayList(); 创建一个集合,底层是数组。
new LinkedList(); 创建一个集合对象,底层是链表。
new TreeSet(); 创建一个集合对象,底层是二叉树。
集合类和相关的接口都在java.util包下
有个继承图在word文档中
Set:无序、不可重复的集合,没有下标
List:有序,可重复的集合,有序的意思是存进去的顺序,取出来也是这个顺序,表现为都有下标
list接口和set接口都继承了collection接口,这个接口又继承了Iterable接口(可遍历的)
Map:具有映射关系的集合,键值对方式存储,跟collection接口没有关系
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/* Collection接口中常用方法,这是超级父接口,孩子都能用
1,add,向集合中添加元素
2,size,集合中元素的个数
3,clear 清空集合
4,contains(object o),判断集合是否包含元素o
5,remove,删除元素
6,isEmpty,判断集合是否为空
7,toArray,将集合转化为数组,用的不多
8,迭代器,适合所有Collection以及子类中使用,Map不能用
*/
public class Test1 {
public static void main(String[] args) {
Collection c =new ArrayList();//多态,因为Collection是一个接口,不能实例化
//接口Collection中的常用方法
c.add(12);//自动装箱,实际上放了一个对象的内存地址,Integer x=new Integer(12)
c.add(new Object());
System.out.println("集合中的元素个数为: "+c.size());
c.clear();
System.out.println("集合中的元素个数为: "+c.size());
c.add("王者");
c.add("星耀");
c.add("钻石");
c.add("铂金");
c.add("黄金");
c.add(new Object());
c.add(true);
// Object [] objects=c.toArray();
System.out.println(c.contains("青铜"));
System.out.println(c.contains("黄金"));
c.remove("黄金");
System.out.println(c.contains("黄金"));
System.out.println(c.isEmpty());
//对集合Collection进行遍历/迭代
//第一步,获取集合对象的迭代器对象Iterator it,相当于一个箭头,最初指向的是-1位置
Iterator it =c.iterator();
//第二步,通过以上获取的迭代器对象开始迭代/遍历集合
/*
集合结构发生改变,迭代器要重新获取,也就是说,如果往集合中加入元素删除元素之后要重新获得迭代器
迭代器对象Iterator it的方法:
1,Boolean hasNext(),返回true则说明仍然有元素可以迭代
2,Object next(),让迭代器前进一位,并且将指向的元素拿到
*/
// if (it.hasNext()){
// //不管当初存进去的是什么,取出来的统一是Object,这是取一次
// Object o =it.next();
// System.out.println(o);
// }
while(it.hasNext()){
Object o =it.next();
System.out.println(o);
}
}
}
详解contains方法
import java.util.ArrayList;
import java.util.Collection;
public class Test01 {
public static void main(String[] args) {
Collection c =new ArrayList();
String s1=new String("abc");
c.add(s1);
String s2=new String("dfg");
c.add(s2);
String x =new String("abc");
boolean flag=c.contains(x);
System.out.println(flag);//输出是true,因为contains方法调用了String类的equals方法,比较的是内容(调用的是对象的equals方法)
//如果调用的是没有重写过的equals方法,比较的就是内存地址,放在集合里的对象要重写equals方法
//remove也是调用equals方法,所以删了x,s1也没了
//自己写的类如果也加入到集合中,就要重写equals方法
}
}
迭代器删除方法
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test1 {
public static void main(String[] args) {
Collection c =new ArrayList();//多态,因为Collection是一个接口,不能实例化
c.add("王者");
c.add("星耀");
c.add("钻石");
c.add("铂金");
c.add("黄金");
c.add(new Object());
c.add(true);
Iterator it =c.iterator();
while(it.hasNext()){
Object o =it.next();
// c.remove(o);//这里删除,集合结构变了,没有重新获得迭代器所以异常
it.remove();//迭代器删了快照里面的
System.out.println(o);//因为赋值给了o,输出的是o的值
}
System.out.println(c.size());
}
}
list集合存储元素,有序可重复,有下标
list有特色的子方法
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
list特有的方法:
1,E get(int index)
2,void add(int index, E element)
3,int indexOf(Object o)
4,int lastIndexOf(Object o)
5,E remove(int index)
6,E set(int index, E element)
*/
public class ListTest01 {
public static void main(String[] args) {
List list=new ArrayList();
list.add("1");
list.add("3");
list.add(new Object());
list.add(1,"king");
Iterator iterator=list.iterator();
while (iterator.hasNext()){
Object o=iterator.next();
System.out.println(o);
}
System.out.println(list.get(2));
//因为有下标,所以list集合有自己特殊的遍历方法
for (int i = 0; i <list.size() ; i++) {
Object o=list.get(i);
System.out.println(o);
}
//获取第一次出现的指引
System.out.println(list.indexOf("king"));
//获取最后一次出现的指引
System.out.println(list.lastIndexOf("king"));
}
}
ArrayList
底层是数组
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
//ArrayList底层是一个数组,尽可能少的扩容
//Arraylist集合用的最多,底层是一个数组,向数组末尾添加元素效率不受影响,检索/查找某个元素操作比较多
public class ArrayListTest {
public static void main(String[] args) {
//底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为0
ArrayList a=new ArrayList();//初始化容量是10,size方法是获取元素的个数,不是容量
System.out.println(a.size());
ArrayList a1=new ArrayList(20);//初始化一个容量为20的
//如果容量满了,自动扩容到原容量的1.5倍,右移一位,>>1,二进制右移一位
Collection c=new HashSet();
c.add(122);
c.add(222);
//通过这种构造方法将hashset集合转换成了list集合
ArrayList a2=new ArrayList(c);
}
}
import java.util.ArrayList;
import java.util.Collections;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList a=new ArrayList();//非线程安全的
//变成线程安全的
Collections.synchronizedList(a);//工具类Collections的方法
a.add("111");
a.add("222");
}
}
Linkedlist
Linkedlist底层是一个双向链表,也有下标,ArrayList检索效率高不是因为有下标,而是因为底层是数组
单向链表
优点:随机增删元素的效率高,因为增删元素不涉及大量元素的位移
缺点:查询效率较低,得从头开始
/*
单链表中节点是基本单元
每个节点Node有两个属性
1,存储的数据
2,下一个节点的内存地址
*/
public class Node {
Object o; //存储的数据
Node next;//下个节点的内存地址
public Node() {
}
public Node(Object o, Node next) {
this.o = o;
this.next = next;
}
}
public class Link {
//头节点
Node header=null;
//向末尾加元素
public void add(Object o){
//创建一个新节点对象
//让之前单链表的末尾节点next指向新节点对象
//有可能元素是第一个
if(header==null){
//说明还没有节点,new个新的作为头节点,这个时候既是头又是末尾节点
header= new Node(o,null);
}else{
//说明头不是空,得找出末尾节点,让next指向,新增的节点
Node currentLastNode=findLast(header);
currentLastNode.next=new Node(o,null);
}
}
private Node findLast(Node node) {
if(node.next==null){
//如果一个节点的next是null
//说明这个节点是末尾节点,返回这个节点就行了
return node;
}
//程序到这了就说明,node不是末尾节点
//node.next是一个节点,存的是下个节点的地址,这是递归
return findLast(node.next);
}
public void del(Object o){
}
public void update(Object no){
}
public void get(Object o){
}
}
双向链表
linkedlist集合没有初始化容量,最初链表没有任何元素,first和last都是null,开发不需要关心具体什么集合,面向接口编程,直接调用接口的方法,只不过底层对象不一样
Vector
底层也是数组,默认容量10,扩容后是原容量的2倍,线程安全,很少用
使用泛型后,迭代器返回的是泛型类型的数据不再是object了
import java.util.ArrayList;
import java.util.Iterator;
/*
泛型是编译阶段特性
优点:1,集合中存储的元素类型统一
2,从集合中迭代中的数据类型统一,不需要大量的强转了,调用子类特有的方法还要转
缺点:集合中的元素缺少多样性
*/
public class Fanxing {
public static void main(String[] args) {
Cat c=new Cat();
Dog d=new Dog();
ArrayList<Animal> list =new ArrayList<Animal>();
list.add(c);
list.add(d);
Iterator<Animal>it=list.iterator(); //迭代器泛型使用
while (it.hasNext()){
Animal a=it.next();
a.move();
}
}
}
class Animal{
public void move(){
System.out.println("动物在移动");
}
}
class Cat extends Animal{
public void cat(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
public void dog(){
System.out.println("吃骨头");
}
}
jdk8之后引入自动类型推断
ArrayList<Animal> list =new ArrayList<>();//后面尖括号不用写
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Foreach {
public static void main(String[] args) {
int [] a={
1,2,3,4,5};
//遍历
for (int i = 0; i <a.length ; i++) {
System.out.println(a[i]);
}
//foreach增强循环,缺点没有下标
/*for(元素类型 变量名:数组或集合){
System.out.println(变量名);
}
*/
for (int data:a) {
System.out.println(data);
}
List<String> list =new ArrayList<>();
list.add("222");
list.add("sad");
list.add("hello");
//遍历使用迭代器
Iterator<String> it =list.iterator();
while (it.hasNext()){
String s=it.next();
System.out.println(s);
}
//使用下标,只针对有下标的
for (int i = 0; i <list.size() ; i++) {
String s1=list.get(i);
System.out.println(s1);
}
//使用foreach
for (String s3:list //因为泛型是String
) {
System.out.println(s3);
}
}
}
与上面的Collection没有继承关系
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/*
java.util.Map接口常用的方法:
V put(K key, V value) 向Map集合中添加键值对
void clear() 清空集合
boolean containsKey(Object key) 判断是否包含某个key
boolean containsValue(Object value) 判断是否包含某个value
V get(Object key) 通过key获取value
boolean isEmpty() 判断集合元素个数是否为0
int size() 获取键值对的个数
V remove(Object key) 通过key删除键值对
Set keySet() 获取Map所有的key,返回的是set集合
Collection values() 获取所有的value,返回一个Collection集合
Set> entrySet() 将Map集合转换成set集合
set集合对象就是1=zhangsan,2=lisi,....,set集合中元素的类型是Map.Entry,跟String都一样是类型的名字,Map.Entry是静态内部类
*/
public class MapTest {
public static void main(String[] args) {
//创建Map集合
Map<Integer,String> map=new HashMap<>();
map.put(1,"张三1");
map.put(2,"张三2");
map.put(3,"张三3");
String s1 =map.get(2);
System.out.println(s1);
System.out.println("键值对的数量是: "+map.size());
map.remove(2);
//contains方法底层调用equals方法,自定义的类需要要重写equals方法
System.out.println(map.containsKey(1));
System.out.println(map.containsValue("李四"));
Collection<String> c =map.values();
for (String s:
c) {
System.out.println(s);
}
}
}
遍历Map集合
/*
遍历Map集合
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapTest01 {
public static void main(String[] args) {
Map<Integer,String>map=new HashMap<>();
map.put(1,"我");
map.put(2,"是");
map.put(3,"中");
map.put(4,"国");
map.put(5,"人");
//第一种方式,通过获取所有key,遍历value
//迭代器
Set<Integer>keys =map.keySet();
Iterator<Integer> it =keys.iterator();
while (it.hasNext()){
int a= it.next();
String s =map.get(a);
System.out.println("key: "+a+" value: "+s);
}
//foreach
for (Integer i:keys
) {
System.out.println("key: "+i +" value: "+map.get(i));
}
//第二种方式,Set> entrySet() 将Map集合转换成set集合
//这里就是Set>
Set <Map.Entry<Integer,String>>set= map.entrySet();
Iterator <Map.Entry<Integer,String>>it1=set.iterator();
while(it1.hasNext()){
//底层是个node对象,里面有值,键等属性
Map.Entry<Integer,String> node= it1.next();
Integer i= node.getKey();
String s=node.getValue();
System.out.println("key: "+i+" value: "+s);
}
//foreach,效率高
for (Map.Entry<Integer,String> node:set
) {
System.out.println(node.getKey()+"---"+node.getValue());
}
}
}
HashMap
equals不重写会调用Object上的比较的就是对象地址,要比较内容
hashcode也要重写,如果hashcode()方法返回固定的值,会导致哈希表就变成了纯单向链表了,散列分布不均匀。如果所有的hashcode()方法返回的是不一样的值,这样就导致底层变成数组了,散列分布不均匀。
所以hashcode()方法重写要有技巧。
HashMap的key值也就是set集合
无序:因为算出来的哈希值也就是数组下标不相同,加哪一个链表上是不一定的
不可重重复:equals方法保证了不可重复,如果key重复了,就覆盖了
如果将自定义类的对象放到HashMap集合或者HashSet集合中,两个方法要同时重写,并且equals方法返回是true,hashcode方法返回的值一定相同,因为既然比较为true了,那么一定是比较了,而且还是在同一个链表上,所以哈希值一定相同,经哈希算法计算的数组下标相同
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/*
HashMap集合:
1,HashMap集合底层是哈希表/散列表的数据结构
2,哈希表是数组和单向链表的结合,一维数组的每个元素是一个单向链表
Node [] tables;,静态内部类 static class Node {有四个属性,hash(key经过hashcode()方法算出的值经过哈希算法算出的值作为数组的下标),key,value,next}
3,掌握map.put(k,v)和map.get(k)的实现原理
4,HashMap默认初始化为16,加载因子是0.75,当底层数组的容量达到百分之七十五的时候开始扩容,初始化容量必须是2的倍数,为了提高存储效率
5,扩容之后是原来的2倍
6,HashMap集合允许键值为null,只能是一个
*/
public class HashMapTest02 {
public static void main(String[] args) {
//这里的key是Integer类,value是String类,这两个类都重写了equals和hashcode方法
Map<Integer,String> map=new HashMap<>() ;
map.put(1,"张三1");
map.put(2222,"张三2");
map.put(3,"张三3");
map.put(3,"张三4");
Set<Map.Entry<Integer,String>>node=map.entrySet();
for (Map.Entry<Integer,String> s:node
) {
System.out.println(s.getKey()+"=="+s.getValue());
}
}
}
java8之后,如果链表的元素超过8个,单向链表会变成红黑树数据结构。当红黑树上的节点数小于6时,红黑树变成单向链表。也是为了提高检索效率,树有分支,只查一边的。
哈希碰撞:哈希值不相同,经过哈希算法得出的数组下标可能相同。比如取余
Hashtable
键值都不允许是null,线程安全的
底层也是哈希表,初始化容量11,默认加载因子0.75,集合扩容为2倍加1
Properties继承的Hashtable
import java.util.Properties;
/*
Properties的key和value都是String类型
被称为属性类对象
是线程安全的
*/
public class PropertiesTest {
public static void main(String[] args) {
Properties p=new Properties();
p.setProperty("密码","123");
p.setProperty("密码1","211");
p.setProperty("密码2","123");
String s1= p.getProperty("密码");
String s2= p.getProperty("密码1");
String s3= p.getProperty("密码2");
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
TreeMap
放进去取出来是有顺序的
无法对自定义类型排序,出现强转错误,是因为自定义Person类没有实现comparable接口
String和Integer都实现了comparable接口,所以可以自己排序
new TreeMap这个集合的时候如果是无参构造还想排序的话得实现接口,还有一种方法是调用有参构造,传进去一个比较器
最终结论:
放到集合TreeSet或者TreeMap集合中key部分的元素如果想做到排序,包括两种方式:
1.放在集合中的元素类实现java.lang.Comparable接口
2.在构造集合的时候,传一个比较器
如果比较规则不发生改变,或者比较规则只有一个,建议第一种
如果频繁改变规则,则传比较器,符合ocp准则
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
Person p1=new Person(12);
Person p2=new Person(11);
Person p3=new Person(15);
TreeSet treeSet=new TreeSet();//这里其实有个参数是比较器
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
}
}
class Person{
int age;
String name;
public Person() {
}
public Person(int age) {
this.age = age;
}
}
/*
Exception in thread "main" java.lang.ClassCastException: a.jihe.Person cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1294)
at java.util.TreeMap.put(TreeMap.java:538)
at java.util.TreeSet.add(TreeSet.java:255)
at a.jihe.TreeSetTest.main(TreeSetTest.java:12)
重写后
返回负值就是升序
compareTo方法返回值很重要:
返回0会覆盖
返回>0,在右子树上找
返回<0,在左子树上找
/*
放在TreeSet集合中的元素要实现Comparable接口
并且compareTo实现方法,equals方法可以不写
*/
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
Person p1=new Person(12);
Person p2=new Person(11);
Person p3=new Person(15);
TreeSet<Person> treeSet=new TreeSet();//这里其实有个参数是比较器
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
for (Person p:treeSet
) {
System.out.println(p);
}
}
}
class Person implements Comparable<Person>{
int age;
String name;
public Person() {
}
public Person(int age) {
this.age = age;
}
//需要在这规定比较的逻辑,比如按照年龄
//k.compareTo(t.key)
@Override
public int compareTo(Person p) {
//p1.compareTo(p2)
//this就是p1,p就是p2
int age1=this.age;
int age2=p.age;
// if (age1==age2){
// return 0;
// }else if (age1>age2){
// return 1;
// }else if (age2
// return -1;
// }
return this.age-p.age;//升序
}
@Override
public String toString() {
return "Person[age= "+age+" name= "+"]";
}
}
年龄相同按照姓名排序,实现Comparable
public int compareTo(Person p) {
//p1.compareTo(p2)
//this就是p1,p就是p2
int age1=this.age;
int age2=p.age;
if(age1==age2){
//年龄相同按照姓名排序
//姓名是String类型,实现了接口了
return this.name.compareTo(p.name);
}else {
return this.age-p.age;//升序
}
}
第二种方法,写一个比较器
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSet11 {
public static void main(String[] args) {
//创建集合的时候使用下面的比较器
//括号里面也可以匿名内部类,new Comparator{},是接口,不能直接new,后面接个大括号
TreeSet<Wugui> treeSet =new TreeSet<>(new WuguiComparator());
Wugui wugui1=new Wugui(12);
Wugui wugui2=new Wugui(16);
Wugui wugui3=new Wugui(13);
treeSet.add(wugui1);
treeSet.add(wugui2);
treeSet.add(wugui3);
for (Wugui w:treeSet
) {
System.out.println(w);
}
}
}
class Wugui{
int age;
public Wugui (int age){
this.age=age;
}
@Override
public String toString() {
return "小乌龟{" +
"age=" + age +
'}';
}
}
//单独写一个比较器
//比较器实现的是java.util.Comparator接口。(Comparable是java.lang包下的)
class WuguiComparator implements Comparator<Wugui> {
@Override
public int compare(Wugui o1, Wugui o2) {
return o1.age-o2.age;
}
}
自平衡二叉树
遵循左小右大原则存放。
遍历二叉树有三种方式:
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
根的位置决定前中后
TreeSet集合和TreeMap集合采用的是:中序遍历
Iterator采取的是中序遍历方式
java.util.Collections是工具类,便于集合操作
java.util.Collection是集合接口,想想哪个word文档
import java.util.*;
public class CollectionsTest {
public static void main(String[] args) {
List list=new ArrayList();
//变成线程安全的
Collections.synchronizedList(list);
//排序,里面的元素对象的类必须实现Comparable接口
list.add(123);
list.add(222);
list.add(111);
Collections.sort(list);
//对Set集合怎么排序,将set转化成list集合
Set set=new HashSet();
set.add(1);
set.add(2);
List list1=new ArrayList(set);
//Collections.sort(list集合,比较器对象);
}
}