Java中的List: 理解与实践

在Java编程语言中,List是一种被广泛使用的集合类型,它提供了一种灵活的方式来存储和操作有序的元素序列。List是Java集合框架(Java Collections Framework)的一部分,是一个接口,提供了一系列标准的方法来对元素进行增加、删除、检索和遍历操作。

List的核心特性

  • 有序性:List中的元素按照插入的顺序进行存储,可以通过元素的索引(位置)来访问它们。
  • 元素唯一性:List允许添加重复的元素,即两个或更多的元素可以有相同的值。
  • 动态扩展:列表的大小不是固定的,它可以根据需要动态地增加或减少元素。

List的实现类

ArrayList

基于动态数组实现,默认初始容量是10,动态数组允许元素的随机访问,即可以通过索引直接访问任何位置的元素。

动态数组(Dynamic Array)是一个可以根据需要进行自动扩容的数据结构。与普通数组相同。然而,与传统的数组(其大小在创建时固定)不同,动态数组在添加更多元素时可以自动地扩大其容量。

  1. 随机访问:动态数组支持快速的随机访问,这意味着可以在常数时间内访问任何索引的元素,即时间复杂度为O(1)。

  2. 自动扩容:当数组中的元素填满所有可用空间时,动态数组可以自动进行扩容以适应更多的元素。这通常涉及以下步骤:

    • 创建一个更大的新数组(通常是旧数组大小的两倍)。
    • 将旧数组中的所有元素复制到新数组中。
    • 释放旧数组,并将引用指向新数组。
  3. 添加/删除元素:向动态数组添加元素通常是一个快速的操作,特别是在数组的末尾添加。但是,如果数组已经满了,那么添加操作需要进行一次扩容,这将涉及到额外的内存分配和元素复制。平均而言,添加操作的时间复杂度是O(1),但在最坏情况下会是O(n)。删除元素也是一种支持的操作,但可能涉及到移动元素以填补被删除元素留下的空隙。

  4. 内存利用率:由于动态数组可能会在其容量达到上限之前就进行扩容,它可能不会始终使用所有分配的内存。这意味着它可能在某些时候会有额外的空间开销。

ArrayList扩容过程的基本步骤:

  1. 检查容量:每次添加元素时,ArrayList都会检查内部数组是否足够大,可以容纳新的元素。如果不够大,那么就需要进行扩容。
  2. 计算新容量ArrayList计算新数组的大小。新容量的计算方式可能依赖于具体的实现,但通常是当前数组容量的1.5到2倍。在OpenJDK中,它是旧容量的1.5倍(即旧容量加上旧容量右移一位,newCapacity = oldCapacity + (oldCapacity >> 1))。
  3. 创建新数组ArrayList创建一个新的更大的数组,大小为计算出的新容量。
  4. 复制元素:将原有数组中的所有元素复制到新数组中。这通常使用System.arraycopy()方法实现,它是一个原生方法,可以快速地将数据从一个数组复制到另一个数组。
  5. 更新引用ArrayList将内部数组的引用更新为新数组。
  6. 添加新元素:现在新数组已经有足够的空间,ArrayList将新元素添加到数组中。

模拟ArrayList扩容过程的简化代码示例:

public static void main(String[] args) {// 假定初始容量为5,仅为示例Object[] elements = new Object[5];int size = 0; // ArrayList的当前元素数量// 添加元素,模拟添加过程中的扩容for (int i = 0; i < 10; i++) { // 添加10个元素,超出初始容量if (size == elements.length) {// 计算新容量:旧容量 + 旧容量的一半int newCapacity = elements.length + (elements.length >> 1);// 创建新数组elements = Arrays.copyOf(elements, newCapacity);}// 添加新元素elements[size++] = "Element " + i; }System.out.println(Arrays.toString(elements));
}

LinkedList

基于双向链表实现,没有固定大小的内部数组,提供了优秀的顺序访问和中间插入/删除操作的性能。

双向链表(Doubly Linked List)是一种数据结构,它由一系列节点(Node)组成,每个节点包含数据以及两个指针,分别指向前一个节点和后一个节点。这种结构允许双向遍历,即可以从头节点遍历到尾节点,也可以从尾节点遍历到头节点。

每个节点通常包含以下部分:

  1. 数据(Data):存储的元素或值。
  2. 前驱指针(Prev):指向链表中上一个节点的指针。
  3. 后继指针(Next):指向链表中下一个节点的指针。

双向链表的第一个节点称为头节点(Head),最后一个节点称为尾节点(Tail)。在双向链表的头节点中,前驱指针通常指向 null,表示没有前一个节点。同样,在尾节点中,后继指针指向 null,表示没有下一个节点。

双向链表的优势在于它可以轻松地向两个方向遍历,并且在链表中间插入或删除节点时,可以更高效地进行,因为可以直接访问任何节点的前驱和后继。这在单向链表中需要更多的步骤,因为你只能从头节点开始按顺序遍历。

双向链表的简单实现:

public class DoublyLinkedListNode<T> {T data;DoublyLinkedListNode<T> prev;DoublyLinkedListNode<T> next;public DoublyLinkedListNode(T data) {this.data = data;this.prev = null;this.next = null;}
}
public class DoublyLinkedList<T> {private DoublyLinkedListNode<T> head;private DoublyLinkedListNode<T> tail;public DoublyLinkedList() {head = null;tail = null;}// 在链表尾部添加节点public void append(T data) {DoublyLinkedListNode<T> newNode = new DoublyLinkedListNode<>(data);// 链表为空if (tail == null) { head = newNode;tail = newNode;} else {tail.next = newNode;newNode.prev = tail;tail = newNode;}}// 在链表头部添加节点public void prepend(T data) {DoublyLinkedListNode<T> newNode = new DoublyLinkedListNode<>(data);// 链表为空if (head == null) { head = newNode;tail = newNode;} else {newNode.next = head;head.prev = newNode;head = newNode;}}}

双向链表相对于数组和单向链表来说,在插入和删除节点时可能更加高效,因为不需要重新排列整个数据结构。然而,它也具有额外的内存开销,因为每个节点都包含两个额外的指针。在实际的软件开发中,选择哪种数据结构取决于具体的应用场景和性能需求。

单向链表(Singly Linked List)是一种线性数据结构,由一系列节点(Node)组成,每个节点包含两部分:数据域和指向下一个节点的指针。这种结构允许顺序访问其元素,从头节点(Head)开始,一直到尾节点(Tail)结束。尾节点的指针指向 null,标记着链表的结束。

每个节点通常包含以下部分:

  1. 数据(Data):存储的元素或值。
  2. 指针(Next):指向链表中下一个节点的指针。

单向链表的头节点是链表的起点,它是访问链表中其他节点的入口。在单向链表中,由于每个节点只包含指向下一个节点的指针,所以你不能直接反向遍历,只能从头节点开始,按顺序向后遍历。

单向链表的简单实现:

public class SinglyLinkedListNode<T> {T data;SinglyLinkedListNode<T> next;public SinglyLinkedListNode(T data) {this.data = data;this.next = null;}
}
public class SinglyLinkedList<T> {private SinglyLinkedListNode<T> head;public SinglyLinkedList() {this.head = null;}// 在链表的末尾添加一个新的节点public void append(T data) {if (head == null) {head = new SinglyLinkedListNode<>(data);return;}SinglyLinkedListNode<T> current = head;while (current.next != null) {current = current.next;}current.next = new SinglyLinkedListNode<>(data);}// 在链表头部添加一个新的节点public void prepend(T data) {SinglyLinkedListNode<T> newHead = new SinglyLinkedListNode<>(data);newHead.next = head;head = newHead;}// 删除具有特定值的节点public void remove(T data) {if (head == null) return;if (head.data.equals(data)) {head = head.next;return;}SinglyLinkedListNode<T> current = head;while (current.next != null) {if (current.next.data.equals(data)) {current.next = current.next.next;return;}current = current.next;}}}

Vector

和ArrayList类似,默认初始容量是10,也会在需要时自动增长其容量,但是它是线程安全的。

每种实现都有其特定的使用场景。如果你需要频繁的随机访问元素,那么ArrayList将是最合适的,因为它提供了优秀的随机访问性能。但是,如果你需要在List中插入和删除元素,特别是在List的开头或中间,LinkedList会更加适合,因为它的插入和删除操作不需要像数组那样进行大量的元素移动。

List的基本操作

  • add(E e):将指定的元素添加到列表的末尾。
  • add(int index, E element):在列表的指定位置插入指定元素。
  • remove(Object o):移除列表中首次出现的指定元素(如果存在)。
  • remove(int index):移除列表中指定位置的元素。
  • get(int index):返回列表中指定位置的元素。
  • set(int index, E element):用指定元素替换列表中指定位置的元素。
  • size():返回列表中的元素个数。
  • isEmpty():如果列表不包含元素,则返回true。
  • contains(Object o):如果列表包含指定元素,则返回true。
  • clear():移除列表中的所有元素。

List的迭代

  • 使用传统的for循环通过索引访问。
  • 使用增强的for-each循环进行迭代。
  • 使用迭代器(Iterator)。

List的线程安全问题

在多线程环境中使用List时,开发者需要注意线程安全的问题。ArrayListLinkedList并不是线程安全的,如果需要在多线程环境下访问和修改List,可以考虑使用Vector或者Collections.synchronizedList方法来包装非线程安全的List:

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;public class SynchronizedListExample {public static void main(String[] args) {// 创建一个同步的ListList<String> syncList = Collections.synchronizedList(new ArrayList<>());// 添加元素syncList.add("Synchronized");syncList.add("List");// 访问元素需要同步块synchronized (syncList) {for (String item : syncList) {System.out.println(item);}}}}

然而,在高并发场景下,VectorCollections.synchronizedList的性能可能不是最优的。在这种情况下,可以考虑使用CopyOnWriteArrayList,它是java.util.concurrent包提供的一个线程安全的List实现,它通过在修改操作(如add、set)时创建底层数组的副本来实现线程安全。

CopyOnWriteArrayList的核心思想是,每当对列表进行修改操作(添加、设置或删除元素)时,它都会创建并使用内部数组的一个新副本。因此,任何写入操作都不会影响到原始数组,从而保证了迭代器不会看到这些变化,避免了并发修改异常。

这里是CopyOnWriteArrayList的一些主要特点:

  1. 线程安全CopyOnWriteArrayList内的所有可变操作(add、set、remove等)都是通过创建内部数组的新副本来实现的,从而避免了线程间的冲突。
  2. 读写分离:读操作(如get、iterator、size等)是在原有数组的基础上进行的,而写操作则在复制的新数组上执行。这种机制称为“写时复制”(Copy-on-Write)。
  3. 迭代器一致性:迭代器返回的是写操作发生时的数组快照。因此,在迭代器创建之后的写操作不会反映在迭代器上,保证了迭代器不会抛出ConcurrentModificationException。迭代器也不支持修改操作,removesetadd方法会抛出UnsupportedOperationException
  4. 内存和性能考虑:由于每次修改都涉及创建数组的副本,对于大的列表或频繁修改的情况,CopyOnWriteArrayList可能会带来显著的内存和性能开销。
  5. 适用场景CopyOnWriteArrayList适合于列表大小相对固定,但需要在多线程环境中经常遍历、读取和枚举的应用场景。如果列表经常发生变化,或者列表非常大,使用CopyOnWriteArrayList可能不是最好的选择。
import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteArrayListExample {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();list.add("Element 1");list.add("Element 2");list.add("Element 3");// 创建一个迭代器,它不会反映在迭代过程中加入的新元素for (String element : list) {System.out.println(element);// 这里的添加操作不会影响迭代器list.add("Element 4");}// 最终列表的内容System.out.println("Final list: " + list);}
}

尽管在迭代过程中向CopyOnWriteArrayList添加了新元素,迭代器仍然只会遍历开始迭代时列表的原始内容。最终的输出将包括新添加的元素。

List的高级用法

除了基本操作,List还提供了一系列高级操作,如排序、查找和转换等。

  • 排序:可以使用Collections.sort方法对List进行排序。
  • 查找:可以使用Collections.binarySearch方法在已排序的List中快速查找元素。
  • 转换:可以使用toArray方法将List转换为数组,或者使用stream方法进行更复杂的数据转换和操作。
// 对List进行排序
Collections.sort(fruits);// 使用二分查找法查找元素
int index = Collections.binarySearch(fruits, "Cherry");// 将List转换为数组
String[] fruitsArray = fruits.toArray(new String[0]);// 使用Stream API进行过滤
List<String> filteredFruits = fruits.stream().filter(f -> f.startsWith("A")).collect(Collectors.toList());

List与Java 8的流

Java 8引入了流(Streams),这为在List上进行复杂的查询和转换操作提供了新的可能性。你可以使用流来执行过滤、映射、排序和其他聚合操作,通常配合lambda表达式使用。

使用流的一个例子:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;public class StreamListExample {public static void main(String[] args) {// 创建一个List实例List<String> items = new ArrayList<>();items.add("One");items.add("Two");items.add("Three");// 使用流过滤和输出元素List<String> filteredItems = items.stream().filter(s -> s.contains("T")).collect(Collectors.toList());System.out.println("Filtered Items:");filteredItems.forEach(System.out::println);}}

性能考量

在决定使用哪种List实现之前,需要考虑以下几个性能相关的因素:

  • 随机访问:如果你需要频繁地通过索引访问元素,ArrayList通常是更好的选择,因为它提供常数时间复杂度的随机访问能力。
  • 插入和删除:如果你的应用场景中经常在List中间插入或删除元素,LinkedList可能会提供更好的性能,因为它只需要改变节点的指针。
  • 内存占用ArrayList可能会预留一些空间以减少扩容操作的频率,这可能导致一定程度的内存浪费。相比之下,LinkedList对于每个元素都需要额外的内存来维护节点之间的链接。
  • 线程安全:如果在多线程环境中使用List,应该考虑线程安全的问题。Vector是同步的,但是通常建议使用Collections.synchronizedListCopyOnWriteArrayList来获得线程安全的List。

常见问题

  • List是否可以存储基本数据类型?:List不能存储基本数据类型,例如intchar等,但可以存储它们的包装类,如IntegerCharacter等。

  • 如何将List转换为数组?:可以使用toArray()方法将List转换为数组。

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;public class ListStreamExample {public static void main(String[] args) {List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Apricot");// 使用Stream API对List进行过滤和转换List<String> filteredFruits = fruits.stream().filter(f -> f.startsWith("A")) // 过滤出以"A"开头的水果.sorted() // 按自然顺序排序.collect(Collectors.toList()); // 收集为ListSystem.out.println("Filtered and Sorted Fruits: " + filteredFruits);}}
    
  • 并发修改异常(ConcurrentModificationException): 当尝试在遍历List的过程中改变其结构(例如添加或删除元素)时,可能会抛出ConcurrentModificationException。要避免这个问题,可以使用迭代器的remove()方法来删除元素,或者采用Java 8及以上版本的新特性,如removeIf()方法。对于并发环境,可以考虑使用线程安全的集合类型,如CopyOnWriteArrayList

  • 类型安全问题: 自Java 5起,Java引入了泛型,使得可以创建特定类型的List(例如List<String>)。尽量避免使用原始类型(raw types),如简单的List,因为这会导致类型转换问题和运行时错误。

  • 忘记使用泛型: 始终使用泛型来声明和初始化List,这有助于编译时类型检查,并减少在运行时出现ClassCastException的风险。

  • 使用不当的equals和hashCode: 当使用包含自定义对象的List时,确保正确重写这些对象的equals()hashCode()方法,这对于列表的搜索和去重操作至关重要。

  • 不正确的遍历和删除: 在for循环中遍历并删除元素可能导致跳过某些元素或抛出ConcurrentModificationException。使用迭代器或Java 8的removeIf方法可以安全地删除元素。

手写ArrayList

import java.util.Arrays;public abstract class SimpleArrayList<E> implements java.util.List<E> {/*** 默认的初始容量*/private static final int DEFAULT_CAPACITY = 10;/*** 内部用来存储元素的数组*/private Object[] elements;/*** 列表的当前元素数量*/private int size;public SimpleArrayList() {// 初始化内部数组elements = new Object[DEFAULT_CAPACITY];}// 确保内部数组有足够的容量来存储新元素private void ensureCapacity() {if (size >= elements.length) {// 如果当前元素数量达到数组容量,需要扩容// 通常扩展到旧容量的1.5倍int newCapacity = elements.length + (elements.length >> 1);// 复制旧数组元素到新的扩容后的数组elements = Arrays.copyOf(elements, newCapacity);}}@Overridepublic boolean add(E e) {// 确保有足够容量添加新元素ensureCapacity();// 将元素添加到数组末尾,并递增大小elements[size++] = e;  return true;}@Overridepublic E get(int index) {// 检查索引范围if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);}// 返回请求索引处的元素return (E) elements[index];  }@Overridepublic E remove(int index) {// 保存要删除的元素E oldValue = get(index);// 计算要移动的元素数目int numMoved = size - index - 1;  if (numMoved > 0) {// 将删除元素后的所有元素向前移动一个位置System.arraycopy(elements, index + 1, elements, index, numMoved);}// 清除引用并递减大小elements[--size] = null;// 返回被删除的元素return oldValue;  }@Overridepublic int size() {// 返回列表当前元素数量return size; }// ... 其他 List 接口方法的实现(略)@Overridepublic boolean isEmpty() {return size == 0;}// ... 实现 Comparable, Serializable 等接口和方法(略)
}

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

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

相关文章

LeetCode_4_困难_寻找两个正序数组的中位数

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 二分查找2.2 划分数组 1. 题目 给定两个大小分别为 m m m 和 n n n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为…

UCF101 数据集介绍与下载

一、介绍 UCF101 是一个现实动作视频的动作识别数据集&#xff0c;收集自YouTube&#xff0c;提供了来自101个动作类别的13320个视频。官方&#xff1a;https://www.crcv.ucf.edu/research/data-sets/ucf101/ 数据集名称&#xff1a;UCF-101&#xff08;2012&#xff09; 总视…

06、Kafka ------ 各个功能的作用解释(ISR 同步副本、非同步副本、自动创建主题、修改主题、删除主题)

目录 CMAK 各个功能的作用解释★ ISR副本 (同步副本&#xff09;★ 非同步副本★ 自动创建主题★ 修改主题★ 删除主题 CMAK 各个功能的作用解释 ★ ISR副本 (同步副本&#xff09; 简单来说 &#xff0c;ISR 副本 就是 Kafka 认为与 领导者副本 同步的副本。 ISR&#xff0…

双位置继电器DLS-5/2TH 额定电压:110VDC 触点形式:7开3闭 柜内安装

系列型号&#xff1a; DLS-5/1电磁式双位置继电器; DLS-5/2电磁式双位置继电器; DLS-5/3电磁式双位置继电器; DLS-5/2G电磁式双位置继电器; DLS-5/3 220VDC双位置继电器 一、用途 1.1用途 DLS-5双位置继电器(以下简称产品)用于各种保护与自动控制系统中&#xff0c;作为切换…

JPEG格式详解Baseline、Progressive的区别

文章目录 JPEG的简介压缩质量/压缩比率色彩空间基线和渐进子采样存储选项 基线和渐进基线格式渐进格式&#xff1a; 子采样4:4:4&#xff08;无损&#xff09;4:2:24:2:0 JPEG的简介 JPEG&#xff08;Joint Photographic Experts Group&#xff09;是一种常见的图像压缩格式&a…

SpringBoot 配置文件加载优先级

SpringBoot 配置文件加载优先级 前言SpringBoot 配置文件加载优先级 前言 最近在使用k8s部署项目的时候,发现Dockerfile文件中的命令后面跟的参数,无法覆盖nacos中的参数,今天有时间正好来整理一下Springboot配置的加载顺序 SpringBoot 配置文件加载优先级 整理加载顺序第一个肯…

电子学会C/C++编程等级考试2023年12月(一级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:数的输入和输出 输入一个整数和双精度浮点数,先将浮点数保留2位小数输出,然后输出整数。 时间限制:1000 内存限制:65536 输入 一行两个数,分别为整数N(不超过整型范围),双精度浮点数F,以一个空格分开。 输出 一行两个数,分…

蓝凌EIS智慧协同平台 ShowUserInfo.aspx SQL注入漏洞复现

0x01 产品简介 蓝凌EIS智慧协同平台是一款专为企业提供高效协同办公和团队合作的产品。该平台集成了各种协同工具和功能,旨在提升企业内部沟通、协作和信息共享的效率。 0x02 漏洞概述 由于蓝凌EIS智慧协同平台 ShowUserInfo.aspx接口处未对用户输入的SQL语句进行过滤或验证…

windows配置电脑网络ip地加的方法

在Windows系统中配置电脑网络IP地址的方法如下&#xff1a; ### 通过图形界面设置 1. **步骤一&#xff1a;打开“网络和互联网设置”** - 点击任务栏右下角的网络图标&#xff08;无线或有线网络图标&#xff09;。 - 在弹出的菜单中选择“网络和共享中心”。 或者&a…

Xcode15 升级问题记录

这里写自定义目录标题 新版本Xcode15升级问题1&#xff1a;rsync error: some files could not be transferred (code 23) at ...参考 新版本Xcode15升级 下载地址&#xff1a;https://developer.apple.com/download/all/ 我目前使用的版本是Xcode15.2 我新创建了一个项目&…

植物大战僵尸小游戏抖音快手直播搭建弹幕插件教程

植物大战弹幕插件功能介绍 该插件由梦歌技术部团队支持开发&#xff0c;本插件软件通过监测抖音弹幕信息&#xff0c;获取礼物数据触发脚本插件对应的功能&#xff1b; 功能目前基本上已经完善&#xff0c;后期功能会陆续上线支持更新&#xff0c;全新的脚本监测稳定方便实用…

文心一言API调用,保姆级案例分享

分享一个调用文心一言API的案例。今天自己用程序去过去文心一言模型中获取结果。 文心一言API调用如何收费&#xff1f; 官方给送了20块钱的体验券&#xff01; 后续收费规则如下 如何开通所需要要的 API key 和 Secret key&#xff1f; api调用需要先在千帆平台开通API key 。…

openGauss学习笔记-193 openGauss 数据库运维-常见故障定位案例-备机卡住-数据库只读

文章目录 openGauss学习笔记-193 openGauss 数据库运维-常见故障定位案例-备机卡住-数据库只读193.1 switchover操作时&#xff0c;主机降备卡住193.1.1 问题现象193.1.2 原因分析193.1.3 处理办法 193.2 磁盘空间达到阈值&#xff0c;数据库只读193.2.1 问题现象193.2.2 原因分…

C语言中的整形提升

整型提升 一、隐式类型转换1.1 整形提升的意义1.2 如何整形提升1.3 练习1.3.1 练习11.3.2 练习2 总结 一、隐式类型转换 C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度&#xff0c;表达式中的字符和短整型操作数在使用之前被转换为普通整型(int)&a…

数据结构第十二弹---堆的应用

堆的应用 1、堆排序2、TopK问题3、堆的相关习题总结 1、堆排序 要学习堆排序&#xff0c;首先要学习堆的向下调整算法&#xff0c;因为要用堆排序&#xff0c;你首先得建堆&#xff0c;而建堆需要执行多次堆的向下调整算法。 但是&#xff0c;使用向下调整算法需要满足一个前提…

面试算法110:所有路径

题目 一个有向无环图由n个节点&#xff08;标号从0到n-1&#xff0c;n≥2&#xff09;组成&#xff0c;请找出从节点0到节点n-1的所有路径。图用一个数组graph表示&#xff0c;数组的graph[i]包含所有从节点i能直接到达的节点。例如&#xff0c;输入数组graph为[[1&#xff0c…

微信小程序Canvas画布绘制图片、文字、矩形、(椭)圆、直线

获取CanvasRenderingContext2D 对象 .js onReady() {const query = wx.createSelectorQuery()query.select(#myCanvas).fields({ node: true, size: true }).exec((res) => {const canvas = res[0].nodeconst ctx = canvas.getContext(2d)canvas.width = res[0].width * d…

tar命令的常见用法

tar 是一种广泛使用的命令行工具&#xff0c;用于在 Unix 和 Unix-like 系统中创建、维护、提取以及管理 tar 归档文件。以下是 tar 命令的常见用法和选项列表&#xff1a; 基本命令选项 创建归档: -c: 创建一个新的归档文件。例子: tar -cf archive.tar files/ 查看归档内容…

2024--Django平台开发-Web框架和Django基础(二)---Mysql多版本共存(Mac系统)

MySQL多版本共存&#xff08;Mac系统&#xff09; 想要在Mac系统上同时安装【MySQL5.7 】【MySQL8.0】版本&#xff0c;需要进行如下的操作和配置。 想要同时安装两个版本可以采取如下方案&#xff1a; 方案1&#xff1a;【讲解】 MySQL57&#xff0c;用安装包进行安装。 MyS…

遗传算法解决函数最大化问题的完整Python实现

遗传算法&#xff08;Genetic Algorithm&#xff0c;简称GA&#xff09;是一种模拟生物进化过程的启发式优化算法。它通过模拟自然选择、交叉和变异等基因操作&#xff0c;来搜索问题的最优解。 遗传算法的基本思想是通过模拟生物的遗传机制来搜索解空间。算法维护一个种群&am…