首页 > 编程语言 >Java学习-集合篇

Java学习-集合篇

时间:2023-06-23 10:01:24浏览次数:54  
标签:Java 元素 学习 println add key 集合 out

集合

什么是集合?有什么用?
数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其他类型的数据。

集合为什么说在开发中使用的较多?
集合是一个容器,是一个载体,可以依次容纳多个对象。
在实际的开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。

集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
注意:集合在java中本身是一个容器,是一个对象。集合在任何时候存储的都是“引用”。

img

集合中存储的是对象的内存地址.png

在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。什么是数据结构?数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。
例如:数组、二叉树、链表、哈希表......这些都是常见的数据结构。
你往集合c1中放数据,可能是放到数组上了。
你往集合c2中放数据,可能是放到二叉树上了。
.......
你使用不同的集合等同于使用了不同的数据结构。

在我们这里,需要掌握的不是精通数据结构,java中已经将数据结构实现了,已经写好了这些常用的集合类,你只需要掌握怎么用?在什么情况下选择哪一种合适的集合去使用即可。
new ArrayList(); 创建一个集合,底层是数组。
new LinkedList(); 创建一个集合对象,底层是链表。
new TreeSet(); 创建一个集合对象,底层是二叉树。
......

集合在java JDK中java.util.*;包下。
所有的集合类和集合接口都在java.util包下。

在java中集合分为两大类:
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
一类是以键值对儿的方式存储元素
以键值对的方式存储元素,这一类集合中超级父类接口:java.util.Map;

集合的继承结构

img

集合的继承结构图.png

img

List下的几个类.png

ArrayList集合底层采用了数组这种数据结构。
ArrayList集合是非线程安全的。
LinkedList集合采用了双向链表数据结构。
Vector集合底层采用了数组这种数据结构。
Vector集合是线程安全的。
Vector所有的方法都有synchronized关键字修饰,所以线程安全,但是效率较低,现在保证线程安全有别的方案,所以Vector使用的较少了。

img

Set下的类和接口.png

Set集合存储元素特点:无序不可重复,无序表示存进去是这个顺序,取出来就不一定是这个顺序了。
另外Set集合中元素没有下标,Set集合中的元素还不能重复。
实际上HashSet集合在new的时候,地城实际上new了一个HashMap集合。向HashSet集合中存储元素,实际上是存储到HashMap集合中了。
HashMap集合是一个哈希表数据结构。
TreeSet集合底层实际上是TreeMap。new TreeSet集合的时候,底层实际上new了一个TreeMap集合。
往TreeSet集合中放数据的时候,实际上是将数据放到TreeMap集合中了。
TreeMap集合底层采用了二叉树数据结构。
补充:SortedSet集合存储元素的特点:
由于继承了Set集合,所以它的特点也是无序不可重复,但是放在SortedSet集合中的元素可以自动排序。我们称为可排序集合。放到该集合中的元素是自动按照大小顺序排序的。

img

集合的继承结构图.png

img

Map集合继承结构图.png

总结:
ArrayList:底层是数组。
LinkedList:底层是双向链表。
Vector:底层是数组,线程安全的,效率较低,使用较少。
HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。
TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了。
HashMap:底层是哈希表。
Hashtable:底层也是哈希表,只不过线程安全的,效率较低,使用较少。
Properties:是线程安全的,并且key和value只能存储字符串String。
TreeMap:底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序。

List集合存储元素的特点:
有序可重复。
有序:存进去的顺序和取出的顺序相同,每一个元素都有下标。
可重复:存进去1,还可以再存储一个1.

Set(Map)集合存储元素的特点:
无序不可重复。
无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标。
不可重复:存进去1,就不能再存储1了。

SortedSet(SortedMap)集合存储元素的特点:
首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。
无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标。
不可重复:存进去1,就不能再存储1了。
可排序:可以按照大小顺序排列。

Map集合的key,就是一个Set集合。
在Set集合中放数据,实际上放到了Map集合的key部分。

Collection接口

Collection中能存放什么元素?
没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
使用了“泛型”之后,Collection中只能存储某个具体的类型。
集合后期会学习“泛型”语法。目前先不用管。Collection中什么都能存,只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。)

Collection中的常用方法

关于java.util.Collection接口中常用的方法。

boolean add(E e) 向集合中添加元素 (E是泛型,目前看成Object就行)

//创建一个集合对象
//多态
Collection c = new ArrayList();
c.add(1200);//自动装箱(java5新特性)
c.add(3.14);//自动装箱
c.add(new Object());
c.add(true);//自动装箱

int size() 获取集合中元素的个数

//获取集合中元素的个数
System.out.println("集合中元素个数是:" + c.size());//4

void clear() 清空集合

//清空集合
c.clear();
System.out.println("集合中元素个数是:" + c.size());//0

boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false

//再向集合中添加元素
c.add("hello");//"hello"对象的内存地址放到了集合中
c.add("world");
c.add("浩克");
c.add("绿巨人");
c.add(1);
//判断集合中是否包含“绿巨人”
bollean flag = c.contains("绿巨人");
bollean flag1 = c.contains("绿巨人2");
System.out.println(flag);//true
System.out.println(flag1);//false
System.out.println(c.contains(1));//true
//获取集合中元素的个数
System.out.println("集合中元素个数是:" + c.size());//5

bollean remove(Object o) 删除集合中某个元素

c.remove("1");
//获取集合中元素的个数
System.out.println("集合中元素个数是:" + c.size());//4

boolean isEmpty() 判断该集合中元素的个数是否为0

//判断集合是否为空(集合中是否存在元素)
System.out.println(c.isEmpty());//false
c.clear();//清空
System.out.println(c.isEmpty());//true

Object[] toArray() 调用这个方法可以把集合转换成数组。

c.add("abc");
c.add("def");
c.add(100);
c.add("helloworld");

//转换成数组(了解,使用不多)
Object[] obj = c.toArray();
for(int i = 0; i < obj.length ; i++){
  //遍历数组
  Object o = obj[i];
  System.out.println(o);
}

关于集合遍历/迭代

以下学习的遍历方式/迭代凡是,事所有Collection通用的一种方式。
在Map集合中不能用。在所有的Collection以及子类中使用。

//创建集合对象
Collection c = new ArrayList();//后面的集合无所谓,主要是看前面的Collection接口,怎么遍历/迭代。
//添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
//对集合Collection进行遍历/迭代
//第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
//第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
/*
    以下两个方法是迭代器对象Iterator中的方法:
        boolean hasNext()  如果仍有元素可以迭代,则返回true
        Object next() 返回迭代的下一个元素
*/
while(it.hasNext){
  //不管你当初存进去什么,取出来统一都是Object。
  Object obj = it.next();
  System.out.println(obj);
}

img

迭代器执行原理.png

img

迭代原理.png

深入Colllection集合的contains方法:
boolean contains(Object o)
判断集合中是否包含某个对象o
如果包含返回true,如果不包含返回false

contains方法是用来判断集合中是否包含某个元素的方法,那么它在底层是怎么判断集合中是否包含某个元素的呢?
调用了equals方法进行比对。
equals方法返回true,就表示包含这个元素。

存放在集合中的类型,一定要重写equals方法。

Collection接口中的remove方法和contains方法底层都会调用equals。

关于集合元素的remove:
重点:当集合的结构发生改变时,迭代器必须重新获取,如果还是用以前老的迭代器,会出现异常:java.ConcurrentModificationException

重点:在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:c.remove(o);迭代过程中不能这样。

出异常的根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了。)

直接通过集合去删除元素,没有通知迭代器,导致迭代器的快照和原集合状态不同。
迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。

在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法,删除元素,不要使用集合自带的remove方法删除元素。

List接口的常用方法

LIst集合存储元素特点:有序可重复
有序:List集合中的元素有下标。从0开始,以1递增
可重复:存储一个1,还可以再存储1。

List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法:
以下只列出List接口特有的常用的方法:
(目前先把E看成Object)
void add(int index,E element)
E get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
E remove(int index)
E set(int index,E element)

这些方法不需要死记硬背,建议自己编写代码测试一下,理解一下,以后开发的时候,还是要翻阅帮助文档。

//创建List类型的集合。
List myLIst = new ArrayList();

//添加元素
myList.add("A");
myList.add("B");
myList.add("C");
myList.add("C");
myList.add("D");
//在列表的指定位置插入指定元素(第一个参数是下标)
//这个方法使用不多,因为对于ArrayList集合来说效率比较低。
myList.add(1,"KING")

//迭代
Iterator it = myList.iterrator();
while(it.hasNext()){
  Object elt = it.next();
  System.out.println(elt);
}

//根据下标获取元素
Object firstObj = myList.get(0);
System.out.println(firstObj);

//因为有下标所以List集合有自己比较特殊的遍历方式
//通过下标遍历。【List集合特有的方式,Set没有】
for(int i = 0; i < myList.size(); i++){
  Object obj = myList.get(i);
  System.out.println(obj);
}

//获取指定对象第一次出现处的索引。
System.out.println(myList.indexOf("KING"));//1

//获取指定对象最后一次出现处的索引
System.out.println(myList.lastIndexOf("C"));//4

//删除指定下标位置的元素
myList.remove(0);//删除下标为0的元素
System.out,println(myList.size());//5

System.out.println("===================");

//修改指定位置的元素
myList.set(2,"Soft");

//遍历集合
for(int i = 0; i < myList.size(); i++){
  Object obj = myList.get(i);
  System.out.println(obj);
}
 

计算机英语:
增删改查这几个单词要知道
增:add、save、new
删:delete、drop、remove
改:update、set、modify
查:find、get、query、select

ArrayList集合

ArrayList集合初始化容量是10。(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10)

ArrayList集合底层是Object类型的数组Object[]。

构造方法:
new ArrayList();
new ArrayList(20);

//默认初始化容量10
List myList1 = new ArrayList();

//指定初始化容量20
List myList2 = new ArrayList(20);

ArrayList集合的扩容:增长到原容量的1.5倍。

ArrayList底层是数组,怎么优化?
尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。这是ArrayList集合比较重要的优化策略。

数组的优点:检索效率比较高(每个元素占用的空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
数组的缺点:随机增删元素效率比较低。另外,数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
但是需要注意的是:向数组末尾添加元素,效率还是很高的。

面试官经常问的一个问题:
这么多的集合中你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。另外,我们检索/查找某个元素的操作比较多。

ArrayList集合还有另一个构造方法

//创建一个HashSet集合
Collection c = new HashSet();
//添加元素到Set集合
c.add(100);
c.add(200);
c.add(900);
c.add(50);

//通过这个构造方法就可以将HashSet集合转换成List集合。
List myList = new ArrayList(c);
for(int i = 0; i < myLisst.size(); i++){
  System.out.println(myList.get(i));
}

位运算

右移 >>
左移 <<
右移n位就是除以2的n次方
左移n位就是乘以2的n次方

LinkedList集合

单向链表数据结构
对于链表数据结构来说:基本的单元是节点Node。
对于单向链表来说:任何一个节点Node中都有两个属性:
第一:存储的数据。第二:下一节点的内存地址。

img

单向链表.png

链表优点:随机增删元素效率较高。(因为增删元素不涉及到大量元素位移)
由于链表上的元素在空间存储上内存地址不连续。所以随即增删元素的时候不会有大量元素位移,因此随即增删效率较高。在以后的开发中,如果遇到随即增删集合中元素的业务比较多时,建议使用LinkedList。

链表缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历。
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。

ArrayList集合之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为智能从头节点开始一个一个遍历。

ArrayList集合是非线程安全的。(不是线程安全的集合。)

img

双向链表.png

LinkedList集合没有初始化容量。
最初这个链表中没有任何元素。first和last引用都是null。
不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是哪个集合。
因为我们要面向接口编程,调用的方法都是接口中的方法。

LinkedList集合底层数据结构是双向链表。
对于链表数据结构来说,随即增删效率较高。检索效率较低。
链表中的元素在空间存储上,内存地址不连续。

Vector集合

Vector底层也是一个数组。
默认初始化容量是10。
扩容之后是原容量的2倍。
Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率比较低,使用较少了。

怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:java.util.Collections;

java.util.Collection是集合接口。
java.util.Collections是集合工具类。

List myList = new ArrayList();//非线程安全的。

//变成线程安全的
Collections.synchronizedList(myList);

//此时myList集合就是线程安全的
myList.add("111");
myList.add("222");
myList.add("333");

泛型

JDK5之后的新特性:泛型
泛型这种语法机制,直在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)

使用泛型好处是什么?
第一:集合中存储的元素类型统一了。
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”。

泛型的缺点是什么?
导致集合中存储的元素缺乏多样性!
大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可。

//创建集合对象
//使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据。
//用泛型来指定集合中存储的数据类型。
List<Animal> myList = new ArrayList<Animal>();

//准备对象
Cat c = new Cat();
Bird b = new Bird();
myList.add(c);
myList.add(b);

//获取迭代器
//这个表示迭代器迭代的是Animal类型。
Iterator<Animal> it = myList.iterator();
while(it.hasNext()){
    //使用泛型之后,每一次迭代返回的数据都是Animal类型。
    Animal a = it.next();
    //这里不需要进行强制类型转换了。直接调用。
    a.move();
    //调用子类特有方法还是需要向下转换的!
    Animal a = it.next();
    if(a instanceof Cat){
        Cat x = (Cat)a;
        x.catchMouse();
    }

    if(a instanceof Bird){
        Bird y = (Bird)a;
        y.fiy();
    }
}


class Animal{
    public void move{
        System.out.println("动物在移动");
    }
}

class Cat extends Animal{
    public void catchMouse(){
        System.out.println("猫抓老鼠");
    }
}

class Bird extends Animal{
    public void fly(){
        System.out.println("鸟儿在飞翔");
    }
}

JDK8之后,引入了:自动类型推断机制(又称为钻石表达式)

//ArrayList<这里的类型会自动推断>,前提是JDK8之后才允许
//自动类型推断,钻石表达式
List<Animal> myList = new ArrayList<>();

自定义泛型可以么?可以
自定义泛型的时候,<>尖括号中的是一个标识符,随便写。
java源代码中经常出现的是:
E是Element单词首字母。
T是Type单词首字母。

增强for循环(foreach)

语法:
for(元素类型 变量名 : 数组或集合){
System.out.println(变量名);
}

foreach有一个缺点:没有下标。在需要使用

HashSet集合

存储时顺序和取出时顺序不同。
不可重复。
放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。

TreeSet集合

TreeSet集合存储元素特点:
无序不可重复的,但是存储的元素可以自动按照大小顺序排序!
称为:可排序集合。
这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。

Map集合

Map和Collection没有继承关系。
Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导地位,value是key的一个附属品。

Map集合的常用方法

V put(K key , V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
Set keySet() 获取Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数
Collection values() 获取Map集合中所有的value,返回一个Collection
Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class Text {
    public static void main(String[] args) {

        //创建map集合对象
        Map<Integer,String> map = new HashMap<>();

        //向map集合中添加键值对
        map.put(1,"zhangsan");//这里1进行了自动装箱
        map.put(2,"lisi");
        map.put(3,"wangwu");
        map.put(4,"zhaoliu");

        //通过key获取value
        String value = map.get(2);
        System.out.println(value);

        //获取键值对的数量
        System.out.println("键值对的数量:" + map.size());//4

        //通过key删除key-value
        map.remove(2);
        System.out.println("键值对的数量:" + map.size());//3

        //判断是否包含某个key
        //contains方法底层调用的都是equals进行比对的,所以自定义类型需要重写equals方法。
        System.out.println(map.containsKey(4));//true
        //System.out.println(map.containsKey(new Integer(4)));//true

        //判断是否包含某个value
        System.out.println(map.containsValue("wangwu"));//true
        //System.out.println(map.containsValue(new String("wangwu")));//true

        //获取所有的value
        Collection<String> values = map.values();
        //foreach
        for (String s : values) {
            System.out.println(s);
        }

        //清空map集合
        map.clear();
        System.out.println("键值对的数量:" + map.size());//0

        //判断是否为空
        System.out.println(map.isEmpty());//true
    }
}

重要的两个方法。

import java.util.*;

/*
    Map集合的遍历
*/
public class Text {
    public static void main(String[] args) {
        //第一种方式:获取所有的key,通过遍历key,来遍历value
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"zhangsan");
        map.put(2,"lisi");
        map.put(3,"wangwu");
        map.put(4,"zhaoliu");
        //遍历Map集合
        //获取所有的key,所有的key是一个Set集合
        Set<Integer> keys =  map.keySet();
        //遍历key,通过key获取value
        //迭代器可以
        /*Iterator<Integer> it = keys.iterator();
        while (it.hasNext()){
            //取出其中一个key
            Integer key = it.next();
            //通过key获取value
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }*/
        //foreach也可以
        for (Integer key: keys) {
            System.out.println(key + "=" + map.get(key));
        }

        //第二种方式:Set<Map.Entry<K,V>> entrySet()
        //以上这个方法是把Map集合直接全部转换成Set集合
        //Set集合中元素的类型是:Map.Entry
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        //遍历Set集合,每一次取出一个Node
        //迭代器
       /* Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while (it2.hasNext()){
            Map.Entry<Integer,String> node = it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + "=" + value);
        }*/
        
        //foreach
        //这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
        //这种方式比较适合于大数据量
        for (Map.Entry<Integer,String> node: set) {
            System.out.println(node.getKey() + "---->" + node.getValue());
        }
    }
}

哈希表数据结构

HashMap集合底层是哈希表/散列表的数据结构。

哈希表是一个怎样的数据结构呢?
哈希表是一个数组和单向链表的结合体。
数组:在查询方面效率很高,随即增删方面效率很低。
单向链表:在随即增删方面效率很高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。

HashMap集合底层的源代码:

public class HashMap{
    //HashMap底层实际上就是一个数组。(一维数组)
    transient Node<K,V>[] table

    //静态的内部类HashMap.Node
    static class Node<K,V> implements Map.Entry<K,V>{
        final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
        final K key;//存储到Map集合中的那个key
        V value;//存储到Map集合中的那个value
        Node<K,V> next;//下一个节点的内存地址
    }
}

哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)

最主要掌握的是:
map.put(k,v)
v = map.get(k)
以上这两个方法的实现原理,是必须掌握的。

map.put(k,v)实现原理:
第一步:先将k,v封装到Node对象当中。
第二步:底层会调用k的hashCode()方法得出hash值,通过通过哈希函数/哈希算法,将hash值转换成数组下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals,如果所有的equals方法返回都是false,那么这个新节点将会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value将会被覆盖。

v = map.get(k)实现原理:
先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么都没有,返回null,如果这个位置上有单向链表,那么拿着参数k和单向链表上的每个节点中的k进行equals,如果所有equals方法返回false,那么get方法返回null,只要其中有一个节点的k和参数k equals的时候返回true,那么此时这个节点的value就是我们要找的value,get方法最终返回这个要找的value。

为什么哈希表的随即增删以及查询效率都很高?
增删是在链表上完成。
查询也不需要都扫描,只需要部分扫描。

HashMap集合的key,会先后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写。

HashMap集合的key部分特点:
无序,不可重复。
为什么无序?因为不一定挂在哪个单向链表上。
不可重复是怎么保证的?equals方法来保证HashMap集合的key不可重复。
如果key重复了,value会覆盖。

放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode()+equalis()方法。

注意:同一个单项链表上,所有节点的hash相同,因为他们的数组下标是一样的,但同一个链表上k和k的equals方法肯定返回的是false,都不相等。

哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们称为:散列分布不均匀。

什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。

假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。
散列分布均匀需要你重写hashCode()方法时有一定的技巧。

重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。

HashMap集合的默认初始化容量是16,默认加载因子是0.75。
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列分布均匀,为了提高HashMap集合的存取效率,所必须的。

向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用。
拿put(k,v)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
拿get(k)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。

注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,再同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。所以hashCode()方法的返回值也应该相同。

hashCode()方法和equals()方法也不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。

终极结论:放在Map集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。

在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。
这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。提高效率。初始化容量16,默认加载因子.75。

对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

扩容之后的容量时原容量的2倍。

Hashtable

HashMap集合key部分允许null吗?
允许。但是要注意:HashMap集合的key null值只能有一个。

Hashtable的key和value都是不能为null的。
HashMap集合的key和value都是可以为null的。

Hashtable方法都带有synchronized:线程安全的。
线程安全有其他的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。

Hashtable和HashMap一样,底层都是哈希表数据结构。

Hashtable的初始化容量是11,默认加载因子是:0.75f

Hashtable的扩容是:原容量 * 2 + 1

属性类Properties类

目前只需要掌握Properties属性类对象的相关方法即可。
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。

//创建一个Properties对象
Properties pro = new Properties();

//需要掌握Properties的两个方法,一个存,一个取。
pro.setProperty("url","jdbc:mysql://localhost:3306/powernode");
pro.setProperty("driver","com.mysql.jdbc.Driver");
pro.setProperty("username","root“);
pro.setProperty("password","123");

//通过key获取value
String url = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");

System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.println(password);

TreeSet

TreeSet集合底层实际上是一个TreeMap。
TreeMap集合底层是一个二叉树。
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。

//创建对象
TreeSet<String> ts = new TreeSet<>();
//添加元素
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
ts.add("zhangsi");
ts.add("wangliu");
//遍历
for(String s : ts ){
    //按照字典顺序,升序!
    System.out.println(s);
}

TreeSet<Integer> ts2 = new TreeSet<>();
ts2.add(100);
ts2.add(200);
ts2.add(900);
ts2.add(800);
ts2.add(600);
ts2.add(10);
for(Integer elt : ts2){
    //升序
    System.out.println(elt);
}

对于自定义类型来说,TreeSet无法排序,因为你没有指定比较规则。没有实现java.long.Comparable接口。
指定规则就需要把Comparable接口的方法实现,编写出比较的逻辑,或者是比较的规则。

compareTo方法的返回值很重要:
返回0表示相同,value会覆盖。
返回>0,会继续在右子树上找。
返回<0,会继续在左子树上找。

自平衡二叉树数据结构

TreeSet集合/TreeMap集合是自平衡二叉树,遵循左小右大原则存放。

遍历二叉树的时候有三种方式:
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
注意:前中后说的是“根”的位置,根在前面是前序,根在中间是中序,根在后面是后序。

TreeSet集合/TreeMap集合采用的是:中序遍历方式。
Iterator迭代器采用的是中序遍历方式。
左根右。

img

自平衡二叉树.png

比较器实现java.util.Comparator接口。(Comparator是java.lang包下的。Comparator是java.util包下的。)

最终结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。

Comparable和Comparator怎么选择?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。

Comparator接口的设计符合OCP原则。

Collections工具类

java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作。

//ArrayList集合不是线程安全的
List<String> list = new ArrayList<>();

//变成线程安全的
Collections.synchronizedList(list);

//排序
list.add("abf");
list.add("abx");
list.add("abc");
list.add("abe");

Collections.sort(list);
for(String s : list){
    System.out.println(s);
}

//注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。

//对Set集合怎么排序呢?
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
//将Set集合转换成List集合
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s : myList){
    System.out.println(s);
}
//这种方式也可以排序
//Collections.sort(List集合,比较器对象);

最后编辑于 :2020.11.15 19:33:57

标签:Java,元素,学习,println,add,key,集合,out
From: https://www.cnblogs.com/javaxubo/p/17498754.html

相关文章

  • java线程的五种状态
    五种状态开始状态(new)就绪状态(runnable)运行状态(running)阻塞状态(blocked)结束状态(dead)状态变化1、线程刚创建时,是new状态2、线程调用了start()方法后,进入runnable状态,此时并未真正执行,需要和其他线程竞争cpu资源3、当该线程竞争到了cpu资源,进入running状态4、线程因为某种......
  • Java中 = 和 += 的区别
    问题shorts1=1;s1=s1+1;有什么错?shorts1=1;s1+=1;有错吗?讨论1)+:在编译器将右边的表达式结果计算出来后,和左边的变量类型比较精度,如果左边的变量精度低于右边的结果的精度,编译器会显式的报错,告诉程序员去强制转型。(所以s1=s1+1出错)最后将表达式的结果复......
  • Java基础:自动装箱和自动拆箱
    感谢,原文链接:https://www.cnblogs.com/dolphin0520/p/3780005.htmlJava的自动包装类型首先基本数据类型:4类8种基本数据类型对应包装器类byte(1字节)Byteshort(2字节)Shortint(4字节)Integerlong(8字节)Longfloat(4字节)Floatdouble(8字节)Doublechar(2字......
  • [QML]事无巨细开始实践QML开发(一)什么是QML,为什么学习QML,先写一个简单的页面
    [QML]从零开始QML开发(一)什么是QML,为什么学习QML,先写一个简单的页面QML开发和QWidget开发的区别QML(QtMeta-ObjectLanguage)是Qt提供的一种声明性语言,用于快速创建用户界面。相对而言,QtWidgets是基于C++的桌面应用程序开发框架。下面是QML和QtWidgets之间的一些优缺点以及为何......
  • 【深度学习】参数量、模型大小、显存
    对于一个深度学习神经网络来说,其通常包含很多卷积层,用于不断提取目标的特征,或对目标进行最终定位或者分类。1数据存储精度与存储空间在深度学习神经网络中,最常见的数据格式是float32,占4个字节(Byte)。类似地,float16,占2个字节。1024个字节为1KB,1024x1024个字节为1MB。那么......
  • 动态图表示学习及动态图分析论文整理分享
       本项目总结了动态图表示学习的有关论文,并给出了详细的中文方法描述、数据集、创新点等。该项目在持续更新中,分享给需要的朋友。目录内容截图......
  • 2022年最新对比学习(Contrastive Learning)相关必读论文整理分享
        要说到对比学习(ContrastiveLearning),首先要从自监督学习开始讲起。自监督学习属于无监督学习范式的一种,特点是不需要人工标注的类别标签信息,直接利用数据本身作为监督信息,来学习样本数据的特征表达,并用于下游任务。    当前自监督学习可以被大致分为两类:    Genera......
  • 肖桐、朱靖波-机器翻译统计建模与深度学习方法
        分享一本由肖桐、朱靖波老师编著,东北大学自然语言处理实验室·小牛翻译 联合出品的新书《机器翻译统计建模与深度学习方法》。本书中文编著,对机器学习相关历史和涉及知识进行详细、全面、深入讲解,非常值得深入阅读、学习。          本书全面回顾了近三十年内......
  • 深度学习面试、从业-从理论到实践一站式资源整理分享
       本资源是一站式深度学习在线百科,内容涵盖零基础入门深度学习、产业实践深度学习、特色课程;深度学习百问、产业实践(开发中) 等等。从理论到实践,从科研到产业应用,各类学习材料一应俱全,旨在帮助开发者高效地学习和掌握深度学习知识,快速成为AI跨界人才。内容全面:无论您是深度......
  • 多伦多大学-强化学习导论-2022
    课程描述    这是一门关于不确定条件下强化学习和顺序决策的入门课程,重点在于理解理论基础。我们研究如何使用价值和策略迭代等动态规划方法来解决具有已知模型的顺序决策问题,以及如何扩展这些方法来解决模型未知的强化学习问题。其他主题包括,RL中的函数逼近、策略梯度方法......