rune과 string에 대해서
rune은 char(문자)이다. 홑 따옴표(' ')로 감싸줘야 한다.
반면 string은 문자열이다. 겹 따옴표(" ")혹은 백틱(` `)로 감싸줘야 한다.
백틱으로 감싸면 이스케이프 문자가 문자 그대로 동작한다.
rune은 4바이트인 반면 string은 16바이트이다.
func main() {
str := "whawta\n thsdlkfjk;l \\t" // \\ 두 번 쓰면 이스케이프를 문자 처리
str2 := `asdf \n asdkfjd \t`
str3 := `
작성한 대로
쓰입니다.
이스케이프 그런거
없 어도 됨
`
fmt.Println(str)
fmt.Println(str2)
fmt.Println(str3)
}
rune이 필요한 이유 : 문자.
Go는 UTF-8 문자코드를 표준 문자코드로 사용합니다. 표준 문자 코드로 사용합니다.
UTF-8은 영문, 숫자를 1바이트, 한국어, 한자 등은 2~3바이트로 표현합니다.
따라서 모든 문자를 2바이트를 할당하는 UTF-16에 비해 크기를 절약할 수 있다는 장점이 있습니다.
UTF-8은 최대 한 문자에 3바이트를 사용한다고 했는데, rune은 4바이트 타입입니다.
int32와 같은 타입인데, 이름만 다를 뿐입니다.
func main() {
char := '권'
fmt.Printf("%T\n", char)
fmt.Println(char)
fmt.Printf("%v\n", char)
}
만약 4바이트가 아닌 1바이트인 슬라이스에 담으면 어떤 결과가 나올까요?
func main() {
s := "안녕" // 한글이므로 ascii로 표현 불가
bs := []byte(s) // 8bit인 byte로 컨버젼
fmt.Println(bs) // ascii 번호 반환하나 잘못됨
rs := []rune(s) // 반면 run으로 컨버전
fmt.Println(rs) // [50504 45397] 과 같은 정상적인 유니코드 포인트를 반환함
}
string과 []rune 슬라이스의 상호 변환
string 타입과 []rune 타입은 모두 문자들의 집합을 나타내기 때문에 상호 변환이 가능합니다.
문자열을 []rune 슬라이스에 담은 다음, 유니코드를 %c로 문자 출력해보면 다음과 같은 겨로가가 나옵니다.
func main() {
str1 := "hello"
str2 := []rune(str1)
fmt.Println(str2) // [104 101 108 108 111]
for _, v := range str2 {
fmt.Printf("%c\n", v) // h e l l o
}
}
문자열 순회하기 => ragne 써서 메모리를 아끼자
아 그거 대충 for 돌리던가 range로 돌리면 되는 거 아님? 이라고 생각했습니다.
그러나 실제로 코드를 돌려보면, 타입은 uint8. 즉 1바이트가 할당된 타입이 나오기 때문에 2~3바이트를 사용하는 한글을 제대로 출력하지 못하게 됩니다.
func main() {
str := "hello 세계"
for i := 0; i < len(str); i++ {
fmt.Printf("%T, %c\n", str[i], str[i]) // 한국어를 제대로 출력 안함
}
}
문자열의 문자를 하나씩 순회하려면
(1) 문자열을 []rune으로 변환 후 for문으로 출력하기 => 불필요한 배열을 생성하게 됨. 메모리 낭비임
(2) range를 사용합니다. => 바로 int32(rune) 출력
각 방법의 예시를 들어보자면 다음과 같습니다.
func main() {
str := "hello 세계"
runeStr := []rune(str) // 불필요한 배열을 생성하게 됨
for i := 0; i < len(runeStr); i++ {
fmt.Printf("%T, %c\n", runeStr[i], runeStr[i]) // int32
}
}
range를 사용하면 int32(rune)으로 타입을 순회하게 되어서 추가 메모리할당 없이 순회할 수 있게 됩니다. range 좋습니다 꼭 쓰세용
func main() {
str := "hello 세계"
for _, v := range str {
fmt.Printf("%T, %c\n", v, v) // int32
}
}
문자열의 부분 바꾸기
String은 불변입니다. 따라서, 아래와 같이 일부 바꾸기가 불가능합니다.
굳이 바꾸고 싶다면, rune 슬라이스로 바꾼다음에 변화시킨 다음 다시 merge 시키는 작업을 해야 합니다...
func main() {
str := "my golang code is awesome"
str[0] = "y" // Error
}
string 변수의 대입과 문자열의 복사
string 구조체는 Len과 Data로 이루어져 있습니다.
string을 변수를 또 다른 변수에 대입하면 Len과 Data만 복사되어, 같은 메모리를 가리키게 됩니다.
즉, string 16바이트만 메모리에 할당될 뿐, 전체 문자열이 복사되지 않는다는 거죠.
func main() {
str := "asdf"
str2 := str // str의 문자열을 통째로 복사하는게 아님. Data와 Len값만 복사합니다.
// 문자열의 포인터 얻어내기
// unsafe.Pointer(&str)는 문자열의 포인터를 반환합니다 0xc000116050
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&str))
strHeader2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))
// 출력해보면 str과 str2의 Data값이 같다.
fmt.Println(strHeader.Data) // &{17593743 4}. Data가 17593743 Len이 4
fmt.Println(strHeader2.Data) // &{17593743 4}. Data가 17593743 Len이 4
}
문자열을 효율적으로 합치기
java를 작성하다보면, String을 사용하기보다 StringBuilder를 사용할 것을 종종 권유받습니다.
go에서도 마찬가지인데, string의 합 연산은 새로운 메모리 공간을 만들어서 두 문자열을 합치기 때문입니다.
func main() {
str1 := "typed is" // 메모리 할당
str2 := "cool" // 메모리 할당
str3 := str1 + " " + str2 // 합친 문자열을 또 메모리에 할당
fmt.Println(str3)
}
golang에서는 이런 문제를 해결하는 방법으로 String.Builder의 WriteRune 함수를 활용하거나, bytes.Buffer의 WriteString을 사용하곤 합니다. 이 문제에 대해선, go에서 두 문자열을 합치기 위한 구체적인 방법론을 다룬 좋은 글이 있어 첨부합니다.
cloudrain21.com/go-how-to-concatenate-strings
'Programming Language > 🐿️ Go (Golang)' 카테고리의 다른 글
Go 설치 (0) | 2021.05.14 |
---|---|
Go 패키지 & Go 모듈 (0) | 2021.05.13 |
Go Routine (2) : 내부 쓰레드 통신 Channels (0) | 2020.12.11 |
Go Routine (1) : concurrency를 위한 고루틴의 원리 및 간단한 활용 + 멀티 코어 (0) | 2020.12.11 |
Go stdlib (2) : sort (0) | 2020.12.08 |