strings.Count beantwortet eine einzige, dafür sehr häufige Frage: Wie oft kommt substr in s vor? Der Rückgabewert ist die Anzahl der nicht-überlappenden Treffer — sobald ein Vorkommen gefunden wurde, setzt die Suche hinter diesem Treffer fort, nicht ein Zeichen weiter. Das macht Count zur natürlichen Wahl für simple Statistiken: Wie viele Zeilenumbrüche enthält ein Text? Wie oft taucht ein Trenner auf? Wie häufig kommt ein Wort vor? Für reine Existenz-Checks reicht weiterhin Contains — Count lohnt sich, wenn die konkrete Zahl gebraucht wird.
Die Signatur ist denkbar schmal: zwei Strings rein, ein int raus. Es gibt keine Optionen, keine Limits, kein Locale — Count arbeitet rein byteweise auf der UTF-8-Repräsentation und liefert die Trefferzahl als positive Ganzzahl (oder Null, wenn nichts gefunden wurde).
package main
import (
"fmt"
"strings"
)
// func Count(s, substr string) int
func main() {
fmt.Println(strings.Count("banana", "an")) // 2
fmt.Println(strings.Count("banana", "na")) // 2
fmt.Println(strings.Count("banana", "x")) // 0
}2
2
0s ist der zu durchsuchende String, substr das Muster — Reihenfolge wie bei allen anderen strings-Funktionen. Beide Argumente können beliebige Bytes enthalten, auch Steuerzeichen oder Multi-Byte-UTF-8.
Das wichtigste Detail von Count: gefundene Vorkommen überschneiden sich nicht. Nach einem Treffer rückt der interne Cursor um die volle Länge von substr weiter — nicht nur um ein Byte. Wer überlappende Treffer braucht (z. B. bei "aa" in "aaaa"), muss strings.Index in einer eigenen Schleife mit Schritt 1 verwenden.
package main
import (
"fmt"
"strings"
)
func main() {
// "aaaa" enthaelt "aa" rein lexikalisch dreimal (Pos 0, 1, 2)
// Count zaehlt aber nicht-ueberlappend:
fmt.Println(strings.Count("aaaa", "aa")) // 2, nicht 3
// Wer Ueberlappung will, braucht manuelle Loop:
overlap := func(s, sub string) int {
n := 0
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
n++
}
}
return n
}
fmt.Println(overlap("aaaa", "aa")) // 3
}2
3Visualisiert: in aaaa greift Count bei Position 0 (aa..) und Position 2 (..aa) — die Position 1 wird übersprungen, weil der Cursor nach dem ersten Treffer auf Byte 2 weitergesetzt wird. Diese Semantik ist konsistent mit strings.Split und strings.Replace.
Wird Count mit einem leeren substr aufgerufen, liefert die Funktion einen scheinbar willkürlichen, in Wahrheit aber wohldefinierten Wert: die Anzahl der Runen plus 1. Mathematisch entspricht das den Positionen zwischen den Zeichen (vor dem ersten, zwischen je zwei Zeichen, nach dem letzten). Wichtig: gezählt werden Runen, nicht Bytes — Umlaute und Emojis tragen jeweils nur einen Schritt bei.
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
func main() {
s := "abc"
fmt.Println(strings.Count(s, "")) // 4 = 3 Runen + 1
fmt.Println(utf8.RuneCountInString(s) + 1) // 4
// Mit Mehrbyte-Runen wird der Unterschied zu len() deutlich:
t := "äöü" // 3 Runen, 6 Bytes
fmt.Println(len(t)) // 6
fmt.Println(utf8.RuneCountInString(t)) // 3
fmt.Println(strings.Count(t, "")) // 4 (Rune-Anzahl + 1)
// Emoji zaehlt ebenfalls als 1 Rune:
fmt.Println(strings.Count("a😀b", "")) // 4
}4
4
6
3
4
4Dieses Verhalten ist konsistent zu strings.Split(s, ""), das genau einen Slice mit Rune-Anzahl Elementen liefert — die Trenner-Positionen sind dieselben.
Count ist intern eine schlanke Schleife über IndexByte/Index, also derselbe Mechanismus wie bei Contains. Bei Single-Byte-Substrings kommt auf gängigen Architekturen eine SIMD-beschleunigte Suchroutine zum Einsatz — das schlägt jede manuell geschriebene for-Schleife in Go deutlich. Die Komplexität ist linear in der Länge von s; substr darf beliebig kurz sein, ohne dass sich der Algorithmus prinzipiell verschlechtert.
Für 99 Prozent aller Anwendungen ist die Frage nach der Performance damit beantwortet: strings.Count ist schnell genug, und alles Eigene wird langsamer.
Es ist verlockend, eine Zählschleife „mal eben" selbst zu schreiben — pädagogisch lehrreich, in Produktion aber fast immer ein Rückschritt. Der folgende Vergleich zeigt eine naive Variante neben dem Stdlib-Aufruf: identisches Ergebnis, aber spürbar mehr Code und kein SIMD-Bonus.
package main
import (
"fmt"
"strings"
)
// Naive, nicht-ueberlappende Zaehlung — funktional korrekt,
// aber langsamer und fehleranfaelliger als strings.Count.
func naiveCount(s, sub string) int {
if sub == "" {
return len([]rune(s)) + 1 // Sonderfall nachbauen
}
n, i := 0, 0
for i+len(sub) <= len(s) {
if s[i:i+len(sub)] == sub {
n++
i += len(sub) // hinter den Treffer springen
} else {
i++
}
}
return n
}
func main() {
s := "banana banana banana"
fmt.Println(naiveCount(s, "an")) // 6
fmt.Println(strings.Count(s, "an")) // 6 — identisch, aber knapper
}6
6Das Stdlib-Count hat zusätzlich den SIMD-Vorteil bei Single-Byte-Pattern, eine getestete Edge-Case-Behandlung für leere Strings und keinen Boilerplate. In neuen Code-Reviews fällt eine handgerollte Zählschleife heute fast immer durch — strings.Count ist das richtige Werkzeug.
Ein klassischer Einsatz ist die Zeilenzahl-Schätzung über Count(text, "\n"). Das ist deutlich schneller als ein bufio.Scanner-Durchlauf und reicht für Logs, Metriken oder Fortschritts-Anzeigen vollkommen aus. Ein Detail muss man kennen: ein Text ohne abschließenden \n enthält genauso viele \n-Zeichen wie Zeilenumbrüche, aber eine Zeile mehr — je nach Definition zählt man entweder die Trenner oder die Inhalte.
package main
import (
"fmt"
"strings"
)
func zeilen(text string) int {
if text == "" {
return 0
}
n := strings.Count(text, "\n")
// Wenn der Text nicht mit \n endet, ist die letzte Zeile
// noch ohne Trenner — also +1.
if !strings.HasSuffix(text, "\n") {
n++
}
return n
}
func main() {
fmt.Println(zeilen("")) // 0
fmt.Println(zeilen("a")) // 1
fmt.Println(zeilen("a\n")) // 1
fmt.Println(zeilen("a\nb")) // 2
fmt.Println(zeilen("a\nb\n")) // 2
fmt.Println(zeilen("a\nb\nc\n")) // 3
}0
1
1
2
2
3Wer \r\n-Zeilenenden (Windows) zu erwarten hat, normalisiert vorher oder zählt \n — Carriage-Returns ohne \n sind heute selten.
Wenn für eine ganze Liste von Wörtern die Häufigkeit im selben Text bestimmt werden soll, bietet sich die Kombination map[string]int plus strings.Count an. Für jede Suchanfrage entsteht so genau ein Count-Lauf — bei wenigen Mustern (z. B. 10–100) ist das in der Praxis unschlagbar einfach. Erst wenn die Patternliste sehr groß wird, lohnt sich ein Multi-Pattern-Algorithmus wie Aho-Corasick.
package main
import (
"fmt"
"sort"
"strings"
)
func zaehleWoerter(text string, woerter []string) map[string]int {
low := strings.ToLower(text)
res := make(map[string]int, len(woerter))
for _, w := range woerter {
res[w] = strings.Count(low, strings.ToLower(w))
}
return res
}
func main() {
text := `Go ist eine Sprache von Google.
Go-Code ist lesbar, Go-Tools sind schnell, und Go-Module helfen.`
stats := zaehleWoerter(text, []string{"go", "tools", "rust"})
// stabile Ausgabe
keys := make([]string, 0, len(stats))
for k := range stats {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%-6s %d\n", k, stats[k])
}
}go 4
rust 0
tools 1Die Lowercase-Normalisierung vor dem Zählen ist ein klassischer Stolperstein: ohne sie würden „Go" und „go" als unterschiedliche Patterns gezählt — fast immer nicht gewollt.
Nicht-überlappend
Count springt nach jedem Treffer um len(substr) weiter — Count("aaaa", "aa") ergibt deshalb 2, nicht 3.
Leerer Substring zählt Runen
Count(s, "") liefert utf8.RuneCountInString(s) + 1 — also Rune-Grenzen, nicht Bytes.
Stdlib schlägt Handarbeit
Eingebautes Count nutzt IndexByte/SIMD und ist verlässlich schneller als jede manuell geschriebene Schleife.
Positionen brauchen Index-Loop
Count liefert nur die Anzahl — für die konkreten Positionen ist eine eigene Index-Schleife oder IndexAll-Pattern nötig.
Threadsafe by Design
Strings sind in Go unveränderlich; Count ist damit ohne Synchronisation aus mehreren Goroutines aufrufbar.
Viele Patterns? Aho-Corasick
Für hunderte gleichzeitig gesuchter Substrings rentiert sich ein Multi-Pattern-Automat statt vieler Count-Läufe.
UTF-8-sicher
Da UTF-8 selbstsynchronisierend ist, schneidet die byteweise Suche niemals fälschlich mitten in eine Rune.
Nur 0/!=0 nötig? Contains
Wenn die exakte Zahl egal ist und nur die Existenz interessiert, ist strings.Contains semantisch klarer und ebenso schnell.