Java集合框架图析(Collection-List)

2023-10-30

Java集合框架图析(Collection-List)

前言

Java 集合,也称作容器,主要是由两大接口 (Interface) 派生出来的:CollectionMap,顾名思义,容器就是用来存放数据的。那么这两大接口的不同之处在于:

  • Collection 存放单一元素;
  • Map 存放 key-value 键值对。

集合框架

在这里插入图片描述

Java集合里使用接口来定义功能,是一套完善的继承体系。Iterator是所有集合的总接口,其他所有接口都继承于它,该接口定义了集合的遍历操作,Collection接口继承于Iterator,是集合的次级接口(Map独立存在),定义了集合的一些通用操作。

Collection

Collection接口集成于Iterator,Collection里还定义了很多方法,这些方法也都会继承到各个子接口和实现类里,而这些 API 的使用也是日常工作和面试常见常考的.
集合的使用最常用的就是CRUD功能:

功能 方法
add()/addAll()
remove()removeAll()
Collection Interface 里并没有直接改元素的操作
contains()/ containsAll()
其他 isEmpty()/size()/toArray()
//确保此集合包含指定的元素(可选操作)。
boolean add(E e); 
//将指定集合中的所有元素添加到此集合中(可选操作)。 
boolean addAll(Collection<? extends E> c); 

//从此集合中移除指定元素的单个实例(如果存在)(可选操作)。
boolean remove(Object o); 
//把集合中的所有元素都删掉
boolean removeAll(Collection<?> c); 

//查下集合中有没有某个特定的元素:
boolean contains(Object o); 
//如果此集合包含指定集合中的所有元素,则返回 true 
boolean containsAll(Collection<?> c); 

//判断集合是否为空
boolean isEmpty(); 

//集合的大小
int size(); 

//把集合转成数组
Object[] toArray(); 

//返回此集合的哈希码值。 
int hashCode()

这些就是Collection中常用的API了,在接口中已经定义好了,子类中也可以实现这些API,子类同时也可以有自己的实现.

List

在这里插入图片描述

List是Collection的子接口,List的特点主要是:有序,可重复
官网文档的描述是:有序集合(也称为序列 )。 该接口的用户可以精确控制列表中每个元素的插入位置。 用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。

ArrayList

ArrayList实现了List接口,是顺序容器,允许放入null元素,底层通过数组实现,每个ArrayList都有一个容量,容器内存储的元素个数不能超过这个容量,当超过时容器会自动扩容。

ArrayList自动扩容

每次当向数组中添加元素时,都要检查添加的元素会不会超过当前数组的长度, 如果超出, 数组将会扩容, 以满足添加数据的需求, 数组扩容通过ensureCapacity(int minCapacity)来实现, 倘若需要添加大量元素之前也可以手动配置ensureCapacity(int minCapacity).
每次扩容, 都会将老数组的内容拷贝到新数组中, 每次数组容量的增长是oldCapacity + (oldCapacity >> 1), 大约是原数组的1.5倍, 这种代价还是蛮高的, 所以我们可以使用之前, 预知需要的元素空间, 在构造ArrayList时, 就指定容量, 避免扩容过程, 或者根据生产的大量需求, 手动配置ensureCapacity(int minCapacity).

/**
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     * @param   minCapacity   the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                 && minCapacity <= DEFAULT_CAPACITY)) {
            modCount++;
            grow(minCapacity);
        }
    }

    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    private Object[] grow() {
        return grow(size + 1);
    }

   
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
 

图析:
在这里插入图片描述

add(E e) add(int index E e)

分析一下:
add(E e)是在尾部加上元素,add方法中可能会有扩容情况出现,但是平均下来复杂度还是O(1)的.
结合源码分析一下:
进入add()方法:

/**	
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     */
    public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }

add()中第一个参数e是我们传输的数据, 第二个参数elementData是是存数据的数组, 第三个参数size是当前数组的长度(当前有效数组的长度). 接着往里面分析

/**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        elementCount = s + 1;
    }

先进 行一个if判断s是否等于当前数组长度, 如果一直就说明存满了, 就grow()方法扩容, 扩容完产生新的数组给elementData[s], 然后后续就正常存了, 存了就siez+1, 继续往里分析grow()

private Object[] grow() {
        return grow(elementCount + 1);
    }

/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

grow()方法传入minCapacity就是目前数组长度, 比如一开始是0, siez+1那长度就变为了1, Arrays.copyOf()将旧数组根据新长度(newCapacity(minCapacity)), 产生一个新数组, 然后旧数据的数据给到新数组. 接着往里分析newCapacity()

/**
     * Returns a capacity at least as large as the given minimum capacity.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity <= 0) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

/**
     * The maximum size of array to allocate (unless necessary).
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

这个方法传入最小需要的长度minCapacity来获取新的长度

  1. 获取旧数组长度, 因为存满了才需要扩容, 所以就是当前数组的总长度
  2. 新长度是旧长度+旧长度右移一位(也就是除2)
  3. 进行判断新的-旧的长度<=0,如果目前存储的数据是默认的是第一次创建,没有传长度默认是0,然后Math.max取(10,1),这里的10是DEFAULT_CAPACITY默认的长度,然后如果是第一次创建经过size+1,minCapacity就是1,所以经过Math.max取的就是10.
  4. 如果是溢出的话就会报错扔了一个异常, 这里的溢出是:因为是int类型的,最大值是2 的 31 次方 - 1, 如果达到最大值再+1, int类型就存不下了, 就溢出了, 超出了位数, 最终因为符号的改变, 变为负数.
  5. 如果都不是3.4的情况,就返回需要的minCapacity
  6. 如果不是就入3的if判断, 说明newCapacity内存扩容够用, 就进行三元运算判断, 如果newCapacity-MAX_ARRAY_SIZE <=0 , (MAX_ARRAY_SIZE也就是我们允许的最大长度,就是Integer.MAX_VALUE - 8) 那就说明是比最大的要小的, 那就返回newCapacity. 就比如现在要20长度, 那肯定是比最大值要小的, 那就直接取的20. 否则就是hugeCapacity(minCapacity)方法, 接着分析.
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

第一个同样是判断是否溢出,前面分析过了, 接着return返回看minCapacity是否在MAX_ARRAY_SIZE到Integer.MAX_VALUE - 8之间,如果是就返回Integer.MAX_VALUE, 否则就是返回MAX_ARRAY_SIZE.

add(int index, E e):
是在特定的位置上加元素,LinkedList 需要先找到这个位置,再加上这个元素,虽然单纯的加这个动作是 O(1) 的,但是要找到这个位置还是 O(n) 的。

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
  • 图析:
    在这里插入图片描述
    在这里插入图片描述
set()

直接对数组指定位置赋值,十分简单

public E set(int index, E element) {
	//下标越界检查
    rangeCheck(index);
    E oldValue = elementData(index);
    //完成对应index赋值
    elementData[index] = element;
    return oldValue;
}
get()

get()同样比较简单,获取对应位置元素

public E get(int index) {
    rangeCheck(index);
    //注意类型转换
    return (E) elementData[index];	
}
remove()

有两种方式,一种通过remove对应下标,另一种通过remove见到的第一次满足o.equals(es[i])的元素.

remove(int index) 是移除对应index上的元素:

public E remove(int index) {
            Objects.checkIndex(index, size);
            checkForComodification();
            E result = root.remove(offset + index);
            updateSizeAndModCount(-1);
            return result;
        }

remove(E e) 是 remove 见到的第一个这个元素:

public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
    }

LinkedList

LinkedList实现了List接口和Deque接口, 也就说可以看作顺序容器,也可以看作一个队列, 又可以看作是栈, 这样看来LinkedList很全能, 当需要使用栈或者队列的时候, 可以考虑使用LinkedList.

基础属性:
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    //长度
    transient int size = 0;
    //指向头结点
    transient Node<E> first;
    //指向尾结点
    transient Node<E> last;
}

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;
    }
}

内部结构图:

在这里插入图片描述
可以看出一个节点中包含三个属性,就是上面源码中的属性, LinkedList底层是一种双向链表的实现.

构造方法
  • public LinkedList() :空的构造方法,啥事情都没有做
  • public LinkedList(Collection<? extends E> c) : 将一个元素集合添加到LinkedList中
添加节点
 public boolean add(E e) {
        linkLast(e);
        return true;
    }

//在链表的最后添加元素
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. 记录当前末尾节点,通过构造另外一个指向末尾节点的指针l
  2. 产生新节点,在末尾添加,next为null
  3. last指向新节点
  4. 做判断链表中原本有无节点,无的话newNode为第一节点,有的话原本记录的末尾节点指向newNode
  5. size++ 计数modCount++
删除节点

LinkedList中有两种方法删除节点

//方法1.删除指定索引上的节点
public E remove(int index) {
    //检查索引是否正确
    checkElementIndex(index);
    //这里分为两步,第一通过索引定位到节点,第二删除节点
    return unlink(node(index));
}

//方法2.删除指定值的节点
public boolean remove(Object o) {
    //判断删除的元素是否为null
    if (o == null) {
        //若是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;
}

通过源码可以看出两个方法都是通过unlink()删除, 还有另一种是通过下标找到对应的节点.

  1. 首先确定index的位置,是靠近first还是last
  2. 若靠近first从头开始查询, 否则从尾部开始查询,很好的运用了双向链表的特征
/**
     * Returns the (non-null) Node at the specified element index.
     */
    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;
        }
    }

unlink()方法的源码分析, 这也是删除节点的核心方法

E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    
    //删除的是第一个节点,first向后移动
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }
    
    //删除的是最后一个节点,last向前移
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
    

    x.item = null; //GC
    size--;
    modCount++;
    return element;
}
  1. 获取到需要删除元素当前的值,指向它前一个节点的引用,以及指向它后一个节点的引用。
  2. 判断删除的是否为第一个节点,若是则first向后移动,若不是则将当前节点的前一个节点next指向当前节点的后一个节点
  3. 判断删除的是否为最后一个节点,若是则last向前移动,若不是则将当前节点的后一个节点的prev指向当前节点的前一个节点
  4. 将当前节点的值置为null
  5. size减少并返回删除节点的值
获取节点

getFirst(), getLast():获取第一个元素,获取最后一个元素

    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

ArrayList和LinkedList比较

List 的实现方式有 LinkedListArrayList 两种,面试常考的就是这两个数据结构如何选择,那就要考虑这两种数据结构能完成什么功能以及效率.
先看两个classes的API和时间复杂度:

功能 方法 ArrayList LinkedList
add(E e) O(1) O(1)
add(int index, E e) O(n) O(n)
remove(int index) O(n) O(n)
remove(E e) O(n) O(n)
set(int index, E e) O(1) O(1)
get(int index) O(1) O(n)
方法 ArrayList LinkedList
add(E e) 在尾部加上元素,虽然会有扩容的情况出现, 平均下来时间复杂度为O(1) 在尾部添加元素,直接改变原本末节点的next指向新节点
add(int index, E e) 先找是O(n),单纯加是O(1) 先通过线性查找找到具体位置,然后修改相关引用完成插入操作
remove(int index) 找到这个元素的过程是 O(1),但是 remove 之后,后续元素都要往前移动一位,所以均摊复杂度是 O(n) 也是要先找到这个 index,这个过程是 O(n) 的,所以整体也是 O(n)
remove(E e) 要先找到这个元素,这个过程是 O(n),然后移除后还要往前移一位,这个更是 O(n),总的还是 O(n) 也是要先找,这个过程是 O(n),然后移走,这个过程是 O(1),总的是 O(n).
  • ArrayList是实现了基于动态数组的数据结构,因地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的),ArrayList要移动数据,所以插入和删除操作效率比较低.
  • LinkedList 基于链表的数据结构 地址是任意的, 对于新增和删除数据有较好的性能,查询较慢 因为LinkedList查询需要移动指针遍历.
public class Test {
        private static ArrayList<Integer> arrayList= new ArrayList<>();

        private static LinkedList<Integer> linkedList = new LinkedList<>();
    public static void main(String[] args) {

        int count = 1000000;	//循环次数
        System.out.println("循环 " + count + " 次,arrayList LinkedList 尾部插入性能测试:");

        testInsert(arrayList, count);
        testInsert(linkedList, count);

        int index = 0;			//插入位置
        count = 100000;
        System.out.println("\n循环 " + count + " 次,arrayList LinkedList 指定位置插入性能测试:");
        testInsertForIndex(arrayList, count, index);
        testInsertForIndex(linkedList, count, index);

        System.out.println("\n循环 " + count + " 次,arrayList LinkedList 查询性能测试");
        getElements(arrayList, count);
        getElements(linkedList, count);

    }

    /**
     * 向默认位置插入元素
     * @param count	循环次数
     */
    public static void testInsert(List<Integer> list, int count){
        long beginTime = System.currentTimeMillis();
        for(int i=0; i<count; i++){
            list.add(1);
        }
        long endTime = System.currentTimeMillis();

        System.out.println(list.getClass().getName() + " 共耗时:" + (endTime - beginTime) + " ms");
    }

    /**
     * 向指定位置插入元素
     * @param count	循环次数
     * @param index	插入位置
     */
    public static void testInsertForIndex(List<Integer> list, int count, int index){
        long beginTime = System.currentTimeMillis();
        for(int i=0; i<count; i++){
            list.add(index,1);
        }
        long endTime = System.currentTimeMillis();

        System.out.println(list.getClass().getName() + " 共耗时:" + (endTime - beginTime) + " ms");
    }

    /**
     * 获取元素
     * @param list
     * @param count
     */
    public static void getElements(List<Integer> list, int count){
        Long beginTime = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            list.get(i);
        }
        Long endTime =  System.currentTimeMillis();
        System.out.println(list.getClass().getName() + " 共耗时:" + (endTime - beginTime) + " ms");
    }
}

结果如下图:
在这里插入图片描述

  • LinkedList插入、删除指定位置的性能是优于ArrayList的,因为ArrayList需要额外的移动损耗,以及拷贝元素.
  • ArrayList在查找和尾部插入删除的性能时优于LinkedList的.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java集合框架图析(Collection-List) 的相关文章

  • 将 java 方法参数设置为最终参数

    那有什么区别final在下面的代码之间进行 将参数声明为有什么好处final public String changeTimezone Timestamp stamp Timezone fTz Timezone toTz return pu
  • 如何创建仅接受字母数字字符的正则表达式? [复制]

    这个问题在这里已经有答案了 可能的重复 字母数字和下划线的正则表达式 https stackoverflow com questions 336210 regular expression for alphanumeric and unde
  • 将更改(永久)保存在数组列表中?

    那可能吗 例如 用户将新的项目 元素添加到数组列表 缓冲读取器进程 中 并且肯定会发生更改 我的问题是 即使用户多次更改数组列表 它也可能会永久存在 即使他们关闭程序并再次打开它 它也会一直存在 注意 不使用 txt 很抱歉问这样的问题 但
  • 内部/匿名类的最佳实践[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 匿名类和静态内部类的最佳实践 设计和性能方面 是什么 就我个人而言 我认为静态内部类提供了更好的封装 并且应该提供更好的性能 因为它们无法访问类
  • Java:Swing:设置JButton的位置

    我想实现以下布局 OK
  • 如何确定 JDialog 显示在哪个屏幕上

    我有一个非常大的应用程序 有多个对话框 我的任务是确保不完全可见的对话框 因为用户将其从可见屏幕区域拉出 移回屏幕中心 当我只处理一个屏幕时 这没问题 它工作得很好 但是 该应用程序的大多数用户的桌面上都有两个屏幕 当我尝试找出对话框显示在
  • 在 alpine / Jprofile 10 中运行 jpenable 时出现 UnsatisfiedLinkError

    当运行 jpenable 以允许在运行 JDK 8 的 alpine 3 3 容器中对 Jprofiler10 进行分析时 我收到 UnsatisfiedLinkError 异常 有任何想法吗 ERROR The agent could n
  • 简单的Java程序插入USB热点后速度慢100倍

    我有以下Java程序 class Main public static void main String args throws java io IOException long start System nanoTime java io
  • Android 的@hide 注解到底有什么作用?

    Android中很多内部API都被标记出来了 hide What exactly这是吗 另一个答案 https stackoverflow com questions 17035271 what does hide mean in the
  • 相对重力

    我最近开始使用jMonkey引擎 这非常好 但我在尝试实现相对重力时陷入了困境 我想让行星彼此围绕轨道运行 不一定是完美的圆形轨道 取决于速度 所以每个对象都应该影响其他对象 我现在拥有的 关闭全球重力 bulletAppState get
  • 如何在不冒 StackOverflowError 风险的情况下使用 CompletableFuture?

    我想遍历异步函数的搜索空间 我将逻辑编码如下 Assuming that a function maps a range of inputs to the same output value minimizes the input valu
  • java多线程中“私有最终对象”锁定有什么用?

    java多线程中 私有最终对象 锁定有什么用 据我的理解 我认为要使一个类成为线程安全的 我们应该使用内部锁定 将所有方法标记为同步并使用 this 将它们锁定在对象的监视器上 或者我们可以用方法中的私有最终对象锁替换类的 this 上标记
  • Java 日期和 MySQL 时间戳时区

    我正在编辑一段代码 其基本功能是 timestamp new Date 然后坚持下去timestamp中的变量TIMESTAMPMySQL 表列 然而 通过调试我看到Date显示在正确时区的对象 GMT 1 当持久化在数据库上时 它是GMT
  • mysql 准备好的语句错误:MySQLSyntaxErrorException

    我使用准备好的语句编写了选择语句 每次尝试运行都会出现此错误 我如何克服这个错误 我的jdbc连接器是mysql connector java 5 1 13 bin jar 我的代码 public Main add ad to getAdD
  • 在Android项目中引用(纯java)项目(找不到类)

    我试图在我的 Android 项目中引用一个纯 java 项目 gt Java 项目有一大堆我需要使用的类 哦 正如第一个回复所指出的 我正在使用 eclipse 是的 唯一的问题是 我总是找不到类 XXX 从方法 com example
  • 如何让 Camel FTP 按需只获取一次

    我对骆驼还很陌生 我一直在尝试让 Camel 根据需要仅通过 FTP 获取单个文件一次 我无法让它发挥作用 这是我尝试过的 让我知道什么是最好的方法以及我的代码有什么问题 1 读取文件后发送一条空消息当收到空消息时 停止路由 from di
  • Java泛型类型参数中的问号是什么意思? [复制]

    这个问题在这里已经有答案了 这是取自斯坦福解析器附带的一些示例的一小段代码 我已经用 Java 进行了大约 4 年的开发 但从未对这种风格的代码应该表示什么有非常深入的理解 List
  • 不鼓励在Web应用程序中使用线程吗?

    我们与同事就在 Java 的 Web 应用程序中使用线程进行了激烈的讨论 他们的观点是 不建议在 Java Web 应用程序中使用线程 因为它们不受容器管理 一般来说 我对此表示同意 因为线程可能会干扰容器 但是 如果它不是 Java EE
  • 与手动搜索列表相比,Collections.binarySearch 的性能如何?

    我想知道该使用哪一个 我有一份学生名单 我想用他的名字搜索一个学生 到目前为止 我是通过迭代列表手动完成的 如下所示 for int i 0 i lt list size i Student student list get i if st
  • Spring Boot 2 中的 401 代替 403

    With 春季启动 https projects spring io spring boot 1 5 6 发布我能够发送 HTTP 状态代码401代替403如中所述如果请求未经身份验证的uri 如何让Spring Security响应未经授

随机推荐

  • 如何在 Ubuntu 20.04 上安装 VLC 媒体播放器

    VLC 是最流行的开源多媒体播放器之一 它是跨平台的 几乎可以播放所有多媒体文件以及 DVD 音频 CD 和不同的流媒体协议 本文介绍如何在 Ubuntu 20 04 上安装 VLC 媒体播放器 VLC 可以通过 Snapcraft 商店作
  • 如何在Linux中检查内核版本

    内核是操作系统的核心组件 它管理系统的资源 是计算机硬件和软件之间的桥梁 您可能需要了解 GNU Linux 操作系统上运行的内核版本的原因有多种 也许您正在调试与硬件相关的问题或了解影响旧内核版本的新安全漏洞 并且您想了解您的内核是否容易
  • 如何在 Ubuntu 18.04 上安装 Memcached

    Memcached 是一个免费开源的高性能内存键值数据存储 它最常用于通过缓存数据库调用结果中的各种对象来加速应用程序 在本教程中 我们将介绍在 Ubuntu 18 04 上安装和配置最新版本 Memcached 的过程 相同的说明适用于
  • 配置 Nginx 错误和访问日志

    Nginx 是一个开源的 高性能的 HTTP 和反向代理服务器负责处理互联网上一些最大网站的负载 管理时NGINX对于 Web 服务器 您将执行的最常见的任务之一是检查日志文件 了解如何配置和读取日志在排除服务器或应用程序问题时非常有用 因
  • 如何在 Debian 10 Linux 上安装 VirtualBox 来宾添加

    虚拟盒子是一款开源 跨平台虚拟化软件 允许您同时运行多个来宾操作系统 虚拟机 VirtualBox 提供了一组可以安装在来宾操作系统中的驱动程序和应用程序 VirtualBox Guest Additions 来宾添加为来宾计算机提供了多种
  • 【JS 逆向百例】百度翻译接口参数逆向

    文章目录 逆向目标 逆向过程 抓包分析 获取 token 获取 sign 完整代码 baidu encrypt js baidufanyi py 逆向目标 目标 百度翻译接口参数 主页 https fanyi youdao com 接口 h
  • 查看: 1280

    查看 1280 回复 0 电容三点式LC振荡器电路组成及工作原理简述 复制链接 husk2012 95 主题 0 听众 3189 积分 VIP会员 收听TA 发消息 电梯直达
  • line-height:1是什么意思

    line height 1是什么意思 其实仍旧是设置行高的一种方法 只不过简化了语句 举个例子 比如此时你设置了font size 20px 之后你设置了line height 1 转义过来的意思就是line height 20px 行高为
  • 傻瓜式-根据自定义规则编码生成

    private final ReentrantLock lock new ReentrantLock public Result addProvider Provider proNew new Provider try lock lock
  • c语言ox是什么意思啊,ox什么意思

    营销树今天精心准备的是 ox什么意思 下面是详解 OX是什么意思 OX是无色透明的化学液体邻二甲苯的英文简写 OX作为英文单词是可数名词 基本含义是牛 读音为 英 ks 美 ks 复数 oxen 同义词 wild ox 例句They are
  • 福大计算机学硕扩招,福州大学2021年推免数据,快来了解这所211大学的保研情况!...

    福州大学是省部共建高校 是 双一流 建设高校 是 211工程 建设高校 学校现有1个国家重点实验室 8个国家级工程研究中心 3个国家国际科技合作基地 3个教育部重点实验室 有11个博士后科研流动站 11个一级学科博士点 2021年推荐优秀应
  • Arthas开源一周年,Github Star 160K,我们一直在坚持什么?

    缘起 最近看到一个很流行的标题 开源XX年 star XXX 我是如何坚持的 看到这样的标题 忽然发觉Arthas从2018年9月开源以来 刚好一年了 正好在这个秋高气爽的时节做下总结和回顾 Arthas Arthas是Alibaba开源的
  • WSL2创建多实例--发行版管理工具wsl2distromanager使用

    简介 wsl2 distro manager是github上的一款WSL的开源实例管理工具 它具有GUI界面 是目前我发现的比较方便的管理工具 项目地址如下 https github com bostrot wsl2 distro mana
  • 氮化镓 服务器电源管理系统报价,基于氮化镓的电源解决方案总体拥有成本评估...

    引言 近年来 电信市场正在朝云计算的方向转变 这导致超大规模数据中心空前快速的增长 而每个机架需要处理的功能也越来越多 反过来 这种趋势也意味着对功率的需求快速增加 而重点则是采用消耗更少电力的更高效 体积更紧凑的电源 散热同样是这里需要考
  • 信息安全保障体系规划方案

    本文转载自公众号爱方案 ID ifangan 本文内容为信息安全技术体系 运维体系 管理体系的评估和规划 是信息安全保障体系的主体 一 概述 1 1引言 本文基于对XX公司信息安全风险评估总体规划的分析 提出XX公司信息安全技术工作的总体规
  • C语言实战例题:必会的 10 个C语言经典练习题,源码分享

    这些都是基本的 C 程序 可以帮助刚踏入 C 编程世界的新手 快来试试吧 1 C 语言编程 Hello World include
  • 虚拟服务器磁盘 厚置备置零,VMware ESXi 虚拟硬盘格式记录:厚置备延迟置零、厚置备置零、精简置备...

    创建磁盘时 会进行两个操作 分配空间 置零 1 厚置备延迟置零 默认的创建格式 创建磁盘时 直接从磁盘分配空间 但对磁盘保留数据不置零 所以当有I O操作时 只需要做置零的操作 磁盘性能较好 时间短 适合于做池模式的虚拟桌面 2 厚置备置零
  • 微信小程序授权登录流程

    自我介绍 我是IT果果日记 微信公众号请搜索 IT果果日记 一个普通的技术宅 定期分享技术文章 欢迎点赞 关注和转发 请多关照 首先 我们要了解什么是微信小程序登录 它的作用是什么 用户登录 微信小程序登录是为了让开发者的服务器获取用户的o
  • MySQL主从复制实现读写分离

    导航 黑马Java笔记 踩坑汇总 JavaSE JavaWeb SSM SpringBoot 瑞吉外卖 SpringCloud SpringCloudAlibaba 黑马旅游 谷粒商城 目录 读写分离 1 1 多台数据库 1 2 MySQL
  • Java集合框架图析(Collection-List)

    Java集合框架图析 Collection List 前言 Java 集合 也称作容器 主要是由两大接口 Interface 派生出来的 Collection 和 Map 顾名思义 容器就是用来存放数据的 那么这两大接口的不同之处在于 Co