Java 8新特性

1. HashMap

JDK 1.8之前,HashMap采用的是数组+链表结构。1.8之后采用数组+链表+红黑树(哈希表)。

JDK1.8之前向Map中放元素

  • 首先创建一个hash表,hash表是数组结构,存在索引下标。存放的值是entry(key-value)。(数组大小默认是16,扩容因子默认0.75,当数组空间占用达到0.75时,自动进行扩容。)
  • 放入对象前,根据对象的hashCode方法计算该对象的hash值。从数组中查找该hash值是否存在,不存在,直接放入数组。
  • 若hash值在数组中存在,形成链表结构。后进入的放前面。

这种数据结构的缺点:存入对象过多时,链表长度也会随之加大,再次放入对象时,需要和链表中的每个元素进行equals运算。效率低。

JDK1.8之后向Map中放元素

  • 但链表长度大于等于7,且数组长度大于64。
  • 红黑树结构除了对集合中的元素进行添加比链表速度慢意外。其他的(删,查)都比链表速度快。

新建一个HashMap

  • 根据数组默认大小和扩容因子,创建一个HashMap
/**
 * Constructs an empty <tt>HashMap</tt> with the default initial capacity
 * (16) and the default load factor (0.75).
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

向HashMap中放入值

  • 调用put方法
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //如果是初始化map,n=0(数组索引值)
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //如果该hash值在数组中不存在,直接放入该值。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
          //否则,将该值放在已存在值得后面,形成链表结构
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //如果链表长度大于等于7(8-1),且数组长度大于64形成红黑树结构
                    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;
}

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 */
static final int MIN_TREEIFY_CAPACITY = 64;

//数组长度大于64才变长红黑树结构
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}


2. 内存结构升级

JDK 1.8之前,方法区属于堆内存中永久区的一部分,另一部分是垃圾回收区。

  • 方法区内存是JVM分配的虚拟内存,当方法区要满的时候,垃圾回收机制会对其进行回收。

JDK1.8之后,方法区从堆永久区中剥离,使用物理内存(直接使用内存条内存。不是分配的虚拟内存),称为元空间

  • 垃圾回收机制优化。物理内存较大,类存放在元空间(方法区),不易被垃圾回收机制所回收。

3. Lambda表达式

4. Stream API

Stream API介绍:
Stream是 Java8中处理集合,数组的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执仃的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。 数据操作结束,产生一个新流,数据源不变。

  • 流(stream)是数据渠道,用于操作数据源(集合,数组等)所生成的元素序列。

  • 集合讲的是数据,流讲的是计算

  • stream自己不会存储元素。

  • stream不会改变数据源对象,会返回一个持有结果的新的stream。

  • stream操作是延迟执行的,意味着会等到需要结果的时候才执行。

4.1 创建stream。

  • 通过一个数据源(集合,数组),获取一个流。
ArrayList<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();

String[] array1 = new String[10];
Stream<String> stream2 = Arrays.stream(array1);

Stream<String> stream3 = Stream.of("1", "2", "3");

//创建一个无限流(迭代)
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);

//终止流
Stream.iterate(0, (x) -> x + 2)
                .limit(10)
                .forEach(System.out::println);
//0 2 4 6 8 10 12 14 16 18

//创建无限流的第二种方式(生成)
Stream.generate(()->Math.random())
                .limit(5)
                .forEach(System.out::println);

4.2 中间操作

  • 中间操作链,对数据源的一系列处理。

4.2.1筛选与切片

  • filter——接收Lambda ,从流中排除某些元素。
  • limit——截断流,使其元素不超过给定数量。
  • skip(n)—跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n)互补
  • distinctl—筛选,通过流所生成元素的hashCode()和equals()去除重复元素

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理而在终止操作时一次性全部处理,称为“惰性求值”。

filter

Person person1 = new Person("张三", 18, "99");
Person person2 = new Person("李四", 22, "85");
Person person3 = new Person("王五", 24, "78");
Person person4 = new Person("赵六", 17, "60");

ArrayList<Person> list = new ArrayList<>();
list.add(person1);
list.add(person2);
list.add(person3);
list.add(person4);

//中间操作,不会执行任何操作
Stream<Person> stream = list.stream().filter(
        (e) ->{
            System.out.println("中间操作");
            return e.getAge()>18;
        }
        );
//终止操作,一次性执行全部内容
stream.forEach(System.out::println);

//内部迭代,迭代操作由Stream API自动完成。
/*
中间操作
中间操作
Person{name='李四', age=22, score='85'}
中间操作
Person{name='王五', age=24, score='78'}
中间操作
*/

limit

list.stream().filter(
        (p)->{
            System.out.println("短路!");
            return p.getAge()>=18;
        })
        .limit(2)
        .forEach(System.out::println);


/* 过滤后有3个结果,截取前两个
//找到满足条件的两条结果后,就不再继续迭代,称之为短路。提高效率
短路!
Person{name='张三', age=18, score='99'}
短路!
Person{name='李四', age=22, score='85'}
*/

skip

  • limit是取前几个,skip是跳过前几个。
list.stream().filter((p)->p.getAge()>=18)
            .skip(1)
            .forEach(System.out::println);
/*
Person{name='李四', age=22, score='85'}
Person{name='王五', age=24, score='78'}
*/

distinct

  • 去重需要重写hashcode和equals方法
//集合中新增值
Person person5 = new Person("赵六", 25, "60");
Person person6 = new Person("赵六", 25, "60");

list.stream().filter((p)->p.getAge()>=18)
        .skip(1)
        .distinct()
        .forEach(System.out::println);
//重写Person类hashcode和equals方法
/*
Person{name='李四', age=22, score='85'}
Person{name='赵六', age=25, score='60'}
*/

4.2.2 映射

map———接收Lambda ,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap———接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

map

List<String> list = Arrays.asList("aaa", "bbb", "ccc");

list.stream().map((s)->s.toUpperCase())
        .forEach(System.out::println);

//去重获取前面集合中的所有名字
list.stream().map(Person::getName)
            .distinct()
            .forEach(System.out::println);
/*
AAA BBB CCC
*/

flatmap

  • 如果使用map,流类似于{{a,a,a},{b,b,b},{c,c,c}}
  • 使用flatmap。流类似于{a,a,a,b,b,b,c,c,c}。将多个流整合为一个流。
  • 类似于集合中的add()和addAll()方法。
//将字符串转换为字符集合。
public static Stream<Character> strToChar(String str){
    ArrayList<Character> list = new ArrayList<>();
    for (char c : str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}

list.stream().flatMap(MyTests::strToChar)
              .forEach(System.out::println);

/*
a a a b b b c c c
*/

4.2.3 排序

  • sorted (自然排序)————Comparable
  • sorted (Comparator com)————定制排序

sorted

  • String对象有自然排序
List<String> list = Arrays.asList("ccc", "bbb", "aaa");
list.stream().sorted().forEach(System.out::println);

//String类中的compareTo方法
public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

定制排序

  • 给自定义对象排序
list.stream().sorted(
        (p1,p2)->{
            //年龄一样按姓名排,年龄不同按年龄排
            if (p1.getAge()==p2.getAge()){
                //String类型排序
                return p1.getName().compareTo(p2.getName());
            }else {
                //年龄从小到大排
                //return p1.getAge()-p2.getAge();
                //年龄从大到小排(int类型)
                return p2.getAge()-p1.getAge();
            }
        }
).forEach(System.out::println);

4.3 终止操作

  • 执行中间操作链,获取结果。

4.3.1 查找与匹配

  • a11Match——检查是否匹配所有元素
  • anyMatch—检查是否至少匹配一个元素
  • noneMatch—--检查是否没有匹配所有元素
  • findFirst-—返回第一个元素
  • findAny——返回当前流中的任意元素
  • count——返回流中元素的总个数
  • max——返回流中最大值
  • min——返回流中最小值

allMatch

List<Person> list = Arrays.asList(new Person("张三", 18, "99", Person.Status.BUSY),
        new Person("李四", 22, "85", Person.Status.FREE),
        new Person("王五", 17, "78", Person.Status.VOLICATION),
        new Person("赵六", 25, "60", Person.Status.BUSY),
        new Person("c陈六", 25, "60", Person.Status.FREE)
);

//查看集合中的对象是否都匹配(满足)枚举状态是BUSY的。
boolean b = list.stream().allMatch((person) ->
    person.getStatus().equals(Person.Status.BUSY)
);
//false
System.out.println(b);

anyMatch

boolean b1 = list.stream().anyMatch((person) ->
        person.getStatus().equals(Person.Status.BUSY));
//true
System.out.println(b1);

noneMatch

boolean b2 = list.stream().noneMatch((person) ->
        person.getStatus().equals(Person.Status.BUSY));
//false ,至少有一个是匹配的就返回false
System.out.println(b2);

findFirst

  • java 8为了避免空指针异常,创建了Optional。将对象封装到Optional中。如果对象为空,可以使用orElse()方法创建一个默认对象代替。
Optional<Person> person = list.stream().sorted(
        (p1, p2) -> {
            if (p1.getAge() == p2.getAge()) {
                return p1.getName().compareTo(p2.getName());
            } else {
                return p2.getAge() - p1.getAge();
            }
        }
).findFirst();
//Optional[Person{name='c陈六', age=30, score='60', Status=FREE}]
System.out.println(person);

findAny

Optional<Person> any = list.stream().filter(p -> p.getStatus().equals(Person.Status.FREE)).findAny();
System.out.println(any);

count

long count = list.stream().count();
System.out.println(count);

max

Optional<Person> max = list.stream().max(Comparator.comparingInt(Person::getAge));
//返回年龄最大的对象
System.out.println(max);

4.3.2 收集

collect—将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

List<Integer> list1 = list.stream().map(Person::getAge).collect(Collectors.toList());
//将Person集合中的对象年龄映射为一个集合 [18, 22, 17, 25, 30]
System.out.println(list1);


Set<Integer> collect = list.stream().map(Person::getAge).collect(Collectors.toSet());
//映射到set集合,去重复元素
System.out.println(collect);


LinkedHashSet<Integer> collect1 = list.stream().map(Person::getAge).collect(Collectors.toCollection(LinkedHashSet::new));
//指令映射集合类型
System.out.println(collect1);
posted @ 2021-12-28 09:42  初夏那片海  阅读(52)  评论(0)    收藏  举报