java中List与AbstractList

news2024/11/24 4:04:46

一、List 接口

List 接口继承了 Collection 接口,在 Collection 接口的基础上增加了一些方法。相对于 Collection 接口,我们可以很明显的看到,List 中增加了非常多根据下标操作集合的方法,我们可以简单粗暴的分辨一个方法的抽象方法到底来自 Collection 还是 List:参数里有下标就是来自 List,没有就是来自 Collection。

可以说,List 接口在 Collection 的基础上,进一步明确了 List 集合运允许根据下标快速存取的特性

1.新增的方法

  • get():根据下标获取指定元素;
  • replaceAll():参数一个函数式接口UnaryOperator<E>,这个方法允许我们通过传入的匿名实现类的方法去对集合中的每一个类做一些处理以后再放回去;
  • sort():对集合中的数据进行排序。参数是 Comparator<? super E>,这个参数让我们传入一个比较的匿名方法,用于数组排序;
  • set():用指定的元素替换集合中指定位置的元素;
  • indexOf():返回指定元素在此列表中首次出现的索引;如果此列表不包含该元素,则返回-1;
  • lastIndexOf():返回指定元素在此列表中最后一次出现的索引,否则返回-1;
  • listIterator():这个是个多态的方法。无参的 listIterator()用于获取迭代器,而有参的 listIterator()可以传入下标,从集合的指定位置开始获取迭代器。指定的索引指示首次调用next将返回的第一个元素。
  • subList():返回此列表中指定的两个指定下标之间的集合的视图。注意,这里说的是视图,因而对视图的操作会影响到集合,反之亦然。

2.同名的新方法

  • add():添加元素。List 中的 add() 参数的(int,E),而 Collection 中的 add() 参数是 E,因此 List 集合中同时存在指定下标和不指定下标两种添加方式
  • remove():删除指定下标的元素。注意,List 的 remove() 参数是 int ,而 Collection 中的 “remove()` 参数是 Objce,也就是说,List 中同时存在根据元素是否相等和根据元素下标删除元素两种方式

3.重写的方法

  • spliterator():List 接口重写了 Collection 接口的默认实现,换成了根据顺序的分割。

二、AbstractList 抽象类

AbstractList 类是一个继承了 AbstractCollection 类并且实现了 List 接口的抽象类,它相当于在 AbstractCollection 后的第二层方法模板。是对 List 接口的初步实现,同时也是 Collection 的进一步实现。

我们可以根据 JavaDoc 简单的了解一下它:

此类提供List接口的基本实现,以最大程度地减少实现由“随机访问”(例如数组)支持的此接口所需的工作。 对于顺序访问数据(例如链表),应优先使用AbstractSequentialList代替此类。 要实现不可修改的列表,程序员只需要扩展此类并为get(int)和size()方法提供实现即可。 要实现可修改的列表,程序员必须另外重写set(int, E)方法(否则将抛出UnsupportedOperationException )。 如果列表是可变大小的,则程序员必须另外重写add(int, E)和remove(int)方法。 不像其他的抽象集合实现,程序员不必提供迭代器实现; 迭代器和列表迭代器由此类在“随机访问”方法之上实现: get(int) , set(int, E) , add(int, E)和remove(int) 。

1.不支持的实现与抽象方法

可以直接通过下标操作的set()add()remove()都是 List 引入的新接口,这些都 AbstractList 都不支持,要使用必须由子类重写。

get()由于不能确定子类是链表还是数组,所以此时get()仍然强制要求子类去实现。

abstract public E get(int index);

public E set(int index, E element) {
    throw new UnsupportedOperationException();
}
public void add(int index, E element) {
    throw new UnsupportedOperationException();
}
public E remove(int index) {
    throw new UnsupportedOperationException();
}

复制

2.内部类们

跟 AbstractCollection 类不同,AbstractList 拥有几个特别的内部类,他们分别的迭代器类:Itr 和 ListItr,对应获取他们的方法是:

  • iterator():获取 Itr 迭代器类;
  • listIterator():获取 ListItr 迭代器类。这是个多态方法,可以选择是否从指定下标开始,默认从下标为0的元素开始迭代;

视图类 SubList 和 RandomAccessSubList:

  • subList():获取视图类,会自动根据实现类是否继承 RandomAccess 而返回 SubList 或 RandomAccessSubList。

这些内部类同样被一些其他的方法所依赖,所以要全面的了解 AbstractList 方法的实现,就需要先了解这些内部类的作用和实现原理。

三、subList方法与内部类

subList()算是一个比较常用的方法了,在 List 接口的规定中,这个方法应该返回一个当前集合的一部分的视图:

public List<E> subList(int fromIndex, int toIndex) {
    // 是否是实现了RandomAccess接口的类
    return (this instanceof RandomAccess ?
            // 是就返回一个可以随机访问的内部类RandomAccessSubList
            new RandomAccessSubList<>(this, fromIndex, toIndex) :
            // 否则返回一个普通内部类SubList
            new SubList<>(this, fromIndex, toIndex));
}

复制

这里涉及到 RandomAccessSubList 和 SubList 这个内部类,其中,RandomAccessSubList 类是 SubList 类的子类,但是实现了 RandomAccess 接口。

1.SubList 内部类

我们可以简单的把 SubList 和 AbstractList 理解为装饰器模式的一种实现,就像 SynchronizedList 和 List 接口的实现类一样。SubList 内部类通过对 AbstractList 的方法进行了再一次的封装,把对 AbstractList 的操作转变为了对 “视图的操作”。

通过对原有的 AbstractList 进行包装,将原本对 AbstractList 操作的方法改为了对 SubList 的操作的方法,是适配器模式思想的一种体现。

我们先看看 SubList 这个类的成员变量和构造方法:

class SubList<E> extends AbstractList<E> {
    // 把外部类AbstractList作为成员变量
    private final AbstractList<E> l;
    // 表示视图的起始位置(偏移量)
    private final int offset;
    // SubList视图的长度
    private int size;

    SubList(AbstractList<E> list, int fromIndex, int toIndex) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > list.size())
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                               ") > toIndex(" + toIndex + ")");
        // 获取外部类的引用
        // 这也是为什么操作视图或者外部类都会影响对方的原因,因为都操作内存中的同一个实例
        l = list;
        // 获取当前视图在外部类中的起始下标
        offset = fromIndex;
        // 当前视图的长度就是外部类截取的视图长度
        size = toIndex - fromIndex;
        this.modCount = l.modCount;
    }
    
}

复制

我们可以参考图片理解一下:

然后 subList 里面的方法就很好理解了:

public E set(int index, E element) {
    // 检查下标是否越界
    rangeCheck(index);
    // 判断是存在并发修改
    checkForComodification();
    // 把元素添加到偏移量+视图下标的位置
    return l.set(index+offset, element);
}

复制

其他方法都差不多,这里便不再多费笔墨了。

2.RandomAccessSubList 内部类

然后是 SubList 的子类 RandomAccessSubList:

class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
    RandomAccessSubList(AbstractList<E> list, int fromIndex, int toIndex) {
        super(list, fromIndex, toIndex);
    }

    public List<E> subList(int fromIndex, int toIndex) {
        return new RandomAccessSubList<>(this, fromIndex, toIndex);
    }
}

复制

我们可以看见,他实际上还是 SubList,但是实现了 RandomAccess 接口。关于这个接口,其实只是一个标记,实现了该接口的类可以实现快速随机访问(下标),通过 for 循环+下标取值会比用迭代器更快。

Vector 和 ArrayList 都实现了这个接口,而 LinkedList 没有。专门做此实现也是为了在实现类调用的 subList()方法时可以分辨这三者。

四、iterator方法与内部类

在 AbstractList 里面,为我们提供了 Itr 和 ListItr 两种迭代器。

迭代器是 AbstractList 中很重要的一块内容,他是对整个接口体系的顶层接口,也就是 Iterable 接口中的 iterator() 方法的实现,源码中的很多涉及遍历的方法,都离不开内部实现的迭代器类。

1.迭代器的 fast-fail 机制

我们知道,AbstractList 默认是不提供线程安全的保证的,但是为了尽可能的避免并发修改对迭代带来的影响,JDK 引入一种 fast-fail 的机制,即如果检测的发生并发修改,就立刻抛出异常,而不是让可能出错的参数被使用从而引发不可预知的错误。

对此,AbstractList 提供了一个成员变量 modCount,JavaDoc 是这么描述它的:

已对该列表进行结构修改的次数。 结构修改是指更改列表大小或以其他方式干扰列表的方式,即正在进行的迭代可能会产生错误的结果。该字段由iterator和listIterator方法返回的迭代器和列表迭代器实现使用。如果此字段的值意外更改,则迭代器(或列表迭代器)将抛出ConcurrentModificationException,以响应下一个,移除,上一个,设置或添加操作。 面对迭代期间的并发修改,这提供了快速失败的行为,而不是不确定的行为。 子类对此字段的使用是可选的。如果子类希望提供快速失败的迭代器(和列表迭代器),则只需在其add(int,E)和remove(int)方法(以及任何其他覆盖该方法导致结构化的方法)中递增此字段即可)。 一次调用add(int,E)或remove(int)不得在此字段中添加不超过一个,否则迭代器(和列表迭代器)将抛出虚假的ConcurrentModificationExceptions。 如果实现不希望提供快速失败迭代器,则可以忽略此字段。

这个时候我们再回去看看迭代器类 Itr 的一部分代码,可以看到:

private class Itr implements Iterator<E> {
    // 迭代器认为后备列表应该具有的modCount值。如果违反了此期望,则迭代器已检测到并发修改。
    int expectedModCount = modCount;
    
    // 检查是否发生并发操作
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

复制

结合代码,我们就不难理解这个 fast-fail 机制是怎么实现的了:

AbstractList 提供了一个成员变量用于记录对集合结构性修改的次数,如果子类希望实现并发修改错误的检查,就需要结构性操作的方法里让modCount+1。这样。在获取迭代器以后,迭代器内部会获取当前的modCount赋值给expectedModCount

当使用迭代器迭代的时候,每一次迭代都会检测modCountexpectedModCount是否相等。如果不相等,说明迭代器创建以后,集合结构被修改了,这个时候再去进行迭代可能会出现错误(比如少遍历一个,多遍历一个),因此检测到后会直接抛出 ConcurrentModificationException异常。

ListItr 继承了 Itr ,因此他们都有一样的 fast-fail机制。

值得一提的是,对于启用了 fast-fail 机制的实现类,只有使用迭代器才能边遍历边删除,原因也是因为并发修改检测:

2.Itr 迭代器

现在,回到 Itr 的代码上:

private class Itr implements Iterator<E> {
    // 后续调用next返回的元素索引
    int cursor = 0;

    // 最近一次调用返回的元素的索引。如果通过调用remove删除了此元素,则重置为-1。
    int lastRet = -1;

    // 迭代器认为后备列表应该具有的modCount值。如果违反了此期望,则迭代器已检测到并发修改。
    int expectedModCount = modCount;
	
    public boolean hasNext() {
        return cursor != size();
    }

    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }
	
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

复制

迭代方法

除了并发修改检测外,迭代器迭代的方式也出乎意料。我们可以看看 hasNext()方法:

public E next() {
    // 检验是否发生并发修改
    checkForComodification();
    try {
        int i = cursor;
        E next = get(i);
        lastRet = i;
        cursor = i + 1;
        return next;
    } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

复制

这个逻辑其实跟链表的遍历是一样的,只不过指针变成了数组的下标。以链表的方式去理解:

我们把循环里调用next()之后的节点叫做下一个节点,反正称为当前节点。假如现在有 a,b,c 三个元素:

  • 当初始化的时候,指向最后一次操作的的节点的指针 lastRet=-1,即当前节点不存在,当前游标 cursor=0,即指向下一个节点 a;
  • 当开始迭代的时候,把游标的值赋给临时指针 i,然后通过游标获取并返回下一个节点 a,再把游标指向 a 的下一个节点 b,此时 cursor=1lastRet=-1i=1
  • 接着让lastRet=i,也就是当前指针指向新的当前节点 a,现在 lastRet=0cursor=1`,完成了对第一个节点 a 的迭代;
  • 重复上述过程,把节点中的每一个元素都处理完。

现在我们知道了迭代的方式,cursorlastRet 的作用,也就不难理解 remove()方法了:

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        // 调用删除方法
        AbstractList.this.remove(lastRet);
        if (lastRet < cursor)
		   // 因为删除了当前第i个节点,所以i+1个节点就会变成第i个节点,
            // 调用next()以后cursor会+1,因此如果不让cursor-1,就会,next()以后跳过原本的第i+1个节点
            // 拿上面的例子来说,你要删除abc,但是在删除a以后会跳过b直接删除c
            cursor--;
        // 最近一个操作的节点被删除了,故重置为-1
        lastRet = -1;
        // 因为调用了外部类的remove方法,所以会改变modCount值,迭代器里也要获取最新的modCount
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
    }
}

复制

至于hasNext()方法没啥好说的,如果 cursor已经跟集合的长度一样长了,说明就已经迭代到底了。

2.ListItr 迭代器

ListItr 继承了 Itr 类,并且实现了 ListIterator 接口。其中,ListIterator 接口又继承了 Iterator 接口。他们的类关系图是这样的:

ListIterator 接口在 Iterator 接口的基础上,主要提供了六个新的抽象方法:

  • hasPrevious():是否有前驱节点;
  • previous():向前迭代;
  • nextIndex():获取下一个元素的索引;
  • previousIndex():返回上一个元素的索引;
  • set():替换元素;
  • add():添加元素;

可以看出来,实现了 ListIterator 的 ListItr 类要比 Itr 更加强大,不但可以向后迭代,还能向前迭代,还可以在迭代过程中更新或者添加节点。

private class ListItr extends Itr implements ListIterator<E> {
    // 可以自己设置迭代的开始位置
    ListItr(int index) {
        cursor = index;
    }
	
    // 下一节点是否就是第一个节点
    public boolean hasPrevious() {
        return cursor != 0;
    }

    public E previous() {
        // 检查并发修改
        checkForComodification();
        try {
            // 让游标指向当前节点
            int i = cursor - 1;
            // 使用AbstractList的get方法获取当前节点
            E previous = get(i);
            lastRet = cursor = i;
            return previous;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }
	
    // 获取下一节点的下标
    public int nextIndex() {
        return cursor;
    }

    // 获取当前节点(下一个节点的上一个节点)的下标
    public int previousIndex() {
        return cursor-1;
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            AbstractList.this.set(lastRet, e);
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            // 往下一个节点的位置添加新节点
            AbstractList.this.add(i, e);
            lastRet = -1;
            cursor = i + 1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

复制

这里比较不好理解的是下一节点还有当前节点这个概念,其实可以这么理解:cursor游标指定的必定是下一次 next()操作要得到的节点,因此cursor在操作前或者操作后指向的必定就是下一节点,因此相对下一节点,cursor其实就是当前节点,相对下一节点来说就是上一节点。

也就是说,假如现在有 a,b,c 三个元素,现在的 cursor 为2,也就是指向 b。调用 next()以后游标就会指向 c,而调用previous()以后游标又会指回 b。

至于lastRet这个成员变量只是用于记录最近一次操作的节点是哪个,跟方向性是无关。

五、AbstractList 实现的方法

1.add

注意,现在现在 AbstractList 的 add(int index, E e)仍然还不被支持,add(E e)只是定义了通过 add(int index, E e)把元素添加到队尾的逻辑。

// 不指定下标的add,默认逻辑为添加到队尾
public boolean add(E e) {
    add(size(), e);
    return true;
}

复制

关于 AbstractList 和 AbstractCollection 中 add()方法之间的关系是这样的:

AbstractList 这里的 add(E e)就非常有模板方模式提到的“抽象类规定算法骨架”这个感觉了。AbstractCollection 接口提供了 add(E e)的初步实现(尽管只是抛异常),然后到了 AbstractList 中就完善了 add(E e)方法的逻辑——通过调用 add(int index,E e)方法把元素插到队尾,但是具体的 add(int index,E e)怎么实现再交给子类决定。

2.indexOf/LastIndexOf

public int indexOf(Object o) {
    ListIterator<E> it = listIterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return it.previousIndex();
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return it.previousIndex();
    }
    return -1;
}

public int lastIndexOf(Object o) {
    ListIterator<E> it = listIterator(size());
    if (o==null) {
        while (it.hasPrevious())
            if (it.previous()==null)
                return it.nextIndex();
    } else {
        while (it.hasPrevious())
            if (o.equals(it.previous()))
                return it.nextIndex();
    }
    return -1;
}

复制

3.addAll

这里的addAll来自于List 集合的 addAll。参数是需要合并的集合跟起始下标:

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    boolean modified = false;
    for (E e : c) {
        add(index++, e);
        modified = true;
    }
    return modified;
}

复制

这里的 rangeCheckForAdd()方法是一个检查下标是否越界的方法:

private void rangeCheckForAdd(int index) {
    // 不得小于0或者大于集合长度
    if (index < 0 || index > size())
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

复制

4.removeRange

这个方法是 AbstractList 私有的方法,一般被子类用于删除一段多个元素,实现上借助了 ListIter 迭代器。

protected void removeRange(int fromIndex, int toIndex) {
    ListIterator<E> it = listIterator(fromIndex);
    // 从fromIndex的下一个开始,删到toIndex
    for (int i=0, n=toIndex-fromIndex; i<n; i++) {
        it.next();
        it.remove();
    }
}

复制

六、AbstractList 重写的方法

1.equals

equals()方法比较特殊,他是来自于 Collection 和 List 接口中的抽象方法,在 AbstractList 得中实现,但是实际上也是对 Object 中方法的重写。考虑到 equals()情况特殊,所以我们也认为它是一个重写的方法。

我们可以先看看 JavaDoc 是怎么说的:

比较指定对象与此列表是否相等。当且仅当指定对象也是一个列表,并且两个列表具有相同的大小,并且两个列表中所有对应的元素对相等时,才返回true

然后再看看源码是什么样的:

public boolean equals(Object o) {
    // 是否同一个集合
    if (o == this)
        return true;
    // 是否实现了List接口
    if (!(o instanceof List))
        return false;
	
    // 获取集合的迭代器并同时遍历
    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        // 两个集合中的元素是否相等
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    // 是否两个集合长度相同
    return !(e1.hasNext() || e2.hasNext());
}

复制

从源码也可以看出,AbstractList 的 equals() 是要求两个集合绝对相等的:顺序相等,并且相同位置的元素也要相等。

2.hashCode

hashCode()equals()情况相同。AbstractList 重新定义了 hashCode()

public int hashCode() {
    int hashCode = 1;
    for (E e : this)
        hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
    return hashCode;
}

复制

新的计算方式会获取集合中每一个元素的 hashCode 去计算集合的 hashCode,这可能是考虑到原本情况下,同一个集合哪怕装入的元素不同也会获得相同的 hashCode,可能会引起不必要的麻烦,因此重写了次方法。

我们可以写个测试看看:

List<String> list1 = new ArrayList<>();
list1.add("a");
System.out.println(list1.hashCode()); // 128
list1.add("c");
System.out.println(list1.hashCode()); // 4067

复制

七、总结

List 接口继承了 Collection 接口,新增方法的特点主要体现在可以通过下标去操作节点,可以说大部分下标可以作为参数的方法都是 List 中添加的方法。

AbstractList 是实现了 List 的抽象类,他实现了 List 接口中的大部分方法,同时他继承了 AbstractCollection ,沿用了一些 AbstractCollection 中的实现。这两个抽象类可以看成是模板方法模式的一种体现。

他提供了下标版的 add()remove()set()的空实现。

AbstractList 内部提供两个迭代器,Itr 和 ListItr,Itr 实现了 Iterator接口,实现了基本的迭代删除,而 ListItr 实现了ListIterator,在前者的基础上增加了迭代中添加修改,以及反向迭代的相关方法,并且可以从指定的位置开始创建迭代器。

AbstractList 的 SubList 可以看成 AbstractList 的包装类,他在实例化的时候会把外部类实例的引用赋值给成员变量,同名的操作方法还仍然是调用 AbstractList 的,但是基于下标的调用会在默认参数的基础上加上步长,以实现对“视图”的操作,这是适配器模式思想的一种体现。

AbstractList 引入了并发修改下 fast-fail 的机制,在内部维护一个成员变量 modelCount,默认为零,每次结构性修改都会让其+1。在迭代过程中会默认检查 modelCount是否符合预期值,否则抛出异常。值得注意的是,这个需要实现类的配合,在实现 add()等方法的时候要让 modelCount+1。对于一些实现类,在迭代中删除可能会抛出 ConcurrentModificationExceptions,就是这方面的问题。

AbstractList 重写了 hashCode()方法,不再直接获取实例的 HashCode 值,而遍历集合,根据每一个元素的 HashCode 计算集合的 HashCode,这样保证了内容不同的相同集合不会得到相同的 HashCode。

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

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

相关文章

C++——动态管理(类和对象收尾)

作者&#xff1a;几冬雪来 时间&#xff1a;2023年5月14日 内容&#xff1a;C内存管理讲解 目录 前言&#xff1a; 1.类的对象&#xff08;收尾&#xff09;&#xff1a; 1.友元函数&#xff1a; 2.内部类&#xff1a; 3.匿名对象&#xff1a; 4.优化&#xff1a; 2.…

常见基础算法

一、排序 & 查找算法 1.1 冒泡排序 相邻的数据进行比较。每次遍历找到一个最大值。 public void sort(int[] nums) {if (nums null) {return;}for (int i 0; i < nums.length; i) {for (int j 0; j < nums.length - 1 - i; j) {if (nums[j] > nums[j 1]…

Python每日一练(20230515) 只出现一次的数字 I\II\III

目录 1. 只出现一次的数字 Single Number 2. 只出现一次的数字 II Single Number II 3. 只出现一次的数字 III Single Number III &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 leetcod…

开源项目ChatGPT-website再次更新,累计下载使用1600+

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

数据交换方式(电路,报文,虚电路分组交换,数据报分组交换)

电路交换&#xff1a; 电路交换是通信网中最早出现的一种交换方式&#xff0c;在进行数据传输前&#xff0c;两个结点之间必须先建立一条专用&#xff08;双方独占&#xff09;的物理通信链路。该线路在整个数据传输期间一直被独占&#xff0c;用户始终占用端到端的固定传输带…

python实现带有操作界面的计算器程序,实现基本的数值计算,支持负数、小数、加减乘除等运算。

一、程序要求 python实现带有操作界面的计算器程序,实现基本的数值计算,支持负数、小数、加减乘除等运算。 预期计算器界面如下: 二、代码实现 1、python3自带tkinter,可以用来做可视化界面: import tkinter as tk import re 2、新建窗口对象,设置高宽、设置标题和背景…

【分布族谱】正态分布和对数正态分布的关系

文章目录 正态分布对数正态分布的推导测试 正态分布 正态分布&#xff0c;最早由棣莫弗在二项分布的渐近公式中得到&#xff0c;而真正奠定其地位的&#xff0c;应是高斯对测量误差的研究&#xff0c;故而又称Gauss分布。。测量是人类定量认识自然界的基础&#xff0c;测量误差…

UEFI 界面实例解析

这篇文章主要记录一些setup界面的实例&#xff0c;这些实例都是EDK上的&#xff0c;我们可以看到如下图&#xff1a; 上面三个为banner&#xff0c;下面的都是通过LABLE动态加载的&#xff0c;代码如下&#xff1a; 我们可以看到 UiListThirdPartyDrivers (HiiHandle, &gEf…

Sentinel 熔断降级和黑白名单控制

一、熔断降级 1、概述 除了流量控制以外&#xff0c;对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块&#xff0c;可能是另外的一个远程服务、数据库&#xff0c;或者第三方 API 等。例如&#xff0c;支付的时候&#xff0c;…

【C++ 入坑指南】(06)运算符

文章目录 一、算术运算符二、赋值运算符三、比较运算符四、逻辑运算符五、算法题5.1、拆分位数 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 内置了丰富的运算符&#xff0c;并提供了以下类型的运算符&#xff1a; 运算符类型作用算术运算符用于处理四则运算赋值…

交换机配置第十二讲(ACL访问控制)

1.实验介绍 设备规划 类型名称数量终端PC3路由器AR22403 IP规划 主机 ip链接交换机端口网关client1192.168.1.2AR1-g/0/0/0192.168.1.1client2192.168.2.2AR2-g/0/0/1192.168.2.1client3192.168.3.2AR3-g/0/0/1192.168.3.1 2. 连线图介绍 连线顺序 3. 基础配置介绍 我们首…

基于SSM的高校共享单车管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

〖大前端 - 基础入门三大核心之JS篇㉞〗- JavaScript 的「立即执行函数IIFE」

当前子专栏 基础入门三大核心篇 是免费开放阶段。推荐他人订阅&#xff0c;可获取扣除平台费用后的35%收益&#xff0c;文末名片加V&#xff01;说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费开放&#xff0c;购买任意白宝书体系化专栏可加入TFS…

Threejs进阶之十四:在uniapp中使用threejs创建三维图形

在uniapp中使用threejs 一、uni-app介绍二、新建uni-app项目三、安装three.js库四、在vue组件中引入three.js库五、创建场景(Scene)和相机(Camera)六、创建渲染器(Renderer)七、创建物体和灯光八、渲染场景(Scene)九、运行测试核心代码 一、uni-app介绍 uni-app是一个基于Vue.…

AutoSar CanNm笔记

文章目录 网络管理目的CanNM与其他模块之间关系主动唤醒和被动唤醒状态管理1. 总线睡眠模式&#xff08;Bus-Sleep Mode&#xff09;2. 准备总线睡眠模式&#xff08;Prepare Bus-Sleep Mode&#xff09;3. 网络模式&#xff08;Network Mode&#xff09;3.1 重复报文状态(Repe…

SD-如何训练自己的Lora模型

官方地址&#xff1a;GitHub - bmaltais/kohya_ss 尝试过mac和Ubuntu&#xff0c;装上后都会有问题 Windows按照官方步骤安装即可 第一步 git clone https://github.com/bmaltais/kohya_ss.git cd kohya_sspython -m venv venv .\venv\Scripts\activatepip install torch1.…

1710_开源pdf阅读器SumatraPDF使用体验

全部学习汇总&#xff1a; GreyZhang/g_GNU: After some years I found that I do need some free air, so dive into GNU again! (github.com) 被很多国产免费软件折腾的电脑有点扛不住了&#xff0c;从昨天起打算在Windows上开始开源之路。先用LibreOffice换掉了之前一直觉得…

ansible roles常用用法

目录 一、说明 二、创建 ansible 环境 三、实验操作 四、install_ansible.sh 脚本内容 一、说明 该文档是日常经常使用的模板&#xff0c;通过该例子让更多的初学者了解ansible 剧本的写法&#xff0c;很多情况&#xff0c;可以按照该模版来套用即可。 读者不需要下载…

GPT前2代版本简介

承接上文ChatGPT进化的过程简介 2018年&#xff0c;Google的Bert和OpenAI的GPT绝代双骄&#xff0c;两者非常像&#xff0c;都是语言模型&#xff0c;都基本上是无监督的方式去训练的&#xff0c;你给我一个文本&#xff0c;我给你一个语言模型出来。 GPT前两代没有什么特别的…

好看的皮囊千篇一律,有趣的书籍万里挑一,学习Java必读的两款书籍推荐

今天给各位学习Java的小伙伴儿们推荐两本Java路线上必不可少的书籍&#xff0c;核心卷1和卷2&#xff0c;大家可根据自己的情况种草。正所谓&#xff0c;书多不压身。 Java核心技术卷1 Java 诞生 27 年来&#xff0c;这本享誉全球的 Java 经典著作《Core Java》一路伴随着 J…