你好,游客 登录
背景:
阅读新闻

第5章 Scala基本数据结构---Iterator&HashMap&Tuple&String

[日期:2021-09-26] 来源:  作者: [字体: ]

本文来自艾叔编著的《零基础快速入门Scala》免费电子书,添加文末艾叔微信,获取完整版的PDF电子书

5.4  Iterator(迭代器)

1. 迭代器是什么

Iterator迭代器提供了一种访问集合类的标准方法,ArrayArraBufferList等都提供了迭代器(Iterator),来遍历集合中的元素。因此,如果要访问集合元素,使用迭代器即可,而不需要向使用者暴露具体的集合实现。

2. 为什么使用迭代器

如果要在一个函数中访问某个集合,通常的方法是将此集合作为参数传递进去,然后遍历此集合,这样做的缺点是,每种集合需要定义一个专门的函数(需要传递具体的集合,参数不一样),既麻烦,又不好扩展,还容易出错。如果使用迭代器,只需要定义一个函数,传递的参数使用迭代器,就可以访问ArrayArraBufferList等集合。

很多的函数库提供的接口中,都使用了Iterator,因此,无论是从自身编码,还是使用别人的代码来说,Iterator都是非常重要的。

3. 迭代器基本使用

下面的例子,演示如何通过Iterator来遍历集合中的每个元素。

1行,定义一个Array并赋初始值;

2行,获得ariteratoriterator初始时,会指向ar的第一个元素;

4~6行,while循环,it.hasNext用于判断当前迭代器是否还有元素,如果有,返回true,执行第5行,it.next()用于获取迭代器所指向的当前元素,同时,移动迭代器指针,指向下一个元素。

      1 val ar = Array(1,2,3,4,5)

      2 val it = ar.iterator

      3

      4 while(it.hasNext){

      5   println(it.next())

      6 }

4. 迭代器只能使用一次

迭代器一旦调用next,指针就会向后移动,之前的元素就无法再访问了,因此,使用迭代器,每个元素只能被访问一次。

下面的例子,第3行,使用了一个新的变量newIt来保存迭代器it的值,当it遍历完ar所有元素后,newIt再来遍历,没有输出,说明it所所指向的迭代器已经记录了遍历的位置,即使使用newIt来遍历,也依旧是从it遍历后的位置开始。

      1 val ar = Array(1,2,3,4,5)

      2 val it = ar.iterator

      3 val newIt = it

      4

      5 while(it.hasNext){

      6   println(it.next())

      7 }

      8

      9 println("*****************")

     10 while(newIt.hasNext){

     11   println(newIt.next())

     12 }

如果把第3行,修改为:

3 val newIt = ar.iterator

那么,第10~12行是有输出的,这是因为itnewIt指向了两个不同的iteratorar.iterator每次调用,都会生成新的iterator

5.5  HashMap

1. HashMap是什么

HashMap中存储的keyvalue的键值对,其中value表示要存储的数据,key标识value身份。例如,学生的学号和姓名就是一个键值对,学号是学生的唯一标识,是key,姓名则是value。在整个HashMap中,key是唯一的,不可以重复,value可以重复。使用key,在HashMap中可以快速获取到该key所对应的Value

HashMap在编程中的非常重要,与ArrayBufferArray相比:

  • 它的key可以是Int,也可以是String等,可以表示多种形式的key->value,更灵活,而ArrayBufferArray只能通过Int下标来索引;
  • ArrayBufferArray如果下标不连续的话,就会出现空洞,浪费存储空间,而HashMap不要求Key连续,且紧凑存储,不会浪费空间。

2. mutable.HashMap基本使用

ScalaHashMap分为可更改的(mutable)和不可更改的(immutable)两种。

首先看mutableHashMap,例子代码演示如何创建HashMap,如何向HashMap中插入、读取数据,具体如下。

1行,import mutableHashMap

2行,创建一个mutableHashMapHashMap[Int, String]表示KeyIntValueString

3~6行,使用put方法向HashMap中插入4个键值对,Key为学号,Value是学生姓名;

8行,使用getOrElseUpdate方法,来获取Key所对应的ValuegetOrElseUpdate的第一个参数为Key,第二个参数是Default Value,如果HashMap中不存在对应的Key,则向HashMap中插入(KeyDefault Value)。

      1 import scala.collection.mutable.HashMap

      2 val stuInfo = new HashMap[Int, String]

      3 stuInfo.put(1001, "Tom")

      4 stuInfo.put(1002, "Mike")

      5 stuInfo.put(1003, "Rose")

      6 stuInfo.put(1004, "Tom")

      7

      8 println(stuInfo.getOrElseUpdate(1002, "NoName"))

      9 println(stuInfo.getOrElseUpdate(1004, "NoName"))

再来看mutable HashMap的其它操作,包括,删除Key-value,使用iterator来遍历整个HashMap

1行,使用remove来删除Key=1003的键值对;

3行,获得HashMapiterator

4~7行,使用iter.hasNext判断iterator中是否还有未遍历的元素,使用iter.next()来获取iterator中所遍历的元素,得到的元素是有KeyValue组成的一个元组:(KeyValue),使用_1可以得到元组的第一个元素,即Key,使用_2可以得到元组的第二个元素,即Value

      1 stuInfo.remove(1003)

      2

      3 val iter = stuInfo.iterator

      4 while(iter.hasNext){

      5   val stu = iter.next()

      6   println("id " + stu._1 + " name " + stu._2)

      7 }

最终的输出结果可以看到,1003的键值对没有,因为在上面使用remove删除掉了。

 

3. mutable.HashMap更新操作

可以通过Key来更新mutable.HashMapKey所对应的Value

例子代码如下:

1行,打印Key=1002的键值对的Vaule,输出应该是“Mike”;

2行,更新Key=1002的键值对的Vaule,更新后的值为“Alan”;

3行,验证,打印Key=1002的键值对的Vaule,输出是“Alan”;

      1 println(stuInfo.getOrElseUpdate(1002, "NoName"))

      2 stuInfo.put(1002, "Alan")

      3 println(stuInfo.getOrElseUpdate(1002, "NoName"))

Put可以直接调用,不需要管Key是否存在,如果不存在,就是insert插入操作,如果存在,则是update更新操作。

4. immutable.HashMap基本使用

immutableHashMap是不可更改的,也就是说HashMap一旦创建好(里面的��值对也在创建时传入HashMap),HashMap中的元素就不可用被更改,包括:向HashMap插入新的键值对,删除或更新已有的键值对。唯一能做的操作是,遍历HashMap

immutableHashMap例子代码如下,演示如何创建HashMap,以及如何遍历和访问HashMap中的元素。

1行,引入imutableHashMap

2行,创建HashMap,并且传入键值对,初始化,之后HashMap中的内容就不能再改变了;

3~4行,使用getOrElse来获取对应KeyValue,例如获取Key=1002Value,获取Key=1004Value,如果对应的Key没有Value,也就是说没有这个键值对,就会返回“NoName”;

6~8行,使用for循环来遍历HashMap,同样的,也可以使用iterator来遍历HashMap

      1 import scala.collection.immutable.HashMap

      2 val stuInfo = HashMap(1001->"Tom", 1002->"Mike", 1003->"Rose", 1004->"Tom")

      3 println(stuInfo.getOrElse(1002, "NoName"))

      4 println(stuInfo.getOrElse(1004, "NoName"))

      5

      6 for(stu <- stuInfo){

      7   println("id " + stu._1 + " name " + stu._2)

      8 }

5. HashSet

有些情况下,只需要判断Key是否存在,而不需要存储Key所对应的Value,例如,统计一门选修课的选课人数,则只需记录选修学生的id即可,此时,可以使用mutable.HashSet

下面的例子代码,演示了HashSet的常用操作。

1行,引入HashSet所在的package

2行,创建一个HashSet,内部暂时没有数据;

3~6行,使用+=,向HashSet插入4个数据;

9~13行,使用contains来判断HashSet中是否存在对应的id,并做不同的输出;

      1 import scala.collection.mutable.HashSet

      2 val stuInfo = new HashSet[Int]

      3 stuInfo += 1001

      4 stuInfo += 1002

      5 stuInfo += 1003

      6 stuInfo += 1004

      7

      8 val id=1001

      9 if(stuInfo.contains(id)){

     10   println("id " + id + " exists")

     11 }else{

     12   println("id " + id + " does not exists")

     13 }

Mutable.HashSet中的数据是不重复的,如果向HashSet中插入相同的数据,例如在第7行后面,向stuInfo再插入1001,也不会报错,然HashSet中不会出现21001

HashSet同样有immutable的,一旦创建就不可更改。

6. 其它要注意的地方

1)HashMapMap关系:1. HashMapMap的子类,Map的功能HashMap都有;2. HashMap支持序列化,可用于Spark传参,Map不支持序列化,不能用于Spark传参。结论:HashMap可以替换Map

2)有关数据类型的性能比较,我们可以参考下面��链接:https://docs.scala-lang.org/overviews/collections/performance-characteristics.html

5.6  Tuple(元组)

Scala可以非常方便地,将不同的数据类型聚合在一起,构成一个复合类型,这个复合类型称为Tuple(元组)。

元组声明非常简单,使用小括号()表示元组,括号内,依次是各种类型,例如(Int, String, Int),就是一个3元组,包括3种数据,依次是IntStringInt类型。

Tuple的例子代码如下,首先声明一个3元组,直接用括号+值即可,值之间用逗号隔开,元组内不需要指定类型,Scala会根据值,进行推断。

scala> val tomInfo = (1001, "Tom", 18)

tomInfo: (Int, String, Int) = (1001,Tom,18)

使用下划线_+序号,可以访问元组中对应序号的元素,序号从1开始,最左边第一个为_1,向右依次增长。

scala> println("id " + tomInfo._1 + " name " + tomInfo._2 + " age " + tomInfo._3)

id 1001 name Tom age 18

Scala的元组使用非常方便,但是,Scala元组从本质上讲,是一个复合对象,因此,相对基本数据类型来说,有额外的开销。在性能关键处,特别是涉及数据传参,数据高频次,大量使用的场合,从性能的角度,并不推荐使用元组,而是应尽量使用基本数据类型,典型的二元组(Int, Int),从效率上讲就不如两个Int合并成Long

元组内的值是不可修改的,例如,下面的操作就会报错。

scala> tomInfo._1 = 1002

即使声明tomInfo时,使用var,仍然会报错。

5.7  String

String类型表示字符串,Scala并没有单独实现String类,而是直接继承了JavaString实现。

1. 声明String引用

下面的代码声明了一个String引用st,声明时不需要指明st的类型为String,因为Scala会根据“Hello World!”自动推断str的类型为String

scala> val str = "Hello World!"

2. 字符串比较

使用equals来判断两个字符串是否相等。

scala> print(str.equals("Hello World!"))

true

Scala中,使用==也可以判断字符串是否相等,但是,在Java==,判断的是两个引用的地址是否相等,而不是字符串的值是否相等。因此,建议使用equals来判断字符串是否相等,这个是通用的,不管使用Scala还是Java,都可以,不建议使用==

==Scala中实际上是str的一个函数,str==Hello World!,是str.==(Hello World!)的简写。

3. 字符串拼接

使用+号,可以将两个字符串拼接起来,构成一个新的字符串。

声明String引用str1

scala> val str1 = "Hello "

str1: String = "Hello "

声明String引用str2

scala> val str2 = "World!"

str2: String = World!

使用+号,将两个字符串拼接起来。

scala> val str = str1 + str2

str: String = Hello World!

也可以使用concat函数达到相同的目的。

scala> val str = str1.concat(str2)

str: String = Hello World!

4. 遍历字符串的每个字符

可以使用for循环来遍历字符串的每个字符。

scala> for(c <- str) println(c)

也可以使用map来操作字符串中的每个字符,例如将str中的所有字符变成大写。

scala> var newStr = str.map(c=>c.toUpper)

newStr: String = HELLO WORLD!

也可以使用filter,其中_表示字符串中的每个字符。

scala> var newStr = str.filter(_!='l')

newStr: String = Heo Word!

5. 打印字符串长度

可以使用length来获取字符串长度,所谓长度,是指字符串中所包含字符的个数。例如“Hello World!”就有12个字符。

scala> println(str.length)

12

6. 翻转字符串

使用reverse函数,可以生成一个翻转的字符串。

scala> println(str.reverse)

!dlroW olleH

7. String的内部存储

String内部使用Char数组来存储字符串,下图是String的实现源码,value是一个char数组,用来存储字符串。

 

 5-1 String源码实现

不管是中文还是英文字符,都是一个Char存储一个字符,一个Char要占用2Byte

验证代码如下,首先声明一个中英文混合的字符串。

scala> val str = "你好,Hello!"

str: String = 你好,Hello!

然后以十六进制形式,输出每个字符的值。

scala> for(c<-str)print(c.toHexString + " ")

4f60 597d ff0c 48 65 6c 6c 6f 21

可以看到前面的3个字符“你好,”,每个字符占用了2Byte,而后面的英文字符则只占用了一个Byte。但是,由于每个字符都是一个Char,一个Char占用2Byte,因此,即使后面的英文字符每个只需要1Byte,但仍然占用了2Byte的空间。因此,上面的字符串至少占用了2*9=18字节的空间。

如果整个字符串都是英文字符,使用String会造成很大的空间浪费,此时,可以将String转成Byte,节省空间。

使用getBytes,可以将String存储为指定字符集的Bytes数组。

所谓字符集,是指字符在计算机中的表示形式,例如,最ascii码就是最常见的字符集,字符集可以看做一张映射表,表有两列,第一列是各种字符,第二列则是各种数字,每个字符使用一个唯一的数字来表示,例如数字32表示空格,97表示字母a等等;

目前,最常用的字符集是UTF-8,它使用1个字节来表示英文字符,使用3个字节来表示中文字符;

String内部存储所使用的字符集是unicode,不管中文还是英文字符,都使用2个字节来表示。

如下,getBytes返回的是将str转换为UTF-8字符集编码的Bytes数组,根据UTF-8的规则,每个中文字符使用3Byte,每个英文字符使用1Byte,总共有:3*3+6=15个字符。

scala> val strBytes = str.getBytes

strBytes: Array[Byte] = Array(-28, -67, -96, -27, -91, -67, -17, -68, -116, 72, 101, 108, 108, 111, 33)

getBytes可以指定字符集,例如,返回使用gb2312字符集编码的Bytes数组,只需要在getBytes传入字符集gb2312即可,如果传入的字符集是错误的,或者是不支持的,Scala会报错提示。

如下所示,每个中文字符使用2Byte,每个英文字符使用1Byte,总共有2*3+6=12个字符,比前面的UTF-8更节省空间。

scala> val strBytes = str.getBytes("gb2312")

strBytes: Array[Byte] = Array(-60, -29, -70, -61, -93, -84, 72, 101, 108, 108, 111, 33)

反过来,String也可以根据指定的字符集,来解析Byte数组,构成新的字符串。只需要在创建String时,传入Byte数组和字符集即可。

scala> val newStr = new String(strBytes, "gb2312")

newStr: String = ���好,Hello!

8. 去除String前后的空格

使用trim函数可以去除字符串前后空格,例子代码如下,Hello前后都有空格,使用trim之后,最终的字符串长度是5,即Hello的长度,说明前后的空格都被去掉了。

scala> println("   Hello  ".trim().length)

5

有关String还有一个非常重要的知识点:正则表达式,它用来对字符串进行各种处理,此部分内容将放置到Scala I/O接口之后。

 

加艾叔微信,加入Linux(Shell+Zabbix)、大数据(Spark+Hadoop)、云原生(Docker+Kubernetes)技术交流群

 

 

关注艾叔公众号,获取更多一手信息

 

 

 



收藏 推荐 打印 | 阅读:
相关新闻