Java集合核心知识点总结

news2024/9/22 7:31:22

Java集合概述

从集合特点角度出发,Java集合可分为映射集、和单元素集合。如下图所示,单元素集合类图如下:

  1. collection包 : 工具单元素集合我们又可以分为,存储不可重复元素的Set集合,可顺序存储重复元素的List,以及FIFOQueue

在这里插入图片描述

  1. 映射集合(Map接口下的类):另一大类就是映射集,他的特点就是每一个元素都是由键值对组成,我们可以通过key找到对应的value,类图如下,集合具体详情笔者会在后文阐述。

在这里插入图片描述

List集合

list即顺序表,它是按照插入顺序存储的,元素可以重复。从底层结构角度,顺序表还可以分为以下两种:

  1. ArrayList : ArrayList实现顺序表的选用的底层结构为数组,以下便是笔者从list源码找到的list底层存储元素的变量
transient Object[] elementData;
  1. LinkedList : 顺序链表底层是双向链表,由一个个节点构成,节点有双指针,分别指向前驱节点和后继节点。
private static class Node<E> {
        E item;
        // 指向后继节点
        Node<E> next;
        //指向前驱节点
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
  1. Vector:底层同样使用的是数组,vector现在基本不用了,这里仅仅做个了解,它底层用的也是数组。
 protected Object[] elementData;

List集合常见面试题

ArrayList容量是10,给它添加一个元素会发生什么?

我们不妨看看这样一段代码,可以看到我们将集合容量设置为10,第11次添加元素时,由于list底层使用的数组已满,会进行动态扩容,这个动态扩容说白了就是创建一个更大的容器将原本的元素拷贝过去,我们不妨基于下面的代码进行debug一下

ArrayList<Integer> arrayList=new ArrayList<>(10);
		for (int i = 0; i < 10; i++) {
			arrayList.add(i);
		}
		arrayList.add(10);

add源码如下,可以看到在添加元素前会对容量进行判断

public boolean add(E e) {
		//判断本次插入位置是否大于容量
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

步入ensureCapacityInternal,会看到它调用ensureExplicitCapacity,它的逻辑就是判断当前插入元素后的最小容量是否大于数组容量,如果大于的话会直接调用动态扩容方法grow

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

	     //如果插入的元素位置大于数组位置,则会进行动态扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

可以看到扩容的逻辑很简单创建一个新容器大小为原来的1.5倍,将原数组元素拷贝到新容器中

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //创建一个新容器大小为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
       ....略去细节
       //将原数组元素拷贝到新容器中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

针对动态扩容导致的性能问题,你有什么解决办法嘛?

我们可以提前调用ensureCapacity顶下最终容量一次性完成动态扩容提高程序执行性能。

@Test
    public void listCapacityTest2() {
        int size = 1000_0000;
        ArrayList<Integer> list = new ArrayList<>(1);
        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            list.add(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("无显示扩容,完成时间:" + (end - start));


        ArrayList<Integer> list2 = new ArrayList<>(1);
        start = System.currentTimeMillis();
        list2.ensureCapacity(size);
        for (int i = 0; i < size; i++) {
            list.add(i);
        }
        end = System.currentTimeMillis();
        System.out.println("显示扩容,完成时间:" + (end - start));
    }

输出结果

@Test
    public void listCapacityTest2() {
        int size = 1000_0000;
        ArrayList<Integer> list = new ArrayList<>(1);
        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            list.add(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("无显示扩容,完成时间:" + (end - start));


        ArrayList<Integer> list2 = new ArrayList<>(1);
        start = System.currentTimeMillis();
        list2.ensureCapacity(size);
        for (int i = 0; i < size; i++) {
            list.add(i);
        }
        end = System.currentTimeMillis();
        System.out.println("显示扩容,完成时间:" + (end - start));
    }

ArrayList和LinkedList性能差异

  1. 先来看看头插法的性能差距
@Test
    public void addFirstTest() {
        int size = 10_0000;
        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();

        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            arrayList.add(0, i);
        }
        long end = System.currentTimeMillis();
        System.out.println("arrayList头插时长:" + (end - start));


        start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            linkedList.add(0, i);
        }
        end = System.currentTimeMillis();
        System.out.println("linkedList 头插时长:" + (end - start));


        start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            ((LinkedList<Integer>) linkedList).addFirst(i);
        }
        end = System.currentTimeMillis();
        System.out.println("linkedList 头插时长:" + (end - start));


    
    }

输出结果如下,可以看出linkedList 自带的addFirst性能最佳。原因也很简单,链表头插直接拼接一个元素就好了,不想arraylist那样需要将整个数组元素往后挪,而且arraylist的动态扩容机制还会进一步增加工作时长。

    /**
         * 输出结果
         *
         * arrayList头插时长:1061
         * linkedList 头插时长:5
         * linkedList 头插时长:4
         */
  1. 尾插法的性能比较,同理我们也写下下面这段代码
@Test
    public void addLastTest() {
        int size = 10_0000;
        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();

        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            arrayList.add(i, i);
        }
        long end = System.currentTimeMillis();
        System.out.println("arrayList 尾插时长:" + (end - start));


        start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            linkedList.add(i, i);
        }
        end = System.currentTimeMillis();
        System.out.println("linkedList 尾插时长:" + (end - start));


        start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            ((LinkedList<Integer>) linkedList).addLast(i);
        }
        end = System.currentTimeMillis();
        System.out.println("linkedList 尾插时长:" + (end - start));


      
    }

输出结果,可以看到还是链表稍快一些,为什么arraylist这里性能也还不错呢?原因也很简单,无需为了插入一个节点维护其他位置。

  /**
         *输出结果
         * arrayList 尾插时长:6
         * linkedList 尾插时长:5
         * linkedList 尾插时长:3
         */
  1. 随机插入:为了公平实验,笔者将list初始化工作都放在计时之外,避免arrayList动态扩容的时间影响最终实验结果
@Test
    public void randAddTest() {
        int size = 100_0000;
        ArrayList arrayList = new ArrayList(size);
        add(size, arrayList, "arrayList");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            arrayList.add(50_0000, 1);
        }
        long end = System.currentTimeMillis();
        System.out.println("arrayList randAdd :" + (end - start));


        LinkedList linkedList = new LinkedList();
        add(size, linkedList, "linkedList");
        start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            linkedList.add(50_0000, 1);
        }
        end = System.currentTimeMillis();
        System.out.println("linkedList randAdd :" + (end - start));
    }

从输出结果来看,随机插入也是arrayList性能较好,原因也很简单,arraylist随机访问速度远远快与linklist

arrayList插入元素时间 18
arrayList randAdd :179
linkedList插入元素时间 105
linkedList randAdd :5353

ArrayList 和 Vector 区别了解嘛?

这个问题我们可以从以下几个维度分析:

  1. 底层数据结构:两者底层存储都是采用数组,我们可以从他们的源码了解这一点

ArrayList存储用的是new Object[initialCapacity];

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

Vector底层存储元素用的是 new Object[initialCapacity];

public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
  1. 线程安全性:Vector 为线程安全类,ArrayList 线程不安全,如下所示我们使用ArrayList进行多线程插入出现的索引越界问题。
 @Test
    public void listAddTest2() throws InterruptedException {

        List<Integer> list = new ArrayList<>();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        Thread.sleep(5000);
        System.out.println(list.size());
        /**
         * java.lang.ArrayIndexOutOfBoundsException: 70
         * 	at java.util.ArrayList.add(ArrayList.java:463)
         * 	at com.guide.collection.CollectionTest.lambda$listAddTest2$3(CollectionTest.java:290)
         * 	at java.lang.Thread.run(Thread.java:748)
         * 71
         */
    }

Vector 线程安全代码示例

@Test
    public void listAddTest() throws InterruptedException{
        List<Integer> list = new Vector<>();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        Thread.sleep(5000);
        System.out.println(list.size());//2000
    }

原因很简单,vectoradd方法有加synchronized 关键字

 public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

ArrayList 与 LinkedList 的区别

  1. 底层存储结构:ArrayList 底层使用的是数组,LinkedList 底层使用的是链表

  2. 线程安全性:两者都是线程不安全,因为add方法都没有任何关于线程安全的处理。

  3. 随机访问性:虽然两者都支持随机访问,但是链表随机访问不太高效。感兴趣的读者可以使用下面这段代码分别使用100w数据量的数组或者链表get数据就会发现,ArrayList 随机访问速度远远高于LinkedList

@Test
    public void arrTest() {
        int size = 100_0000;
        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();

        add(size, arrayList, "arrayList");


//        要维护节点关系和创建节点耗时略长
        /**
         * void linkLast(E e) {
         *         final Node<E> l = last;
         *         final Node<E> newNode = new Node<>(l, e, null);
         *         last = newNode;
         *         if (l == null)
         *             first = newNode;
         *         else
         *             l.next = newNode;
         *         size++;
         *         modCount++;
         *     }
         */
        add(size, linkedList, "linkedList");
        /**
         * 输出结果
         * arrayList插入元素时间 52
         * linkedList插入元素时间 86
         */


        get(size, arrayList, "arrayList");
        /**
         * Node<E> node(int index) {
         *         // assert isElementIndex(index);
         *
         *         if (index < (size >> 1)) {
         *             Node<E> x = first;
         *             for (int i = 0; i < index; i++)
         *                 x = x.next;
         *             return x;
         *         } else {
         *             Node<E> x = last;
         *             for (int i = size - 1; i > index; i--)
         *                 x = x.prev;
         *             return x;
         *         }
         *     }
         */
        get(size, linkedList, "linkedList");
    }


    private void get(int size, List<Integer> list, String arrType) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            list.get(i);
        }
        long end = System.currentTimeMillis();
        System.out.println(arrType + "获取元素时间 " + (end - start));
    }

    private void add(int size, List<Integer> list, String arrType) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            list.add(i);
        }
        long end = System.currentTimeMillis();
        System.out.println(arrType + "插入元素时间 " + (end - start));
    }

输出结果

arrayList插入元素时间 44
linkedList插入元素时间 89
arrayList获取元素时间 5
linkedList获取元素时间 1214464

可以看到链表添加时间和访问时间都远远大于数组,原因也很简单,之所以随机访问时间长是因为底层使用的是链表,所以无法做到直接的随机存取。
而插入时间长是因为需要插入节点时要遍历位置且维护前驱后继节点的关系。

 /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
  1. 内存空间占用:ArrayList 的空 间浪费主要体现在在 List列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)

ArrayList 的扩容机制了解过嘛?

JavaArrayList 底层默认数组大小为10,的动态扩容机制即ArrayList 确保元素正确存放的关键,了解核心逻辑以及如何基于该机制提高元素存储效率也是很重要的,感兴趣的读者可以看看读者编写的这篇博客:

Java数据结构与算法(动态数组ArrayList和LinkList小结)

尽管从上面来看两者各有千秋,但是设计者认为若无必要,都使用用Arraylist即可。

Set集合

Set集元素不可重复,存储也不会按照插入顺序排序。适合存储那些需要去重的场景。set大概有两种:

  1. HashSet:HashSet要求数据唯一,但是存储是无序的,所以基于面向对象思想复用原则,Java设计者就通过聚合关系封装HashMap,基于HashMapkey实现了HashSet

从源码我们就可以看到HashSetadd方法就是通过HashMapput方法实现存储唯一元素(key作为set的值,value统一使用PRESENT这个object对象)

public boolean add(E e) {
		// 底层逻辑是插入时发现这个元素有的话就不插入直接返回集合中的值,反之插入成功返回null,所以判断添加成功的代码才长下面这样
        return map.put(e, PRESENT)==null;
    }
  1. LinkedHashSet:LinkedHashSet即通过聚合封装LinkedHashMap实现的。`

  2. TreeSet:TreeSet底层也是TreeMap,一种基于红黑树实现的有序树。关于红黑树可以参考笔者之前写过的这篇文章:

数据结构与算法之红黑树小结

public TreeSet() {
        this(new TreeMap<E,Object>());
    }

Map集合

Map即映射集,适合存储键值对类型的元素,key不可重复,value可重复,我们可以更具key找到对应的value。

HashMap(重点)

JDK1.8HashMap默认是由数组+链表组成,通过keyhash选择合适的数组索引位置,当冲突时使用拉链法解决冲突。当链表长度大于8且数组长度大于64的情况下,链表会变成红黑树,减少元素搜索时间。(注意若长度小于64链表长度大于8只会进行数组扩容)

LinkedHashMap

LinkedHashMap继承自HashMap,他在HashMap基础上增加双向链表,由于LinkedHashMap维护了一个双向链表来记录数据插入的顺序,因此在迭代遍历生成的迭代器的时候,是按照双向链表的路径进行遍历的,所以遍历速度远远快于HashMap,具体可以查阅笔者写的这篇文章:

Java集合LinkedHashMap小结

Hashtable简介

数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的。

Set和Map常见面试题

HashMap 和 Hashtable 的区别(重点)

  1. 从线程安全角度:HashMap 线程不安全、Hashtable 线程安全。
  2. 从底层数据结构角度:HashMap 初始情况是数组+链表,特定情况下会变数组+红黑树Hashtable 则是数组,核心源码:private transient Entry<?,?>[] table;
  3. 从保存数值角度:HashMap 允许null键或null值,但是只允许一个。
  4. 从初始容量角度考虑:HashMap默认16,扩容都是基于当前容量*2Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1
  5. 从性能角度考虑:Hashtable 每次添加都会上synchronized 锁,所以性能很差。

HashMap 和 HashSet有什么区别

HashSet 聚合了HashMap ,通俗来说就是将HashMap 的key作为自己的值存储来使用。

HashMap 和 TreeMap 有什么区别

类图如下,TreeMap 底层是有序树,所以对于需要查找最大值或者最小值等场景,TreeMap 相比HashMap更有优势。因为他继承了NavigableMap接口和SortedMap 接口。

在这里插入图片描述

如下源码所示,我们需要拿最大值或者最小值可以用这种方式或者最大值或者最小值

 @Test
    public void treeMapTest(){
        TreeMap<Integer,Object> treeMap=new TreeMap<>();
        treeMap.put(3213,"231");
        treeMap.put(434,"231");
        treeMap.put(432,"231");
        treeMap.put(2,"231");
        treeMap.put(432,"231");
        treeMap.put(31,"231");
        System.out.println(treeMap.toString());
        System.out.println(treeMap.firstKey());
        System.out.println(treeMap.lastEntry());
        /**
         * 输出结果
         * 
         * {2=231, 31=231, 432=231, 434=231, 3213=231}
         * 2
         * 3213=231
         */
    }

HashSet 实现去重插入的底层工作机制了解嘛?

当你把对象加入HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode 值作比较,如果没有相符的 hashcodeHashSet 会认为对象没有重复出现,直接允许插入了。但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet就会将其直接覆盖返回插入前的值。

对此我们不妨基于下面这样一段代码进行debug了解一下究竟

  HashSet<String> set=new HashSet<>();
        set.add("1");
        set.add("1");

而通过源码我们也能看出,底层就是调用HashMapput方法,若返回空则说明这个key没添加过

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

map.put底层返回值的核心逻辑是基于hashMap的源码如下,可以看到hashsetonlyIfAbsent设置为false,若插入成功返回null,反之则会将用新值将旧值进行覆盖,返回oldValue

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

		//通过哈希计算新元素要插入的位置有没有元素
        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);
                        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;
                }
            }
            //如果e不为空说明当前位置之前有过元素,将新值覆盖旧的值并返回旧值
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

如下图所示,第2次add就会返回上次addvalue,只不过对于hashSet而言返回的就是private static final Object PRESENT = new Object();全局不可变对象而已。

在这里插入图片描述

能不能从底层数据结构比较 HashSet、LinkedHashSet 和 TreeSet 使用场景、不同之处

  1. HashSet:可在不要求元素有序但唯一的场景。

  2. LinkedHashSet:可用于要求元素唯一、插入或者访问有序性的场景,或者FIFO的场景。

  3. TreeSet:要求支持有序性且按照自定义要求进行排序的元素不可重复的场景。

更多关于HashMap的知识

Java集合hashMap小结

更多ConcurrentHashMap

Java并发容器小结

优先队列PriorityQueue

关于优先队列的文章,笔者已将该文章投稿给了Javaguide,感兴趣的读者可以参考一下这篇文章:

https://github.com/Snailclimb/JavaGuide/blob/main/docs/java/collection/priorityqueue-source-code.md

Java集合使用以及工具类小结

Java集合使用以及工具类小结

一些常见的笔试题

以下代码分别输出多少?

 List a=new ArrayList<String>();
        a.add(null);
        a.add(null);
        a.add(null);
        System.out.println(a.size());//3
        Map map=new HashMap();
        map.put("a",null);
        map.put("a",null);
        map.put("a",null);
        System.out.println(map.size());//1

参考文献

Java集合常见面试题总结(上)

ArrayList源码&扩容机制分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1311771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DNS:从域名解析到网络连接

目录 解密 DNS&#xff1a;从域名解析到网络连接的不可或缺 1. DNS的基本工作原理 1.1 本地解析器查询 1.2 递归查询 1.3 迭代查询 1.4 TLD 查询 1.5 权威 DNS 查询 2. DNS的重要性与作用 2.1 地址解析与负载均衡 2.2 网络故障处理与容错 2.3 安全性与防护 3. DNS的…

生产实践:基于K8S的私有化部署解决方案

随着国内数字化转型的加速和国产化进程推动&#xff0c;软件系统的私有化部署已经成为非常热门的话题&#xff0c;因为私有化部署赋予了企业更大的灵活和控制权&#xff0c;使其可以根据自身需求和安全要求定制和管理软件系统。下面分享下我们的基于k8S私有化部署经验。 私有化…

Springboot管理系统数据权限过滤(二)——SQL拦截器

上一节Springboot管理系统数据权限过滤——ruoyi实现方案对数据权限实现方案有了认识&#xff0c;本文将进一步优化权限过滤方案&#xff0c;实现对业务代码零入侵。 回顾上一章中权限方案&#xff1a; 主要是通过注解拦截&#xff0c;拼接好权限脚本后&#xff0c;放到对象变…

每天五分钟计算机视觉:GoogLeNet的核心模型结构——Inception

本文重点 当构建卷积神经网络的时候,我们需要判断我们的过滤器的大小,这往往也作为一个超参数需要我们进行选择。过滤器的大小究竟是 11,33 还是 55,或者要不要添加池化层,这些都需要我们进行选择。而本文介绍的Inception网络的作用就是代替你来决定,把它变成参数的一部…

Improving IP Geolocation with Target-Centric IP Graph (Student Abstract)

ABSTRACT 准确的IP地理定位对于位置感知的应用程序是必不可少的。虽然基于以路由器为中心&#xff08;router-centric &#xff09;的IP图的最新进展被认为是前沿的&#xff0c;但一个挑战仍然存在&#xff1a;稀疏IP图的流行&#xff08;14.24%&#xff0c;少于10个节点&…

界面控件DevExpress .NET MAUI v23.1 - 发布一系列新组件

DevExpress拥有.NET开发需要的所有平台控件&#xff0c;包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。屡获大奖的软件开发平台DevExpress 今年第一个重要版本v23.1正式发布&#xff0c;该版本拥有众多…

蓝牙模块安全指南:保护你的设备和数据

随着蓝牙技术在各个领域的广泛应用&#xff0c;设备之间的无线连接变得越来越普遍。然而&#xff0c;与此同时&#xff0c;蓝牙连接也面临着潜在的安全风险。本文将为你提供一份蓝牙模块安全指南&#xff0c;帮助你保护设备和数据免受潜在的威胁。 1. 更新至最新蓝牙协议版本&a…

java实现冒泡排序及其动图演示

冒泡排序是一种简单的排序算法&#xff0c;它重复地遍历要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。重复这个过程直到整个数列都是按照从小到大的顺序排列。 具体步骤如下&#xff1a; 比较相邻的两个元素&#xff0c;如果前…

swagger的ApiModelProperty设置字段的顺序

需求 让前端可以直接通过swagger就能知道各个字段是什么意思 如何配置 比如&#xff0c;我们设置了ApiModelProperty ApiModelProperty("用户主键")private Long userId;在swagger页面能直接看到注释 但是这个顺序是按照字母排序的&#xff0c;明显不符合我们的要…

鸿蒙Web组件_学习

Web组件概述 Web组件用于在应用程序中显示Web页面内容&#xff0c;为开发者提供页面加载、页面交互、页面调试等能力。 页面加载&#xff1a;Web组件提供基础的前端页面加载的能力&#xff0c;包括加载网络页面、本地页面、Html格式文本数据。页面交互&#xff1a;Web组件提供…

【EI会议征稿】第五届机械仪表与自动化国际学术会议(ICMIA 2024)

第五届机械仪表与自动化国际学术会议&#xff08;ICMIA 2024&#xff09; The 5th International Conference on Mechanical Instrumentation and Automation 2024年第五届机械仪表与自动化国际学术会议&#xff08;ICMIA 2024&#xff09;定于2024年4月5-7日在中国武汉隆重…

通俗易懂:插入排序算法全解析(C++)

插入排序算法是一种简单直观的排序算法&#xff0c;它的原理就像我们玩扑克牌时整理手中的牌一样。下面我将用通俗易懂的方式来解释插入排序算法的工作原理。 假设我们手上有一副无序的扑克牌&#xff0c;我们的目标是将它们从小到大排列起来。插入排序算法的思想是&#xff0…

Kibana搜索数据利器:KQL与Lucene

文章目录 一、搜索数据二、KQL查询1、字段搜索2、逻辑运算符3、通配符4、存在性检查5、括号 三、Lucene查询1、字段搜索2、逻辑运算符3、通配符4、范围搜索5、存在性检查6、括号 四、总结 一、搜索数据 默认情况下&#xff0c;您可以使用 Kibana 的标准查询语言&#xff0c;该…

el-table自定义表格数据

如上所示&#xff1a; 表格内的数据是&#xff1a;当前班级所在名次段的人数 / 当前班级1至n名的累计人数 5/12 也就是 5/75 需要变更为&#xff1a; 截至到当前名次段总人数&#xff08;上次考试&#xff09; / 截至到当前名次段总人数&#xff08;本次考试&#xff09…

Electron 跨平台打包

最近利用 Electron 制作跨平台安装包&#xff0c;记录步骤&#xff0c;踩坑多多。 首先&#xff0c;一步步搭建项目 一、搭建环境 初始化 package.json&#xff0c;这里要求 node 版本不低于14.16&#xff0c;我用的 v14.16.0&#xff0c;16版本在 Linux 下容易出现安装依赖…

✺ch2——OpenGL图像管线

目录 基于C图形应用&#xff06;管线概览OpenGL类型第一个C/OpenGL应用程序◍API (1) GLSL类型着色器——画一个点的程序◍API (2)◍API (3) 栅格化像素操作——Z-buffer算法检测 OpenGL 和 GLSL 错误◍API (4) 从顶点来构建一个三角形场景动画◍API (5) OpenGL某些方面的数值—…

12.4~12.14概率论复习与相应理解(学习、复习、备考概率论,这一篇就够了)

未分配的题目 概率计算&#xff08;一些转换公式与全概率公式&#xff09;与实际概率 &#xff0c;贝叶斯 一些转换公式 相关性质计算 常规&#xff0c;公式的COV与P 复习相关公式 计算出新表达式的均值&#xff0c;方差&#xff0c;再套正态分布的公式 COV的运算性质 如…

ShenYu网关注册中心之HTTP注册原理

文章目录 1、客户端注册流程1.1、读取配置1.1.1、用于注册的 HttpClientRegisterRepository1.1.2、用于扫描构建 元数据 和 URI 的 SpringMvcClientEventListener 1.2、扫描注解&#xff0c;注册元数据和URI1.2.1、构建URI并写入Disruptor1.2.2、构建元数据并写入Disruptor1.2.…

Java获取当前用户当前工作目录

方法一&#xff1a;使用System.getProperty(“user.dir”)函数可以获取用户当前工作目录 例如&#xff0c;Java工程的文件布局如下&#xff1a; 主类文件&#xff0c;获取用户当前的工作目录&#xff1a; package com.thb;public class Test5 {public static void main(Stri…

RNN介绍及Pytorch源码解析

介绍一下RNN模型的结构以及源码&#xff0c;用作自己复习的材料。 RNN模型所对应的源码在&#xff1a;\PyTorch\Lib\site-packages\torch\nn\modules\RNN.py文件中。 RNN的模型图如下&#xff1a; 源码注释中写道&#xff0c;RNN的数学公式&#xff1a; 表示在时刻的隐藏状态…