转自:https://se77en.cc/

Array(数组)

内部机制

在 Go 语言中数组是固定长度的数据类型,它包含相同类型的连续的元素,这些元素可以是内建类型,像数字和字符串,也可以是结构类型,元素可以通过唯一的索引值访问,从 0 开始。

数组是很有价值的数据结构,因为它的内存分配是连续的,内存连续意味着可是让它在 CPU 缓存中待更久,所以迭代数组和移动元素都会非常迅速。

数组声明和初始化

通过指定数据类型和元素个数(数组长度)来声明数组。

// 声明一个长度为5的整数数组
var array [5]int

一旦数组被声明了,那么它的数据类型跟长度都不能再被改变。如果你需要更多的元素,那么只能创建一个你想要长度的新的数组,然后把原有数组的元素拷贝过去。

Go 语言中任何变量被声明时,都会被默认初始化为各自类型对应的 0 值,数组当然也不例外。当一个数组被声明时,它里面包含的每个元素都会被初始化为 0 值。

一种快速创建和初始化数组的方法是使用数组字面值。数组字面值允许我们声明我们需要的元素个数并指定数据类型:

// 声明一个长度为5的整数数组
// 初始化每个元素
array := [5]int{7, 77, 777, 7777, 77777}

如果你把长度写成 ...,Go 编译器将会根据你的元素来推导出长度:

// 通过初始化值的个数来推导出数组容量
array := [...]int{7, 77, 777, 7777, 77777}

如果我们知道想要数组的长度,但是希望对指定位置元素初始化,可以这样:

// 声明一个长度为5的整数数组
// 为索引为1和2的位置指定元素初始化
// 剩余元素为0值
array := [5]int{1: 77, 2: 777}

使用数组

使用 [] 操作符来访问数组元素:

array := [5]int{7, 77, 777, 7777, 77777}
// 改变索引为2的元素的值
array[2] = 1

我们可以定义一个指针数组:

array := [5]*int{0: new(int), 1: new(int)}

// 为索引为0和1的元素赋值
*array[0] = 7
*array[1] = 77

在 Go 语言中数组是一个值,所以可以用它来进行赋值操作。一个数组可以被赋值给任意相同类型的数组:

var array1 [5]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
array1 = array2

注意数组的类型同时包括数组的长度和可以被存储的元素类型,数组类型完全相同才可以互相赋值,比如下面这样就不可以:

var array1 [4]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
array1 = array2 // 编译器会报错
Compiler Error:
cannot use array2 (type [5]string) as type [4]string in assignment

拷贝一个指针数组实际上是拷贝指针值,而不是指针指向的值:

var array1 [3]*string
array2 := [3]*string{new(string), new(string), new(string)}
*array2[0] = "Red"
*array2[1] = "Blue"
*array2[2] = "Green" array1 = array2
// 赋值完成后,两组指针数组指向同一字符串

多维数组

数组总是一维的,但是可以组合成多维的。多维数组通常用于有父子关系的数据或者是坐标系数据:

// 声明一个二维数组
var array [4][2]int // 使用数组字面值声明并初始化
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}} // 指定外部数组索引位置初始化
array := [4][2]int{1: {20, 21}, 3: {40, 41}} // 同时指定内外部数组索引位置初始化
array := [4][2]int{1: {0: 20}, 3: {1: 41}}

同样通过 [] 操作符来访问数组元素:

var array [2][2]int

array[0][0] = 0
array[0][1] = 1
array[1][0] = 2
array[1][1] = 3

也同样的相同类型的多维数组可以相互赋值:

var array1 = [2][2]int
var array2 = [2][2]int array[0][0] = 0
array[0][1] = 1
array[1][0] = 2
array[1][1] = 3 array1 = array2

因为数组是值,我们可以拷贝单独的维:

var array3 [2]int = array1[1]
var value int = array1[1][0]

在函数中传递数组

在函数中传递数组是非常昂贵的行为,因为在函数之间传递变量永远是传递值,所以如果变量是数组,那么意味着传递整个数组,即使它很大很大很大。。。

举个栗子,创建一个有百万元素的整形数组,在64位的机器上它需要8兆的内存空间,来看看我们声明它和传递它时发生了什么:

var array [1e6]int
foo(array)
func foo(array [1e6]int) {
...
}

每一次 foo 被调用,8兆内存将会被分配在栈上。一旦函数返回,会弹栈并释放内存,每次都需要8兆空间。

Go 语言当然不会这么傻,有更好的方法来在函数中传递数组,那就是传递指向数组的指针,这样每次只需要分配8字节内存:

var array [1e6]int
foo(&array)
func foo(array *[1e6]int){
...
}

但是注意如果你在函数中改变指针指向的值,那么原始数组的值也会被改变。幸运的是 slice(切片)可以帮我们处理好这些问题,来一起看看。

Slice(切片)

内部机制和基础

slice 是一种可以动态数组,可以按我们的希望增长和收缩。它的增长操作很容易使用,因为有内建的 append 方法。我们也可以通过 relice 操作化简 slice。因为 slice 的底层内存是连续分配的,所以 slice 的索引,迭代和垃圾回收性能都很好。

slice 是对底层数组的抽象和控制。它包含 Go 需要对底层数组管理的三种元数据,分别是:

  1. 指向底层数组的指针
  2. slice 中元素的长度
  3. slice 的容量(可供增长的最大值)

创建和初始化

Go 中创建 slice 有很多种方法,我们一个一个来看。

第一个方法是使用内建的函数 make。当我们使用 make 创建时,一个选项是可以指定 slice 的长度:

slice := make([]string, 5)

如果只指定了长度,那么容量默认等于长度。我们可以分别指定长度和容量:

slice := make([]int, 3, 5)

当我们分别指定了长度和容量,我们创建的 slice 就可以拥有一开始并没有访问的底层数组的容量。上面代码的 slice 中,可以访问3个元素,但是底层数组有5个元素。两个与长度不相干的元素可以被 slice 来用。新创建的 slice 同样可以共享底层数组和已存在的容量。

不允许创建长度大于容量的 slice:

slice := make([]int, 5, 3)

Compiler Error:
len larger than cap in make([]int)

惯用的创建 slice 的方法是使用 slice 字面量。跟创建数组很类似,不过不用指定 []里的值。初始的长度和容量依赖于元素的个数:

// 创建一个字符串 slice
// 长度和容量都是 5
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}

在使用 slice 字面量创建 slice 时有一种方法可以初始化长度和容量,那就是初始化索引。下面是个例子:

// 创建一个字符串 slice
// 初始化一个有100个元素的空的字符串 slice
slice := []string{99: ""}

nil 和 empty slice

有的时候我们需要创建一个 nil slice,创建一个 nil slice 的方法是声明它但不初始化它:

var slice []int

创建一个 nil slice 是创建 slice 最基本的方法,很多标准库和内建函数都可以使用它。当我们想要表示一个并不存在的 slice 时它变得非常有用,比如一个返回 slice 的函数中发生异常的时候。

创建 empty slice 的方法就是声明并初始化一下:

// 使用 make 创建
silce := make([]int, 0) // 使用 slice 字面值创建
slice := []int{}

empty slice 包含0个元素并且底层数组没有分配存储空间。当我们想要表示一个空集合时它很有用处,比如一个数据库查询返回0个结果。

不管我们用 nil slice 还是 empty slice,内建函数 appendlencap的工作方式完全相同。

使用 slice

为一个指定索引值的 slice 赋值跟之前数组赋值的做法完全相同。改变单个元素的值使用 [] 操作符:

slice := []int{10, 20, 30, 40, 50}
slice[1] = 25

我们可以在底层数组上对一部分数据进行 slice 操作,来创建一个新的 slice:

// 长度为5,容量为5
slice := []int{10, 20, 30, 40, 50} // 长度为2,容量为4
newSlice := slice[1:3]

在 slice 操作之后我们得到了两个 slice,它们共享底层数组。但是它们能访问底层数组的范围却不同,newSlice 不能访问它头指针前面的值。

计算任意 new slice 的长度和容量可以使用下面的公式:

对于 slice[i:j] 和底层容量为 k 的数组
长度:j - i
容量:k - i

必须再次明确一下现在是两个 slice 共享底层数组,因此只要有一个 slice 改变了底层数组的值,那么另一个也会随之改变:

slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
newSlice[1] = 35

改变 newSlice 的第二个元素的值,也会同样改变 slice 的第三个元素的值。

一个 slice 只能访问它长度范围内的索引,试图访问超出长度范围的索引会产生一个运行时错误。容量只可以用来增长,它只有被合并到长度才可以被访问(这句话有误,解释见蓝色字体):

slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
newSlice[3] = 45 Runtime Exception:
panic: runtime error: index out of range

容量可以被合并到长度里,通过内建的 append 函数。也就是说容量是不可以改变的,长度是可以通过append增长的

《Go并发编程实战 第一版》P55 有个很好的解释:

“我们可以把切片想象成朝向底层数组的一个窗口。这个窗口是我们查看底层数组中元素值的途径。这个值的长度就是我们当前可以看到的低成数组中的元素值的数量。而他的容量则表示我们最多看到多少个当前底层数组中的元素值”

array := [...]string{"Go","Python"."Java","C","C++","PHP"}
slice := array[:]
slice[] //会引发panic,索引值超过切片的长度。可以看到Go到C
slice = slice[:cap(slice)] //扩大了slice的长度,现在slice的长度和容留一样大.可以看到Go到PHP
slice = slice[::cap(slice)-] //slice最多看到Go到C++

slice 增长

slice 比 数组的优势就在于它可以按照我们的需要增长,我们只需要使用 append方法,然后 Go 会为我们做好一切。

使用 append 方法时我们需要一个源 slice 和需要附加到它里面的值。当 append 方法返回时,它返回一个新的 slice,append 方法总是增长 slice 的长度,另一方面,如果源 slice 的容量足够,那么底层数组不会发生改变,否则会重新分配内存空间。

// 创建一个长度和容量都为5的 slice
slice := []int{10, 20, 30, 40, 50} // 创建一个新的 slice
newSlice := slice[1:3] // 为新的 slice append 一个值
newSlice = append(newSlice, 60)

因为 newSlice 有可用的容量,所以在 append 操作之后 slice 索引为 3 的值也变成了 60,之前说过这是因为 slice 和 newSlice 共享同样的底层数组。

如果没有足够可用的容量,append 函数会创建一个新的底层数组,拷贝已存在的值和将要被附加的新值:

// 创建长度和容量都为4的 slice
slice := []int{10, 20, 30, 40} // 附加一个新值到 slice,因为超出了容量,所以会创建新的底层数组
newSlice := append(slice, 50)

append 函数重新创建底层数组时,容量会是现有元素的两倍(前提是元素个数小于1000),如果元素个数超过1000,那么容量会以 1.25 倍来增长。

slice 的第三个索引参数

slice 还可以有第三个索引参数来限定容量,它的目的不是为了增加容量,而是提供了对底层数组的一个保护机制,以方便我们更好的控制 append 操作,举个栗子:

source := []string{"apple", "orange", "plum", "banana", "grape"}

// 接着我们在源 slice 之上创建一个新的 slice
slice := source[2:3:4]

新创建的 slice 长度为 1,容量为 2,可以看出长度和容量的计算公式也很简单:

对于 slice[i:j:k]  或者 [2:3:4]

长度: j - i       或者   3 - 2
容量: k - i 或者 4 - 2

如果我们试图设置比可用容量更大的容量,会得到一个运行时错误:

slice := source[2:3:6]

Runtime Error:
panic: runtime error: slice bounds out of range

限定容量最大的用处是我们在创建新的 slice 时候限定容量与长度相同,这样以后再给新的 slice 增加元素时就会分配新的底层数组,而不会影响原有 slice 的值:

source := []string{"apple", "orange", "plum", "banana", "grape"}

// 接着我们在源 slice 之上创建一个新的 slice
// 并且设置长度和容量相同
slice := source[2:3:3] // 添加一个新元素
slice = append(slice, "kiwi")

如果没有第三个索引参数限定,添加 kiwi 这个元素时就会覆盖掉 banana。

内建函数 append 是一个变参函数,意思就是你可以一次添加多个元素,比如:

s1 := []int{1, 2}
s2 := []int{3, 4} fmt.Printf("%v\n", append(s1, s2...)) Output:
[1 2 3 4]

迭代 slice

slice 也是一种集合,所以可以被迭代,用 for 配合 range 来迭代:

slice := []int{10, 20, 30, 40, 50}

for index, value := range slice {
fmt.Printf("Index: %d Value: %d\n", index, value)
} Output:
Index: 0 Value: 10
Index: 1 Value: 20
Index: 2 Value: 30
Index: 3 Value: 40
Index: 4 Value: 50

当迭代时 range 关键字会返回两个值,第一个是索引值,第二个是索引位置值的拷贝。注意:返回的是值的拷贝而不是引用,如果我们把值的地址作为指针使用,会得到一个错误,来看看为啥:

slice := []int{10, 20, 30 ,40}

for index, value := range slice {
fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
} Output:
Value: 10 Value-Addr: 10500168 ElemAddr: 1052E100
Value: 20 Value-Addr: 10500168 ElemAddr: 1052E104
Value: 30 Value-Addr: 10500168 ElemAddr: 1052E108
Value: 40 Value-Addr: 10500168 ElemAddr: 1052E10C

value 变量的地址总是相同的因为它只是包含一个拷贝。如果想得到每个元素的真是地址可以使用 &slice[index]。

如果不需要索引值,可以使用 _ 操作符来忽略它:

slice := []int{10, 20, 30, 40}

for _, value := range slice {
fmt.Printf("Value: %d\n", value)
} Output:
Value: 10
Value: 20
Value: 30
Value: 40

range 总是从开始一次遍历,如果你想控制遍历的step,就用传统的 for 循环:

slice := []int{10, 20, 30, 40}

for index := 2; index < len(slice); index++ {
fmt.Printf("Index: %d Value: %d\n", index, slice[index])
} Output:
Index: 2 Value: 30
Index: 3 Value: 40

同数组一样,另外两个内建函数 len 和 cap 分别返回 slice 的长度和容量。

多维 slice

也是同数组一样,slice 可以组合为多维的 slice:

slice := [][]int{{10}, {20, 30}}

需要注意的是使用 append 方法时的行为,比如我们现在对 slice[0] 增加一个元素:

slice := [][]int{{10}, {20, 30}}
slice[0] = append(slice[0], 20)

那么只有 slice[0] 会重新创建底层数组,slice[1] 则不会。

在函数间传递 slice

在函数间传递 slice 是很廉价的,因为 slice 相当于是指向底层数组的指针,让我们创建一个很大的 slice 然后传递给函数调用它:

slice := make([]int, 1e6)

slice = foo(slice)

func foo(slice []int) []int {
...
return slice
}

在 64 位的机器上,slice 需要 24 字节的内存,其中指针部分需要 8 字节,长度和容量也分别需要 8 字节。

Map

内部机制

map 是一种无序的键值对的集合。map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

map 是一种集合,所以我们可以像迭代数组和 slice 那样迭代它。不过,map 是无序的,我们无法决定它的返回顺序,这是因为 map 是使用 hash 表来实现的。

map 的 hash 表包含了一个桶集合(collection of buckets)。当我们存储,移除或者查找键值对(key/value pair)时,都会从选择一个桶开始。在映射(map)操作过程中,我们会把指定的键值(key)传递给 hash 函数(又称散列函数)。hash 函数的作用是生成索引,索引均匀的分布在所有可用的桶上。hash 表算法详见:July的博客–从头到尾彻底解析 hash 表算法

创建和初始化

Go 语言中有多种方法创建和初始化 map。我们可以使用内建函数 make 也可以使用 map 字面值:

// 通过 make 来创建
dict := make(map[string]int) // 通过字面值创建
dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}

使用字面值是创建 map 惯用的方法(为什么不使用make)。初始化 map 的长度依赖于键值对的数量。

map 的键可以是任意内建类型或者是 struct 类型,map 的值可以是使用 ==操作符的表达式。slice,function 和 包含 slice 的 struct 类型不可以作为 map 的键,否则会编译错误:

dict := map[[]string]int{}

Compiler Exception:
invalid map key type []string

使用 map

给 map 赋值就是指定合法类型的键,然后把值赋给键:

colors := map[string]string{}
colors["Red"] = "#da1337"

如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对,否则会报运行时错误:

var colors map[string]string
colors["Red"] = "#da1337" Runtime Error:
panic: runtime error: assignment to entry in nil map

测试 map 的键是否存在是 map 操作的重要部分,因为它可以让我们判断是否可以执行一个操作,或者是往 map 里缓存一个值。它也可以被用来比较两个 map 的键值对是否匹配或者缺失。

从 map 里检索一个值有两种选择,我们可以同时检索值并且判断键是否存在:

value, exists := colors["Blue"]
if exists {
fmt.Println(value)
}

另一种选择是只返回值,然后判断是否是零值来确定键是否存在。但是只有你确定零值是非法值的时候这招才管用:

value := colors["Blue"]
if value != "" {
fmt.Println(value)
}

当索引一个 map 取值时它总是会返回一个值,即使键不存在。上面的例子就返回了对应类型的零值。

迭代一个 map 和迭代数组和 slice 是一样的,使用 range 关键字,不过在迭代 map 时我们不使用 index/value 而使用 key/value 结构:

colors := map[string]string{
"AliceBlue": "#f0f8ff",
"Coral": "#ff7F50",
"DarkGray": "#a9a9a9",
"ForestGreen": "#228b22",
} for key, value := range colors {
fmt.Printf("Key: %s Value: %s\n", key, value)
}

如果我们想要从 map 中移除一个键值对,使用内建函数 delete(要是也能返回移除是否成功就好了,哎。。。):

delete(colors, "Coral")

for key, value := range colors {
fmt.Println("Key: %s Value: %s\n", key, value)
}

在函数间传递 map

在函数间传递 map 不是传递 map 的拷贝。所以如果我们在函数中改变了 map,那么所有引用 map 的地方都会改变:

func main() {
colors := map[string]string{
"AliceBlue": "#f0f8ff",
"Coral": "#ff7F50",
"DarkGray": "#a9a9a9",
"ForestGreen": "#228b22",
} for key, value := range colors {
fmt.Printf("Key: %s Value: %s\n", key, value)
} removeColor(colors, "Coral") for key, value := range colors {
fmt.Printf("Key: %s Value: %s\n", key, value)
}
} func removeColor(colors map[string]string, key string) {
delete(colors, key)
}

执行会得到以下结果:

Key: AliceBlue Value: #F0F8FF
Key: Coral Value: #FF7F50
Key: DarkGray Value: #A9A9A9
Key: ForestGreen Value: #228B22 Key: AliceBlue Value: #F0F8FF
Key: DarkGray Value: #A9A9A9
Key: ForestGreen Value: #228B22

可以看出来传递 map 也是十分廉价的,类似 slice。

Set

Go 语言本身是不提供 set 的,但是我们可以自己实现它,下面就来试试:

package main

import(
"fmt"
"sync"
) type Set struct {
m map[int]bool
sync.RWMutex
} func New() *Set {
return &Set{
m: map[int]bool{},
}
} func (s *Set) Add(item int) {
s.Lock()
defer s.Unlock()
s.m[item] = true
} func (s *Set) Remove(item int) {
s.Lock()
s.Unlock()
delete(s.m, item)
} func (s *Set) Has(item int) bool {
s.RLock()
defer s.RUnlock()
_, ok := s.m[item]
return ok
} func (s *Set) Len() int {
return len(s.List())
} func (s *Set) Clear() {
s.Lock
defer s.Unlock()
s.m = map[int]bool{}
} func (s *Set) IsEmpty() bool {
if s.Len() == 0 {
return true
}
return false
} func (s *Set) List() []int {
s.RLock()
defer s.RUnlock()
list := []int{}
for item := range s.m {
list = append(list, item)
}
return list
} func main() {
// 初始化
s := New() s.Add(1)
s.Add(1)
s.Add(2) s.Clear()
if s.IsEmpty() {
fmt.Println("0 item")
} s.Add(1)
s.Add(2)
s.Add(3) if s.Has(2) {
fmt.Println("2 does exist")
} s.Remove(2)
s.Remove(3)
fmt.Println("list of all items", S.List())
}

注意我们只是使用了 int 作为键,你可以自己实现用 interface{} 作为键,做成更通用的 Set,另外,这个实现是线程安全的。

总结

  • 数组是 slice 和 map 的底层结构。
  • slice 是 Go 里面惯用的集合数据的方法,map 则是用来存储键值对。
  • 内建函数 make 用来创建 slice 和 map,并且为它们指定长度和容量等等。slice 和 map 字面值也可以做同样的事。
  • slice 有容量的约束,不过可以通过内建函数 append 来增加元素。
  • map 没有容量一说,所以也没有任何增长限制。
  • 内建函数 len 可以用来获得 slice 和 map 的长度。
  • 内建函数 cap 只能作用在 slice 上。
  • 可以通过组合方式来创建多维数组和 slice。map 的值可以是 slice 或者另一个 map。slice 不能作为 map 的键。
  • 在函数之间传递 slice 和 map 是相当廉价的,因为他们不会传递底层数组的拷贝。

Go语言—— Array,Slice,Map 和 Set的更多相关文章

  1. go - 复合类型 array, slice, map

    Go 语言支持复合类型: 数组:array 切片:slice 指针:pointer 字典:map 通道:chan 结构体:struct 接口:interface 1. array   同一类型数据的集 ...

  2. JS中集合对象(Array、Map、Set)及类数组对象的使用与对比

    原文地址 在使用js编程的时候,常常会用到集合对象,集合对象其实是一种泛型,在js中没有明确的规定其内元素的类型,但在强类型语言譬如Java中泛型强制要求指定类型. ES6引入了iterable类型, ...

  3. JS中集合对象(Array、Map、Set)及类数组对象的使用与对比(转载)

    在使用js编程的时候,常常会用到集合对象,集合对象其实是一种泛型,在js中没有明确的规定其内元素的类型,但在强类型语言譬如Java中泛型强制要求指定类型. ES6引入了iterable类型,Array ...

  4. 深度解密Go语言之Slice

    目录 当我们在说 slice 时,到底在说什么 slice 的创建 直接声明 字面量 make 截取 slice 和数组的区别在哪 append 到底做了什么 为什么 nil slice 可以直接 a ...

  5. javascript 一些函数的实现 Function.prototype.bind, Array.prototype.map

    * Function.prototype.bind Function.prototype.bind = function() { var self = this, context = [].shift ...

  6. Go语言 映射(map)

    Go语言  映射(map) 1. 什么是 map2. 创建 map3. 访问 map4. nil map和空map5. map中元素的返回值6. len()和delete()7. 测试map中元素是否 ...

  7. 003-Tuple、Array、Map与文件操作入门实战

    003-Tuple.Array.Map与文件操作入门实战 Tuple 各个元素可以类型不同 注意索引的方式 下标从1开始 灵活 Array 注意for循环的until用法 数组的索引方式 上面的for ...

  8. Array(数组)--map方法

    关于Array.prototype.map() MDN 给的定义是: 在作用数组元素的每一项上调用一个方法(callback),返回一个新数组: 使用格式:arr.map(callback[,this ...

  9. JS之Array.slice()方法

    1.Array.slice(startIndex,endIndex); 返回由原始数组从startIndex到endIndex-1的元素构成的新数组; startIndex:默认值0,如果startI ...

随机推荐

  1. djando模板----第一django模板应用

    Django模板 我们已经知道,模板函数的函数的返回值就是返回给客户端的数据,但如果返回数据很复杂,如果一个非常大的html页面,直接将页面代码固化在python脚本文件中是不合适的,当然 也可以将h ...

  2. AWS 存储服务(三)

    目录 AWS S3 业务场景 挑战 解决方案 S3的好处 S3 属性 存储桶 Buckets 对象 Object S3 特性 S3 操作 可用性和持久性 一致性 S3 定价策略 S3高级功能 存储级别 ...

  3. VS2010开发.cpp与.c的注意事项

    VS2010开发.cpp与.c的注意事项 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 强大的VS2010,正是由于vs2010的完全封装,让现在的wind ...

  4. Sping 补充完成修改功能

    1.视图层完整示例 <form action="#" th:action="@{/update/{id}(id=${user.id})}" th:obje ...

  5. js修改css属性值

    推荐用dom.style.setProperty('属性','属性值'); 例如: $("#id")[0].style.setProperty('margin-top','1px' ...

  6. 26.Spark创建RDD集合

    打开eclipse创建maven项目 pom.xml文件 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:x ...

  7. (六)Cookie 知识点总结 (来自那些年的笔记)

    如果你想要转载话,可不可以不要删掉下面的 作者信息 呀!: 作者:淮左白衣 写于 2018年4月18日18:47:41 来源笔者自己之前学javaWeb的时候,写的笔记 : 目录 如果你想要转载话,可 ...

  8. Java语言资源国际化步骤

    语言资源国际化步骤:   1. 定义资源文件(如:language),需要使用命令native2ascii命令进行转码:(native2ascii是jdk中的转码工具,在jdk的bin目录下)   2 ...

  9. Oracle常用基础语句(杂)

    打开服务 WIN + R services.msc 登录 --方法1 --WIN + R --CMD sqlplus / as sysdba --方法2,常用 --WIN + R --CMD --&q ...

  10. 【C#】上机实验一

    1.开发一个控制台应用程序,根据提示从键盘获取一个华氏温度,请转换并输出对应的摄氏温度. using System; namespace Project { class Program { publi ...