Maison  >  Article  >  développement back-end  >  Aller à l'analyse des chaînes

Aller à l'analyse des chaînes

Guanhui
Guanhuiavant
2020-06-12 18:21:233013parcourir

Aller à l'analyse des chaînes

Qu'est-ce qu'une chaîne ?

Dans Go, une chaîne est une séquence d'octets immuable (éventuellement vide). Pour nous, le maître mot ici est immuable. Étant donné que les tranches d'octets sont mutables, la conversion entre une chaîne et un []octet nécessite généralement une allocation et une copie, ce qui coûte cher.

Sous le capot, les chaînes de Go sont (actuellement) représentées comme une longueur et un pointeur vers les données de chaîne

Qu'est-ce que la chaîne résidente ?

Considérez ce code :

b := []byte("hello")
s := string(b)
t := string(b)

s et t sont des chaînes, ils ont donc tous deux des pointeurs de longueur et de données. Leurs longueurs sont évidemment les mêmes. Qu’en est-il de leurs pointeurs de données ?

Le langage Go ne peut pas nous fournir une méthode de recherche directe. Mais nous pouvons utiliser unsafe pour sonder :

func pointer(s string) uintptr {
    p := unsafe.Pointer(&s)
    h := *(*reflect.StringHeader)(p)
    return h.Data
}

(Cette fonction devrait renvoyer unsafe.Pointer. Voir le numéro Go 19367 pour plus de détails.)

Si nous fmt.Println(pointer(s), pointer ( t)), nous obtiendrons des informations similaires à 4302664 4302632. Les pointeurs sont différents ; ils ont deux copies distinctes des données bonjour.

(Ceci est un lien d'exercice. Si vous voulez l'essayer, que se passe-t-il si vous changez "bonjour" en "h" ? Explication)

Supposons que vous souhaitiez réutiliser les données uniques bonjour Copie? Il s'agit d'une résidence en chaîne. La résidence String présente deux avantages. L'avantage évident est que vous n'avez pas besoin d'attribuer et de copier des données. Un autre avantage est qu'il accélère les vérifications de l'égalité des chaînes. Si deux chaînes ont la même longueur et le même pointeur de données, elles sont égales ; il n'est pas nécessaire de vérifier les octets.

Depuis Go 1.14, Go ne conserve pas la plupart des chaînes. Comme d’autres formes de mise en cache, la persistance a des coûts : synchronisation pour la sécurité de la concurrence, complexité du garbage collector et code supplémentaire à exécuter à chaque fois qu’une chaîne est créée. Et, comme la mise en cache, il existe des situations où elle peut être plus nuisible qu’utile. Si vous traitez des mots dans un dictionnaire, où aucun mot n'apparaît deux fois, la persistance des chaînes fait perdre du temps et de la mémoire.

Persistance manuelle des chaînes

Il est possible de conserver manuellement les chaînes dans Go. Ce dont nous avons besoin, c'est d'un moyen de trouver une chaîne existante à réutiliser à partir d'une tranche d'octets, peut-être en utilisant quelque chose comme map[[]byte]string . Si la recherche réussit, la chaîne existante est utilisée ; si elle échoue, nous convertissons et stockons la chaîne pour une utilisation ultérieure.

Il n'y a qu'un seul problème ici : vous ne pouvez pas utiliser []byte comme clé pour une carte.

Grâce aux optimisations à long terme du compilateur, nous pouvons utiliser map[string]string à la place. Une optimisation ici est que les opérations de mappage dont les clés sont des tranches d'octets transformés ne génèrent pas réellement de nouvelles chaînes utilisées lors des recherches.

m := make(map[string]string)
b := []byte("hello")
s := string(b) // 分配了
_ = m[string(b)] // 不分配!

(Des optimisations similaires s'appliquent à d'autres cas où le compilateur peut prouver que la tranche d'octets convertie ne sera pas modifiée pendant l'utilisation, comme switch string(b), lorsque tous changent)

Tous le code nécessaire pour conserver la chaîne est le suivant :

func intern(m map[string]string, b []byte) string {
    // 查找一个存在的字符串来重用
    c, ok := m[string(b)]
    if ok {
        // 找到一个存在的字符串
        return c
    }
    // 没有找到,所以制作一个并且存储它
    s := string(b)
    m[s] = s
    return s
}

C'est facile

Nouvelle difficulté (concurrence) Symptômes)

Notez que cette routine manuelle est en attente pousse le problème de maintien dans le code appelant. Vous devez gérer l'accès simultané à la carte ; vous devez déterminer la durée de vie de la carte (et de tout ce qu'elle contient) et vous payez le coût supplémentaire d'une recherche de carte chaque fois que vous avez besoin d'une chaîne ;

Pousser ces décisions sur le code appelant peut donner de meilleures performances. Par exemple, disons que vous décodez json en map[string]interface{}. Le décodeur json peut ne pas être concurrent. Le cycle de vie de la carte peut être lié au décodeur json. Et les clés de cette carte sont susceptibles d'être répétées souvent, ce qui est le meilleur cas pour la résidence de chaînes ; cela rend le coût supplémentaire de recherche de carte intéressant.

Un package d'aide

Si vous ne voulez pas prendre en compte aucune de ces complications et que vous êtes prêt à accepter une légère baisse de performance et que des cordes résident là-bas, vous pourriez be Pour le code utile, il existe un package pour cela : github.com/josharian/intern.

Comment cela fonctionne est un horrible abus de sync.Pool. Il stocke les cartes des résidents dans sync.Pool, les récupérant selon les besoins. Cela résout très bien le problème de l’accès simultané, car l’accès à sync.Pool est simultanément sécurisé. Cela résout principalement le problème de durée de vie, car le contenu sync.Pool finira généralement par être récupéré. (Pour des lectures connexes sur la gestion des durées de vie, voir le numéro Go 29696.)

Tutoriels recommandés : "PHP" "Tutoriel GO"

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer