首页 > 编程语言 >Java集合(严重缺失版)

Java集合(严重缺失版)

时间:2022-10-05 10:23:19浏览次数:80  
标签:map Java list System public println 集合 缺失 out

此笔记源自于b站up主 青空の霞光 的视频和他的笔记。

笔记出处:JavaSE笔记(四)泛型与集合类_青空の霞光的博客-CSDN博客

失策了,我应该先看前面的数据结构的。找资源的时候要先了解一下,前面有没有先决条件,是我贪快了。

Java集合

认识集合类

集合表示一组对象,称为其元素。一些集合允许重复的元素,而另一些则不允许。一些集合是有序的,而其他则是无序的。

集合类其实就是为了更好地组织、管理和操作我们的数据而存在的,包括列表、集合、队列、映射等数据结构。

集合类最顶层不是抽象类而是接口,因为接口代表的是某个功能,而抽象类是已经快要成形的类型,不同的集合类的底层实现是不相同的,同时一个集合类可能会同时具有两种及以上功能(既能做队列也能做列表),所以采用接口会更加合适,接口只需定义支持的功能即可。 img

注:Queue 翻译为 队列。TreeSet翻译为 树集合

数组与集合

相同之处:

  1. 它们都是容器,都能够容纳一组元素。

不同之处:

  1. 数组的大小是固定的,集合的大小是可变的。

  2. 数组可以存放基本数据类型,但集合只能存放对象。

  3. 数组存放的类型只能是一种,但集合可以有不同种类的元素。

集合根接口Collection

本接口中定义了全部的集合基本操作,我们可以在源码中看看。

使用前记得导入包

import java.util.Collection;

aint size();   //返回集合的大小  
boolean isEmpty();  //是否为空  
boolean contains(Object o);//是否包含某个对象  
Iterator<E> iterator();   //迭代器 后面会提到.为 Iterator 接口实例化  
Object[] toArray();     //集合转化为数组.如果传入参数为空,则返回Object[]数组,  
<T> T[] toArray(T[] a);//如果传入参数为T[],则返回参数为传入参数的运行时类型。  
boolean add(Object o);//增加一个具体的元素  
boolean remove(Object o);//移除一个具体的元素  
boolean containsAll(Collection c) //是否包含另一个集合中的所有元素。  
boolean addAll(Collection c)    //把参数collection中的所有元素批量添加到当前Collection结构中  
                                 //   取并集  
boolean removeAll(Collection c)  //removeAll用于删除参数collection中的所有元素;  
boolean retainAll(Collection c)//取交集  
void clear() //快速删除所有元素,清空  
boolean equals(Object o)     //判断是否相等  
int hashCode()                //返回哈希值

集合类的使用

List列表

ArrayList

首先介绍ArrayList,它的底层是用数组实现的,内部维护的是一个可改变大小的数组,也就是我们之前所说的线性表!继承自List接口。

初始化

import java.util.ArrayList; // 引入 ArrayList 类  
​  
ArrayList<E> objectName =new ArrayList<>();  // 初始化  
//E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型。  
//objectName: 对象名。  
ArrayList<E> objectName =new ArrayList<>(100); //做个优化,给它个默认初始长度。因为前期扩容会比较频繁

i

增,查,删,改

add()    //增加元素  
add(位置,元素)      
get(地址)    //访问 ArrayList 中的元素可以使用  
set(地址,元素)方法 //修改 ArrayList 中的元素      
remove()  //删除 ArrayList 中的元素.可以指定下标,或指定元素  
clone() //复制一份 arraylist  
indexOf() //返回 arraylist 中元素的索引值 不存在返回-1    
lastIndexOf() //返回指定元素在 arraylist 中最后一次出现的位置    
toString() //将 arraylist 转换为字符串    
subList() //截取部分 arraylist 的元素    
//等等还有很多

还能调用Arrays类

Arrays.sort()

LinkedList

LinkedList

(这一部分是 java LinkedList (详解) - 憨豆只干扎瓦 - 博客园的)

因为跳过数据结构部分,所以视频讲源码我听不太懂,所以就直接换成如何操作了

1、链表 (Linked List) 是一种常见的基础数据结构,是一种线性表,但是链表不会按线性表的顺序存储数据,而是每个节点里存到下一个节点的地址

2、它是一个队列,可以当双端队列来使用

3、它是一个集合,可以根据索引随机访问集合中的元素,还实现了Deque接口

4、LinedList是一个List集合,它的实现方式和ArrayList是完全不同的,ArrayList的底层是通过一个动态的Object[]数组实现的,而LinkedList的底层是通过链表来实现的,因此它的随机访问速度是比较差的,但是它的删除,插入操作很快。

5、LinkedList是基于双向循环链表实现的,除了可以当作链表操作外,它还可以当作栈、队列和双端队列来使用。

6、LinkedList同样是非线程安全的,只在单线程下适合使用。

与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。

  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。

  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

LinkedList类

  • LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。

  • LinkedList 实现 List 接口,能进行列表的相关操作。

  • LinkedList 实现了 Queue 接口,可作为队列使用。

  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。

  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。

  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

  • LinkedList 是非同步的。

LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下:

// 引入 LinkedList 类  
 import java.util.LinkedList;

LinkedList<E> list = new LinkedList<E>();   // 普通创建方法  
//或者  
 LinkedList<E> list = new LinkedList(Collection<? extends E> c); // 使用集合创建链表
public boolean add(E e) //链表末尾添加元素,返回是否成功,成功为 true,失败为 false。  
public void add(int index, E element) //向指定位置插入元素。  
public boolean addAll(Collection c) //将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。  
public boolean addAll(int index, Collection c) //将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。  
public void addFirst(E e) //元素添加到头部。  
public void addLast(E e) //元素添加到尾部。  
public boolean offer(E e) //向链表末尾添加元素,返回是否成功,成功为 true,失败为 false。  
public boolean offerFirst(E e) //头部插入元素,返回是否成功,成功为 true,失败为 false。  
public boolean offerLast(E e) //尾部插入元素,返回是否成功,成功为 true,失败为 false。  
public void clear() //清空链表。  
public E removeFirst() //删除并返回第一个元素。  
public E removeLast() //删除并返回最后一个元素。  
public boolean remove(Object o) //删除某一元素,返回是否成功,成功为 true,失败为 false。  
public E remove(int index) //删除指定位置的元素。  
public E poll() //删除并返回第一个元素。  
public E remove() //删除并返回第一个元素。  
public boolean contains(Object o) //判断是否含有某一元素。  
public E get(int index)// 返回指定位置的元素。  
public E getFirst() //返回第一个元素。  
public E getLast() //返回最后一个元素。  
public int indexOf(Object o) //查找指定元素从前往后第一次出现的索引。  
public int lastIndexOf(Object o) //查找指定元素最后一次出现的索引。  
public E peek() //返回第一个元素。  
public E element() //返回第一个元素。  
public E peekFirst() //返回头部元素。  
public E peekLast() //返回尾部元素。  
public E set(int index, E element) //设置指定位置的元素。  
public Object clone() //克隆该列表。  
public Iterator descendingIterator() //返回倒序迭代器。  
public int size() //返回链表元素个数。  
public ListIterator listIterator(int index) //返回从指定位置开始到末尾的迭代器。  
public Object[] toArray() //返回一个由链表元素组成的数组。  
public T[] toArray(T[] a) //返回一个由链表元素转换类型而成的数组。

小练习

import java.util.LinkedList;

public class LinkList {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<String>();
        list.add("I");
        list.add("do");
        list.add("not");
        list.add("love");
        list.add("you");
        System.out.println(list);
        System.out.println(list.size());
        list.remove(2);
        System.out.println(list.contains("not"));
        list.add(2,"really");
       // String a = list.poll();
        System.out.println(list.poll());

        for (String s : list) {
            System.out.print(s+" ");
        }
    }
}
/*
[I, do, not, love, you]
5
false
I
do really love you 
*/

它还实现了Queue接口,所以LinkedList也能被当做一个队列或是栈来使用。

public static void main(String[] args) {      
    LinkedList<String> list = new LinkedList<>();      
    list.offer("A");   //入队    
    System.out.println(list.poll());  //出队      
    list.push("A");    list.push("B");    //进栈      
    list.push("C");      
    System.out.println(list.pop());    System.out.println(list.pop());    //出栈          
    System.out.println(list.pop());}  
​

利用代码块来快速添加内容 前面我们学习了匿名内部类,我们就可以利用代码块,来快速生成一个自带元素的List

List<String> list = new LinkedList<String>(){  
   {      
    //初始化时添加    
    this.add("A");    
    this.add("B");}  
};  
System.out.println(list);

如果是需要快速生成一个只读的List,后面会讲解Arrays工具类。

集合的排序

List<Integer> list = new LinkedList<Integer>(){    
    //Java9才支持匿名内部类使用钻石运算符    
   {        this.add(10);        
             this.add(2);          
             this.add(5);          
             this.add(8);   }  
};  
list.sort((a, b) -> {    //这是个lamada表达式 int compare(o1,o2),还可以再简化  
    //排序已经由JDK实现,现在只需要填入自定义规则,完成Comparator接口实现    
    return a - b;    
    //返回值小于0,表示a应该在b前面,返回值大于0,表示b应该在a后面,等于0则不进行交换  
});  
System.out.println(list);

迭代器

集合的遍历

所有的集合类,都支持foreach循环!

public static void main(String[] args) {
        LinkedList<Integer> list  = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        for (Integer i :list){
            System.out.println(i);
        }
       LinkedList<String> list1 = new LinkedList<String>();
        list1.add("Hi,");
        list1.add("Long ");
        list1.add("time ");
        list1.add("no ");
        list1.add("see ");
        for (String s : list1) {
            System.out.print(s);
        }
    }
/*
1
2
3
Hi,Long time no see 
*/

当然,也可以使用JDK1.8新增的forEach方法,它接受一个Consumer接口实现:

 list.forEach(integer -> {
            System.out.println(integer);
        }
 );
 //甚至可以简化成这样
 list.forEach(System.out::println);//方法的返回值和它的参数一致,且为静态就可以用
 //这种:: 来简化

 list1.forEach(s -> {
            System.out.println(s);
                }
        );

从JDK1.8开始,lambda表达式开始逐渐成为主流,我们需要去适应函数式编程的这种语法,包括批量替换,也是用到了函数式接口来完成的。

可百度:Java8 四大函数式接口

list.replaceAll((i) -> {
  if(i == 2) return 3;   //将所有的2替换为3
  else return i;   //不是2就不变
});
System.out.println(list);
Iterable和Iterator接口

我们之前学习数据结构时,已经得知,不同的线性表实现,在获取元素时的效率也不同,因此我们需要一种更好地方式来统一不同数据结构的遍历。

由于ArrayList对于随机访问的速度更快,而LinkedList对于顺序访问的速度更快,因此在上述的传统for循环遍历操作中,ArrayList的效率更胜一筹,因此我们要使得LinkedList遍历效率提升,就需要采用顺序访问的方式进行遍历,如果没有迭代器帮助我们统一标准,那么我们在应对多种集合类型的时候,就需要对应编写不同的遍历算法,很显然这样会降低我们的开发效率,而迭代器的出现就帮助我们解决了这个问题。

我们先来看看迭代器里面方法:

public interface Iterator<E> {
  //...
}

每个集合类都有自己的迭代器,通过iterator()方法来获取:

注意:Iterator和Iterable不是一个东西,不要搞混了

Iterator<Integer> iterator = list.iterator();   //生成一个新的迭代器
while (iterator.hasNext()){    //判断是否还有下一个元素
  Integer i = iterator.next();     //获取下一个元素(获取一个少一个)
  System.out.println(i);
}

迭代器生成后,默认指向第一个元素,每次调用next()方法,都会将指针后移,当指针移动到最后一个元素之后,调用hasNext()将会返回false,迭代器是一次性的,用完即止,如果需要再次使用,需要调用iterator()方法。

ListIterator<Integer> iterator = list.listIterator();   //List还有一个更好地迭代器实现ListIterator

ListIterator是List中独有的迭代器,在原有迭代器基础上新增了一些额外的操作。

比如

ListIterator<Integer> iterator = list.listIterator();
        while (iterator.hasNext()){
            Integer i = iterator.next();
            System.out.println(i);
        }
        while (iterator.hasPrevious()){
            Integer i = iterator.previous();
            System.out.println(i);
        }

Set集合

我们之前已经看过Set接口的定义了,我们发现接口中定义的方法都是Collection中直接继承的,因此,Set支持的功能其实也就和Collection中定义的差不多,只不过使用方法上稍有不同。

Set集合特点:

  • 不允许出现重复元素

  • 不支持随机访问(不允许通过下标访问)

首先认识一下HashSet,它的底层就是采用哈希表实现的(我们在这里先不去探讨实现原理,因为底层实质上维护的是一个HashMap,我们学习了Map之后再来讨论)

public static void main(String[] args) {
    HashSet<Integer> set = new HashSet<>();
    set.add(120);    //支持插入元素,但是不支持指定位置插入
    set.add(13);
    set.add(11);
    for (Integer integer : set) {
      System.out.println(integer);
    }
}

运行上面代码发现,最后Set集合中存在的元素顺序,并不是我们的插入顺序,这是因为HashSet底层是采用哈希表来实现的,实际的存放顺序是由Hash算法决定的。

那么我们希望数据按照我们插入的顺序进行保存该怎么办呢?我们可以使LinkedHashSet

public static void main(String[] args) {
    LinkedHashSet<Integer> set = new LinkedHashSet<>();  //会自动保存我们的插入顺序
    set.add(120);
    set.add(13);
    set.add(11);
    for (Integer integer : set) {
        System.out.println(integer);
    }
}

LinkedHashSet底层维护的不再是一个HashMap,而是LinkedHashMap,它能够在插入数据时利用链表自动维护顺序,因此这样就能够保证我们插入顺序和最后的迭代顺序一致了。

还有一种Set叫做TreeSet,它会在元素插入时进行排序:

public static void main(String[] args) {
    TreeSet<Integer> set = new TreeSet<>();
    set.add(1);
    set.add(3);
    set.add(2);
    System.out.println(set);
}
/*[1,2,3]  自然规则*/

可以看到最后得到的结果并不是我们插入顺序,而是按照数字的大小进行排列。当然,我们也可以自定义排序规则:

public static void main(String[] args) {
    TreeSet<Integer> set = new TreeSet<>((a, b) -> b - a);   //在创建对象时指定规则即可
    // a - b 升序,b - a 降序
    set.add(1);
    set.add(3);
    set.add(2);
    System.out.println(set);
}

现在的结果就是我们自定义的排序规则了。

虽然Set集合只是粗略的进行了讲解,但是学习Map之后,我们还会回来看我们Set的底层实现,所以说最重要的还是Map。本节只需要记住Set的性质、使用即可。

Map映射

什么是映射

我们在高中阶段其实已经学习过映射了,映射指两个元素的之间相互“对应”的关系,也就是说,我们的元素之间是两两对应的,是以键值对的形式存在。

Map接口

Map就是为了实现这种数据结构而存在的,我们通过保存键值对的形式来存储映射关系。

Key --- value , Key不能重复,value可以

我们先来看看Map接口中定义了哪些操作。

HashMap和LinkedHashMap

HashMap的实现过程,相比List,就非常地复杂了,它并不是简简单单的表结构,而是利用哈希表存放映射关系,我们来看看HashMap是如何实现的,首先回顾我们之前学习的哈希表,它长这样:

哈希表的本质其实就是一个用于存放后续节点的头结点的数组,数组里面的每一个元素都是一个头结点(也可以说就是一个链表),当要新插入一个数据时,会先计算该数据的哈希值,找到数组下标,然后创建一个新的节点,添加到对应的链表后面。

而HashMap就是采用的这种方式,我们可以看到源码中同样定义了这样的一个结构:

/**
 * The table, initialized on first use, and resized as
 * necessary. When allocated, length is always a power of two.
 * (We also tolerate length zero in some operations to allow
 * bootstrapping mechanics that are currently not needed.)
 */
transient Node<K,V>[] table;

这个table表会在第一次使用时初始化,同时在必要时进行扩容,并且它的大小永远是2的倍数!

/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

我们可以看到默认的大小为2的4次方,每次都需要是2的倍数,也就是说,下一次增长之后,大小会变成2的5次方。

我们现在需要思考一个问题,当我们表中的数据不断增加之后,链表会变得越来越长,这样会严重导致查询速度变慢,首先想到办法就是,我们可以对数组的长度进行扩容,来存放更多的链表,那么什么情况下会进行扩容呢?

/**
 * The load factor for the hash table.
 *
 * @serial
 */
final float loadFactor;

我们还发现HashMap源码中有这样一个变量,也就是负载因子,那么它是干嘛的呢?

负载因子其实就是用来衡量当前情况是否需要进行扩容的标准。我们可以看到默认的负载因子是0.75

/**
 * The load factor used when none specified in constructor.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

但是这样依然有一个问题,链表过长的情况还是有可能发生,所以,为了从根源上解决这个问题,在JDK1.8时,引入了红黑树这个数据结构。

当链表的长度达到8时,会自动将链表转换为红黑树,这样能使得原有的查询效率大幅度降低!当使用红黑树之后,我们就可以利用二分搜索的思想,快速地去寻找我们想要的结果,而不是像链表一样挨个去看。

/**
 * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
 * extends Node) so can be used as extension of either regular or
 * linked node.
 */
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {

除了Node以外,HashMap还有TreeNode,很明显这就是为了实现红黑树而设计的内部类。不过我们发现,TreeNode并不是直接继承Node,而是使用了LinkedHashMap中的Entry实现,它保存了前后节点的顺序(也就是我们的插入顺序)。

/**
 * HashMap.Node subclass for normal LinkedHashMap entries.
 */
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

LinkedHashMap是直接继承自HashMap,具有HashMap的全部性质,同时得益于每一个节点都是一个双向链表,保存了插入顺序,这样我们在遍历LinkedHashMap时,顺序就同我们的插入顺序一致。当然,也可以使用访问顺序,也就是说对于刚访问过的元素,会被排到最后一位。

public static void main(String[] args) {
    HashMap<Integer, String> map = new HashMap<>();  //以访问顺序
    map.put(1, "A");
    map.put(3, "B");
    map.put(2, "C");
    System.out.println(map);
}//按哈希表的循序
public static void main(String[] args) {
    HashMap<Integer, String> map = new LinkedHashMap<>();  //以访问顺序
    map.put(1, "A");
    map.put(3, "B");
    map.put(2, "C");

    System.out.println(map);
}
//按我们输入的顺序
public static void main(String[] args) {
    LinkedHashMap<Integer, String> map = new LinkedHashMap<>(16, 0.75f, true);  /
    map.put(1, "A");                          //初始值,负载因子,以访问顺序
    map.put(2, "B");
    map.put(3, "C");
    map.get(2);
    System.out.println(map);
}

观察结果,我们发现,刚访问的结果被排到了最后一位。(被访问的都会被丢到最后一位)

TreeMap

TreeMap其实就是自动维护顺序的一种Map,就和我们前面提到的TreeSet一样:

/**
 * The comparator used to maintain order in this tree map, or
 * null if it uses the natural ordering of its keys.
 *
 * @serial
 */
private final Comparator<? super K> comparator;

private transient Entry<K,V> root;

/**
* Node in the Tree.  Doubles as a means to pass key-value pairs back to
* user (see Map.Entry).
*/

static final class Entry<K,V> implements Map.Entry<K,V> {

我们发现它的内部直接维护了一个红黑树,就像它的名字一样,就是一个Tree,因为它默认就是有序的,所以说直接采用红黑树会更好。我们在创建时,直接给予一个比较规则即可。

 public static void main(String[] args) {
        TreeMap<Integer,String> treeMap =  new TreeMap<>();
        treeMap.put(1,"A");
        treeMap.put(3,"B");
        treeMap.put(2,"C");
        System.out.println(treeMap);
}
/* {1=A, 2=C, 3=B}  */
 public static void main(String[] args) {
        TreeMap<Integer,String> treeMap =  new TreeMap<>((a,b)-> b-a);
        treeMap.put(1,"A");
        treeMap.put(3,"B");
        treeMap.put(2,"C");
        System.out.println(treeMap);
 }
 /*{3=B, 2=C, 1=A}*/
Map的使用

我们首先来看看Map的一些基本操作:

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "A");
    map.put(2, "B");
    map.put(3, "C");
    System.out.println(map.get(1));    //获取Key为1的值
    System.out.println(map.getOrDefault(0, "K"));  //不存在就返回K
       map.remove(1);   //移除这个Key的键值对
}

由于Map并未实现迭代器接口,因此不支持foreach,但是JDK1.8为我们提供了forEach方法使用:

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "A");
    map.put(2, "B");
    map.put(3, "C");
    map.forEach((k, v) -> System.out.println(k+"->"+v));

      for (Map.Entry<Integer, String> entry : map.entrySet()) {   //也可以获取所有的Entry来foreach
      int key = entry.getKey();
      String value = entry.getValue();
      System.out.println(key+" -> "+value);
    }
}

我们也可以单独获取所有的值或者是键:

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "A");
    map.put(2, "B");
    map.put(3, "C");
    System.out.println(map.keySet());   //直接获取所有的key
    System.out.println(map.values());   //直接获取所有的值
}
再谈Set原理

通过观察HashSet的源码发现,HashSet几乎都在操作内部维护的一个HashMap,也就是说,HashSet只是一个表壳,而内部维护的HashMap才是灵魂!

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

我们发现,在添加元素时,其实添加的是一个键为我们插入的元素,而值就是PRESENT常量:

/**
 * Adds the specified element to this set if it is not already present.
 * More formally, adds the specified element <tt>e</tt> to this set if
 * this set contains no element <tt>e2</tt> such that
 * <tt>(e==null ? e2==null : e.equals(e2))</tt>.
 * If this set already contains the element, the call leaves the set
 * unchanged and returns <tt>false</tt>.
 *
 * @param e element to be added to this set
 * @return <tt>true</tt> if this set did not already contain the specified
 * element
 */
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

观察其他的方法,也几乎都是在用HashMap做事,所以说,HashSet利用了HashMap内部的数据结构,轻松地就实现了Set定义的全部功能!

再来看TreeSet,实际上用的就是我们的TreeMap:

/**
 * The backing map.
 */
private transient NavigableMap<E,Object> m;

同理,这里就不多做阐述了。

JDK1.8新增方法使用

最后,我们再来看看JDK1.8中集合类新增的一些操作(之前没有提及的)首先来看看compute方法:

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "A");
    map.put(2, "B");
    map.compute(1, (k, v) -> {   //compute会将指定Key的值进行重新计算,若Key不存在,v会返回null
        return v+"M";     //这里返回原来的value+M
    });
      map.computeIfPresent(1, (k, v) -> {   //当Key存在时存在则计算并赋予新的值
      return v+"M";     //这里返回原来的value+M
    });
    System.out.println(map);
}

也可以使用computeIfAbsent,当不存在Key时,计算并将键值对放入Map

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "A");
    map.put(2, "B");
    map.computeIfAbsent(0, (k) -> {   //若不存在则计算并插入新的值
        return "M";     //这里返回M
    });
    System.out.println(map);
}

merge方法用于处理数据:

public static void main(String[] args) {
    List<Student> students = Arrays.asList(
            new Student("yoni", "English", 80),
            new Student("yoni", "Chiness", 98),
            new Student("yoni", "Math", 95),
            new Student("taohai.wang", "English", 50),
            new Student("taohai.wang", "Chiness", 72),
            new Student("taohai.wang", "Math", 41),
            new Student("Seely", "English", 88),
            new Student("Seely", "Chiness", 89),
            new Student("Seely", "Math", 92)
    );
    Map<String, Integer> scoreMap = new HashMap<>();//Integer::sum也可以写成(a,b)-> a+b
    students.forEach(student -> scoreMap.merge(student.getName(), student.getScore(), Integer::sum));
    scoreMap.forEach((k, v) -> System.out.println("key:" + k + "总分" + "value:" + v));
}

static class Student {
    private final String name;
    private final String type;
    private final int score;

    public Student(String name, String type, int score) {
        this.name = name;
        this.type = type;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    public String getType() {
        return type;
    }
}

java HashMap merge() 方法

merge() 方法会先判断指定的 key 是否存在,如果不存在,则添加键值对到 hashMap 中。

merge() 方法的语法为:

hashmap.merge(key, value, remappingFunction)

参数说明:

key - 键

value - 值

remappingFunction - 重新映射函数,用于重新计算值

返回值

如果 key 对应的 value 不存在,则返回该 value 值,如果存在,则返回通过 remappingFunction 重新计算后的值。

        String k = "key";
        HashMap<String, Integer> map = new HashMap<String, Integer>() {{
            put(k, 1);
        }};
        map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);

等价于

    String k = "key";
    HashMap<String, Integer> map = new HashMap<String, Integer>() {{
        put(k, 1);
    }};
    Integer newVal = 2;
    if(map.containsKey(k)) {
        map.put(k, map.get(k) + newVal);
    } else {
        map.put(k, newVal);
    }

如果K不存在,就会自动加上这个键值对。

集合的嵌套

既然集合类型中的元素类型是泛型,那么能否嵌套存储呢?

public static void main(String[] args) {
    Map<String, List<Integer>> map = new HashMap<>();   //每一个映射都是 字符串<->列表
    map.put("卡布奇诺今犹在", new LinkedList<>());
    map.put("不见当年倒茶人", new LinkedList<>());
    System.out.println(map.keySet());
    System.out.println(map.values());
}
/*[浪花淘尽英雄, 滚滚长江东逝水]
[[], []]
*/

通过Key获取到对应的值后,就是一个列表:

map.get("卡布奇诺今犹在").add(10);
System.out.println(map.get("卡布奇诺今犹在").get(0));
//10

你也可以使用List来套娃别的:

public static void main(String[] args) {
    List<Map<String, Set<String>>> list = new LinkedList<>();
}

流Stream和Optional的使用

Stream

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

img

它看起来就像一个工厂的流水线一样!我们就可以把一个Stream当做流水线处理:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");

      //移除为B的元素
      Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            if(iterator.next().equals("B")) iterator.remove();
        }

      //Stream操作
    list = list     //链式调用
            .stream()    //获取流
            .filter(e -> !e.equals("B"))   //只允许所有不是B的元素通过流水线
            .collect(Collectors.toList());   //将流水线中的元素重新收集起来,变回List
    System.out.println(list);
}

可能从上述例子中还不能感受到流处理带来的便捷,我们通过下面这个例子来感受一下:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
      list.add(3);

    list = list
            .stream()
                  .distinct()   //去重(使用equals判断)
            .sorted((a, b) -> b - a)    //进行倒序排列
            .map(e -> e+1)    //每个元素都要执行+1操作
            .limit(2)    //只放行前两个元素
            .collect(Collectors.toList());

    System.out.println(list);
}
/*[4, 3]*/

当遇到大量的复杂操作时,我们就可以使用Stream来快速编写代码,这样不仅代码量大幅度减少,而且逻辑也更加清晰明了(如果你学习过SQL的话,你会发现它更像一个Sql语句)

注意:不能认为每一步是直接依次执行的!

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(3);

list = list
        .stream()
        .distinct()   //断点
        .sorted((a, b) -> b - a)
        .map(e -> {
            System.out.println(">>> "+e);   //断点
            return e+1;
        })
        .limit(2)   //断点
        .collect(Collectors.toList());
//实际上,stream会先记录每一步操作,而不是直接开始执行内容,当整个链式调用完成后,才会依次进行!
/*
>>> 3
>>> 2
[4, 3]*/

在这里插入图片描述

Stream可以由数组或集合创建,对流的操作分为两种:

中间操作,每次返回一个新的流,可以有多个。(筛选filter、映射map、排序sorted、去重组合skip—limit)
终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。(遍历foreach、匹配find–match、规约reduce、聚合max–min–count、收集collect)

另外,Stream有几个特性:

stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

接下来,我们用一堆随机数来进行更多流操作的演示:

public static void main(String[] args) {
    Random random = new Random();  //Random是一个随机数工具类
    random
            .ints(-100, 100)   //生成-100~100之间的,随机int型数字(本质上是一个IntStream)
            .limit(10)   //只获取前10个数字(这是一个无限制的流,如果不加以限制,将会无限进行下去!)
            .filter(i -> i < 0)   //只保留小于0的数字
            .sorted()    //默认从小到大排序
            .forEach(System.out::println);   //依次打印
}

我们可以生成一个统计实例来帮助我们快速进行统计:

public static void main(String[] args) {
    Random random = new Random();  //Random是一个随机数工具类
    IntSummaryStatistics statistics = random
            .ints(0, 100)
            .limit(100)
            .summaryStatistics();    //获取语法统计实例
    System.out.println(statistics.getMax());  //快速获取最大值
    System.out.println(statistics.getCount());  //获取数量
    System.out.println(statistics.getAverage());   //获取平均值
}

普通的List只需要一个方法就可以直接转换到方便好用的IntStream了:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.stream()
            .mapToInt(i -> i)    //将每一个元素映射为Integer类型(这里因为本来就是Integer)
            .summaryStatistics();
}

我们还可以通过flat来对整个流进行进一步细分:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A,B");
    list.add("C,D");
    list.add("E,F");   //我们想让每一个元素通过,进行分割,变成独立的6个元素
    list = list
            .stream()    //生成流
            .flatMap(e -> Arrays.stream(e.split(",")))    //分割字符串并生成新的流
            .collect(Collectors.toList());   //汇成新的List
    System.out.println(list);   //得到结果
}

我们也可以只通过Stream来完成所有数字的和,使用reduce方法:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    int sum = list
            .stream()                       //第一个元素就是第一个元素的值
            .reduce((a, b) -> a + b)   //计算规则为:a是上一次计算的值,b是当前要计算的参数,这里是求和
            .get();    //我们发现得到的是一个Optional类实例,不是我们返回的类型,通过get方法返回得到的值
    System.out.println(sum);
}

通过上面的例子,我们发现,Stream不喜欢直接给我们返回一个结果,而是通过Optinal的方式,那么什么是Optional呢?

Optional

Optional类是Java8为了解决null值判断问题,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE(NullPointerException)。总而言之,就是对控制的一个判断,为了避免空指针异常。

public static void main(String[] args) {
 String str = null;
 if(str != null){ //当str不为空时添加元素到List中
 list.add(str);
 }
}

有了Optional之后,我们就可以这样写:

public static void main(String[] args) {
    String str = null;
    Optional<String> optional = Optional.ofNullable(str);   //转换为Optional
    optional.ifPresent(System.out::println);  //当存在时再执行方法
}

我们可以选择直接get或是当值为null时,获取备选值:

public static void main(String[] args) {
    String str = null;
    Optional optional = Optional.ofNullable(str);   //转换为Optional(可空)
    System.out.println(optional.orElse("这是一个备选值"));
         // System.out.println(optional.get());   这样会直接报错
}

同样的,Optional也支持过滤操作和映射操作,不过是对于单对象而言:

public static void main(String[] args) {
    String str = "A";
    Optional optional = Optional.ofNullable(str);   //转换为Optional(可空)
    System.out.println(optional.filter(s -> s.equals("B")).get());   //被过滤了,此时元素为null,获取时报错
}
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    String str = "A";
    Optional optional = Optional.ofNullable(str);   //转换为Optional(可空)
    System.out.println(optional.map(s -> s + "A").get());   //在尾部追加一个A
}

一部分操作

isPresent()            如果所包含的对象不为null,则返回true,反之返回false
isEmpty()              如果存在值,则返回false;否则,返回ture
void [ifPresent]([Consumer]<? super [T]> consumer)
如果存在值,则使用该值调用指定的使用者;否则,什么都不做。
get()                   我们想要的是存储在Optional中的值,我们可以通过get()来获取它。
但是,当该值为null时,此方法将引发异常。这就需要  orElse() 方法来紧急救援。
orElse(替换值)      返回值(如果存在);反之,返回替换的值。
orElseGet() 返回值(如果存在);否则,调用other并返回该调用的结果。
采用供应商功能接口,该接口将被调用并返回调用的值:

Optional的预期用途主要是作为返回类型。获取此类型的实例后,可以提取该值(如果存在)或提供其他行为(如果不存在)。

Arrays和Collections的使用

Arrays是一个用于操作数组的工具类,它给我们提供了大量的工具方法:

/**
 * This class contains various methods for manipulating arrays (such as
 * sorting and searching). This class also contains a static factory
 * that allows arrays to be viewed as lists. <- 注意,这句话很关键
 *
 * @author Josh Bloch
 * @author Neal Gafter
 * @author John Rose
 * @since  1.2
 */
public class Arrays {

由于操作数组并不像集合那样方便,因此JDK提供了Arrays类来增强对数组操作,比如:

public static void main(String[] args) {
    int[] array = {1, 5, 2, 4, 7, 3, 6};
    Arrays.sort(array);   //直接进行排序(底层原理:进行判断,元素少使用插入排序,大量元素使用双轴快速/归并排序)
    System.out.println(array);  //由于int[]是一个对象类型,而数组默认是没有重写toString()方法,因此无法打印到想要的结果
    System.out.println(Arrays.toString(array));  //我们可以使用Arrays.toString()来像集合一样直接打印每一个元素出来
}
public static void main(String[] args) {
    int[] array = {1, 5, 2, 4, 7, 3, 6};
    Arrays.sort(array);
    System.out.println("排序后的结果:"+Arrays.toString(array));
    System.out.println("目标元素3位置为:"+Arrays.binarySearch(array, 3));  //二分搜素,必须是已经排序好的数组!
}
public static void main(String[] args) {
    int[] array = {1, 5, 2, 4, 7, 3, 6};
    Arrays
            .stream(array)    //将数组转换为流进行操作
            .sorted()
            .forEach(System.out::println);
}
public static void main(String[] args) {
    int[] array = {1, 5, 2, 4, 7, 3, 6};
    int[] array2 = Arrays.copyOf(array, array.length);  //复制一个一模一样的数组
    System.out.println(Arrays.toString(array2));

    System.out.println(Arrays.equals(array, array2));  //比较两个数组是否值相同

    Arrays.fill(array, 0);   //将数组的所有值全部填充为指定值
    System.out.println(Arrays.toString(array));

    Arrays.setAll(array2, i -> array2[i] + 2);  //依次计算每一个元素(注意i是下标位置)
    System.out.println(Arrays.toString(array2));   //这里计算让每个元素值+2
}

思考:当二维数组使用Arrays.equals()进行比较以及Arrays.toString()进行打印时,还会得到我们想要的结果吗?

public static void main(String[] args) {
    Integer[][] array = {{1, 5}, {2, 4}, {7, 3}, {6}};
    Integer[][] array2 = {{1, 5}, {2, 4}, {7, 3}, {6}};
    System.out.println(Arrays.toString(array));    //这样还会得到我们想要的结果吗?
//不能[[Ljava.lang.Integer;@1e643faf, [Ljava.lang.Integer;@6e8dacdf, [Ljava.lang.Integer;@7a79be86, [Ljava.lang.Integer;@34ce8af7]

    System.out.println(Arrays.equals(array2, array));    //这样还会得到true吗?
//false
    System.out.println(Arrays.deepToString(array));  
 //使用deepToString就能到打印多维数组
//[[1, 5], [2, 4], [7, 3], [6]]
    System.out.println(Arrays.deepEquals(array2, array));   //使用deepEquals就能比较多维数组
//true
}

那么,一开始提到的当做List进行操作呢?我们可以使用Arrays.asList()来将数组转换为一个 固定长度的List

public static void main(String[] args) {
    Integer[] array = {1, 5, 2, 4, 7, 3, 6};
    List<Integer> list = Arrays.asList(array);   //不支持基本类型数组,必须是对象类型数组
    //Arrays.asList("A", "B", "C");  也可以逐个添加,因为是可变参数

    list.add(1);    //此List实现是长度固定的,是Arrays内部单独实现的一个类型,因此不支持添加操作
    list.remove(0);   //同理,也不支持移除

    list.set(0, 8);   //直接设置指定下标的值就可以
    list.sort(Comparator.reverseOrder());   //也可以执行排序操作
    System.out.println(list);   //也可以像List那样直接打印
}

文字游戏:allows arrays to be viewed as lists,实际上只是当做List使用,本质还是数组,因此数组的属性依然存在!因此如果要将数组快速转换为实际的List,可以像这样:

public static void main(String[] args) {
    Integer[] array = {1, 5, 2, 4, 7, 3, 6};
    List<Integer> list = new ArrayList<>(Arrays.asList(array));
}

通过自行创建一个真正的ArrayList并在构造时将Arrays的List值传递。

既然数组操作都这么方便了,集合操作能不能也安排点高级的玩法呢?那必须的,JDK为我们准备的Collocations类就是专用于集合的工具类:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    Collections.max(list);
    Collections.min(list);
}

当然,Collections提供的内容相比Arrays会更多,希望大家下去自行了解,这里就不多做介绍了。

集合类编程实战

没看前面的数据结构,我不大会这个编程实战。但我以后还会返回来学的,即使不是现在和近期

唉,我以后能成为很厉害的人吗

那就继续,去见证它的终局。

反转链表

1 <- 3 <- 5 <- 7 <- 9  转换为 9 <- 7 <- 5 <- 3 <- 1

现在有一个单链表,尝试将其所有节点倒序排列

public class Main {
    public static void main(String[] args) {
        Node head = new Node(1);
        head.next = new Node(3);
        head.next.next = new Node(5);
        head.next.next.next = new Node(7);
        head.next.next.next.next = new Node(9);

        head = reverse(head);

        while (head != null){
            System.out.println(head.value+" ");
            head = head.next;
        }
    }

    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static Node reverse(Node head) {
        //在这里实现
    }
}

重建二叉树

现在知道二叉树的前序: GDAFEMHZ,以及中序: ADEFGHMZ,请根据已知信息还原这颗二叉树。

实现计算器

实现一个计算器,要求输入一个计算公式(含加减乘除运算符,没有负数但是有小数),得到结果,比如输入:1+4*3/1.321,得到结果为:2.2

字符串匹配(KMP算法)

现在给定一个主字符串和一个子字符串,请判断主字符串是否包含子字符串,例如主字符串:ABCABCDHI,子字符串:ABCD,因此主字符串包含此子字符串;主字符串:ABCABCUISA,子字符串:ABCD,则不包含。

标签:map,Java,list,System,public,println,集合,缺失,out
From: https://www.cnblogs.com/tiangong/p/16755149.html

相关文章

  • 常见问题集合
    $‘\r‘:commandnotfound的解决方法在Linux系统中,运行Shell脚本,出现了如下错误:one-more.sh:line1:$'\r':commandnotfound出现这样的错误,是因为Shell脚本在Wind......
  • JavaSpring的jdbcTemplate事务
    1、使用JdbcTemplate需要引入的依赖(spring的maven项目)<!--jdbcTemplate实现jar--><dependency><groupId>org.springframework</groupId><artifactId>spring......
  • 经常会采坑的javascript原型应试题
    一.前言原型和原型链在面试中历来备受重视,经常被提及。说难可能也不太难,但要真正完全理解,吃透它,还是要多下功夫的。下面为大家简单阐述我对原型和原型链的理解,若是觉得有......
  • 14_Java中的抽象类
    Java中的抽象类一、抽象类的概述例如我们前面使用的动物类,并不是一个具体的事物,在这个类中我们是不应该给出方法的具体实现的,应当只是声明在Java中,一个没有......
  • 15_Java中的接口
    Java中的接口一、接口概述接口就是一种公共的规范标准,只要符合标准,大家都可以通用Java中的接口更多体现在对行为的抽象二、接口的特点1、接口用关......
  • java-内部类
    内部类的基本使用内部类概念在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类我们把一个类放在另一个类的内部定义,称为内部类(innerclass)......
  • 05-RabbitMQ控制台入门及其Java简单操作
    MQ控制台简单操作建立Exchange新建Exchange成功新建Queue新建Queue成功建立Exchange与Queue的关系建立关系成功路由键:就是指发送到Exchange的消息,通......
  • Java泛型总结
     为什么会有泛型?泛型是用来干什么的?泛型其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛......
  • XX学Python·集合
    集合集合是一个无序,不重复的数据序列。无序:程序员无法控制其排布顺序,无法使用索引查找或修改数据不重复:没法在集合里放入相同的值,会自动去重,类似于字典的键定......
  • (组合数据类型)编程输入两个字符串,转换成对应的集合存于集合变量a和b中(不能存其它变量)
    样例输入12324 样例输出{'1','2','3'}&{'2','4'}={'2'} 样例输入123424 样例输出{'1','2','3','4'}&{'2','4'}={'2'......