Java 的集合

news2024/11/24 9:31:55

一、Collection 

1、ArrayList 

底层采用数组实现,操作大多基于对数组的操作。
在添加和删除时需要做 System.arraycopy(native层方法) 拷贝工作。
添加元素时可能会扩容,这要大量的拷贝工作,删除元素时,会把后面的元素向前拷贝。
所以增、删时效率不高。但set()、get()效率高。

1.ArrayList 的增、删方法

//这是添加元素的方法,size默认为0
public boolean add(E e) {
	ensureCapacityInternal(size + 1);
	//元素添加到集合 技术点:size++表示选赋值后加1,++size表示先加1后赋值。
	elementData[size++] = e;
	return true;
}

private void ensureCapacityInternal(int minCapacity) {
	//如果是使用无参构造会进入到这里,那么minCapacity 值为10,第一次进来 minCapacity 是1,最大值就是 DEFAULT_CAPACITY 了。
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	ensureExplicitCapacity(minCapacity);
}

//最核心的grow方法
private void ensureExplicitCapacity(int minCapacity) {
   modCount++;
   //如果第一次进入,minCapacity 为10,条件成立,进行扩容。当集合数据存满后,继续扩容。
  if (minCapacity - elementData.length > 0)
	 grow(minCapacity);
}
//扩容核心方法 
private void grow(int minCapacity) {
   //默认为零
   int oldCapacity = elementData.length;
   //oldCapacity >> 1 是位运算右移一位,相当于是除以2。所以从这里可以看出扩容后newCapacity 是原来的1.5倍。
   //如果集合数据存满后,再次扩容newCapacity=10+5
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   //如果是无参构造 第一次进入 newCapacity为0,minCapacity为10,条件成立
   if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
   if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity = hugeCapacity(minCapacity);
	//扩容开始 拷贝elementData数组为新数组,长度为newCapacity
	elementData = Arrays.copyOf(elementData, newCapacity);
}



//根据索引删除
public E remove(int index) {
	if (index >= size)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

	modCount++;
	E oldValue = (E) elementData[index];
	int numMoved = size - index - 1;
	//拷贝数组
	if (numMoved > 0) 
		System.arraycopy(elementData, index+1, elementData, index, numMoved);
	elementData[--size] = null; 
	return oldValue;
}

//根据对象删除
public boolean remove(Object o) {
	if (o == null) {
		for (int index = 0; index < size; index++)
			if (elementData[index] == null) {
				fastRemove(index);
				return true;
			}
	} else {
		//遍历进行查找
		for (int index = 0; index < size; index++)
			if (o.equals(elementData[index])) {
				fastRemove(index);
				return true;
			}
	}
	return false;
}

2.ArrayList 的改、查方法

// set() 方法
public E set(int index, E element) {
	if (index >= size)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

	E oldValue = (E) elementData[index];
	elementData[index] = element;
	return oldValue;
}

// get() 方法
public E get(int index) {
	if (index >= size)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

	return (E) elementData[index];
}

2、ArrayDeque 

3、LinkedList 

双向链表结构,Node中保存了数据、前指针、后指针。
增删数据时,只更换修改节点的前后指针,无需拷贝,速度较快。
查询时需要遍历,速度较慢。
链表不存在容量不足的问题,没有扩容机制,更适合删除和添加。

//头节点指针
transient Node<E> first;

//尾节点指针
transient Node<E> last;

public LinkedList() {
}
//Node实例,next:上一个元素的指针;prev:下一个元素的指针。
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.LinkedList的增、删方法

add: 尾部时(默认新元素插入尾部)
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:插入到链表头部
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

remove:删除头节点 
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

remove:尾结点
private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

remove:按对象删除,需要遍历节点
public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

2.LinkedList的set、get方法

set:根据index修改元素,需要遍历元素
public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}


get:查询采用二分查找
//node方法用于查找当前节点
//判断index值是不是小于整个链表长度的一半,整个if/else逻辑是在判断查找的位置是距离链表头近还是链表尾近
public E get(int key, E valueIfKeyNotFound) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    if (i < 0 || mValues[i] == DELETED) {
        return valueIfKeyNotFound;
    } else {
        return (E) mValues[i];
    }
}

4、TreeSet 

二、Map 

1、TreeMap 

2、HashMap 

默认长度16,扩容因子0.75。无序,线程不安全,key、value 都可以为 null,key是包装类型。
jdk1.7用头插法,由 数组 + 链表 组成,链表是为了解决哈希冲突。
jdk1.8用尾插法,由 数组+链表+红黑树(红黑树条件:链表长度大于8,且数组长度大于64)。

1.核心代码 

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    // 声明了一个局部变量 tab,局部变量 Node 类型的数据 p,int 类型 n,i
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 首先将当前 hashmap 中的 table(哈希表)赋值给当前的局部变量 tab,然后判断tab 是不是空或者长度是不是 0,实际上就是判断当前 hashmap 中的哈希表是不是空或者长度等于 0
    if ((tab = table) == null || (n = tab.length) == 0)
    // 如果是空的或者长度等于0,代表现在还没哈希表,所以需要创建新的哈希表,默认就是创建了一个长度为 16 的哈希表
        n = (tab = resize()).length;
    // 将当前哈希表中与要插入的数据位置对应的数据取出来,(n - 1) & hash])就是找当前要插入的数据应该在哈希表中的位置,如果没找到,代表哈希表中当前的位置是空的,否则就代表找到数据了, 并赋值给变量 p
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);//创建一个新的数据,这个数据没有下一条,并将数据放到当前这个位置
    else { //代表要插入的数据所在的位置是有内容的
    // 声明了一个节点 e, 一个 key k
        Node<K,V> e; K k;
        if (p.hash == hash && //如果当前位置上的那个数据的 hash 和我们要插入的 hash 是一样,代表没有放错位置
        // 如果当前这个数据的 key 和我们要放的 key 是一样的,实际操作应该是就替换值
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 将当前的节点赋值给局部变量 e
            e = p;
        else if (p instanceof TreeNode)//如果当前节点的 key 和要插入的 key 不一样,然后要判断当前节点是不是一个红黑色类型的节点
            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) // 重新计算当前链表的长度是不是超出了限制
                        treeifyBin(tab, hash);//超出了之后就将当前链表转换为树,注意转换树的时候,如果当前数组的长度小于MIN_TREEIFY_CAPACITY(默认 64),会触发扩容,我个人感觉可能是因为觉得一个节点下面的数据都超过8 了,说明 hash寻址重复的厉害(比如数组长度为 16 ,hash 值刚好是 0或者 16 的倍数,导致都去同一个位置),需要重新扩容重新 hash
                    break;
                }
                // 如果当前遍历到的数据和要插入的数据的 key 是一样,和上面之前的一样,赋值给变量 e,下面替换内容
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // 如果当前的节点不等于空,
            V oldValue = e.value;// 将当前节点的值赋值给 oldvalue
            if (!onlyIfAbsent || oldValue == null)
                e.value = value; // 将当前要插入的 value 替换当前的节点里面值
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;// 增加长度
    if (++size > threshold)
        resize();// 如果当前的 hash表的长度已经超过了当前 hash 需要扩容的长度, 重新扩容,条件是 haspmap 中存放的数据超过了临界值(经过测试),而不是数组中被使用的下标
    afterNodeInsertion(evict);
    return null;
}

2.扩容的方法 

final Node<K,V>[] resize() {
    // 创建一个临时变量,用来存储当前的table
    Node<K,V>[] oldTab = table;
    // 获取原来的table的长度(大小),判断当前的table是否为空,如果为空,则把0赋值给新定义的oldCap,否则以table的长度作为oldCap的大小
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 创建临时变量用来存储旧的阈值,把旧table的阈值赋值给oldThr变量
    int oldThr = threshold;
    // 定义变量newCap和newThr来存放新的table的容量和阈值,默认都是0
    int newCap, newThr = 0;
    // 判断旧容量是否大于0
    if (oldCap > 0) {
        // 判断旧容量是否大于等于 允许的最大值,2^30
        if (oldCap >= MAXIMUM_CAPACITY) {
            // 以int的最大值作为原来HashMap的阈值,这样永远达不到阈值就不会扩容了
            threshold = Integer.MAX_VALUE;
            // 因为旧容量已经达到了最大的HashMap容量,不可以再扩容了,将阈值变成最大值之后,将原table返回
            return oldTab;
        }
        // 如果原table容量不超过HashMap的最大容量,将原容量*2 赋值给变量newCap,如果newCap不大于HashMap的最大容量,并且原容量大于HashMap的默认容量
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 将newThr的值设置为原HashMap的阈值*2
            newThr = oldThr << 1; // double threshold
    }
    // 如果原容量不大于0,即原table为null,则判断旧阈值是否大于0
    else if (oldThr > 0) // 如果原table为Null且原阈值大于0,说明当前是使用了构造方法指定了容量大小,只是声明了HashMap但是还没有真正的初始化HashMap(创建table数组),只有在向里面插入数据才会触发扩容操作进而进行初始化
        // 将原阈值作为容量赋值给newCap当做newCap的值。由之前的源码分析可知,此时原阈值存储的大小就是调用构造函数时指定的容量大小,所以直接将原阈值赋值给新容量
        newCap = oldThr;
    // 如果原容量不大于0,并且原阈值也不大于0。这种情况说明调用的是无参构造方法,还没有真正初始化HashMap,只有put()数据的时候才会触发扩容操作进而进行初始化
    else {               // zero initial threshold signifies using defaults
        // 则以默认容量作为newCap的值
        newCap = DEFAULT_INITIAL_CAPACITY;
        // 以初始容量*默认负载因子的结果作为newThr值
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 经过上面的处理过程,如果newThr值为0,说明上面是进入到了原容量不大于0,旧阈值大于0的判断分支。需要单独给newThr进行赋值
    if (newThr == 0) {
        // 临时阈值 = 新容量 * 负载因子
        float ft = (float)newCap * loadFactor;
        // 设置新的阈值 保证新容量小于最大总量   阈值要小于最大容量,否则阈值就设置为int最大值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                    (int)ft : Integer.MAX_VALUE);
    }
    // 将新的阈值newThr赋值给threshold,为新初始化的HashMap来使用
    threshold = newThr;
    // 初始化一个新的容量大小为newCap的Node数组
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    // 将新创建的数组赋值给table,完成扩容后的新数组创建
    table = newTab;
    // 如果旧table不为null,说明旧HashMap中有值
    if (oldTab != null) {
        // 如果原来的HashMap中有值,则遍历oldTab,取出每一个键值对,存入到新table
        for (int j = 0; j < oldCap; ++j) {
            // 创建一个临时变量e用来指向oldTab中的第j个键值对,
            Node<K,V> e;
            // 将oldTab[j]赋值给e并且判断原来table数组中第j个位置是否不为空
            if ((e = oldTab[j]) != null) {
                // 如果不为空,则将oldTab[j]置为null,释放内存,方便gc
                oldTab[j] = null;
                // 如果e.next = null,说明该位置的数组桶上没有连着额外的数组
                if (e.next == null)
                    // 此时以e.hash&(newCap-1)的结果作为e在newTab中的位置,将e直接放置在新数组的新位置即可
                    newTab[e.hash & (newCap - 1)] = e;
                // 否则说明e的后面连接着链表或者红黑树,判断e的类型是TreeNode还是Node,即链表和红黑树判断
                else if (e instanceof TreeNode)
                    // 如果是红黑树,则进行红黑树的处理。将Node类型的e强制转为TreeNode,之所以能转换是因为TreeNode 是Node的子类
                    // 拆分树,具体源码解析会在后面的TreeNode章节中讲解
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                // 当前节不是红黑树,不是null,并且还有下一个元素。那么此时为链表
                else { // preserve order
                    /*
                        这里定义了五个Node变量,其中lo和hi是,lower和higher的缩写,也就是高位和低位,
                        因为我们知道HashMap扩容时,容量会扩到原容量的2倍,
                        也就是放在链表中的Node的位置可能保持不变或位置变成 原位置+oldCap,在原位置基础上又加了一个数,位置变高了,
                        这里的高低位就是这个意思,低位指向的是保持原位置不变的节点,高位指向的是需要更新位置的节点
                    */
                    // Head指向的是链表的头节点,Tail指向的是链表的尾节点
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    // 指向当前遍历到的节点的下一个节点
                    Node<K,V> next;
                    // 循环遍历链表中的Node
                    do {
                        next = e.next;
                        /*
                            如果e.hash & oldCap == 0,注意这里是oldCap,而不是oldCap-1。
                            我们知道oldCap是2的次幂,也就是1、2、4、8、16...转化为二进制之后,
                            都是最高位为1,其它位为0。所以oldCap & e.hash 也是只有e.hash值在oldCap二进制不为0的位对应的位也不为0时,
                            才会得到一个不为0的结果。举个例子,我们知道10010 和00010 与1111的&运算结果都是 0010  ,
                            但是110010和010010与10000的运算结果是不一样的,所以HashMap就是利用这一点,
                            来判断当前在链表中的数据,在扩容时位置是保持不变还是位置移动oldCap。
                        */
                        // 如果结果为0,即位置保持不变
                        if ((e.hash & oldCap) == 0) {
                            // 如果是第一次遍历
                            if (loTail == null)
                                // 让loHead = e,设置头节点
                                loHead = e;
                            else
                                // 否则,让loTail的next = e
                                loTail.next = e;
                            // 最后让loTail = e
                            loTail = e;
                        }
                        /*
                            其实if 和else 中做的事情是一样的,本质上就是将不需要更新位置的节点加入到loHead为头节点的低位链表中,将需要更新位置的节点加入到hiHead为头结点的高位链表中。
                            我们看到有loHead和loTail两个Node,loHead为头节点,然后loTail是尾节点,在遍历的时候用来维护loHead,即每次循环,
                            更新loHead的next。我们来举个例子,比如原来的链表是A->B->C->D->E。
                            我们这里把->假设成next关系,这五个Node中,只有C的hash & oldCap != 0 ,
                            然后这个代码执行过程就是:
                            第一次循环: 先拿到A,把A赋给loHead,然后loTail也是A
                            第二次循环: 此时e的为B,而且loTail != null,也就是进入上面的else分支,把loTail.next =
                                        B,此时loTail中即A->B,同样反应在loHead中也是A->B,然后把loTail = B
                            第三次循环: 此时e = C,由于C不满足 (e.hash & oldCap) == 0,进入到了我们下面的else分支,其
                                        实做的事情和当前分支的意思一样,只不过维护的是hiHead和hiTail。
                            第四次循环: 此时e的为D,loTail != null,进入上面的else分支,把loTail.next =
                                        D,此时loTail中即B->D,同样反应在loHead中也是A->B->D,然后把loTail = D
                        */
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 遍历结束,即把table[j]中所有的Node处理完
                    // 如果loTail不为空,也保证了loHead不为空
                    if (loTail != null) {
                        // 此时把loTail的next置空,将低位链表构造完成
                        loTail.next = null;
                        // 把loHead放在newTab数组的第j个位置上,也就是这些节点保持在数组中的原位置不变
                        newTab[j] = loHead;
                    }
                    // 同理,只不过hiHead中节点放的位置是j+oldCap
                    if (hiTail != null) {
                        hiTail.next = null;
                        // hiHead链表中的节点都是需要更新位置的节点
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    // 最后返回newTab
    return newTab;
}

 

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

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

相关文章

剑指oferr68-II.二叉树的最近公共祖先

为什么这道题的难度是easy&#xff0c;我感觉挺难的啊&#xff0c;我想了挺久没有一点思路就直接看题解了。题解有两种解法&#xff0c;先看第一种存储父节点 class Solution {Map<Integer,TreeNode> parent new HashMap<Integer,TreeNode>();Set<Integer>…

ffmpeg2段视频合成一段

查看分辨率 帧率和编码器 ffprobe -v error -select_streams v:0 -show_entries streamcodec_name,width,height,avg_frame_rate -of defaultnoprint_wrappers1 rs2.mp4得到&#xff0c;编码器&#xff0c;分辨率&#xff0c;还有帧率 codec_nameh264 width1920 height1080 avg…

小白到运维工程师自学之路 第五十二集 (三剑客之awk)

一、概述 awk命令是一种在Unix或类Unix系统上使用的文本处理工具。它以行为单位读取输入文件&#xff0c;按照预定义规则对每一行进行处理并生成输出。 通过使用一种简单的编程语言&#xff0c;awk提供了对文本数据进行过滤、处理和转换的强大能力。它可以轻松地提取文本中的…

实验四 交换机 VLAN 配置

文章目录 实验目的实验原理实验内容实验总结 实验目的 理解 VLAN 的概念、原理&#xff1b;掌握基于交换机端口的 VLAN 划分方法&#xff1b;掌握 Cisco2950 交换机的单交换机和跨交换机 VLAN 配置方法。 实验原理 &#xff08;1&#xff09;VLAN 的概念 VLAN&#xff08;Vi…

CQ 社区版 2.2.0 发布 | 配置要求降为 4 核 16G!!!

&#x1f389;&#x1f389;&#x1f389; 喜大普奔&#xff01;&#xff01;&#xff01; 经过我们研发团队的不断努力&#xff0c;CloudQuery 终于「瘦身」成功啦&#xff01;&#xff01;&#xff01; 本次发布的 v2.2.0 版本&#xff0c;推荐配置降为&#xff1a; CPU&a…

【iOS内存管理-内存的几大区域】

前言 iOS内存管理的第一篇章&#xff0c;了解iOS内存的五大分区。 总览 iOS中&#xff0c;内存主要分为五大区域&#xff1a;栈区&#xff0c;堆区&#xff0c;全局区/静态区&#xff0c;常量区和代码区。总览图如下。 如上图所示&#xff0c;代码区是在低地址段存放&#x…

关于AWS MSK Connector Execution Role的解释

尽管在创建AWS MSK Connector时&#xff0c;Execution Role是一个必填项&#xff0c;就像下面这样&#xff1a; 并且在官方文档中给出的Execution Role样例&#xff1a; https://docs.aws.amazon.com/msk/latest/developerguide/msk-connect-service-execution-role.html 中也…

基于JSP+Servlet的学生信息管理系统

用户类型&#xff1a;三角色&#xff08;学生、教师、管理员&#xff09; 项目架构&#xff1a;B/S架构 设计思想&#xff1a;MVC 开发语言&#xff1a;Java语言 前端技术&#xff1a;Layui、HTML、CSS、JS、JQuery、Ajax等技术 后端技术&#xff1a;JSP、Servlet、JDBC、…

基于JSP+Servlet的医药药品管理系统

用户类型&#xff1a;双角色角色&#xff08;患者、管理员[医生]&#xff09; 设计模式&#xff1a;MVC&#xff08;jspservletjavabean) 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言 主要技术&#xff1a;jsp、servlet、jdbc、jsp、html5、jquery、css、js…

【哈希表/字符串-简单】LeetCode 205 同构字符串 Java

需要判断s和t每个位置上的字符是否都一一对应&#xff0c;即s的任意一个字符被t中唯一的字符对应&#xff0c;同时t的任意一个字符被s中唯一的字符对应。这也被称为【双射】的关系。 以示例二为例&#xff0c;t中的字符a和r虽然有唯一的映射o&#xff0c;但对于s中的字符o来说…

Vim批量注释与反注释

在使用vim编写代码的时候&#xff0c;经常需要用到批量注释与反注释一段代码。下面简要介绍其操作。本文记录在mac/linux下的vim批量注释。 一开始我想让vim配置ctrl/快捷键&#xff0c;快速批量注释&#xff0c;但是vim的文档中不支持这样的快捷键。 如果实在要弄也能弄&…

集群基础2——LVS负载均衡httpd服务(nat模式)

文章目录 一、环境说明一、配置调度器网卡二、配置后端服务器三、配置调度器四、验证五、设置https负载均衡 一、环境说明 使用lvs中的nat模型&#xff0c;对http负载均衡集群。 主机IP角色安装服务真实IP&#xff1a;192.168.161.129VIP&#xff1a;192.168.161.130调度服务器…

深信服 网络工程师面试题(二)

指针函数和函数指针的区别 指针函数是指带指针的函数&#xff0c;即本质是一个函数&#xff0c;函数返回类型是某一类型的指针。 首先它是一个函数&#xff0c;只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受&#xff0c;也就是说&#xff0c;指…

牛客HJ99 - 自守数【暴力 + 换位取模】

原题传送门 原题描述 首先我们来看一下原题是怎么描述的&#xff0c;题面很简单&#xff0c;输入n&#xff0c;然后让我们去统计从1 ~ n之间的自守数有几个&#xff0c;那什么是【自守数】呢&#xff0c;上面也说到了&#xff0c;即一个数在平方之后该数的尾数等于该数自身的自…

榜单!高精定位模块/系统「争夺战」,份额Top5供应商都有谁

以当前国内车企落地高速NOA采用的主流方案为例&#xff0c;普遍采用了「高精定位高精地图车端传感器」的多源融合定位策略。其中&#xff0c;在高精定位部分&#xff0c;大部分采用GNSSIMURTK的方案。 从目前的智驾系统演进来看&#xff0c;主流的仍是在L2基础上&#xff0c;通…

k8s 基础命令和常用命令等

通过kubectl命令可以操作和管理K8S资源&#xff0c;对于初学者可以在掌握K8S基础命令的基础上再去学习K8s的原理和架构&#xff0c;那么K8S常用的命令有哪些呢&#xff1f; 01 K8S命令概述 在学习K8s基础命令前&#xff0c;了解和学习docker命令是很有必要的&#xff0c;kub…

c语言--unsigned修饰符

在C语言中&#xff0c;unsigned是一种无符号整数修饰符。它可以与多个整数类型&#xff08;如int、short、long等&#xff09;结合使用&#xff0c;表示该整数类型只包含非负数值。unsigned修饰的整数类型不保存正负号位&#xff0c;因此可以用来表示更大的正整数范围。 例如&…

干货分享|SOLIDWORKS Composer如何解决缺失的actor?

​SOLIDWORKS Composer导入SOLIDWORKS模型&#xff0c;以便用户可以创建图形内容并与更广泛的受众共享项目。但是&#xff0c;有时模型导入时缺少Actor或组件&#xff0c;通常是由于在SOLIDWORKS中以轻量模式加载组件或Composer中的导入设置排除了曲面实体。 轻量模式 轻量模式…

基于DEF生成LIB的方法

基于DEF生成LIB的方法 [生成64位lib] lib /def:libcurl-x64.def /MACHINE:x64 /OUT:libcurl-x64.lib[生成32位lib] lib /def:libcurl-x64.def /OUT:libcurl-x64.lib

flutter开发实战-生日等日期选择器DatePicker

flutter开发实战-生日等日期选择器DatePicker 在开发遇到设置生日等信息需要选择日期&#xff0c;这里用到的是CupertinoDatePicker iOS效果的日期、时间选择器 一、效果图 运行后效果图如下 二、代码实现 我们需要调用底部弹窗显示 //显示底部弹窗static void bottomShe…