• Java 与 Go:可变数组


    可变数组(也称为动态数组)是一种可以在运行时动态增加或减少其大小的数据结构。由于其动态分配大小,灵活性增删改查,动态地管理内存(在需要时动态分配内存空间,以适应数据结构的大小变化,而不会浪费过多的内存空间)以及具有良好的性能特性。因此在许多编程任务中都是非常有用的数据结构。而在Java中最承担该角色的是某些List接口实现类,Go则有切片。那么今天我们来聊一聊ArrayList和SLICE

    ArrayList

    当提及可变数组时,Java中的ArrayList是一个常见的实现。下面是关于ArrayList的详细信息:

    1. 数据结构

    ArrayList 是 Java 中的动态数组实现。它是基于数组的数据结构,可以根据需要自动增长和缩小。ArrayList 实现了 List 接口,因此可以存储任意类型的对象,并且可以通过索引访问这些对象。

    2. 功能特点:

    • 动态大小: ArrayList 的大小可以动态地增长或缩小。当添加元素时,如果底层数组已满,ArrayList 会自动重新分配更大的内存空间来容纳更多的元素。
    • 随机访问: 由于 ArrayList 基于数组实现,因此支持通过索引进行快速随机访问。这使得访问、修改或删除元素的操作具有 O(1) 的时间复杂度。
    • 插入和删除: ArrayList 支持在任意位置插入和删除元素。但是,在列表中间或开头进行插入或删除操作时,需要将后续元素向后移动,因此具有较高的时间复杂度。
    • 允许重复元素: ArrayList 允许存储重复的元素,并且可以按照插入顺序保持元素的顺序。
    • 不是线程安全的: ArrayList 不是线程安全的,如果多个线程同时访问或修改 ArrayList,可能会导致不确定的行为。如果需要在多线程环境中使用,可以考虑使用 Collections.synchronizedList 方法来获得一个线程安全的 ArrayList。

    3. 构造方式:

    在 Java 中,ArrayList 可以通过多种构造方式进行实例化。下面是一些常用的构造方式:

    • 默认构造函数:
    //这将创建一个空的 ArrayList,初始容量为 10。
    ArrayList list = new ArrayList<>();
    
    
    下面是一个例子
    // 创建一个空的 ArrayList
    ArrayList list = new ArrayList<>();
    // 添加元素
    list.add("Apple");
    list.add("Banana");
    list.add("Orange");
    
    // 输出 ArrayList 内容
    System.out.println("ArrayList elements: " + list);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    • 指定初始容量的构造函数:
    //这将创建一个空的 ArrayList,其初始容量由 initialCapacity 参数指定。
    //如果知道大致要存储的元素数量,可以使用这个构造函数来提高性能,避免频繁的扩容操作。
    ArrayList list = new ArrayList<>(initialCapacity);
    
    //举个例子
    // 创建一个初始容量为 20 的 ArrayList
    ArrayList list = new ArrayList<>(20);
    
    // 添加元素
    for (int i = 1; i <= 20; i++) {
        list.add(i);
    }
    
    // 输出 ArrayList 内容
    System.out.println("ArrayList elements: " + list);
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 使用 Collection 来初始化 ArrayList 的构造函数:
    //这将创建一个包含指定集合中的元素的 ArrayList。元素将按照集合的迭代器返回的顺序排列。
    ArrayList list = new ArrayList<>(Collection c);
    
    //举个例子
    String[] array = {"Apple", "Banana", "Orange"};
    
    // 使用 ArrayList 构造函数从 List 初始化 ArrayList
    ArrayList list = new ArrayList<>(Arrays.asList(array));
    
    // 输出 ArrayList 内容
    System.out.println("ArrayList elements: " + list);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 从另一个 ArrayList 复制元素的构造函数:
    //这将创建一个包含指定 ArrayList 中的元素的新 ArrayList。元素的顺序将与原始 ArrayList 保持一致。
    ArrayList list = new ArrayList<>(ArrayList c);
    
    //举个例子
    // 创建一个包含指定集合中的元素的 ArrayList
    ArrayList sourceList = new ArrayList<>();
    sourceList.add("Apple");
    sourceList.add("Banana");
    sourceList.add("Orange");
    
    // 使用集合 sourceList 初始化 ArrayList
    ArrayList targetList = new ArrayList<>(sourceList);
    
    // 输出 ArrayList 内容
    System.out.println("ArrayList elements: " + targetList);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 使用匿名内部类来初始化
    import java.util.ArrayList;
    
    public class ArrayListExample {
        public static void main(String[] args) {
            // 使用匿名内部类初始化 ArrayList
            ArrayList list = new ArrayList() {{
                add("Apple");
                add("Banana");
                add("Orange");
            }};
    
            // 输出 ArrayList 内容
            System.out.println("ArrayList elements: " + list);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这些是 ArrayList 的常见构造方式,可以根据具体的需求和场景选择合适的构造函数来创建 ArrayList 对象。

    4. 增删改查

    ArrayList 提供了一系列的方法来进行增删改查等功能操作:

    • 增加元素(Add):
    • add(E element): 向列表的末尾添加指定的元素。
    • add(int index, E element): 在列表的指定位置插入指定的元素。
    • addAll(Collection c): 将指定集合中的所有元素添加到列表的末尾。
    • addAll(int index, Collection c): 将指定集合中的所有元素插入到列表的指定位置。
    • 删除元素(Remove):
    • remove(Object o): 从列表中删除指定的元素,如果存在的话。
    • remove(int index): 删除列表中指定位置的元素。
    • removeAll(Collection c): 删除列表中包含在指定集合中的所有元素。
    • clear(): 清空列表中的所有元素。
    • 修改元素(Modify):
    • set(int index, E element): 用指定的元素替换列表中指定位置的元素。
    • 查找元素(Retrieve):
    • get(int index): 返回列表中指定位置的元素。
    • indexOf(Object o): 返回列表中指定元素的第一个出现的索引,如果列表不包含此元素,则返回 -1。
    • lastIndexOf(Object o): 返回列表中指定元素的最后一个出现的索引,如果列表不包含此元素,则返回 -1。
    • 其他功能:
    • size(): 返回列表中的元素数。
    • isEmpty(): 如果列表不包含任何元素,则返回 true。
    • contains(Object o): 如果列表包含指定的元素,则返回 true。
    • toArray(): 返回包含列表中所有元素的数组。
      这些方法提供了对 ArrayList 中元素的常见操作。ArrayList 作为一个动态数组,支持在任意位置插入和删除元素,允许元素的重复,并且具有较好的随机访问性能。因此,ArrayList 在很多情况下是一个非常实用的数据结构。
      看例子
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class ArrayListExample {
        public static void main(String[] args) {
            // 创建一个空的 ArrayList
            ArrayList list = new ArrayList<>();
    
            // 添加元素
            list.add("Apple");
            list.add("Banana");
            list.add("Orange");
            System.out.println("After adding elements: " + list);
    
            // 在指定位置插入元素
            list.add(1, "Grapes");
            System.out.println("After inserting Grapes at index 1: " + list);
    
            // 删除指定元素
            list.remove("Banana");
            System.out.println("After removing Banana: " + list);
    
            // 删除指定位置的元素
            list.remove(0);
            System.out.println("After removing element at index 0: " + list);
    
            // 修改指定位置的元素
            list.set(0, "Strawberry");
            System.out.println("After setting element at index 0 to Strawberry: " + list);
    
            // 获取指定位置的元素
            String fruit = list.get(1);
            System.out.println("Element at index 1: " + fruit);
    
            // 检查是否包含某个元素
            boolean containsOrange = list.contains("Orange");
            System.out.println("Contains Orange: " + containsOrange);
    
            // 获取列表的大小
            int size = list.size();
            System.out.println("Size of the list: " + size);
    
            // 清空列表
            list.clear();
            System.out.println("After clearing the list: " + list);
    
            // 创建一个包含指定数组元素的 List
            String[] array = {"Apple", "Banana", "Orange"};
            List tempList = Arrays.asList(array);
    
            // 使用 ArrayList 构造函数从 List 初始化 ArrayList
            ArrayList arrayListFromList = new ArrayList<>(tempList);
            System.out.println("ArrayList from List: " + arrayListFromList);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    5.遍历

    遍历 ArrayList 有几种常用的方法,包括使用迭代器、for-each 循环以及普通的 for 循环。下面是这些方法的示例:

    • 使用迭代器遍历 ArrayList:
    import java.util.ArrayList;
    import java.util.Iterator;
    
    public class ArrayListTraversal {
        public static void main(String[] args) {
            ArrayList list = new ArrayList<>();
            list.add("Apple");
            list.add("Banana");
            list.add("Orange");
    
            // 使用迭代器遍历 ArrayList
            Iterator iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 使用 for-each 循环遍历 ArrayList:
    import java.util.ArrayList;
    
    public class ArrayListTraversal {
        public static void main(String[] args) {
            ArrayList list = new ArrayList<>();
            list.add("Apple");
            list.add("Banana");
            list.add("Orange");
    
            // 使用 for-each 循环遍历 ArrayList
            for (String element : list) {
                System.out.println(element);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 使用普通的 for 循环遍历 ArrayList:
    import java.util.ArrayList;
    
    public class ArrayListTraversal {
        public static void main(String[] args) {
            ArrayList list = new ArrayList<>();
            list.add("Apple");
            list.add("Banana");
            list.add("Orange");
    
            // 使用普通的 for 循环遍历 ArrayList
            for (int i = 0; i < list.size(); i++) {
                String element = list.get(i);
                System.out.println(element);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这些方法都可以用来遍历 ArrayList,你可以根据自己的喜好和代码风格选择合适的方法。通常情况下,使用 for-each 循环会更加简洁和直观,也可以配合stream进行操作。

    小结

    注意:ArrayList非线程安全使用时要注意,且只接受引用类型(可以存Integer但不能是int),这就意味着允许有null值。

    slice

    当谈到 Go 语言的切片(Slice)时,它是一个非常强大且常用的数据结构。切片提供了对序列的抽象,可以像数组一样访问元素,但它比数组更灵活、更强大。

    1.数据结构

    Go 语言中的切片(Slice)是一个动态长度的、灵活的数据结构,它是对数组的一个抽象。切片在底层的数据结构主要包括三个部分:

    • 指针(Pointer): 切片包含一个指向底层数组的指针,该指针指向数组中的第一个元素。通过指针,切片可以访问底层数组中的元素。

    • 长度(Length): 切片包含一个表示当前元素数量的长度字段。长度表示切片当前包含的元素数量,它不能超过底层数组的长度。

    • 容量(Capacity): 切片还包含一个表示可以容纳的最大元素数量的容量字段。容量表示切片从当前位置开始,到底层数组的末尾的元素数量。当向切片追加元素时,如果超过了切片的容量,Go 语言会自动扩展切片的容量,以保证足够的空间。

    type slice struct {
        array unsafe.Pointer // 指向底层数组的指针
        len   int            // 切片当前长度
        cap   int            // 切片当前容量
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在底层数组的基础上,切片提供了灵活的动态增长和缩减,这是因为切片可以通过修改其长度和容量字段来控制元素的数量。同时,由于切片是对底层数组的引用,因此对切片的操作也会影响到底层数组,反之亦然。

    切片的底层数据结构使得它在内存中的表示比较轻量级,并且可以很方便地进行传递和复制。在实际开发中,切片是 Go 语言中常用的数据结构,用来处理动态大小的数据集合,例如字符串、字节数组等

    2.重要特点

    • 动态大小: 切片是动态大小的,可以根据需要动态增长或缩小。与数组不同,切片的长度可以在运行时改变。

    • 引用数组: 切片是对底层数组的引用。在底层数组上进行操作会影响到切片,反之亦然。这意味着切片的创建和操作成本较低,因为它们不需要复制底层数组的数据。

    • 灵活的索引操作: 切片支持灵活的索引操作,可以使用切片表达式来获取子切片或修改切片的内容。

    3.构造方式

    • 直接声明并初始化:

    切片可以直接声明并初始化,使用 [] 表示切片,可以在 [] 中指定切片的长度和容量,也可以不指定。如果不指定长度和容量,切片的长度和容量都为 0。和数组不一样哦,数组至少还得三个dots[…]

    // 声明一个切片并初始化
    var slice1 []int // 声明一个空切片
    slice2 := []int{1, 2, 3} // 声明并初始化一个切片
    
    • 1
    • 2
    • 3
    • 通过数组进行切片:

    可以通过数组来创建切片,通过指定数组的索引范围来获取子切片。

    array := [5]int{1, 2, 3, 4, 5}
    slice := array[1:3] // 从数组的索引 1 开始,到索引 3 结束,
    
    • 1
    • 2
    • 使用 make() 函数创建切片:

    使用内置的 make() 函数来创建切片,该函数的语法为 make([]T, length, capacity),其中 T 表示切片的元素类型,length 表示切片的长度,capacity 表示切片的容量。

    slice := make([]int, 3, 5) // 创建一个长度为 3,容量为 5 的切片
    
    • 1

    这里解释一下长度(len)和容量(cap)的问题
    在 Go 语言中,len() 和 cap() 是两个用于切片(Slice)的内置函数,用于获取切片的长度和容量。

    • len() 函数:

    len() 函数用于获取切片中当前包含的元素数量,即切片的长度。
    切片的长度是切片中当前存储的元素数量。
    无论切片的容量如何,len() 函数都只返回当前切片中实际存储的元素数量。

    • cap() 函数:

    cap() 函数用于获取切片的容量,即切片的最大长度。
    切片的容量是从切片的起始位置到底层数组末尾的元素数量。
    切片的容量是底层数组中分配给切片的内存空间大小。
    下面是一个示例来演示 len() 和 cap() 函数的用法及区别:

    package main
    
    import "fmt"
    
    func main() {
        // 创建一个数组
        array := [5]int{1, 2, 3, 4, 5}
    
        // 对数组进行切片
        slice := array[1:3] // 从数组的索引 1 到索引 3(不包括索引 3)创建切片
    
        // 打印切片的长度和容量
        fmt.Println("Length of slice:", len(slice)) // 输出: 2
        fmt.Println("Capacity of slice:", cap(slice)) // 输出: 4
    
        // 打印切片的内容
        fmt.Println("Elements of slice:", slice) // 输出: [2 3]
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这个示例中,我们先创建了一个包含 5 个元素的数组。然后,我们从数组的索引 1 到索引 3(不包括索引 3)创建了一个切片。这个切片包含了数组的索引 1 和 2 处的元素。所以切片的长度是 2,容量是从索引 1 到数组末尾的元素数量,即 4。

    4.增删改查

    在 Go 语言中,切片(Slice)提供了丰富的操作方法,包括增加、删除、修改和查找元素等功能。下面是针对切片的常见操作示例:

    • 增加元素:

    使用 append() 函数向切片中添加元素。

    package main
    
    import "fmt"
    
    func main() {
        // 声明一个切片
        slice := []int{1, 2, 3}
    
        // 添加元素
        slice = append(slice, 4)
        fmt.Println("Slice after adding element:", slice) // 输出: [1 2 3 4]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 删除元素:

    使用切片的切片操作或者 append() 函数配合切片的方式来删除元素。

    package main
    
    import "fmt"
    
    func main() {
        // 声明一个切片
        slice := []int{1, 2, 3, 4, 5}
    
        // 删除第三个元素(索引为 2)
        slice = append(slice[:2], slice[3:]...)
        fmt.Println("Slice after deleting element:", slice) // 输出: [1 2 4 5]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 修改元素:

    使用索引直接修改切片中的元素。

    package main
    
    import "fmt"
    
    func main() {
        // 声明一个切片
        slice := []int{1, 2, 3}
    
        // 修改第二个元素(索引为 1)
        slice[1] = 5
        fmt.Println("Slice after modifying element:", slice) // 输出: [1 5 3]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 查找元素:

    使用循环遍历切片并比较每个元素。

    package main
    
    import "fmt"
    
    func main() {
        // 声明一个切片
        slice := []int{1, 2, 3, 4, 5}
    
        // 查找元素 3
        for i, v := range slice {
            if v == 3 {
                fmt.Println("Element 3 found at index:", i)
                break
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这些示例演示了如何在 Go 中对切片进行增加、删除、修改和查找元素等操作。切片是 Go 语言中非常灵活和强大的数据结构,可以用来处理动态大小的数据集合。

    5.遍历

    这里跟数组一样,推荐使用for-range循环

    package main
    
    import "fmt"
    
    func main() {
        // 声明一个切片
        slice := []int{1, 2, 3, 4, 5}
    
        // 使用 range 遍历切片
        for index, value := range slice {
            fmt.Printf("Index: %d, Value: %d\n", index, value)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ####总结
    切片不是线程安全的数据结构,如果多个 Goroutine 同时操作同一个切片,可能会出现数据竞争等问题。
    关于append()和copy()这两个函数,我后面会专门写一下。

    总结

    总的来说,Go和Java在设计可变数组时实现了相同的功能,不过细节上相差还是很大的。

  • 相关阅读:
    mysql update更新数据时null字段是否更新进数据库总结
    关于我的博客~ (2022.8.25 -> 在csdn三岁啦)
    【React】《React 学习手册 (第2版) 》笔记-Chapter5-在 React 中使用 JSX
    【英语:语法基础】B1.核心语法-名词与代词
    【uni-app】uni-app之云开发uniCloud跨全栈开发笔记总结,包括一个 schema自动生成代码小案例(附详细截图)
    十个超级有用的JavaScript的高阶面试技巧!
    MySQL专有的SQL语句
    分析Python7个爬虫小案例(附源码)
    量子计算实现:量子算法的实现
    仿SpringBoot启动Dome实现
  • 原文地址:https://blog.csdn.net/weixin_45700531/article/details/136758839