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

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