满地坑,细数List的10个坑

满地坑!细数List的10个坑 今天我们主要来说一说List操作在实际使用中有哪些坑,以及面对这些坑的时候我们要怎么解决。由于篇幅的限制,我们只对一些在业务开发中常见的关键点进行梳理和介绍在实际的工作中,我们不单单是要清除不同类型容器的特性,还要选择适合的容器才能做到事半功倍。我们主要介绍了Arrays.asList转换过程中的一些坑,以及因为操作不当造成的OOM和异常,到最后介绍了线程安全类CopyOnWriteArrayList的一些坑,让我们认识到在丰富的API下藏着许多的陷阱。 阅读详情

满地坑!细数List的10个坑

大家好,我是胖虎,今天我们主要来说一说List操作在实际使用中有哪些坑,以及面对这些坑的时候我们要怎么解决。

1. Arrays.asList转换基本类型数组的坑

在实际的业务开发中,我们通常会进行数组转List的操作,通常我们会使用Arrays.asList来进行转换

但是在转换基本类型的数组的时候,却出现转换的结果和我们想象的不一致。

上代码

int[] arr = {1, 2, 3}; 
List list = Arrays.asList(arr); 
System.out.println(list.size()); 
// 1

实际上,我们想要转成的List应该是有三个对象而现在只有一个

public static List asList(T... a) { 
    return new ArrayList<>(a); 
}

可以观察到 asList方法 接收的是一个泛型T类型的参数,T继承Object对象

所以通过断点我们可以看到把 int数组 整体作为一个对象,返回了一个 List<int[]>

image1.png

那我们该如何解决呢?

方案一:Java8以上,利用Arrays.stream(arr).boxed()将装箱为Integer数组

List collect = Arrays.stream(arr).boxed().collect(Collectors.toList()); System.out.println(collect.size()); 
System.out.println(collect.get(0).getClass()); 
// 3 
// class java.lang.Integer

方案二:声明数组的时候,声明类型改为包装类型

Integer[] integerArr = {1, 2, 3}; 
List integerList = Arrays.asList(integerArr); 
System.out.println(integerList.size()); System.out.println(integerList.get(0).getClass()); 
// 3 
// class java.lang.Integer

2. Arrays.asList返回的List不支持增删操作

我们将数组对象转成List数据结构之后,竟然不能进行增删操作了

private static void asListAdd(){
    String[] arr = {"1", "2", "3"};
    List<String> strings = new ArrayList<>(Arrays.asList(arr));
    arr[2] = "4";
    System.out.println(strings.toString());
    Iterator<String> iterator = strings.iterator();
    while (iterator.hasNext()){
        if ("4".equals(iterator.next())){
            iterator.remove();
        }
    }
    strings.forEach(val ->{
        strings.remove("4");
        strings.add("3");
    });


    System.out.println(Arrays.asList(arr).toString());
}

[1, 2, 4] 
Exception in thread "main" java.lang.UnsupportedOperationException at java.util.AbstractList.remove(AbstractList.java:161) at java.util.AbstractList$Itr.remove(AbstractList.java:374) at java.util.AbstractCollection.remove(AbstractCollection.java:293) at JavaBase.List.AsListTest.lambda$asListAdd$0(AsListTest.java:47) at java.util.Arrays$ArrayList.forEach(Arrays.java:3880) at JavaBase.List.AsListTest.asListAdd(AsListTest.java:46) at JavaBase.List.AsListTest.main(AsListTest.java:20)

初始化一个字符串数组,将字符串数组转换为 List,在遍历List的时候进行移除和新增的操作

抛出异常信息UnsupportedOperationException。

根据异常信息java.lang.UnsupportedOperationException,我们看到他是从AbstractList里面出来的,让我们进入源码一看究竟

我们在什么时候调用到了这个 AbstractList 呢?

其实 Arrays.asList(arr) 返回的 ArrayList 不是 java.util.ArrayList,而是 Arrays的内部类

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable{
    private static final long serialVersionUID = -2764017481108945198L;
    private final E[] a;
    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }

    @Override
    public E get(int index) {}

    @Override
    public E set(int index, E element) {...}

...
}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    public boolean add(E e) {
        add(size(), e);
        return true;
    }
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

}

他是没有实现 AbstractList 中的 add() 和 remove() 方法,这里就很清晰了为什么不支持新增和删除,因为根本没有实现。

3. 对原始数组的修改会影响到我们获得的那个List

一不小心修改了父List,却影响到了子List,在业务代码中,这会导致产生的数据发生变化,严重的话会造成影响较大的生产问题。

第二个坑的源码中,完成字符串数组转换为List之后,

我们将字符串数组的第三个对象的值修改为4,但是很奇怪在打印List的时候,发现List也发生了变化。

public static <T> List<T> asList(T... a) {

    return new ArrayList<>(a);

}

ArrayList(E[] array) {
    a = Objects.requireNonNull(array);
}

asList中创建了 ArrayList,但是他直接引用了原本的数组对象

所以只要原本的数组对象一发生变化,List也跟着变化

所以在使用到引用的时候,我们需要特别的注意。

解决方案:

重新new一个新的 ArrayList 来装返回的 List

List strings = new ArrayList<>(Arrays.asList(arr));

4. java.util.ArrayList如果不正确操作也不支持增删操作

在第二个坑的时候,我们说到了 Arrays.asList 返回的 List 不支持增删操作,

是因为他的自己实现了一个内部类 ArrayList,这个内部类继承了 AbstractList 没有实现 add() 和 remove() 方法导致操作失败。

但是第三个坑的时候,我们利用 java.util.ArrayList 包装了返回的 List,进行增删操作还是会失败,那是为什么呢?

删除方法逻辑:

删除方法.png

在foreach中操作增删,因为因为 modCount 会被修改,与第一步保存的数组修改次数不一致,抛出异常 ConcurrentModificationException

在正确操作是什么?我总结了四种方式

正确操作.png

5. ArrayList中的 subList 强转 ArrayList 导致异常

阿里《Java开发手册》上提过

★

[强制] ArrayList的sublist结果不可強转成ArrayList,否则会抛出ClassCastException

异常,即java.util.RandomAccesSubList cannot be cast to java. util.ArrayList.

说明: subList 返回的是ArrayList 的内部类SubList, 并不是ArrayList ,而是

ArrayList的一个视图,対于SubList子列表的所有操作最终会反映到原列表上。

”
private static void subListTest(){

    List<String> names = new ArrayList<String>() {{

    add("one");

    add("two");

    add("three");

}};
    ArrayList strings = (ArrayList) names.subList(0, 1);
    System.out.println(strings.toString());
}

Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

我猜问题是有八九就是出现在subList这个方法上了

private class SubList extends AbstractList<E> implements RandomAccess {

    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;
    SubList(AbstractList<E> parent,

    int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset + fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}
}

其实 SubList 是一个继承 AbstractList 的内部类,在 SubList 的构建函数中的将 List 中的部分属性直接赋予给自己

SubList 没有创建一个新的 List,而是直接引用了原来的 List(this.parent = parent),指定了元素的范围

所以 subList 方法不能直接转成 ArrayList,他只是ArrayList的内部类,没有其他的关系

因为是引用的关系,所以在这里也需要特别的注意,如果对原来的List进行修改,会对产生的 subList结果产生影响。

List<String> names = new ArrayList<String>() {{
    add("one");
    add("two");
    add("three");
}};

List strings = names.subList(0, 1);

strings.add(0, "ongChange");

System.out.println(strings.toString());

System.out.println(names.toString());

[ongChange, one]

[ongChange, one, two, three]

对subList产生的List做出结构型修改,操作会反应到原来的List上,ongChange也添加到了names中

如果修改原来的List则会抛出异常ConcurrentModificationException

List<String> names = new ArrayList<String>() {{

    add("one");
    add("two");
    add("three");

}};

List strings = names.subList(0, 1);

names.add("four");

System.out.println(strings.toString());

System.out.println(names.toString());

Exception in thread "main" java.util.ConcurrentModificationException

原因:

subList的时候记录this.modCount为3

ConcurrentModificationException1.png

原来的List插入了一个新元素,导致this.modCount不第一次保存的不一致则抛出异常

解决方案:在操作SubList的时候,new一个新的ArrayList来接收创建subList结果的拷贝

List strings = new ArrayList(names.subList(0, 1));

6. ArrayList中的subList切片造成OOM

在业务开发中的时候,他们经常通过subList来获取所需要的那部分数据

在上面的例子中,我们知道了subList所产生的List,其实是对原来List对象的引用

这个产生的List只是原来List对象的视图,也就是说虽然值切片获取了一小段数据,但是原来的List对象却得不到回收,这个原来的List对象可能是一个很大的对象

为了方便我们测试,将vm调整一下 -Xms20m -Xmx40m

private static void subListOomTest(){

IntStream.range(0, 1000).forEach(i ->{

List<Integer> collect = IntStream.range(0, 100000).boxed().collect(Collectors.toList());

data.add(collect.subList(0, 1));

});

}}

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

出现OOM的原因,循环1000次创建了1000个具有10万个元素的List

因为始终被collect.subList(0, 1)强引用,得不到回收

解决方式:

  1. 在subList方法返回SubList,重新使用new ArrayList,来构建一个独立的ArrayList

List list = new ArrayList<>(collect.subList(0, 1));

  1. 利用Java8的Stream中的skip和limit来达到切片的目的

List list = collect.stream().skip(0).limit(1).collect(Collectors.toList());

在这里我们可以看到,只要用一个新的容器来装结果,就可以切断与原始List的关系

7. LinkedList的插入速度不一定比ArrayList快

学习数据结构的时候,我们就已经得出了结论

●对于数组,随机元素访问的时间复杂度是0(1), 元素插入操作是O(n);

●对于链表,随机元素访问的时间复杂度是O(n), 元素插入操作是0(1).

元素插入对于链表来说应该是他的优势

但是他就一定比数组快? 我们执行插入1000w次的操作

private static void test(){
    StopWatch stopWatch = new StopWatch();
    int elementCount = 100000;
    stopWatch.start("ArrayList add");
    List<Integer> arrayList = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
    // ArrayList插入数据
    IntStream.rangeClosed(0, elementCount).forEach(i ->arrayList.add(ThreadLocalRandom.current().nextInt(elementCount), 1));
    stopWatch.stop();

    stopWatch.start("linkedList add");
    List<Integer> linkedList = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
    // ArrayList插入数据
    IntStream.rangeClosed(0, elementCount).forEach(i -> linkedList.add(ThreadLocalRandom.current().nextInt(elementCount), 1));
    stopWatch.stop();
    System.out.println(stopWatch.prettyPrint());
}

StopWatch '': running time = 44507882 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
043836412  098%  elementCount 100 ArrayList add
000671470  002%  elementCount 100 linkedList add

StopWatch '': running time = 196325261 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
053848980  027%  elementCount 10000 ArrayList add
142476281  073%  elementCount 10000 linkedList add

StopWatch '': running time = 26384216979 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
978501580  004%  elementCount 100000 ArrayList add
25405715399  096%  elementCount 100000 linkedList add

看到在执行插入1万、10完次操作的时候,LinkedList的插入操作时间是 ArrayList的两倍以上

那问题主要就是出现在linkedList的 add()方法上

public void add(int index, E element) {

    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}
    
/**
* 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;
    }
}

linkedList的 add()方法主要逻辑

  1. 通过遍历找到那个节点的Node

  2. 执行插入操作

ArrayList的 add()方法

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++;
}
复制代码
  1. 计算最小容量

  2. 最小容量大于数组对象,则进行扩容

  3. 进行数组复制,根据插入的index将数组向后移动一位

  4. 最后在空位上插入新值

根据试验的测试,我们得出了在实际的随机插入中,LinkedList并没有比ArrayList的速度快

所以在实际的使用中,如果涉及到头尾对象的操作,可以使用LinkedList数据结构来进行增删的操作,发挥LinkedList的优势

最好再进行实际的性能测试评估,来得到最合适的数据结构。

8. CopyOnWriteArrayList内存占用过多

CopyOnWrite,顾名思义就是写的时候会将共享变量新复制一份出来,这样做的好处是读操作完全无锁。

CopyOnWriteArrayList的add()方法

public boolean add(E e) {
    // 获取独占锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 获取array
        Object[] elements = getArray();
        // 复制array到新数组,添加元素到新数组
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        // 替换数组
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

CopyOnWriteArrayList 内部维护了一个数组,成员变量 array 就指向这个内部数组,所有的读操作都是基于新的array对象进行的。

因为上了独占锁,所以如果多个线程调用add()方法只有一个线程会获得到该锁,其他线程被阻塞,知道锁被释放, 由于加了锁,所以整个操作的过程是原子性操作

CopyOnWriteArrayList 会将 新的array复制一份,然后在新复制处理的数组上执行增加元素的操作,执行完之后再将复制的结果指向这个新的数组。

由于每次写入的时候都会对数组对象进行复制,复制过程不仅会占用双倍内存,还需要消耗 CPU 等资源,所以当列表中的元素比较少的时候,这对内存和 GC 并没有多大影响,但是当列表保存了大量元素的时候,

对 CopyOnWriteArrayList 每一次修改,都会重新创建一个大对象,并且原来的大对象也需要回收,这都可能会触发 GC,如果超过老年代的大小则容易触发Full GC,引起应用程序长时间停顿。

9. CopyOnWriteArrayList是弱一致性的

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }

    public boolean hasNext() {
        return cursor < snapshot.length;
    }

    public boolean hasPrevious() {
        return cursor > 0;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }

调用iterator方法获取迭代器返回一个COWIterator对象

COWIterator的构造器里主要是 保存了当前的list对象的内容和遍历list时数据的下标。

snapshot是list的快照信息,因为CopyOnWriteArrayList的读写策略中都会使用getArray()来获取一个快照信息,生成一个新的数组。

所以在使用该迭代器元素时,其他线程对该lsit操作是不可见的,因为操作的是两个不同的数组所以造成弱一致性。

private static void CopyOnWriteArrayListTest(){
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
    list.add("test1");
    list.add("test2");
    list.add("test3");
    list.add("test4");
    
    Thread thread = new Thread(() -> {
        System.out.println(">>>> start");
        list.add(1, "replaceTest");
        list.remove(2);
    });
    
    // 在启动线程前获取迭代器
    Iterator<String> iterator = list.iterator();

    thread.start();

    try {
        // 等待线程执行完毕
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
}

>>>> start
test1
test2
test3
test4

上面的demo中在启动线程前获取到了原来list的迭代器,

在之后启动新建一个线程,在线程里面修改了第一个元素的值,移除了第二个元素

在执行完子线程之后,遍历了迭代器的元素,发现子线程里面操作的一个都没有生效,这里提现了迭代器弱一致性。

10. CopyOnWriteArrayList的迭代器不支持增删改

private static void CopyOnWriteArrayListTest(){
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    list.add("test1");
    list.add("test2");
    list.add("test3");
    list.add("test4");

    Iterator<String> iterator = list.iterator();

    while (iterator.hasNext()){
        if ("test1".equals(iterator.next())){
            iterator.remove();
        }
    }

    System.out.println(list.toString());
}

Exception in thread "main" java.lang.UnsupportedOperationException
 at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1178)

CopyOnWriteArrayList 迭代器是只读的,不支持增删操作

CopyOnWriteArrayList迭代器中的 remove()和 add()方法,没有支持增删而是直接抛出了异常

因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没有意义的。

/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code remove}
 *         is not supported by this iterator.
 */
public void remove() {
    throw new UnsupportedOperationException();
}

/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code set}
 *         is not supported by this iterator.
 */
public void set(E e) {
    throw new UnsupportedOperationException();
}

/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code add}
 *         is not supported by this iterator.
 */
public void add(E e) {
    throw new UnsupportedOperationException();
}

总结

由于篇幅的限制,我们只对一些在业务开发中常见的关键点进行梳理和介绍

在实际的工作中,我们不单单是要清除不同类型容器的特性,还要选择适合的容器才能做到事半功倍。

我们主要介绍了Arrays.asList转换过程中的一些坑,以及因为操作不当造成的OOM和异常,

到最后介绍了线程安全类CopyOnWriteArrayList的一些坑,让我们认识到在丰富的API下藏着许多的陷阱。

在使用的过程中,需要更加充分的考虑避免这些隐患的发生。

最后一张思维导图来回顾一下~

2023黑马Java集合相关面试题 数组(Array)是一种用连续的内存空间存储相同数据类型数据的线性数据结构。我们定义了这么一个数组之后,在内存的表示是这样的:现在假如,我们通过arrar[1],想要获得下标为1这个元素,但是现在栈内存中指向的堆内存数组的首地址,它是如何获取下标为1这个数据的?二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。二叉树每个节点的左子树和右子树也分别满足二叉树的定义。 阅读详情

相关推荐

【R语言】富集分析可视化代码(整理版)

分别介绍了如何使用DAVID在线分析工具对基因进行GO/KEGG功能富集分析、使用R ggplot包对获得的基因GO/KEGG功能富集结果进行可视化、使用R clusterProfiler包和R AnnotationHub包对基因进行GO/KEGG功能富集分析、OrgDb包制作以及结果可视化等。故,我重新整理了一波,并补充了可对多个分组的富集结果进行可视化以及富集结果绘制圈图等的代码。关注“在打豆豆的小潘学长”公众号,发送“富集分析3”获得完整代码包和演示数据。一、单组富集结果可视化。

weixin_54004950的博客 1万+

java.lang.UnsupportedOperationException 异常的解决方法及相关原因

java.lang.UnsupportedOperationException解决方法

小时候超帅的博客 3万+

别再死记硬背公式了!用ADS手把手教你仿真一个4GHz镜像抑制混频器(附完整工程文件)

本文详细介绍了使用Keysight ADS软件设计和仿真4GHz镜像抑制混频器的完整流程。从理论分析到实际工程实现,涵盖分支线耦合器设计、二极管建模、噪声系数仿真及性能优化等关键步骤,帮助工程师摆脱公式死记硬背,掌握实用的射频电路设计技能。附完整工程文件,可直接应用于实际项目开发。

weixin_28683391的博客 498

List 移除元素 报错 java.lang.UnsupportedOperationException异常信息:

总之需要对list进行remove操作 要使用 ArraysList进行初始化list 下面再介绍下 java8新的方法 removeIf 可以使代码变简洁,比如对上面的代码进行简化 只需要1行代码。:说这个异常前先说一下另一个异常java.util.ConcurrentModificationException 当我们在循环list的过程中 如果对 list进行remove操作。这个跟初始化list有关系-我的list是这样初始化的。经过查资料找到了原因。

weixin_56800842的博客 2466

验证方法、符合性方法与设计保证的关系

未来,随着适航标准和工程实践的不断演进,验证与符合性方法将持续优化,以应对日益复杂的系统设计需求。设计保证过程中的验证活动具有双重作用,既用于设计过程的质量控制和风险管理,又作为适航符合性方法的重要支持材料。在未来的航空电子系统开发中,进一步优化设计保证与验证活动的协同作用,将有助于应对日益复杂的工程挑战,推动航空安全技术的发展。为了全面理解这二者之间的关系,本章将系统说明设计保证过程中的验证活动如何既服务于设计保证,又作为符合性方法中的核心证明材料。最终,通过功能测试和环境测试验证硬件的实际性能。

qq_41854911的博客 554

ANSYS——“There is at least 1 small equation solver pivot term”问题的解决办法

ANSYS——“There is at least 1 small equation solver pivot term”问题的解决办法

python_AI_fans的博客 1万+

java.lang.UnsupportedOperationException at java.util.AbstractList.remove(AbstractList.java:161) at

由于Arrays.asList(arr)方法返回的list,不支持add,move操作,具体原因见:https://blog.csdn.net/liu_005/article/details/74091805 解决办法自己实现一个数组转list的方法: private static List&lt;String&gt; transferArrayToList(String[] array){...

weixin_40314398的博客 3717

寄存器与计数器设计(Verilog)

always @(posedge Reset or posedge Clock)//请在括号中补充敏感信号列表。always @(negedge Resetn , posedge Clock)//请在括号中补充敏感信号列表。always @(negedge Resetn,posedge Clock)//请在括号中补充敏感信号列表。//为了便于自动评测,请勿更改n的值。//为了便于自动评测,请勿更改n的值。//为了便于自动评测,请勿更改n的值。//为了便于自动评测,请勿更改n的值。

M_inherit的博客 366

满地坑!细数List的10个坑!

来源:juejin.cn/post/7143266514722881544满地坑!细数List的10个坑大家好,我是胖虎,今天我们主要来说一说List操作在实际使用中有哪些坑,以及面对这些坑的时候我们要怎么解决。1. Arrays.asList转换基本类型数组的坑在实际的业务开发中,我们通常会进行数组转List的操作,通常我们会使用Arrays.asList来进行转换但是在转换基本类型的数组的时候...

qq_14958051的博客 113

【时空图神经网络 & 交通】相关模型3:STFGNN | 空间-时间依赖性 | 基于数据驱动的方法来生成“时间图” | 时空融合图模块,门控卷积模块 | 动态时间规整 | 顶会文章解析

STFGNN(时空融合图神经网络)在交通流量预测中的优势主要体现在以下几个方面:首先,它提出了一种数据驱动的方法生成“时间图”,弥补了传统空间图可能无法反映的某些相关性。其次,STFGNN通过并行处理不同时间段的空间图和时间图,有效地学习隐藏的时空依赖关系。此外,它将融合图模块与门控卷积模块整合到一个统一层中,通过层层堆叠处理长序列,进一步增强了模型的表达能力。实验结果表明,STFGNN在多个交通数据集上表现出色,优于其他基准方法,展示了其在实际应用中的强大潜力。

追光者♂:记录、分享、总结、提升,现象级专栏《Python从入门到人工智能》作者,无惧黑暗,坚信曙光 386

16、经皮功能性电刺激助行:Parastep系统全解析

本文全面解析了经皮功能性电刺激助行系统——Parastep系统,涵盖其发展历史、技术参数、系统设计、患者准入标准、训练方案及临床效果。作为首个获得FDA批准并商业化的非侵入性FES助行系统,Parastep为胸段截瘫患者提供了独立站立与行走的可能性,并在改善下肢血流、肌肉质量、心理状态等方面展现出显著医学与心理益处。文章还对比了其他FES系统,分析了Parastep的优势与局限,并探讨了未来技术改进方向与应用拓展前景。

l3m4n的博客 110

满地坑-细数List的10个坑

大家好,我是小郭,今天我们主要来说一说List操作在实际使用中有哪些坑,以及面对这些坑的时候我们要怎么解决。由于篇幅的限制,我们只对一些在业务开发中常见的关键点进行梳理和介绍在实际的工作中,我们不单单是要清除不同类型容器的特性,还要选择适合的容器才能做到事半功倍。我们主要介绍了Arrays.asList转换过程中的一些坑,以及因为操作不当造成的OOM和异常,到最后介绍了线程安全类CopyOnWriteArrayList的一些坑,让我们认识到在丰富的API下藏着许多的陷阱。

AS011x的博客 128

Altium Designer(AD)仿真实验操作指南

1:运行仿真2:设置仿真3:放置电源模块4:放置器件模块5:探针。

XU157303764的博客 8903

满地坑!细数List的10个坑!看了都说好!

由于篇幅的限制,我们只对一些在业务开发中常见的关键点进行梳理和介绍在实际的工作中,我们不单单是要清除不同类型容器的特性,还要选择适合的容器才能做到事半功倍。我们主要介绍了Arrays.asList转换过程中的一些坑,以及因为操作不当造成的OOM和异常,到最后介绍了线程安全类CopyOnWriteArrayList的一些坑,让我们认识到在丰富的API下藏着许多的陷阱。在使用的过程中,需要更加充分的考虑避免这些隐患的发生。最后一张思维导图来回顾一下~List中的坑.png。

m0_71777195的博客 202

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

org.apache.xmlbeans.impl.values.XmlValueDisconnectedException

tableName---SHPG_XXJBQK_2017 tableName---SHPG_XWDGK_2017 tableName---SHPG_XKZYJBQK_2017 五月 08, 2018 5:13:47 下午 org.apache.catalina.core.StandardWrapperValve invoke 严重: Servlet.service() for servlet [s...

qq 117791303 1万+

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

JAVA——使用Arrays.asList()的注意事项(修改List会同步修改原数组)

Arrays.asList()方法的官方文档介绍: asList @SafeVarargs public static<T>List<T>asList(T...a) Returns a fixed-size list backed by the specified array. (Changes to the returned list "write through" to the array.) This method acts as bridge betwe...

m0_37965811的博客 1692

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

JAVA中循环删除list中元素的方法总结

印象中循环删除list中的元素使用for循环的方式是有问题的,但是可以使用增强的for循环,然后今天在使用时发现报错了,然后去科普了一下,再然后发现这是一个误区。下面就来讲一讲。。伸手党可直接跳至文末。看总结。。   JAVA中循环遍历list有三种方式for循环、增强for循环(也就是常说的foreach循环)、iterator遍历。   1、for循环遍历list for(int i...

u011298391的专栏 572

java list remove_List集合的坑,你知道吗?

学如逆水行舟,不进则退经过几年的工作经验,我发现List有很多坑,之前公司有些实习生一不小心就踩到了,所以我打算写一篇文章总结一下,希望看到这篇文章的人能不再踩到坑,代码没bug。做个快乐的程序员。迭代时删除元素 使用for-each迭代遍历时,删除集合中的元素,会报错。 private static List<String> list = new ArrayList<&...

weixin_39637924的博客 842

Java集合相关面试题

具体分析:list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层是它是进行了数组的拷贝,跟原来是元素就没啥关系了,所以即使list修改了以后,数组也不受影响。jdk1.8之前采用的是拉链法。具体分析:因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,线程一先将A移入新的链表,再将B插入到链头,由于另外一个线程的原因,B的next指向了A,所以B->A->B,形成循环。

m0_62154035的博客 1206

使用stream的skip方法进行分页处理

在日常开发过程中,将查询的数据进行分页处理是非常常见的需求,而有时候 PageHelper的 startPage 方法对查询数据进行分页后,我们需要对这数据集进行再处理,而导致分页数据丢失一部分,只能查询第一页的数据。所以我们可以先做业务处理得到数据集,最终使用 Java 8 的 stream 的 skip 方法进行分页处理。

m0_49692893的博客 1476

CopyOnWriteArrayList一个鸡肋的类

首先来看一个问题:JDK 5在java.util.concurrent里引入了ConcurrentHashMap,在需要支持高并发的场景,我们可以使用它代替HashMap。但是为什么没有ArrayList的并发实现呢?难道在多线程场景下我们只有Vector这一种线程安全的数组实现可以选择么?为什么在java.util.concurrent 没有一个类可以代替Vector呢? 有的人可能直接回答,...

赶路人儿 6341
上一篇: 答应我, 不要再用 if (obj = null) 判空了
下一篇: 程序员初学者频繁使用count(*),被组长批评后怒怼:性能并不拉垮
java小姜在线冲
博客等级 码龄4年 53粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值