java中的集合体系结构、Collection与Map下的集合概述

目录

集合分为单例集合与多例集合

一、关于单例集合(Collection)的体系结构

1.List集合

List接口下的实现类:ArrayList和LinkedList、Vector

ArrayList:

LinkedList:

ArrayList与LinkedList性能比较

Vector

2.Set集合

HashSet

LinkedHashSet

TreeSet

二、关于多例集合(Map)的体系结构

 1.HashMap

2.TreeMap

3.HashMap与TreeMap比较

4.其他Map集合

5.集合总结

各位客官请听下回分解

创作不易,如果觉得还不错的话,可以点个赞给我个鼓励嘛🙌🙌🙌🙌


集合分为单例集合与多例集合

一、关于单例集合(Collection)的体系结构

Collection存自定义对象,扩展remove,contains方法的应用

class Person{
	String name;
	public Person(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + "]";
	}
	@Override
	public boolean equals(Object obj) {
		if(this.getClass()==obj.getClass()) {
			Person o = (Person) obj;
			return this.name.equals(o.name);
		}
		return false;
	}
}
public class Test2 {
	public static void main(String[] args) {
		//Collection扩展: remove,contains
		//使用Collection存储自定义类型,观察移除或包含的操作
		Collection col = new ArrayList();
		col.add(new Person("zs"));
		col.add(new Person("ls"));
		System.out.println(col);
		//contains内部用equals去比较是否包含,不重写,则此处调Object的equals比较地址
		//重写后,往往按比较属性
		System.out.println(col.contains(new Person("zs"))); //false
		System.out.println(col.remove(new Person("zs")));
		System.out.println(col);
	}
}

单例集合的体系结构图与介绍:

 所以由单例集合的体系结构图可知,List集合与Set集合的顶层父接口都是Collection,它们都继承了Collecton接口,所以可以通过接口实现多态

如:Collection col = new ArrayList();//左边父类引用、右边实现类(左边编译类型,右边运行类型)

     List  list=new ArrayList();  //这两种都是通过接口实现多态的方式,因为是接口引用,虽然实现类实现了接口,但是只能调用父类接口中被子类重写的方法。当我们需要调用实现类中独有的方法的时候,这个时候就需要通过面向对象的方式创建对象

如:我们需要调用ArrayList类中的独有方法的时候,则需要以面向对象方式创建ArrayList对象

ArrayList list=new ArrayList();

在这点上,Set与List是相同的

1.List集合

List集合是Collection的子接口,在Collection标准基础上,扩充了一些与下标操作相关的方法(也就是说关于下标的操作是List独有的,它的父接口Collection是没有操作下标的行为

存储元素特点:有序,有下标,允许重复

List的常用方法

//List的基本操作及存储特点:
List list = new ArrayList(); //ArrayList是List的实现类
list.add(1);
list.add(3);
list.add(2);
list.add(2);  //允许重复
System.out.println(list); //1,3,2,2 有序

list.add(1, 666); //指定下标位置添加元素
System.out.println(list);
List list2 = new ArrayList();
list2.add(5);
list.addAll(1, list2); //指定位置存集合
System.out.println(list);

System.out.println(list.get(2)); //根据下标取元素
list.set(2, 999);  //指定位置的修改
System.out.println(list);

List list3 = list.subList(1, 3); //提取子集合
System.out.println(list3); //根据开始和结束下标提取集合,不包括结束下标

//除以上方法外,Collection接口中提供的方法依然能用
list.remove(0); //根据下标移除
System.out.println(list);

List的循环遍历(普通for、增强for、迭代器

增强for的本质是迭代器,但是比迭代器的效率更高(可以通过反编译工具查看增强for的具体实现原理)

//List循环遍历:
List list = new ArrayList();
list.add(1);
list.add(3);
list.add(2);
//1.基本for
for(int i=0;i<list.size();i++) {
    System.out.println(list.get(i));
}
System.out.println("==========");
//2.增强for
for (Object o : list) {
    System.out.println(o);
}
System.out.println("=====ListIterator(扩展)=====");
ListIterator li = list.listIterator();  //有迭代器功能和反向遍历
while(li.hasNext()) { //判断是否有下一个
    System.out.println(li.next());  //有下一个则取出
}
System.out.println("-----------");
while(li.hasPrevious()) { //判断是否有上一个(在迭代器指向最后,才能用)
    System.out.println(li.previous()); //取出后,指向上一个
}

List接口下的实现类:ArrayList和LinkedList、Vector

List有两个重要的实现类,ArrayList和LinkedList,他们的存储特点都是一致的,调用方法也相同;但是存储原理不同

ArrayList:

存储原理:通过数组扩容方式进行存储

细化原理:当存储第一个元素,内部开十个数组空间用于存元素;当没空间了,则进行扩容,并拷贝原数组值,每次扩当前元素的一半。(以原来空间的1.5倍进行扩容)

说明:数组是连续的空间,本身有下标标记,很容易就找到下标的元素

 

 

LinkedList:

存储原理:通过双向链表方式存储

细化原理:每次存值,都开空间节点,准备三个属性,pre,data,next;data存值,pre节点指向上一个节点,next指向下一个节点;当存储第二个值,开空间,并将旧节点的next执行新节点,新节点的pre指向上一个节点;这样就构成了双向链表。

说明:链表的空间不连续,通过上一个或下一个节点的指向(其实类似指针)去定位元素

ArrayList与LinkedList性能比较
  1. 添加

    向后追加:ArrayList和LinekdList都需要开空间,ArrayList稍快,(因为ArrayList是连续的空间,所以直接往最后面添加速度会比LinkedList快

    指定位置追加:ArrayList考虑集体搬迁 PK LinkedList考虑定位(向指定位置添加的话,ArrayList是连续的空间,就需要添加位置的后面的所有元素进行挪动,而LinkedList的话只需要创建一个新的结点,然后将上一个结点与下一个结点的位置互相指向就好了)所以LinkedList会快点

  2. 删除(下标删)

    与指定位置追加类似,ArrayList要集体搬迁 ,LinkedList要定位;LinkedList快

  3. 查找

    查找就是定位;数组是连续内存空间,本身有下标标记,所以定位快;ArrayList快

  4. 修改

    修改=查找+赋值;ArrayList快

结论:后续常用ArrayList,因为常用操作是查找和向后追加

 

Vector

描述:和ArrayList类似,都是通过数组扩容进行数据存储;区别在于Vector加了锁;

与ArrayList的区别:

Vector: 安全,性能低;在多线程中数据不会混乱;只是被安全ArrayList所取代

ArrayList:不安全,性能高;倾向于用在单线程中

案例:

//和ArrayList的操作是类似的
List list = new Vector();
list.add(1);
list.add(3);
list.add(2);
for (Object object : list) {
    System.out.println(object);
}
//Vector除了上述功能外,自身也提供了存储和遍历的方法
Vector vector = new Vector();
vector.addElement(5);
vector.addElement(2);
vector.addElement(8);
Enumeration enumer = vector.elements();  //枚举器遍历
System.out.println("===============");
while(enumer.hasMoreElements()) { //和迭代器类似
    System.out.println(enumer.nextElement());
}

2.Set集合

Set接口:Collection的子接口,Set接口没有提供抽象方法,全部继承Collection

Set的特点:无序、无下标,唯一(无序是指存进去的时候是无序的,每次输出按照输入时候固定的无序)

两个实现类都可以实现该特点:HashSet, TreeSet

基本应用:

//基本使用:
Set<Integer> set = new HashSet<Integer>();
set.add(11);
set.add(33);
set.add(22);
set.add(22);  //唯一
System.out.println(set);  //HashSet特点:无序,唯一
//Set是Collection的子接口,所以Collection的常用方法,Set可完全继承

//循环遍历:1.基本for   无下标,不能使用
//for(int i=0;i<set.size();i++) {}

//增强for:有增强for,所以肯定有迭代器
for (Integer integer : set) {
    System.out.println(integer);
}

因为没有下标,所以通过下标进行循环的基本for,set用不了

HashSet

HashSet存储方式:hash算法+数组+链表

细化原理:

存储的对象会得到一个hash值,hash值在hash表中(数组)得到一个下标位置;根据这个下标值在链表中进行判断该下标位置是否有值,如果没有则直接存储;如果有,与该位置的链表元素一一比较;如果相等,则退出,确定唯一性;如果都不相等,则存储到该链表中。

结论:存储的元素要确定唯一性,则必须重写hashCode和equals(因为不重写都是Object的方法,它们比较的都是地址)

 

 

验证案例:存储自定义对象,该对象的类必须重写hashCode和equals,重写后,按属性来匹配

因为通过HashSet存储自定义对象的时候,如果不重写equals和hashCode方法默认使用的是Object的方法,Object的equals和hashCode,比较的是地址(内存空间),当存入hashSet的时候,如果不重写哪怕对象中的元素相等,但是因为new了,所以是两个不一样的地址,所以还是会存入进去,重写之后我们可以比较他们的内容而不是地址

如:hashset.add(new Student("hh",6))

    hashset.add(new Student("hh",6))   不重写,因为判断地址不一样,所以两个都会存进去

                                                                  重写后,因为判断内容一样,所以不会重复存入进去

class Student{
	String name;
	int    age;
	public Student(String name,int age) {
		this.name = name;
		this.age  = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		//...系统产生
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		//...系统产生
		return true;
	}
}
public class Test2 {
	public static void main(String[] args) {
		Set<Student> set = new HashSet<Student>();
		set.add(new Student("zs", 30));
		set.add(new Student("ls", 20));
		set.add(new Student("zs", 30));
		System.out.println(set); //几个?
	}
}
LinkedHashSet

LinkedHashSet也是Set接口的实现类,是HashSet的子类;存储方式与HashSet类似,就是调用父类的方法实现的。(了解)

存储特点:有序,唯一

//LinkedHashSet:HashSet的子类; 特点:有序,唯一
public class Test3 {
	public static void main(String[] args) {
		Set<Integer> set = new LinkedHashSet<Integer>();
		set.add(1);
		set.add(5);
		set.add(3);
		set.add(2);
		set.add(1); //唯一性
		System.out.println(set); //[1, 5, 3, 2]  有序
	}
}
TreeSet

Set接口的另一个重要实现类-TreeSet

存储特点:可排序,无下标,唯一   (无下标就不能用基本for)

TreeSet原理分析

TreeSet的存储方式:二叉树

细化原理:存第一个元素时,作为根节点;再次存储,则与根比较;比根大则放右边,比根小放左边;查看左/右子树是否有节点,如果没有,则直接存;如果有节点,则继续比较;依次类推;如果找到相等的,则退出,确定唯一性;否则,比较到最后,存进来。

结论:存自定义对象主要要看比较的规则:1.自然排序法(Comparable接口) 2.比较器法

 

 TreeSet存储自定义对象,实现比较排序,通过1自然排序法(也就是通过实现Comparable接口,来重写compareTo方法,实现升序或者降序排序)

案例:自定义类只有一个属性的情况:

//自定义类为一个属性的情况:
//说明:存储自定义对象,自定义类要么实现Comparable接口;要么使用比较器
class Student implements Comparable<Student>{
    String name;
    public Student(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + "]";
    }
    @Override
    public int compareTo(Student o) { //重写的比较方法中,只要返回结果于源码对应上即可
        //将对象的比较,转成属性的比较
        //String的compareTo:前面的小返回<0  前面的大则返回>0  相等则返回0
        //return this.name.compareTo(o.name); //返回<0则存左子树;>0则存右子树;=0唯一性
        
        return o.name.compareTo(this.name);  //比较规则反过来则是降序排列
    }

public class Test2 {
    public static void main(String[] args) {
        Set<Student> set = new TreeSet<Student>();
        set.add(new Student("zs"));
        //ClassCastException: Student cannot be cast to Comparable
        set.add(new Student("ls"));
        set.add(new Student("ww"));
        set.add(new Student("zs"));
        
        System.out.println(set);
    }
}

自定义类有两个属性的情况下:

//自定义类有两个属性:比较使,按两个属性的规则来比
//比较规则,按两个属性来比较
class Man implements Comparable<Man>{
    String name;
    int    age;
    public Man(String name,int age) {
        this.name = name;
        this.age  = age;
    }
    @Override
    public int compareTo(Man o) {
        //规则:先按姓名的升序比较;如果姓名相同,再按年龄降序比较
        if(this.name.equals(o.name)) {
            return o.age-this.age; //int类型直接用-比较
        }
        return this.name.compareTo(o.name);
    }
    @Override
    public String toString() {
        return "Man [name=" + name + ", age=" + age + "]";
    }
}
public class Test3 {
    public static void main(String[] args) {
        Set<Man> set = new TreeSet<Man>();
        set.add(new Man("zs", 20));
        set.add(new Man("ls", 20));
        set.add(new Man("zs", 20));
        set.add(new Man("ls", 30));
        set.add(new Man("ls", 25));
        System.out.println(set);
    }
}

2.TreeSet的比较器排序法

案例:this---->o  为升序,反过来则降序

//TreeSet存储自定义对象,使用比较器法进行排序和确定唯一性
class Student{
    String name;
    int    age;
    public Student(String name,int age) {
        this.name = name;
        this.age  = age;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
}
public class Test1 {
    public static void main(String[] args) {
        Set<Student> set = new TreeSet<Student>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //规则:先按姓名的降序排列;姓名相同,则按年龄升序排列
                if(o1.name.equals(o2.name)) { //姓名相同,则按年龄升序
                    return o1.age-o2.age;
                }
                return o2.name.compareTo(o1.name); //先按姓名的降序
            }
        });
        set.add(new Student("zs", 30));
        set.add(new Student("ls", 40));
        set.add(new Student("zs", 25));
        set.add(new Student("zs", 32));
        System.out.println(set);
    }
}

二、关于多例集合(Map)的体系结构

概述:和Collection同级的根接口,Collection子孙类都是存单个对象;Map接口子孙类存的都是两个对象(键值对)

Map接口有两个重要的实现类: HashMap,TreeMap; 此外还有Hashtable,Properties

多例集合的体系结构图与介绍:

 1.HashMap

描述:HashMap是Map接口的实现类,集合中存储键值对

存储特点:key无序,无下标,唯一;  确定唯一性后,后面的value会覆盖前面的。

关于HashMap集合的遍历:

1因为没有下标,所以基本for循环不可用

map有size方法得到大小(长度),但是没有get(i)的方法

2.因为没有统一的类型,键有类型,值也有类型,所以不能用迭代器

//for(String key :map) {}

3.因为增强for循环的原理就是通过迭代器,既然迭代器不可用,那么增强for也不可用

正确方法1:通过间接循环,通过keySet()方法得到所有的键,然后放入Set集合中,然后对键进行遍历,在遍历的时候,通过get(key)方法得到键所对应的值

案例:

Map<String, Integer> map = new HashMap<String, Integer>();
map.put("aa", 666);
map.put("cc", 999);
map.put("bb", 666);
map.put("bb", 999);  //key无序,唯一;key相等后,value会覆盖前面的
System.out.println(map);

System.out.println(map.get("cc")); //根据key获取value值

Collection<Integer> col = map.values(); //将所有value放入Collection集合
System.out.println(col)

Set<String> set = map.keySet();
for (String key : set) {
    System.out.println(key+"--"+map.get(key));
}

正确方法2:把键值对作为一个实体存入Set集合中,然后对Set集合进行遍历,取出实体中的键值对

案例:

//2.entrySet:将键值对作为实体存到Set;再循环Set取出实体拿出里面的键值对
Set<Entry<String, Integer>> entries = map.entrySet();
for(Entry<String, Integer> entry:entries) {
    System.out.println(entry.getKey()+"=="+entry.getValue());
}

 原理分析:

HashMap的存储原理与HashSet是类似的,都是通过hash算法+数组+链表的方式

结论:存储key的类要重写hashCode和equals才能确定唯一性

因为如果存储<String ,Integer>这个键是String类的,String类重写了hashCode与equals方法

所以比较的是键的内容,所以当内容相同时候,会进行覆盖

        如果键是自定义对象类型的话,它默认是Object的hashCode和equals方法,它比较的是键的地址,哪怕键的内容是一样的,但是hash值也是不一样,它也会看作是不一样的,所以会重复添加。当我们重写自定义对象的equals方法和hashCode方法后,可以让它比较键的内容(也就是自定义对象中的内容)

如:不重写时候:

//HashMap的key存储自定义对象:
class Student{
	String name;
	int    age;
	public Student(String name,int age) {
		this.name = name;
		this.age  = age;
	}
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		//...
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		//...
		return true;
	}
}
public class Test2 {
	public static void main(String[] args) {
		Map<Student, Integer> map = new HashMap<Student, Integer>();
		map.put(new Student("zs", 13), 666);
		map.put(new Student("ls", 13), 999);
		map.put(new Student("zs", 20), 999);
		map.put(new Student("ls", 13), 999);
		System.out.println(map); //
	}
}

会有两个 Student("ls", 13), 999),如果重写后 只有一个Student("ls", 13), 999)

2.TreeMap

TreeMap的存储特点:key可排序,唯一; 确定唯一后,后面的值覆盖前面的

存储原理:通过二叉树存储,TreeMap的实现原理与TreeSet类似的

TreeMap的key存储自定义对象,验证排序和唯一性的原理,案例:

//TreeMap的key存储自定义对象,验证原理--有两个属性
class Student implements Comparable<Student>{
    String name;
    int    age;
    public Student(String name,int age) {
        this.name = name;
        this.age  = age;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
    @Override
    public int compareTo(Student o) {
        //规则:先按年龄降序排,年龄相同,则按姓名升序
        if(age==o.age) {
            return name.compareTo(o.name);
        }
        return o.age-age;
    }
}
public class Test2 {
    public static void main(String[] args) {
        Map<Student, Integer> map = new TreeMap<Student, Integer>();
        map.put(new Student("zs", 12), 666);
        map.put(new Student("zs", 12), 666);
        map.put(new Student("ls", 20), 666);
        map.put(new Student("ww", 12), 666);
        System.out.println(map); //不重写,则报错
    }
}

3.HashMap与TreeMap比较

  1. 优先选择HashMap,因为性能更高

  2. 除非需要排序,才选择TreeMap; HashSet与TreeSet也类似

案例:

//案例:有一个字符串,求出每个字符串的个数:  “ddcada”; 得出结果:d--3   a--2   c--1
String a = "ddcada";  //将字符串转字符数组,再循环遍历
char[] cs = a.toCharArray();

Map<Character, Integer> map = new HashMap<>();//key为字符  值为次数
for(int i=0;i<cs.length;i++) {
    Integer count = map.get(cs[i]); //根据key获取value; 没有则返回null
    if(count==null) { //为空
        map.put(cs[i], 1); //存储1个
    }else { //不为null,次数+1
        map.put(cs[i], count+1);
    }
}
System.out.println(map);

4.其他Map集合

除类HashMap与TreeMap实现类以外,还有Hashtable和Properties集合

//Hashtable:和HashMap类似,也是通过hash算法来存储
//区别在于Hashtable是线程安全,性能低key或value都不能存null;多线程使用安全的HashMap
//HashMap是线程不安全,性能高;key或value都能存null--倾向于单线程使用
Map<String, Integer> map = new Hashtable<String, Integer>();
//map.put(null, 666);
//map.put("aa", null);

Map<String, Integer> map2 = new HashMap<String, Integer>();
map2.put(null, 666);
map2.put("aa", null);
System.out.println(map2);

//Hashtable有一个常用的子类--Properties
//应用:往往Properties通过面向对象的方式使用--里面提供了独有方法
//key和value一般都是String类型;  在IO流中会补充加载配置文件的用法
Properties p = new Properties();
p.put("aa", "11");
p.put("cc", "66");
p.put("bb", "55");
System.out.println(p);

Set<String> set = p.stringPropertyNames(); //类似keySet  将key放入Set集合
for(String key:set) {
    System.out.println(key+"--"+p.getProperty(key));
}

5.集合总结

Collection  VS  Map:          存单个对象             存键值对
Collections  VS Collection:  工具类                 集合接口  
List   VS    Set:            有序,有下标,可重复     无序,无下标唯一
ArrayList VS  LinkedList      数组扩容;查询修改快     双向链表; 增删快
Set   VS   Map                单个对象               存键值对
HashSet  VS   HashMap         对象无序,唯一          key无序唯一; 两者原理一致;具体实现Map完成
TreeSet  VS   TreeMap         对象可排序,唯一        key可排序唯一;两者原理一致;具体实现Map完成 
HashSet  VS   TreeSet         对象无序,唯一          对象可排序唯一
HashMap  VS   TreeMap         key无序,唯一          key可排序唯一  

各位客官请听下回分解

创作不易,如果觉得还不错的话,可以点个赞给我个鼓励嘛🙌🙌🙌🙌

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值