Сразу после функции разбора файла RDB, упомянутой в прошлый раз, после того, как этап анализа данных завершен, следующий вопрос заключается в том, как сохранить проанализированные данные.Redis имеет несколько типов данных, строку, хеш, список, zset, набор, о чем я думал План состоит в том, чтобы определить структуру данных для каждого типа данных и сохранить данные в разные структуры данных в соответствии с разными типами данных, но этот подход приводит к большому количеству избыточного кода. Код для запуска такой:
type Rdb struct {
… // 其他属性
strObj map[string]string
hashObj map[string]map[string]string
…// 其他结构体定义
}
// 保存string的函数
func (r *Rdb) saveStrObj(redisKey string, redisVal string) {
r.strObj[redisKey] = redisVal
}
// 保存hash的函数
func (r *Rdb) saveHashObj(redisKey string, hashField string, hashVal string) {
item, ok := r.hashObj[redisKey]
if !ok {
item = make(map[string]string)
r.hashObj[redisKey] = item
}
item[hashField] = hashVal
}
В этом методе много избыточного кода, например, нужно написать два набора похожих кодов для сохранения строки и хеш-структуры, а при инициализации Rdb-структуры нужно инициализировать все структуры, а затем передать в Rdb.В функции инициализации, например:
strObj := make(map[string]string)
hashObj := make(map[string]map[string]string)
rdb := &Rdb{…, strObj, hashObj}
Такой код громоздок в написании и не прост в сопровождении, а если его использовать в проекте с большим количеством типов данных, такой код будет выглядеть возмутительно. Например, в этой практике данные redis представляют собой все пары ключ-значение, тип ключа фиксированный — строка, но тип значения имеет различные типы, такие как карта, строка и т. д., поэтому я подумал о существует ли общий тип. Этот метод может помочь в достижении желаемой функциональности.
универсальное программирование
Общее программирование — это стиль или парадигма языков программирования. Обобщения позволяют программистам, пишущим код на строго типизированных языках программирования, использовать типы, указанные позже, указывая эти типы в качестве параметров во время создания экземпляра. (Из Википедии)
Просто поймите, общее программирование относится к программированию не для определенного типа, метод не для каких-то конкретных типов данных, но действителен для большинства типов данных.
Например, для разработки функции сложения не только поддерживается сложение целых чисел, но также может быть реализовано сложение с плавающей запятой, строк, массивов и т. д.
Прежде чем я начну знакомить вас с универсальной программной реализацией языка Go, я хотел бы поговорить об универсальной реализации языка C. Опять же, язык C мне нравится больше всего.
Общая реализация на языке C
На примере функции обмена переменными реализация на языке C реализована нетипизированным указателем void *, см. следующий код:
// 交换函数,泛型实现版本
void swap(void *p1, void *p2)
{
size_t size = (sizeof(p1) == sizeof(p2)) ? sizeof(p1) : -1;
char temp[size];
memcpy(temp, p1, sizeof(p1));
memcpy(p1, p2, sizeof(p2));
memcpy(p2, temp, sizeof(temp));
}
Затем с помощью универсальной версии функции подкачки проверьте ее, выполнив замену целых чисел, чисел с плавающей запятой и строк:
int main()
{
int a = 1;
int b = 42767;
swap(&a, &b);
float f1 = 1.234;
float f2 = 2.345;
swap(&f1, &f2);
char str1[6] = "hello";
char str2[10] = "world ooo";
swap(str1, str2);
printf("a: %d, b: %d\n", a, b);
printf("f1: %f, f2: %f\n", f1, f2);
printf("str1: %s, str2: %s\n", str1, str2);
}
Результат после компиляции и выполнения следующий:
Ключом к реализации универсальной версии функции обмена являются функции void * и memcpy, которые являются операцией копирования памяти, поскольку данные хранятся в памяти в двоичном виде, пока тип операции обмена то же самое, то будет скопирован тип, занятый memcpy.size data, чтобы добиться такого же типа обмена данными. Следует отметить, что универсальное программирование на языке C небезопасно, например, в этой функции обмена, если выполняется обмен данными разных типов, например, обмен типами short и int:
short a = 1;
int b = 5;
swap(&a, &b);
Этот вызов не сообщит об ошибке и может быть выполнен, но результат обмена зависит от порядка байтов в системе.Этот обмен не имеет смысла и требует от программиста дополнительных проверок и специальных оценок.
Дженерики в Go
В языке Go нет настоящего универсального типа, его универсальный тип реализуется с использованием характеристик интерфейса {}, поскольку интерфейс {} также является типом, и до тех пор, пока методы в интерфейсе {} реализованы, они могут принадлежать к тому же типу Тип, пустой интерфейс {} не имеет никаких методов, тогда любой тип может использоваться как один и тот же класс (это немного похоже на объект Java, суперкласс всех классов).
interface{}
interface{} — это тип языка Go, который можно понимать как тип интерфейса в Java. В языке Go interface{} определяет набор методов. Пока реализован метод, заданный в interface{}, можно сказать быть реализует этот интерфейс. Тип interface{} языка Go — это статический тип данных, который проверяется во время компиляции, но он также является динамическим типом данных, поскольку его можно использовать для хранения нескольких типов данных.
Интерфейс{} языка Go обеспечивает своего рода утиную типизацию, которая используется как динамические типы данных в PHP, но если вы попытаетесь использовать интерфейс{} с объявлениями других методов для сохранения int, скомпилируйте Устройство все равно сообщит об ошибке ошибка.
Взяв код в начале в качестве примера, что происходит с кодом после использования interface{} вместо этого?
Определите структуру RedisObject, которая содержит объект Redis, сохраните тип объекта, занятую длину, значение объекта, а значение использует пустой тип интерфейса{}:
type RedisObject struct {
objType int
objLen int
objVal interface{}
}
При сохранении значения просто присвойте значение непосредственно RedisObject:
func (r *Rdb) saveStrObj(redisKey string, strVal string) {
redisObj := NewRedisObject(RDB_TYPE_STRING, r.loadingLen, strVal)
r.mapObj[redisKey] = redisObj
}
func (r *Rdb) saveHash(hashKey string, hashField string, hashValue string) {
item, ok := r.mapObj[hashKey]
if !ok {
tmpMap := make(map[string]string)
item = NewRedisObject(RDB_TYPE_HASH, 0, tmpMap)
r.mapObj[hashKey] = item
}
item.objVal.(map[string]string)[hashField] = hashValue
}
Для строкового типа его значение представляет собой простую строку, которую можно присвоить с помощью оператора r.mapObj[redisKey] = redisObj, в то время как хэш-объект относительно сложен.Сначала проверьте, является ли ключ hashKey допустимым объектом, если нет, вам нужно создать новый хеш-объект. При сохранении вам нужно преобразовать objVal (тип интерфейса{}) в объект пары "ключ-значение", а затем присвоить значение. Конкретный код objVal.(map[string]string) , что означает, что анализирует objVal типа interface{} в значение типа map[string][string].
утверждение типа
Вышеупомянутая технология преобразования типов objVal называется утверждением типа, которое представляет собой технологию преобразования между типами.В отличие от преобразования типов утверждение типа выполняется между интерфейсами.
грамматика
<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言
<目标类型的值> := <表达式>.( 目标类型 )  //非安全类型断言
Если утверждение не выполняется, возникает паника. Чтобы предотвратить слишком много паники, перед утверждением необходимо сделать определенные суждения. В этом разница между безопасными и небезопасными утверждениями. Утверждения безопасного типа могут получать логические значения для определения успешно ли утверждение.
Кроме того, вы также можете получить конкретный тип переменной через t.(type).
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
Суммировать
Благодаря этой небольшой практике, в дополнение к лучшему пониманию универсального программирования, я изучил принципы универсального программирования на языке Go и понял, что interface{} также является ярким пятном в языке Go. Суть также понятна. программы представляет собой стопку двоичных файлов внизу.Разбор данных заключается не в определении типа данных, а в том, что программа считывает соответствующие байты в соответствии с типом переменной, а затем использует различные способы ее анализа. Так называемые типы — это просто разные способы чтения памяти.
Оригинал статьи, ограниченный стиль написания, недостаток знаний и знаний, если есть неточности в статье, сообщите пожалуйста.
Если эта статья была вам полезна, ставьте лайк, спасибо ^_^
Для более интересного контента, пожалуйста, обратите внимание на личный публичный номер.