Collections.reverse 代码思考-超越昨天的自己系列(13)

本文深入探讨了Collections.reverse方法的源码实现,分析了不同情况下(如ArrayList与LinkedList)的反转策略及其性能表现。同时,文章还介绍了RandomAccess接口的作用、marker interface的概念以及Iterator和ListIterator的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点进Collections.reverse的代码瞄了眼,然后就开始了一些基础知识的收集。

现在发现知道的越多,知道不知道的越多。

列几个记录下:

reverse方法源码:

 
/**
     * Reverses the order of the elements in the specified list.<p>
     *
     * This method runs in linear time.
     *
     * @param  list the list whose elements are to be reversed.
     * @throws UnsupportedOperationException if the specified list or
     *         its list -iterator does not support the <tt>set</tt> operation.
     */
    public static void reverse (List<?> list) {
        int size = list.size();
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            for ( int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else {
            ListIterator fwd = list.listIterator();
            ListIterator rev = list.listIterator(size);
            for ( int i=0, mid=list.size()>>1; i<mid; i++) {
           Object tmp = fwd.next();
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }

 

1,首先看见RandomAccess

List 实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。

将操作随机访问列表的最佳算法(如 ArrayList)应用到连续访问列表(如 LinkedList)时,可产生二次项的行为。如果将某个算法应用到连续访问列表,那么在应用可能提供较差性能的算法前,鼓励使用一般的列表算法检查给定列表是否为此接口的一个 instanceof,如果需要保证可接受的性能,还可以更改其行为。

现在已经认识到,随机和连续访问之间的区别通常是模糊的。例如,如果列表很大时,某些 List 实现提供渐进的线性访问时间,但实际上是固定的访问时间。这样的 List 实现通常应该实现此接口。实际经验证明,如果是下列情况,则 List 实现应该实现此接口,即对于典型的类实例而言,此循环:

   for (int i=0, n=list.size(); i < n; i++)
         list.get(i);
 

的运行速度要快于以下循环:

     
for (Iterator i=list.iterator(); i.hasNext(); )
         i.next();

 

2,标记接口(marker interface

  又叫Tagging Interfaces。标识接口是没有任何方法和属性的接口。标识接口不对实现它的类有任何语义上的要求,它仅仅表明实现它的类属于一个特定的类型。常见的有Serializable  Cloneable    Remote    EventListener 
你当然可以任意定义没有任何方法和属性的接口,但肯定不应该称为标识接口,因为JDK里的“标识接口”不光是“只有个名字”这么简单,更重要的是,实现这些标志接口的类,确实多了功能,尽管你看不到这些功能是怎么实现的。比如,Serializable,实现了这个接口,那这个序列化的工作,到底是谁做的那?Cloneable,实现了这个接口,并在重写的clone()方法里只是调用了一下super.clone(),就产生了一个全新的对象,要知道Object里的clone()方法是没有任何实现的,这个克隆的工作,到底是谁完成的那?JVM or Reflection,但是你看不到它们。 
拿java.io.Serializable接口作为例子来说明一下。 如果存在一个对象,它实现了java.io.Serializable接口,由于接口本身没有定义任何方法行为。所以实现接口的行为由java编译器来完成。当一个java类实现了这个接口,在编译过程中,java编译器会发现这个类的对象是属于java.io.Serializable这种类型,那么编译器就会为这个特殊的类实现序列化所要求的特殊的行为,使得该类的对象可以在不同虚拟机之间传递。 所以说,我们需要有一个标记的东西 来通知java编译器这个特殊的属性,我们就定义了标识接口。
关于标志接口的对于错,争论是有的: 
标志接口是对接口的误用,应该被避免,使用标志接口的类,都是一些相当古老的类。Java 5 加入 注解 特性后,标志接口更不会再有出现的必要。 
使用注解来标识类,方法等的特定标签更加灵活,这又是一个可以扩展学习的点。
 
3,Iterator 和 ListIterator
想到个问题,比如list个通过get获取其中元素,为什么要有迭代器呢?
Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
 

例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:
        for(int i=0; i<array.size(); i++) { ... get(i) ... } 
    客户端都必须事先知道集合的内部结构,访问代码和集合本身是紧耦合,无法将访问逻辑从集合类和客户端代码中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。
  更恐怖的是,如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须全部重写。
为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

         for(Iterator it = c.iterater(); it.hasNext(); ) { ... }

  奥秘在于客户端自身不维护遍历集合的"指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。

  客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前","向后","取当前元素"的命令,就可以间接遍历整个集合
 

ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator 没有此功能。

四、都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。
 
测试:
尝试用ArrayList 和 linkedList 来使用两种方式进行翻转操作:
一种操作是使用源码中swap的方式,一种使用ListInterator。
第一个测试结果:结果1:7656 结果2:2
可见像linkedList 这种是不肯能使用swap方式去翻转的,代码中也做了处理。链表,在使用随机访问每次耗时太长,导致这种结果。
public static void main(String[] args) {
        List list =new LinkedList();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        int size = list.size();
        long t1 = System.currentTimeMillis();
        for ( int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
            swap(list, i, j);
        long t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);//结果1
        long t3 = System.currentTimeMillis();
        ListIterator fwd = list.listIterator();
        ListIterator rev = list.listIterator(size);
        for ( int i=0, mid=list.size()>>1; i<mid; i++) {
            Object tmp = fwd.next();
            fwd.set(rev.previous());
            rev.set(tmp);
        }
        
        long t4 = System.currentTimeMillis();
        System.out.println(t4 - t3);//结果2
    }
    
    public static void swap(List<?> list, int i, int j) {
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }

那么ArrayList 使用这两种方式的效果呢?

测试结果相差无几,随着增大数据量,swap要好于ListInterator,但是有时微乎其微,所以这个reverse代码中并没有对大数据量的ArrayList进行swap方式,减少了代码冗余,也没有降低什么性能。

 

 

 

---------------------------------------

还有很多扩展学习的地方,继续前进吧。

 

<think>我们面对一个错误链: 1. 在导入PyQt5时,执行到`__init__.py`中的代码:`__path__ = __import__('pkgutil').extend_path(__path__, __name__)` 2. 这行代码引发了`from collections import namedtuple`的`ImportError: cannot import name 'namedtuple'` 首先,我们需要理解这个错误链。通常,`namedtuple`是`collections`模块的一个标准组件,所以正常情况下导入它不应该出错。错误提示表明在导入`namedtuple`时失败,这可能是由于以下原因: - Python环境问题:可能是Python安装不完整或损坏。 - 命名冲突:当前目录或模块中有一个名为`collections.py`的文件,导致导入时优先导入了这个文件而不是标准库的`collections`模块。 - 环境变量问题:PYTHONPATH可能被修改,导致导入了非标准的模块。 根据引用[2]中提到的类似问题(关于找不到Qt5Core.dll),我们知道PyQt5的导入有时会受环境变量影响。但这里的问题不是找不到DLL,而是在导入`namedtuple`时失败,所以我们需要先解决这个前置问题。 ### 解决步骤 #### 1. 检查Python环境 首先,确保Python环境正常。可以尝试在同一个Python环境中导入`collections.namedtuple`: ```python from collections import namedtuple ``` 如果这行代码单独执行也报错,说明Python环境有问题。可能需要重装Python。 #### 2. 检查命名冲突 检查当前工作目录或PyQt5包所在目录下是否有名为`collections.py`(或`collections.pyc`)的文件。因为Python在导入模块时,会首先搜索当前目录,然后才是标准库。如果存在这样的文件,它会覆盖标准库的`collections`模块,导致错误。 解决方法:重命名或删除这个文件。 #### 3. 检查PYTHONPATH 检查PYTHONPATH环境变量是否包含了一些非标准的路径,这些路径可能包含了一个有问题的`collections`模块。 可以在代码中打印`sys.path`来查看导入路径: ```python import sys print(sys.path) ``` 如果发现有不必要的路径,可以临时修改: ```python import sys # 移除有问题的路径 sys.path = [p for p in sys.path if "problematic_path" not in p] ``` 然后再尝试导入PyQt5。 #### 4. 检查PyQt5安装 如果上述步骤都没有解决问题,可能是PyQt5安装不完整或损坏。尝试重新安装PyQt5: ```bash pip uninstall PyQt5 pip install PyQt5 ``` 或者安装特定版本: ```bash pip install PyQt5==5.15.4 ``` #### 5. 使用虚拟环境 为了避免系统环境的影响,可以尝试在虚拟环境中安装PyQt5: ```bash python -m venv venv source venv/bin/activate # Linux/Mac venv\Scripts\activate # Windows pip install PyQt5 ``` ### 引用说明 在解决类似环境问题时,参考了引用[2]中关于PyQt5导入问题的处理思路,即环境变量设置和重新安装库的方法[^2]。同时,引用[3]和[4]也提到了导入错误的其他可能原因,如相对导入问题和包结构问题[^3][^4],但本例中错误发生在标准库导入,所以更可能是环境问题。 §§相关问题§§ 1. 如何检查Python环境中是否存在命名冲突? 2. 如何正确设置Python的PYTHONPATH环境变量? 3. 为什么在虚拟环境中安装PyQt5可以解决导入问题? 4. 除了重装PyQt5,还有哪些方法可以修复损坏的Python包?</think>### 解决导入PyQt5时出现的`namedtuple`导入错误 您遇到的错误链是: ``` __init__.py 中 __path__ = __import__('pkgutil').extend_path(__path__, __name__) 引发 from collections import namedtuple 的 ImportError: cannot import name 'namedtuple' ``` 这个问题的根本原因是**Python环境中的`collections`模块被意外覆盖或损坏**,导致标准库中的`namedtuple`无法导入。以下是具体分析和解决方案: --- ### 原因分析 1. **命名冲突**(最常见): - 当前目录或PYTHONPATH路径中存在名为`collections.py`的自定义文件 - Python导入时优先加载了此文件而非标准库模块 - 验证方法:在报错前添加`print(collections.__file__)`,若非标准库路径则存在冲突 2. **环境损坏**: - Python安装不完整或标准库文件损坏 - 常见于非常规方式安装的Python(如某些打包环境) 3. **路径配置问题**: - `sys.path`被意外修改,导致加载顺序错乱 - 参考引用[2]中提到的PATH问题[^2] --- ### 解决方案 #### ✅ 方法1:检查并移除冲突文件(推荐) 1. 在项目根目录执行: ```bash # 查找所有可能冲突的collections.py find . -name "collections.py" ``` 2. 重命名或删除这些文件(如改为`my_collections.py`) 3. 清除pycache: ```bash find . -name "__pycache__" -exec rm -rf {} + ``` #### ✅ 方法2:强制使用标准库 在导入PyQt5前显式指定标准库路径: ```python import sys import os # 确保标准库优先 stdlib_path = os.path.dirname(os.__file__) sys.path.insert(0, os.path.join(stdlib_path, "collections")) # 再导入PyQt5 from PyQt5 import QtCore, QtWidgets # 正常执行 ``` #### ✅ 方法3:重建Python环境 1. 创建新虚拟环境: ```bash python -m venv clean_env source clean_env/bin/activate # Linux/Mac clean_env\Scripts\activate # Windows ``` 2. 重新安装PyQt5: ```bash pip install --no-cache-dir PyQt5 ``` #### ✅ 方法4:诊断环境状态 运行诊断脚本: ```python import collections try: from collections import namedtuple print("✅ namedtuple 导入成功") print(f"collections模块路径: {collections.__file__}") except ImportError: print("❌ 标准库损坏!建议:") print("1. 重命名项目中的collections.py文件") print("2. 使用官方Python安装包重装Python") print("3. 检查sys.path:", sys.path) ``` --- ### 预防措施 1. **避免使用标准库同名文件**: - 永远不要创建`sys.py`, `os.py`, `collections.py`等文件名 2. **规范导入顺序**: ```python # 先导入标准库 import sys import collections # 再导入第三方库 from PyQt5 import QtWidgets ``` 3. **使用虚拟环境**: - 参考引用[3]中的目录结构规范[^3],隔离项目依赖 > 参考引用[2]的经验:当遇到核心模块导入错误时,优先检查环境隔离和路径配置[^2]。对于PyQt5这类依赖复杂的库,虚拟环境能解决90%的异常问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值