这可能不是你第一次听说数据结构。作为有经验的开发人员,你可能已经将它们分别与其他编程语言或 Dart 编程语言本身一起使用过。
数据结构是软件开发和计算机科学的核心。它们是构建具有不同复杂程度的系统的重要基础之一。
随着 Dart 以惊人的速度增长,主要是由于流行的 Flutter 框架,清楚地了解这种语言中可用的数据结构以及如何使用它们执行操作很快变得至关重要。
让我们继续探索并在构建 Dart 或 Flutter 应用程序时会遇到的各种数据结构上执行一些 CRUD 操作,包括详细的Dart和Flutter数据结构用法示例。
先决条件
- Dart的基础知识
- 你的机器上安装了Dart SDK
- 你选择的任何文本编辑器或 IDE!
Dart和Flutter数据结构使用教程:列表
列表是作为单个实体存储和引用的有序数据集合。列表中的每个元素都通过它的索引访问,索引是指它的位置。索引从 开始0
并继续到n - 1
,n
是列表的长度。
列表的一些实际用例是:
- 存储你的浏览历史记录
- 在音乐播放器中存储歌曲或在视频播放器中存储视频
- 在浏览器上浏览网页
当数据动态增长时,列表最适合。列表中项目的排列由它们的添加顺序决定。这意味着第一个添加的元素的索引为0
,第二个添加的元素的索引为1
,依此类推。
在 Dart 中,列表可以是可增长的,也可以是固定长度的。你可以在创建列表时通过将列表的growable
属性设置为true
或来指定此项false
。
当列表设置为 时growable
,列表的大小是灵活的。在这种情况下,你可以将项目添加到列表中,增加其容纳项目的容量。
另一方面,固定长度列表保留你在创建时指定的确切长度。直接或通过列表操作(例如add
或 )更改其大小的尝试remove
将导致错误。
创建一个空的、可增长的列表
// Creating an empty list in a non-null-safe program.
var list = List();
print(list); // []
list.add(1);
print(list); // [1]
list.add(2);
print(list); // [1, 2]
// Creating an empty list in a null-safe program.
var nullSafeList = List.empty(growable: true);
print(nullSafeList); // []
nullSafeList.add(1);
print(nullSafeList); // [1]
nullSafeList.add(2);
print(nullSafeList); // [1, 2]
如何使用Dart和Flutter数据结构?在上面的代码块中,我们演示了创建空的、可增长的列表的两种技术。Dart 团队弃用了List()
创建空列表的方法,你不能在空安全程序中应用该方法。他们用空白方括号[]
.
因此,如果你想创建一个空的可增长列表,建议你使用上面示例中显示的空白方括号样式。
创建一个固定长度的列表
Dart和Flutter数据结构用法示例:有时,你希望确保列表的长度在程序的整个生命周期内不会发生变化。下面的例子展示了如何通过创建一个固定长度的列表来实现这一点:
// Creating a list with a fixed length.
var list = List.filled(3, 0);
print(list); // [0, 0, 0]
list[1] = 3;
print(list); // [0, 3, 0]
list.add(1); // error
在上面的例子中,我们list
用它的filled
构造函数初始化变量,用相同的值填充列表。构造函数中的第一个参数是列表的长度,第二个参数表示列表中元素的初始值。
此构造函数还接受可选的第三个数据类型参数bool
,你可以使用它来设置growable
列表的属性。默认情况下,此属性为false
,意味着长度是固定的。传递true
将使列表的大小变得灵活,并且你可以调用改变列表长度的操作。
否则,也就是说,如果growable
保留其默认值(即false
),则长度不能改变。这意味着我无法添加新元素或从列表中删除元素,因为这会改变列表的大小或长度。我只能修改列表中现有元素的值或执行不改变大小的任何其他操作。
调用任何改变上述列表长度的操作都会引发错误。
创建具有初始化值的列表
你还可以创建一个列表并同时为其分配值。
var list = [1,2,3,4,5,6];
print(list); // [1,2,3,4,5,6]
为相同数据类型的项目创建列表
我们上面说明的示例是可以包含不同类型数据的列表。这意味着你可以有类型的数据int
,String
,bool
在同一列表等。
List 是一种通用数据类型,可以包含完全相同的数据类型的元素。
// creating a list of Strings with a fixed length of 5 and all // elements have initial values - "foo";
var stringList = List<String>.filled(5, "foo");
print(stringList); // [foo, foo, foo, foo, foo]
stringList[4] = "bar";
print(stringList); // [foo, foo, foo, foo, bar]
// stringList[2] = 3; // error
// creating a growable list of integers.
var intList = List<int>.empty(growable: true);
print(intList); // []
intList.add(3);
print(intList); // [3]
// intList.add("doe"); // error
从列表中检索项目
请记住,列表中的项目是使用它们的索引来标识的。要从列表中获取项目,请使用其索引定位该项目。
var values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
// using the index to retrieve its respective element.
print(values[3]); // 40
print(values[7]); // 80
print(values[0]); // 10
// using a for loop to access an array
for(int i = 0; i < values.length; i++ ){
print("The element in index $i is ${values[i]}");
}
Output
/**
The element in index 0 is 10
The element in index 1 is 20
The element in index 2 is 30
The element in index 3 is 40
The element in index 4 is 50
The element in index 5 is 60
The element in index 6 is 70
The element in index 7 is 80
The element in index 8 is 90
The element in index 9 is 100
**/
修改列表中的项目
你还可以通过将新值通过索引重新分配给所需项目来更改列表中的值。
var values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
// using the index to retrieve its respective element.
print(values[3]); // 40
print(values[7]); // 80
print(values[0]); // 10
// modifying an item in the list by reassigning a new value to each //index
values[3] = 12;
values[7] = 19;
values[0] = 38;
print(values[3]); // 12
print(values[7]); // 19
print(values[0]); // 38
修改列表中的一系列项目
你还可以使用setAll()
方法修改列表中的项目序列。这个方法有两个参数:第一个是你想要修改的东西的起始索引,第二个是包含新值的列表。
请注意,新列表的长度不得大于从起始索引获得的长度。否则,应用程序将抛出错误。
var values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
print(values); // [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
// modifying the items in the list by reassigning a new values
values.setAll(4, [1,3,4,5,6]);
print(values); // [10, 20, 30, 40, 1, 3, 4, 5, 6, 100]
从列表中删除项目
你可以使用remove()
方法从列表中删除元素。此方法删除列表中项目的第一个实例。
var values = [15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
print(values); // [15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
// remove an instance of 18. This removes the first occurence of the instance in the list.
values.remove(18);
print(values); // [15, 16, 17, 19, 20, 21, 22, 23, 24]
// remove the value at index 8
values.removeAt(8);
print(values); // [15, 16, 17, 19, 20, 21, 22, 23]
// remove items that meet a condition, in this case even numbers.
values.removeWhere((int num) => num % 2 == 0);
print(values); [15, 17, 19, 21, 23]
// remove items between index 1 and 3
values.removeRange(1,4);
print(values); // [15, 23]
// remove the last item in the list.
values.removeLast();
print(values); // [15]
迭代一个列表
你可以使用 afor loop
或forEach()
列表的方法遍历列表中的项目。
var values = [15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
// iterating with for loop
for(int i = 0; i < values.length; i++){
print(values[i]);
}
// Iterating with for each method
values.forEach((int num) => print(num));
你还可以使用Iterator
实例遍历列表,这允许你对列表中的每个项目执行操作。
var iterator = values.iterator;
while(iterator.moveNext()){
print(iterator.current);
}
洗牌列表
如何使用Dart和Flutter数据结构?Dart 中的列表有一个隐式shuffle
方法,你可以调用它来对列表中的项目进行混洗。
var values = [15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
print(values);
values.shuffle();
print(values);
Flutter 数据结构:Map
映射是存储为键值对的动态通用项目集合。键是唯一的实体,用于引用和检索它们各自的值。
这些键和值也称为条目,可以是你在创建映射或使其动态化时可以选择声明的任何数据类型。你需要对值进行的任何交互都需要通过它们各自的键来访问它们。
Map 数据结构的一些实际用例是:
- 字典——字是键,意思是值
- 你的联系人列表——联系人姓名可视为key和完整的联系人信息,value
- 你也可以在创建银行系统时使用它,其中帐号是键,帐户实体或详细信息是值
创建空Map
你可以通过以下两种方式之一创建空Map:
通过构造函数:
var map = Map();
print(map); // {}
通过它的文字:
var map = {};
print(map); // {}
初始化映射中的值
你还可以在使用创建的文字技术时初始化映射的值。
var map = {"name": "dami", "age": "10"};
print(map);
向Map添加条目
请记住,每个条目都包含一个键及其各自的值。向映射添加条目时,你可以在方括号中指定键并为其分配值。你还可以使用addAll()
方法将来自不同Map对象的条目集合添加到你想要的Map中。
同样,putIfAbsent()
如果Map中不存在提供的键,你可以使用该方法添加条目。
var map = {};
print(map);
// adding an entry whose key is "name" and value is "dami"
map["name"] = "dami";
print(map);
// adding an entry whose key is "age" and value is 10
map['age'] = 10;
print(map);
//adding a collection of entries
map.addAll({"school": "semicolon", "degree": "Bsc"});
print(map);
//adding an entry if the key does not already exist
map.putIfAbsent("school", () => "semicolon");
print(map);
检索条目的值
使用条目的键作为对条目的引用来检索条目的值。
var map = {'name': 'dami', 'age': 10};
print(map['name']);
var nameValue = map['name'];
print(nameValue);
更新Map中的条目
你可以通过简单地通过其键为其重新分配新值来更新条目的值。
var map = {'name': 'dami', 'age': 10};
print(map['name']);
// assigning a new value to the key
map['name'] = 'john';
print(map['name']);
从Map中删除条目
Dart和Flutter数据结构用法示例:你可以通过调用该remove()
方法从Map对象中删除键。此方法将键作为参数并删除具有匹配键及其对应值的条目。
var map = {
'ten': 10,
'eleven': 11,
'twelve': 12,
'thirteen': 13,
'fourteen': 14,
'fifteen': 15,
'sixteen': 16
};
map.remove('twelve');
print(map);
你还可以使用 删除满足特定条件的条目removeWhere()
。这个方法需要一个两个参数的函数来执行它的任务。函数中的第一个参数表示键,第二个参数表示值。
下面是我们删除所有偶数值和以字符开头的键的示例t
。
// remove all entries with even number values
map.removeWhere((key, value) => value % 2 == 0);
print(map);
//remove all entries with key starting with 't'
map.removeWhere((key, value) => key.startsWith('t'));
print(map);
创建Map以包含特定数据类型
Dart和Flutter数据结构使用教程:你可以创建一个包含已定义数据类型的键和值的映射。例如,你可能希望Map包含String
数据类型和int
值的键。下面是一个示例,说明如何进行此操作:
Map<String, int> map = {'first': 10, 'second': 20, 'third': 30, 'fourth': 40};
map['fifth'] = 50;
print(map);
map['sixth'] = "value" // error
// defining the data types via the constructor
var secondMap = Map<String, int>();
secondMap['name'] = 5;
print(secondMap); // {name: 5}
secondMap['age'] = 'six'; //error
在上面的示例中,添加数据类型的值String
将引发错误,因为我们已指定int
在映射中仅接受值。
遍历Map
你可以使用forEach()
或for...in
技术遍历Map。
// iterating through the keys of the map using the for...in loop to print all the values
for(String k in map.keys){
print(map[k]);
}
在for...in
上面的循环示例中,我们遍历映射中的键。对于每次迭代,我们获取每个键,将其存储在变量 中k
,并使用它来打印Map中的值。
该forEach()
方法采用一个双参数函数,正如我们在该removeWhere()
方法中所描述的那样。使用该forEach()
方法可以同时对键和值执行操作。
map.forEach((key, value) {print("key = $key, value = $value");});
// key = ten, value = 10
// key = eleven, value = 11
// key = twelve, value = 12
// key = thirteen, value = 13
// key = fourteen, value = 14
// key = fifteen, value = 15
// key = sixteen, value = 16
Flutter 数据结构:Set
集合是独特项目的集合。与列表不同,集合是特别独特的数据结构,可确保集合中不存在重复值。
集合的一个非常常见的现实用例是检查两个集合的交集。也就是说,你可以有效地获取两个集合共有的元素。这是一个片段,说明了这一点:
Set set = {1,2,3,4,5, 6};
Set other = {5,6,7,8};
print(set.intersection(other)); // {5,6}
集合最适合存储唯一值,其顺序作为变量不是必需的。以下是我们将详细介绍的三种类型的集合:
HashSet
这种类型的集合没有指定的迭代顺序。的hashcode
和equalTo()
方法确定该组中的项目的顺序。HashSet
当插入顺序不重要,并且你想要存储唯一值时最适合。
LinkedHashSet
该集合根据项目插入的顺序存储数据——因此,如果你先插入项目 A,然后再插入项目 B,那么在迭代该集合时,你肯定会在 B 之前获得 A。当使用其文字创建集合的实例时,它是应用的默认实现。它还接受空值。
SplayTreeSet
的默认操作SplayTreeSet
是存储可比较的数据。例如,如果你插入数值,则SplayTreeSet
默认情况下对它们进行排序;插入一个字符串和一个数值会抛出一个错误,因为它无法将它们相互比较。
同样,插入空值将引发错误。SplayTreeSet
当你想以你确定的形式存储数据时,也可以使用。你还可以通过在构造函数中传递一个比较函数来指定项目应该如何比较。
让我们比较所有三种集合类型:
HashSet | LinkedHashSet | SplayTreeSet |
在插入、检索和删除数据时,它的复杂度为 O(1)。 | 在插入、检索和删除数据时,它的复杂度为 O(1)。 | 在插入、检索和删除数据时,它的复杂度为 O(log(n))。 |
允许空值。 | 允许空值。 | 不允许null 值。 |
它使用哈希码和equalTo() 方法来比较项目。 | 它使用哈希码和equalTo() 方法来比较项目。 | 它使用Comparable.compareTo() 方法来比较项目。 |
现在,让我们来看看使用集合执行 CRUD 操作。
Dart和Flutter数据结构用法示例:创建一个集合
你可以通过其实现者的任何构造函数或文字创建一个集合。
// Creating an empty hashSet.
Set hashSet = HashSet();
// creating an empty splayTreeSet
var splayTreeSet = SplayTreeSet();
// creating an empty linked hash set
Set linkedHashSet = LinkedHashSet();
// creating an empty linked hash set by literal.
Set literalSet = {};
// creating a linked hash set of integer values.
var intSet = <int> {2,3,4,5};
向集合中添加元素
Set 有一个add()
方法可以用来插入数据。当你尝试添加集合中已存在的值时,此新值将被忽略。该集合将此新值与其中已存在的值进行比较,并在添加之前检查其数据中没有任何数据等于此新值。
此方法返回一个bool
值——true
如果添加了数据并且false
新数据是重复的。
var set = <int> {2,3,4,5};
print(set); // {2, 3, 4, 5}
// adding a unique item
print(set.add(1)); // true
print(set); // {2, 3, 4, 5, 1}
// adding a duplicate item
print(set.add(2)); // false
print(set); // {2, 3, 4, 5, 1}
更新集合中的元素
如何使用Dart和Flutter数据结构?集合数据结构没有更新其数据的默认方法。这是因为修改集合中的值可能会改变集合的迭代顺序。例如,如果你使用的是 a LinkedHashSet
,则可以在集合的末尾添加新值。这会根据你插入数据的方式更改已定义的数据顺序。
但是,你可以使用其 map 方法更新集合中的值。此更新创建了一个新Set
实例,你必须将其存储在变量中以供进一步参考。
var set = <int> {1,2,3,4,5,6,7,8,9};
print(set); // {1, 2, 3, 4, 5, 6, 7, 8, 9}
// updating the value 4 to 11
var newSet = set.map((e) => e == 4 ? 11 : e).toSet();
print(newSet); // {1, 2, 3, 11, 5, 6, 7, 8, 9}
请注意,SplayTreeSet
如果使用上述技术将较低的值替换为高于集合中其余数据的值,则不必将这种特殊的排序功能应用于。
从集合中删除元素
你可以使用remove()
方法从集合中删除项目。要从集合中删除项目,请传递与要从集合中删除的数据相同的值。此方法还返回一个bool
数据类型——再次返回,true
如果要删除的值存在于集合中,则返回 ,否则返回false
。
var set = <int> {1, 2, 3, 4, 5};
print(set); // {1, 2, 3, 4, 5}
// removing an item that exists in the set
var isRemoved = set.remove(4);
print(isRemoved); // true
print(set); // {1, 2, 3, 5}
// removing an item that does not exist in the set
isRemoved = set.remove(20);
print(isRemoved); // false
print(set); // {1, 2, 3, 5}
正如我们对 map 和 list 所做的那样,你也可以从集合中删除满足指定条件的一个或多个项目。这可以使用removeWhere()
方法完成,操作类似于我在上面说明的列表和Map部分中描述的方式。
迭代集合
你可以使用for...in
循环或forEach()
方法对集合进行迭代。
var set = <int> {1,2,3,4,5};
// iterating a set using the for...in loop
for(int value in set){
print(value);
}
//iterating a set using the forEach
set.forEach((element) {print(element);});
// Using the iterator object
var iterator = set.iterator;
while(iterator.moveNext()){
print(iterator.current);
}
Dart 数据结构:栈
Dart和Flutter数据结构使用教程:栈是一个抽象的集合,它以有序的顺序存储数据。栈中只有一个入口点和出口点。栈使用后进先出 (LIFO) 模型——最后一个进入栈的项目也是第一个离开栈的项目。
将 Dart 栈视为一堆书可能会有所帮助。你只能从栈顶部挑选一本书并将一本书添加到栈顶部。在书堆底部或中间挑选一本书需要你先取出顶部的书,然后才能找到所需的书。
需要注意的是,这里讨论的栈数据结构与Flutter Stack 小部件不同。尽管它们具有相同的底层结构,但它们在应用和操作方面有所不同。
Flutter Stack 小部件使你能够将小部件放在彼此的正上方。组合小部件显示为层,因此栈顶部的小部件位于屏幕的最前端。然后,最顶层栈下方的小部件出现在彼此的后面。如果你想了解更多信息,可以阅读 Flutter Stack 小部件。
Dart 栈操作
- push:向栈中添加一个元素的过程
- pop:从栈中删除一个项目的过程
- top:查看栈顶item而不移除的过程
当你想要执行以下操作时,你通常会应用栈:
- 执行需要你撤销/重做一项或一组任务的操作,就像在文字处理器中一样。
- 在构建计算器时评估数学表达式。
创建 Dart 栈
Dart 使用外部包来实现栈数据结构。在终端上运行以下命令以安装栈包:
dart pub add stack
// create a dynamic stack to hold data of any type
Stack dynamicStack = Stack();
// create a stack of int to hold int values
Stack<int> intStack = Stack();
让我们探索如何在栈上执行操作。
如何使用Dart和Flutter数据结构:将项目推送到 Dart 栈
kStack<int> intStack = Stack();
// pushing items into the stack.
intStack.push(3);
intStack.push(4);
intStack.push(5);
// printing the values of the items in the stack
intStack.print();
// 5
// 4
// 3
从 Dart 栈中弹出一个项目
Stack<int> intStack = Stack();
// pushing items into the stack.
intStack.push(3);
intStack.push(4);
intStack.push(5);
// printing the length of the stack
print(intStack.length); // 3
// popping the element at the top of the stack
print(intStack.pop()); // 5
print(intStack.length); // 2
检查栈是否包含一个值
Dart和Flutter数据结构用法示例:你可以使用contains()
方法检查栈中是否存在值。true
如果值存在,则此方法返回,false
否则返回。
Stack<int> intStack = Stack();
// pushing items into the stack.
intStack.push(3);
intStack.push(4);
intStack.push(5);
// checking that an existent value exists in the stack
print(intStack.contains(4)); // true
// checking that a non-existent value exists in the stack
print(intStack.contains(8)); // false
该contains()
方法也存在于列表和队列数据结构中。队列与栈非常相似,不同之处在于它使用先进先出模型 (FIFO)。
如果你曾经使用过队列,就会注意到我们在列表和栈部分中说明的一些操作,例如add()
, remove()
, removeWhere()
, Iterator
, forEach()
, 的elementAt()
工作方式几乎相同。
选择正确的数据结构
在编写算法来解决复杂的任务时,你可能面临的一个决定是选择正确的数据结构来解决你的问题。有时可能有多种数据结构可以完成工作,但你必须考虑每种数据结构选择解决任务所需的复杂性和计算能力。
选择数据结构时要考虑的最大因素之一涉及程序中使用的算法的复杂性。你应该首先确定算法中最常用的操作:你会进行大量插入、删除或检索吗?如果你执行多次插入并从集合中检索数据,并且你特别关注唯一值,则可能需要考虑映射或集合。
以下是在数据结构中执行的操作复杂性的摘要,以指导你选择下一个应用程序:
数据结构 | 插入 | 移除 | 包含 |
List | O(1) | O(n) | O(n) |
LinkedHashSet | O(1) | O(1) | O(1) |
SplayTreeSet | O(log n) | O(log n) | O(log n) |
Map | O(1) | O(1) | O(1) |
Dart和Flutter数据结构使用教程结论
对数据结构的扎实理解是每个软件开发人员的基本要求。在本文中,我们探讨了 Dart 和 Flutter 中一些最常用数据结构的 CRUD 操作,以及在为算法选择合适的数据结构时应考虑的事项。有关更多信息,你可以查看官方文档。