---
title: "Was ist eine Tabelle und Metatabelle in Lua? - API7.ai"
description: "Tabelle ist der einzige Datenstrukturierungsmechanismus in Lua. Wir können Tabellen verwenden, um gewöhnliche Arrays, Symboltabellen, Mengen, Datensätze, Warteschlangen und andere Datenstrukturen darzustellen. Erfahren Sie mehr in diesem Beitrag."
url: "https://api7.ai/de/learning-center/openresty/lua-table-and-metatable"
---

# Was ist eine Tabelle und Metatabelle in Lua?

API7.ai

October 11, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

Heute lernen wir über die einzige Datenstruktur in LuaJIT: `table`.

Im Gegensatz zu anderen Skriptsprachen mit reichhaltigen Datenstrukturen hat LuaJIT nur eine Datenstruktur, `table`, die nicht zwischen Arrays, Hashes, Sammlungen usw. unterschieden wird, sondern eher gemischt ist. Schauen wir uns eines der zuvor erwähnten Beispiele an.

```
local color = {first = "red", "blue", third = "green", "yellow"}
print(color["first"])                 --> output: red
print(color[1])                         --> output: blue
print(color["third"])                --> output: green
print(color[2])                         --> output: yellow
print(color[3])                         --> output: nil
```

In diesem Beispiel enthält die Tabelle `color` ein Array und einen Hash und kann ohne gegenseitige Beeinflussung darauf zugegriffen werden. Zum Beispiel können Sie die Funktion `ipairs` verwenden, um nur den Array-Teil der Tabelle zu durchlaufen.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
for k, v in ipairs(color) do
      print(k)
end
'
```

Die `table`-Operationen sind so entscheidend, dass LuaJIT die Standard-Lua-5.1-Table-Bibliothek erweitert und OpenResty die LuaJIT-Table-Bibliothek noch weiter erweitert. Schauen wir uns jede dieser Bibliotheksfunktionen an.

## Die Table-Bibliotheksfunktionen

Beginnen wir mit den Standard-Table-Bibliotheksfunktionen. Lua 5.1 hat nicht viele Table-Bibliotheksfunktionen, daher können wir sie schnell durchgehen.

### `table.getn` Anzahl der Elemente ermitteln

Wie wir im Kapitel [Standard Lua und LuaJIT](https://api7.ai/learning-center/openresty/luajit-vs-lua/) erwähnt haben, ist das Ermitteln der korrekten Anzahl aller Tabellenelemente ein großes Problem in LuaJIT.

Für Sequenzen können Sie `table.getn` oder den unären Operator `#` verwenden, um die korrekte Anzahl der Elemente zurückzugeben. Das folgende Beispiel gibt die erwartete Anzahl von 3 zurück.

```
$ resty -e 'local t = { 1, 2, 3 }
  print(table.getn(t))
```

Für Tabellen, die nicht sequentiell sind, kann der korrekte Wert nicht zurückgegeben werden. Im zweiten Beispiel ist der zurückgegebene Wert 1.

```
$ resty -e 'local t = { 1, a = 2 }
  print(#t) '
```

Glücklicherweise wurden solche schwer verständlichen Funktionen durch Erweiterungen von LuaJIT ersetzt, auf die wir später eingehen werden. Verwenden Sie im Kontext von OpenResty daher nicht die Funktion `table.getn` und den unären Operator `#`, es sei denn, Sie wissen explizit, dass Sie die Sequenzlänge ermitteln.

Außerdem sind `table.getn` und der unäre Operator `#` nicht O(1)-zeitkomplex, sondern O(n), was ein weiterer Grund ist, sie nach Möglichkeit zu vermeiden.

### `table.remove` Entfernt das angegebene Element

Die zweite Funktion ist `table.remove`, die Elemente in der Tabelle basierend auf Indizes entfernt, d.h., nur die Elemente im Array-Teil der Tabelle können entfernt werden. Schauen wir uns noch einmal das `color`-Beispiel an.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
  table.remove(color, 1)
  for k, v in pairs(color) do
      print(v)
  end'
```

Dieser Code entfernt das `blue` mit dem Index 1. Sie fragen sich vielleicht, wie ich den Hash-Teil der Tabelle löschen kann? Es ist so einfach wie das Setzen des Werts, der dem Schlüssel entspricht, auf `nil`. Somit wird im `color`-Beispiel das `green`, das `third` entspricht, gelöscht.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
  color.third = nil
  for k, v in pairs(color) do
      print(v)
  end'
```

### `table.concat` Elementverkettungsfunktion

Die dritte Funktion ist die `table.concat`-Elementverkettungsfunktion. Sie verkettet die Elemente der Tabelle gemäß den Indizes. Da dies wieder auf Indizes basiert, gilt es immer noch für den Array-Teil der Tabelle. Wieder mit dem `color`-Beispiel.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
print(table.concat(color, ", "))'
```

Nach der Verwendung der `table.concat`-Funktion gibt es `blue, yellow` aus, und der Hash-Teil wird übersprungen.

Darüber hinaus kann diese Funktion auch die Startposition des Index angeben, um die Verkettung durchzuführen; zum Beispiel wird sie wie folgt geschrieben:

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow", "orange"}
print(table.concat(color, ", ", 2, 3))'
```

Diesmal ist die Ausgabe `yellow, orange`, wobei `blue` übersprungen wird.

Unterschätzen Sie diese scheinbar nutzlose Funktion nicht, aber sie kann unerwartete Effekte bei der Leistungsoptimierung haben und ist einer der Hauptakteure in unseren späteren Kapiteln zur Leistungsoptimierung.

### `table.insert` Fügt ein Element ein

Schließlich schauen wir uns die `table.insert`-Funktion an. Sie fügt ein neues Element am angegebenen Index ein, was den Array-Teil der Tabelle beeinflusst. Zur Veranschaulichung verwenden wir wieder das `color`-Beispiel.

```
$ resty -e 'local color = {first = "red", "blue", third = "green", "yellow"}
table.insert(color, 1,  "orange")
print(color[1])
'
```

Sie können sehen, dass das erste Element von `color` zu `orange` wird, aber natürlich können Sie den Index auch nicht angeben, sodass es standardmäßig am Ende der Warteschlange eingefügt wird.

Ich sollte darauf hinweisen, dass `table.insert` eine weit verbreitete Operation ist, aber die Leistung ist nicht gut. Wenn Sie Elemente nicht basierend auf dem angegebenen Skript einfügen, müssen Sie jedes Mal LuaJITs `lj_tab_len` aufrufen, um die Array-Länge zu erhalten, um am Ende der Warteschlange einzufügen. Wie bei `table.getn` ist die Zeitkomplexität zum Ermitteln der Tabellenlänge O(n).

Daher sollten wir versuchen, die Verwendung von `table.insert` in Hot-Code zu vermeiden. Zum Beispiel:

```
local t = {}
for i = 1, 10000 do
     table.insert(t, i)
end
```

## LuaJITs Table-Erweiterungsfunktionen

Als nächstes schauen wir uns die Table-Erweiterungsfunktionen von LuaJIT an. LuaJIT erweitert das Standard-Lua mit zwei nützlichen Table-Funktionen zum Erstellen und Leeren einer Tabelle, die ich im Folgenden beschreiben werde.

### `table.new(narray, nhash)` Erstellt eine neue Tabelle

Die erste Funktion ist `table.new(narray, nhash)`. Anstatt sich selbst zu vergrößern, wenn Elemente eingefügt werden, wird diese Funktion den Speicherplatz der angegebenen Array- und Hash-Größe vorab zuweisen, was ihre beiden Parameter `narray` und `nhash` bedeuten. Selbstvergrößerung ist eine kostspielige Operation, die Speicherzuweisung, `resize` und `rehash` beinhaltet und nach Möglichkeit vermieden werden sollte.

Beachten Sie hier, dass die Dokumentation für `table.new` nicht auf der LuaJIT-Website zu finden ist, sondern tief in der [erweiterten Dokumentation](https://github.com/openresty/luajit2/blob/v2.1-agentzh/doc/extensions.html) des GitHub-Projekts, sodass es schwer zu finden ist, selbst wenn Sie danach googeln, daher kennen nicht viele Ingenieure diese Funktion.

Hier ist ein einfaches Beispiel, und ich zeige Ihnen, wie es funktioniert. Zunächst ist diese Funktion erweitert, daher müssen Sie sie vor der Verwendung `require`.

```
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
   t[i] = i
end
```

Wie Sie sehen können, erstellt dieser Code eine neue Tabelle mit 100 Array-Elementen und 0 Hash-Elementen. Natürlich können Sie je nach Bedarf eine neue Tabelle mit 100 Array-Elementen und 50 Hash-Elementen erstellen, was legal ist.

```
local t = new_tab(100, 50)
```

Alternativ können Sie, wenn Sie die voreingestellte Speicherplatzgröße überschreiten, sie weiterhin normal verwenden, aber die Leistung wird sich verschlechtern, und der Sinn der Verwendung von `table.new` geht verloren.

Im folgenden Beispiel haben wir eine voreingestellte Größe von 100, verwenden aber 200.

```
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 200 do
   t[i] = i
end
```

Sie müssen die Größe des Array- und Hash-Speicherplatzes in `table.new` gemäß dem tatsächlichen Szenario voreinstellen, um eine Balance zwischen Leistung und Speichernutzung zu finden.

### `table.clear()` Leert die Tabelle

Die zweite Funktion ist die Clear-Funktion `table.clear()`. Sie löscht alle Daten in einer Tabelle, gibt jedoch den von den Array- und Hash-Teilen belegten Speicher nicht frei. Daher ist sie nützlich, wenn Lua-Tabellen recycelt werden, um den Overhead durch wiederholtes Erstellen und Zerstören von Tabellen zu vermeiden.

```
$ resty -e 'local clear_tab =require "table.clear"
local color = {first = "red", "blue", third = "green", "yellow"}
clear_tab(color)
for k, v in pairs(color) do
     print(k)
end'
```

Es gibt jedoch nicht viele Szenarien, in denen diese Funktion verwendet werden kann, und in den meisten Fällen sollten wir diese Aufgabe dem LuaJIT-GC überlassen.

## OpenResty's Table-Erweiterungsfunktionen

Wie ich am Anfang erwähnt habe, pflegt OpenResty seinen eigenen LuaJIT-Zweig, der auch Table erweitert, [mit mehreren neuen APIs](https://github.com/openresty/luajit2/#new-api): `table.isempty,` `table. isarray`, `table.nkeys` und `table.clone`.

Bevor Sie diese neuen APIs verwenden, überprüfen Sie bitte die Version von OpenResty, da die meisten dieser APIs nur in Versionen von OpenResty nach 1.15.8.1 verwendet werden können. Dies liegt daran, dass OpenResty etwa ein Jahr vor Version 1.15.8.1 keine neue Version hatte und diese APIs in diesem Release-Intervall hinzugefügt wurden.

Ich habe einen Link zum Artikel hinzugefügt, daher verwende ich `table.nkeys` als Beispiel. Die anderen drei APIs sind aus Namenssicht leicht zu verstehen, daher schauen Sie sich die GitHub-Dokumentation an, und Sie werden sie verstehen. Ich muss sagen, dass die Dokumentation von OpenResty von sehr hoher Qualität ist, einschließlich Codebeispielen, ob sie JIT-fähig sind, worauf zu achten ist usw. Sie ist um mehrere Größenordnungen besser als die Dokumentation von Lua und LuaJIT.

Okay, zurück zur `table.nkeys`-Funktion. Ihr Name mag Sie verwirren, aber es ist eine Funktion, die die Länge der Tabelle ermittelt und die Anzahl der Elemente der Tabelle zurückgibt, einschließlich der Elemente des Arrays und des Hash-Teils. Daher können wir sie anstelle von `table.getn` verwenden, zum Beispiel wie folgt.

```
local nkeys = require "table.nkeys"
print(nkeys({}))  -- 0
print(nkeys({ "a", nil, "b" }))  -- 2
print(nkeys({ dog = 3, cat = 4, bird = nil }))  -- 2
print(nkeys({ "a", dog = 3, cat = 4 }))  -- 3
```

## Metatable

Nachdem wir über die Table-Funktion gesprochen haben, schauen wir uns die aus `table` abgeleitete `metatable` an. Die Metatable ist ein einzigartiges Konzept in Lua und wird in realen Projekten weit verbreitet verwendet. Es ist keine Übertreibung zu sagen, dass Sie sie in fast jeder `lua-resty-*`-Bibliothek finden können.

`Metatable` verhält sich wie Operatorüberladungen; zum Beispiel können wir `__add` überladen, um die Verkettung von zwei Lua-Arrays zu berechnen, oder `__tostring`, um Funktionen zu definieren, die in Zeichenketten umgewandelt werden.

Lua bietet andererseits zwei Funktionen zur Handhabung von Metatabellen.

- Die erste ist `setmetatable(table, metatable)`, die eine Metatable für eine Tabelle einrichtet.
- Die zweite ist `getmetatable(table)`, die die Metatable der Tabelle abruft.

Nach all dem sind Sie vielleicht mehr daran interessiert, was sie tut, also schauen wir uns an, wofür Metatable speziell verwendet wird. Hier ist ein Codeausschnitt aus einem realen Projekt.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1,
  patch = 1
  }
version = setmetatable(version, {
    __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

Wir definieren zuerst eine Tabelle namens `version`, und wie Sie sehen können, besteht der Zweck dieses Codes darin, die Versionsnummer in `version` auszugeben. Wir können jedoch nicht direkt die `version` ausgeben. Sie können versuchen, dies zu tun, und Sie werden sehen, dass die direkte Ausgabe nur die Adresse der Tabelle ausgibt.

```
print(tostring(version))
```

Daher müssen wir die Zeichenkettenkonvertierungsfunktion für diese Tabelle anpassen, was `__tostring` ist, und hier kommt die Metatable ins Spiel. Wir verwenden `setmetatable`, um die `__tostring`-Methode der Tabelle `version` zurückzusetzen, um die Versionsnummer auszugeben: 1.1.1.

Zusätzlich zu `__tostring` überschreiben wir in realen Projekten oft die folgenden zwei Metamethoden in der Metatable.

**Eine davon ist \_\_index**. Wenn wir ein Element in einer Tabelle nachschlagen, suchen wir es zuerst direkt in der Tabelle, und wenn wir es nicht finden, gehen wir weiter zum `__index` der Metatable.

Wir entfernen das `patch` aus der `version`-Tabelle im folgenden Beispiel.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = function(t, key)
         if key == "patch" then
             return 2
         end
     end,
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

In diesem Fall erhält `t.patch` keinen Wert, daher geht es zur `__index`-Funktion, die 1.1.2 ausgibt.

`__index` kann nicht nur eine Funktion, sondern auch eine Tabelle sein, und wenn Sie den folgenden Code ausführen, werden Sie sehen, dass sie dasselbe Ergebnis erzielen.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = {patch = 2},
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(tostring(version))
'
```

**Eine weitere Metamethode ist \_\_call**. Sie ähnelt einem Funktor, der es einer Tabelle ermöglicht, aufgerufen zu werden.

Lassen Sie uns auf dem obigen Code aufbauen, der die Versionsnummer ausgibt, und sehen, wie man eine Tabelle aufruft.

```
$ resty -e '
local version = {
  major = 1,
  minor = 1,
  patch = 1
  }
local function print_version(t)
     print(string.format("%d.%d.%d", t.major, t.minor, t.patch))
end
version = setmetatable(version,
     {__call = print_version})
  version()
'
```

In diesem Code verwenden wir `setmetatable`, um der Tabelle `version` eine Metatable hinzuzufügen, und die `__call`-Metamethode darin verweist auf die Funktion `print_version`. Wenn wir also versuchen, `version` als Funktion aufzurufen, wird hier die Funktion `print_version` ausgeführt.

Und `getmetatable` ist die Operation, die mit `setmetatable` gepaart ist, um die bereits gesetzte Metatable zu erhalten, wie im folgenden Code.

```
$ resty -e ' local version = {
  major = 1,
  minor = 1
  }
version = setmetatable(version, {
     __index = {patch = 2},
     __tostring = function(t)
      return string.format("%d.%d.%d", t.major, t.minor, t.patch)
    end
  })
  print(getmetatable(version).__index.patch)
'
```

Zusätzlich zu diesen drei Metamethoden, über die wir heute gesprochen haben, gibt es einige weniger häufig verwendete Metamethoden, die Sie in der [Dokumentation](http://lua-users.org/wiki/MetamethodsTutorial) nachschlagen können, um mehr darüber zu erfahren, wenn Sie auf sie stoßen.

## Objektorientierung

Schließlich sprechen wir über Objektorientierung. Wie Sie vielleicht wissen, ist Lua keine objektorientierte Sprache, aber wir können Metatabellen verwenden, um OO zu implementieren.

Schauen wir uns ein praktisches Beispiel an. [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql/blob/master/lib/resty/mysql.lua) ist der offizielle MySQL-Client von OpenResty und verwendet Metatabellen zur **Simulation** von Klassen und Klassenmethoden, die wie folgt verwendet werden.

```
    $ resty -e 'local mysql = require "resty.mysql" -- zuerst die lua-resty-Bibliothek referenzieren
    local db, err = mysql:new() -- Eine neue Instanz der Klasse erstellen
    db:set_timeout(1000) -- Methoden einer Klasse aufrufen
```

Sie können den obigen Code direkt mit der `resty`-Befehlszeile ausführen. Diese Codezeilen sind leicht zu verstehen; das einzige, was Sie möglicherweise verwirren könnte, ist.

**Warum ist es bei einem Klassenmethodenaufruf ein Doppelpunkt statt eines Punkts?**

Tatsächlich sind sowohl Doppelpunkte als auch Punkte hier in Ordnung, und `db:set_timeout(1000)` und `db.set_timeout(db, 1000)` sind genau gleichwertig. Der Doppelpunkt ist ein syntaktischer Zucker in Lua, der es ermöglicht, das erste Argument `self` einer Funktion auszulassen.

Wie wir alle wissen, gibt es vor dem Quellcode keine Geheimnisse, also schauen wir uns die konkrete Implementierung an, die den obigen Codezeilen entspricht, damit Sie besser verstehen können, wie man objektorientiert mit Metatabellen modelliert.

```
local _M = { _VERSION = '0.21' } -- Verwenden der Tabelle zur Simulation einer Klasse
local mt = { __index = _M } -- mt ist kurz für metatable, __index verweist auf die Klasse selbst
-- Konstruktor der Klasse
function _M.new(self)
    local sock, err = tcp()
    if not sock then
          return nil, err
    end
    return setmetatable({ sock = sock }, mt) -- Beispiel für die Simulation von Klassen mit Tabelle und Metatable
end

-- Memberfunktionen einer Klasse
function _M.set_timeout(self, timeout) -- Verwenden des self-Arguments, um eine Instanz der Klasse zu erhalten, die Sie bearbeiten möchten
  local sock = self.sock
  if not sock then
      return nil, "not initialized"
  end

  return sock:settimeout(timeout)
end
```

Die Tabelle `_M` simuliert eine Klasse, die mit einer einzelnen Membervariablen `_VERSION` initialisiert wird, und definiert anschließend Memberfunktionen wie `_M.set_timeout`. Im Konstruktor `_M.new(self)` geben wir eine Tabelle zurück, deren Metatable `mt` ist, und die `__index`-Metamethode von `mt` verweist auf `_M`, sodass die zurückgegebene Tabelle eine Instanz der Klasse `_M` simuliert.

## Zusammenfassung

Nun, das schließt den Hauptinhalt für heute ab. Table und Metatable werden in OpenResty's `lua-resty-*`-Bibliothek und OpenResty-basierten Open-Source-Projekten stark verwendet. Ich hoffe, diese Lektion erleichtert es Ihnen, den Quellcode zu lesen und zu verstehen.

Es gibt neben der Table noch andere Standardfunktionen in Lua, die wir in der nächsten Lektion gemeinsam lernen werden.

Abschließend möchte ich Ihnen eine zum Nachdenken anregende Frage stellen. Warum simuliert die `lua-resty-mysql`-Bibliothek OO als eine Schicht der Verpackung? Willkommen, diese Frage in den Kommentaren zu diskutieren, und willkommen, diesen Artikel mit Ihren Kollegen und Freunden zu teilen, damit wir uns austauschen und gemeinsam vorankommen können.

Share article link

Share

Related Posts

[![Was macht OpenResty so besonders?](https://static.api7.ai/2022/10/14/634974e090ab4.png)](https://api7.ai/learning-center/openresty/openresty-getting-started)

## [Was macht OpenResty so besonders?](https://api7.ai/learning-center/openresty/openresty-getting-started)

[![Was passiert, wenn es einen Konflikt zwischen Lua-Regeln und der NGINX-Konfiguration gibt?](https://static.api7.ai/2022/10/14/634974e117ebd.png)](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

## [Was passiert, wenn es einen Konflikt zwischen Lua-Regeln und der NGINX-Konfiguration gibt?](https://api7.ai/learning-center/openresty/conflict-between-lua-rules-and-nginx)

Table of Contents

- [Die Table-Bibliotheksfunktionen](#die-table-bibliotheksfunktionen)

  - [`table.getn` Anzahl der Elemente ermitteln](#tablegetn-anzahl-der-elemente-ermitteln)
  - [`table.remove` Entfernt das angegebene Element](#tableremove-entfernt-das-angegebene-element)
  - [`table.concat` Elementverkettungsfunktion](#tableconcat-elementverkettungsfunktion)
  - [`table.insert` Fügt ein Element ein](#tableinsert-f%C3%BCgt-ein-element-ein)

- [LuaJITs Table-Erweiterungsfunktionen](#luajits-table-erweiterungsfunktionen)

  - [`table.new(narray, nhash)` Erstellt eine neue Tabelle](#tablenewnarray-nhash-erstellt-eine-neue-tabelle)
  - [`table.clear()` Leert die Tabelle](#tableclear-leert-die-tabelle)

- [OpenResty's Table-Erweiterungsfunktionen](#openrestys-table-erweiterungsfunktionen)

- [Metatable](#metatable)

- [Objektorientierung](#objektorientierung)

- [Zusammenfassung](#zusammenfassung)
