strings.HasSuffix ist das Gegenstück zu HasPrefix und beantwortet eine einzige Frage: Endet der String s mit der Zeichenfolge suffix? Die Rückgabe ist ein schlichter bool — keine Position, kein neuer String, keine Allokation. Intern wird zunächst die Länge geprüft (ist suffix länger als s, kann es kein Suffix sein), dann erfolgt ein byteweiser Vergleich des Endbereichs von s mit suffix. Weil Go-Strings UTF-8-kodiert sind und der Vergleich auf Bytes operiert, ist die Funktion automatisch sicher für Mehrbyte-Zeichen: identische Byte-Sequenzen am Ende bedeuten identische Runen am Ende. Typische Einsatzgebiete sind Dateiendungs-Erkennung, MIME-Type-Heuristiken und URL-Pfad-Routing.
Die Signatur ist minimal und liest sich wie ein logisches Prädikat. Zwei Strings rein, ein Wahrheitswert raus — ohne Fehlerkanal, ohne Optionen, ohne überraschende Sonderfälle.
func HasSuffix(s, suffix string) boolDer erste Parameter ist der zu prüfende String, der zweite das gesuchte Suffix. Die Reihenfolge ist wichtig und entspricht der natürlichen Leserichtung — „hat s das Suffix suffix?".
Ein Sonderfall, der in der Praxis oft übersehen wird: Ein leeres Suffix ist immer ein gültiges Suffix jedes Strings, auch des leeren Strings selbst. Das ergibt sich mathematisch sauber aus der Definition (jeder String endet mit der leeren Sequenz) und ist konsistent mit HasPrefix("", "").
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.HasSuffix("report.pdf", ""))
fmt.Println(strings.HasSuffix("", ""))
fmt.Println(strings.HasSuffix("", ".pdf"))
}true
true
falseWenn ein Suffix dynamisch aus Konfiguration oder Nutzereingabe stammt, lohnt sich eine explizite Leer-Prüfung vorab — sonst klassifiziert die Funktion plötzlich „alles" als Match.
Die drei Suffix-Funktionen der Standardbibliothek bilden eine sinnvolle Familie. HasSuffix antwortet nur, TrimSuffix handelt nur, und CutSuffix (ab Go 1.20) kombiniert beides in einem Aufruf. Welche Variante passt, hängt davon ab, ob nach dem Check noch ein „rest" gebraucht wird.
| Funktion | Rückgabe | Modifiziert String? | Erfolg signalisiert? |
|---|---|---|---|
HasSuffix(s, suffix) | bool | nein | über Rückgabewert |
TrimSuffix(s, suffix) | string | ja, falls Suffix passt | nein — nur Ergebnis |
CutSuffix(s, suffix) | (string, bool) | ja, falls Suffix passt | über zweite Rückgabe |
Wer nur eine Bedingung in einem if braucht, greift zu HasSuffix. Wer immer trimmt (und der Original-String reichen würde, falls nichts passt), nutzt TrimSuffix. Wer sowohl wissen will, ob getrimmt wurde, als auch das Ergebnis braucht, ist mit CutSuffix am besten bedient.
HasSuffix läuft in O(len(suffix)) — also linear in der Länge des Suffix, nicht in der Länge von s. Bei typischen Dateiendungen mit drei bis fünf Bytes ist das im Bereich weniger CPU-Zyklen. Es findet keine Allokation statt, der Vergleich passiert in-place auf den zugrundeliegenden Byte-Slices.
In Hot-Paths (z. B. einem Router, der pro Request mehrere Endungen prüft) ist HasSuffix deutlich schneller als jede Regex-basierte Alternative — selbst eine kompilierte regexp.Regexp zahlt Overhead für Zustandsmaschine und Capture-Logik, den der Längen-Check hier komplett spart.
Oft soll geprüft werden, ob ein Pfad auf eine von mehreren Endungen passt — Bilder, Videos, Archive. Die idiomatische Lösung ist ein simpler for range-Loop über einen Slice von Endungen.
package main
import (
"fmt"
"strings"
)
func hatBildEndung(name string) bool {
endungen := []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
for _, e := range endungen {
if strings.HasSuffix(name, e) {
return true
}
}
return false
}
func main() {
fmt.Println(hatBildEndung("urlaub.jpg"))
fmt.Println(hatBildEndung("doku.pdf"))
fmt.Println(hatBildEndung("logo.webp"))
}true
false
trueBei einer kleinen, festen Liste ist der Loop perfekt: lesbar, allokationsfrei, branchpredictor-freundlich. Bei sehr vielen Endungen (mehrere Dutzend) lohnt sich ein map[string]struct{}-Set in Kombination mit filepath.Ext, weil der Hash-Lookup dann konstante Zeit garantiert statt linearer Suche.
HasSuffix vergleicht byteweise — und damit strikt case-sensitiv. "foto.JPG" endet aus Go-Sicht nicht auf ".jpg", weil J (0x4A) und j (0x6A) unterschiedliche Bytes sind. Bei Dateinamen aus Nutzereingaben, Uploads oder fremden Dateisystemen (insbesondere Windows-Quellen) ist Großschreibung in Endungen aber häufig.
package main
import (
"fmt"
"strings"
)
func istJPG(name string) bool {
return strings.HasSuffix(strings.ToLower(name), ".jpg")
}
func main() {
fmt.Println(istJPG("urlaub.jpg"))
fmt.Println(istJPG("URLAUB.JPG"))
fmt.Println(istJPG("Foto.Jpg"))
}true
true
trueDas Pattern HasSuffix(ToLower(name), ".endung") ist der etablierte Weg. Alternativ funktioniert strings.EqualFold(filepath.Ext(name), ".jpg") — etwas teurer, dafür ohne komplette Lower-Konvertierung des gesamten Pfads.
In Upload-Handlern, Galerie-Scannern oder Konverter-Tools muss eine Datei oft anhand der Endung in eine Kategorie einsortiert werden. Eine kompakte Klassifizierungsfunktion liefert pro Datei einen sprechenden Typ-String, den das aufrufende System weiterverarbeiten kann.
package main
import (
"fmt"
"strings"
)
func kategorie(name string) string {
n := strings.ToLower(name)
switch {
case hatEine(n, ".jpg", ".jpeg", ".png", ".gif", ".webp", ".avif"):
return "bild"
case hatEine(n, ".mp4", ".mov", ".mkv", ".webm"):
return "video"
case hatEine(n, ".zip", ".tar", ".gz", ".7z"):
return "archiv"
case hatEine(n, ".pdf", ".docx", ".odt"):
return "dokument"
default:
return "unbekannt"
}
}
func hatEine(s string, endungen ...string) bool {
for _, e := range endungen {
if strings.HasSuffix(s, e) {
return true
}
}
return false
}
func main() {
dateien := []string{"urlaub.JPG", "clip.mp4", "backup.tar.gz", "vertrag.pdf", "skript.go"}
for _, d := range dateien {
fmt.Printf("%-20s -> %s\n", d, kategorie(d))
}
}urlaub.JPG -> bild
clip.mp4 -> video
backup.tar.gz -> archiv
vertrag.pdf -> dokument
skript.go -> unbekanntDie Variadic-Hilfsfunktion hatEine macht den switch lesbar und lokal — die Endungs-Listen stehen direkt neben dem Case-Label, ohne globale Maps oder Konstanten. Für komplexere Logik (z. B. Magic-Byte-Validierung statt nur Endung) wäre net/http.DetectContentType der seriöse nächste Schritt, aber für Routing- und UI-Hinweise reicht der Endungs-Check.
Im HTTP-Routing taucht HasSuffix regelmäßig auf — etwa um zwischen Verzeichnis-Pfaden (Trailing Slash) und Datei-Pfaden zu unterscheiden, oder um Content-Negotiation per Datei-Endung statt per Accept-Header zu implementieren.
package main
import (
"fmt"
"strings"
)
type Antwort struct {
Modus string
Typ string
}
func klassifiziere(pfad string) Antwort {
switch {
case strings.HasSuffix(pfad, "/"):
return Antwort{Modus: "verzeichnis", Typ: "html"}
case strings.HasSuffix(pfad, ".json"):
return Antwort{Modus: "ressource", Typ: "application/json"}
case strings.HasSuffix(pfad, ".xml"):
return Antwort{Modus: "ressource", Typ: "application/xml"}
case strings.HasSuffix(pfad, ".html"):
return Antwort{Modus: "ressource", Typ: "text/html"}
default:
return Antwort{Modus: "ressource", Typ: "text/plain"}
}
}
func main() {
pfade := []string{"/blog/", "/api/users.json", "/feed.xml", "/about.html", "/healthz"}
for _, p := range pfade {
a := klassifiziere(p)
fmt.Printf("%-20s -> %-12s %s\n", p, a.Modus, a.Typ)
}
}/blog/ -> verzeichnis html
/api/users.json -> ressource application/json
/feed.xml -> ressource application/xml
/about.html -> ressource text/html
/healthz -> ressource text/plainDer Trailing-Slash-Check ist ein klassisches Muster: Pfade mit / am Ende werden als „Verzeichnis-Index" interpretiert (typischerweise zu index.html aufgelöst), alles andere als konkrete Ressource. Für ernsthafte Router empfiehlt sich net/http.ServeMux (ab Go 1.22 mit Pattern-Matching) oder ein dediziertes Routing-Paket — aber für kleine APIs und Static-Sites reicht der direkte HasSuffix-Switch.
O(len(suffix)) Vergleich
Laufzeit hängt nur an der Suffix-Länge, nicht an der Länge von s — typische Endungen kosten wenige CPU-Zyklen.
Leeres Suffix immer true
HasSuffix(s, "") liefert immer true, auch für s == "" — bei dynamischen Suffixen vorab auf leer prüfen.
Strikt case-sensitiv
Byteweiser Vergleich macht .JPG und .jpg zu unterschiedlichen Suffixen — relevant bei Datei-Uploads aus heterogenen Quellen.
Zum Entfernen TrimSuffix
HasSuffix antwortet nur — wer das Suffix abschneiden will, nutzt direkt TrimSuffix ohne vorherigen Check.
CutSuffix kombiniert Check + Trim
Seit Go 1.20 liefert CutSuffix Rest und Erfolgs-Flag in einem Aufruf — spart die Doppel-Prüfung HasSuffix + TrimSuffix.
Dateiendungen mit ToLower normalisieren
Pattern HasSuffix(strings.ToLower(name), ".jpg") ist der idiomatische Weg gegen Großschreibung in Endungen.
Threadsafe ohne State
Reiner Vergleich ohne globalen Zustand — beliebig viele Goroutines können parallel HasSuffix aufrufen.
Schneller als Regex
Für Endungs-Prüfungen schlägt HasSuffix jede regexp-Lösung um Größenordnungen — kein Zustandsautomat, keine Capture-Logik.
Weiterführende Ressourcen
Externe Quellen
strings.HasSuffixstrings.CutSuffixstrings.TrimSuffixfilepath.Extals spezialisiertes Tool