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 ContainsCount 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).

Go signatur.go
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
}
Output
2
2
0

s 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.

Go nicht_ueberlappend.go
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
}
Output
2
3

Visualisiert: 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.

Go leer.go
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
}
Output
4
4
6
3
4
4

Dieses 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.

Go naiv_vs_count.go
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
}
Output
6
6

Das 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.

Go zeilen.go
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
}
Output
0
1
1
2
2
3

Wer \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.

Go statistik.go
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])
	}
}
Output
go     4
rust   0
tools  1

Die 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.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das strings-Paket — String-Manipulation

Zur Übersicht