On parle Windows, C#, Apple, Android, Js, …

LINQ to XML et la lecture des très gros fichiers

LINQ to XML est relativement facile à implémenter pour lire de gros fichiers et pour requêter des fichiers XML.
Par exemple, considérons ce fichier XML :

    <?xml version="1.0" encoding="utf-8" ?>
    <users>
        <user name="User1" groupid="4" />
        <user name="User2" groupid="1" />
        <user name="User3" groupid="3" />
        <user name="User4" groupid="1" />
        <user name="User5" groupid="1" />
        <user name="User6" groupid="2" />
        <user name="User7" groupid="1" />
    </users>

Supposons que vous souhaitez trouver tous les enregistrements avec groupid > 2. Vous pouvez être tenté d’implémenter la requête suivante :

    XElement doc = XElement.Load("users.xml");
    var users = from u in doc.Elements("user")
                where u.Attribute("groupid") != null &&
                int.Parse(u.Attribute("groupid").Value) > 2
                select u;
    Console.WriteLine("{0} users match query", users.Count());

Il y a un piège dans cette implémentation. La méthode XElement.Load va lire tout le fichier XML en mémoire avant de s’exécuter, et ce fichier peut être très gros.

Non seulement, la requête va prendre beaucoup de temps à s’exécuter mais elle risque en plus de consommer tout la mémoire. Si vous avez un très gros fichier XML, vous devez utiliser un système de cache pour lire l’essentiel plutôt que de lire une première fois l’ensemble du contenu en mémoire. XmlReader est une jolie alternative pour nous permettre d’avoir uniquement l’enregistrement courant au lieu de l’ensemble de la mémoire et ainsi considérablement améliorer les performances.

Nous commençons par la définition d’une class “User” qui sera utilisée pour représenter une simple record:

    public class User 
    {
        public string Name { get; set; }
        public int GroupId { get; set; }
    }

Ensuite, nous étendons la classe XmlReader avec la méthode User:

    public static IEnumerable<User> Users(this XmlReader source)
    {
        while (source.Read())
        {
            if (source.NodeType == XmlNodeType.Element && 
                source.Name == "user")
            {
                int groupId;
                int.TryParse(source.GetAttribute("groupid"), out groupId);
                yield return new User
                {
                    GroupId = groupId,
                    Name = source.GetAttribute("name")
                };
            }
        }
    }

Et finalement nous exécutons la requête:

    using (XmlReader reader = XmlReader.Create("users.xml"))
    {
        var users = from u in reader.Users()
                    where u.GroupId > 2
                    select u;
        Console.WriteLine("{0} users match query", users.Count());
    }

Conclusion: La seconde approche s’exécute plus rapidement et utilise beaucoup moins de mémoire que la première.
La différence est significative sur de très gros fichier XML. Ainsi, si vous devez manipuler de très gros fichiers XML, faites très attention avec LINQ to XML.

Laissez un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.