212 votes

Comment comparer deux chaînes de version en Java ?

Existe-t-il un idiome standard pour comparer les numéros de version ? Je ne peux pas simplement utiliser un String compareTo directement parce que je ne sais pas encore quel sera le nombre maximum de versions mineures. Je dois comparer les versions et que les règles suivantes soient respectées :

1.0 < 1.1
1.0.1 < 1.1
1.9 < 1.10

0 votes

Avez-vous essayé simplement de supprimer les points et de parser la chaîne résultante en tant que entier ? Actuellement, j'utilise quelque chose de similaire à ce qui suit : String version = "1.1.2".replace(".", ""); int number = Integer.parseInt(version); // = 112. Vous pourriez comparer le numéro à un autre et ainsi trouver la version la plus récente. De plus, vous pouvez vérifier si la chaîne version correspond à un certain motif comme \\d+\\.\\d+\\.\\d pour vous assurer que le résultat comporte au moins 3 chiffres.

8 votes

@RegisteredUser Comment cela fonctionnerait-il avec quelque chose comme ceci : 1.12.1 et 1.1.34 ?

0 votes

Vous devriez vous assurer que chaque partie a la même longueur. Ainsi, pour comparer les deux versions de votre exemple, elles doivent ressembler à ceci: 1.12.01 et 1.01.34. En Java, vous pourriez y parvenir en fractionnant d'abord à l'aide de la balise . et en comparant ensuite la longueur de chaque élément. Ensuite, il suffit de mettre tous les éléments dans une seule chaîne, de la convertir en entier, puis de la comparer à l'autre version qui a été convertie de la même manière.

214voto

alex Points 628

Une autre solution pour ce vieux post (pour ceux que cela pourrait aider) :

public class Version implements Comparable {

    private String version;

    public final String get() {
        return this.version;
    }

    public Version(String version) {
        if(version == null)
            throw new IllegalArgumentException("La version ne peut pas être nulle");
        if(!version.matches("[0-9]+(\\.[0-9]+)*"))
            throw new IllegalArgumentException("Format de version invalide");
        this.version = version;
    }

    @Override public int compareTo(Version that) {
        if(that == null)
            return 1;
        String[] thisParts = this.get().split("\\.");
        String[] thatParts = that.get().split("\\.");
        int length = Math.max(thisParts.length, thatParts.length);
        for(int i = 0; i < length; i++) {
            int thisPart = i < thisParts.length ?
                Integer.parseInt(thisParts[i]) : 0;
            int thatPart = i < thatParts.length ?
                Integer.parseInt(thatParts[i]) : 0;
            if(thisPart < thatPart)
                return -1;
            if(thisPart > thatPart)
                return 1;
        }
        return 0;
    }

    @Override public boolean equals(Object that) {
        if(this == that)
            return true;
        if(that == null)
            return false;
        if(this.getClass() != that.getClass())
            return false;
        return this.compareTo((Version) that) == 0;
    }

}

Version a = new Version("1.1");
Version b = new Version("1.1.1");
a.compareTo(b) // retourne -1 (ab)
a.equals(b)    // retourne false

Version a = new Version("1.0");
Version b = new Version("1");
a.compareTo(b) // retourne 0 (a=b)
a.equals(b)    // retourne true

Version a = new Version("1");
Version b = null;
a.compareTo(b) // retourne 1 (a>b)
a.equals(b)    // retourne false

List versions = new ArrayList();
versions.add(new Version("2"));
versions.add(new Version("1.0.5"));
versions.add(new Version("1.01.0"));
versions.add(new Version("1.00.1"));
Collections.min(versions).get() // retourne la version minimale
Collections.max(versions).get() // retourne la version maximale

// ATTENTION
Version a = new Version("2.06");
Version b = new Version("2.060");
a.equals(b)    // retourne false

Edit :

@daiscog: Merci pour votre remarque, ce morceau de code a été développé pour la plate-forme Android et comme recommandé par Google, la méthode "matches" vérifie toute la chaîne de caractères contrairement à Java qui utilise un modèle régulier. (Documentation Android - Documentation JAVA)

2 votes

C'est la meilleure solution à mon avis. Je l'ai restreinte à des codes de version à 3 éléments en la modifiant en if (!version.matches("[0-9]+(\\.[0-9]+){0,2}") et en ajoutant une variable: private static final int[] PRIME = { 2, 3, 5 }; J'ai pu créer le hashCode manquant pour le code ci-dessus: @Override public final int hashCode() { final String[] parts = this.get().split("\\."); int hashCode = 0; for (int i = 0; i < parts.length; i++) { final int part = Integer.parseInt(parts[i]); if (part > 0) { hashCode += PRIME[i] ^ part; } } return hashCode; }

1 votes

Tu devrais au moins mettre en cache les appels implicites à Pattern.compile(), étant donné que ta logique est appelée avec une complexité de O(N log N).

1 votes

Cette implémentation remplace equals(Object that) et doit donc remplacer hashCode(). Deux objets qui sont égaux doivent retourner le même hashCode sinon vous pouvez rencontrer des problèmes si vous utilisez ces objets avec des collections hashées.

120voto

Alex Dean Points 3997

C'est vraiment facile en utilisant Maven:

import org.apache.maven.artifact.versioning.DefaultArtifactVersion;

DefaultArtifactVersion minVersion = new DefaultArtifactVersion("1.0.1");
DefaultArtifactVersion maxVersion = new DefaultArtifactVersion("1.10");

DefaultArtifactVersion version = new DefaultArtifactVersion("1.11");

if (version.compareTo(minVersion) < 0 || version.compareTo(maxVersion) > 0) {
    System.out.println("Désolé, votre version n'est pas prise en charge");
}

Vous pouvez obtenir la bonne chaîne de dépendance pour l'Artéfact Maven à partir de cette page:

org.apache.maven
maven-artifact
3.0.3

5 votes

J'ai créé un gist avec des tests sur comment cela peut être fait : gist.github.com/2627608

12 votes

Parfait, ne réinventez pas la roue!

11 votes

Le seul souci est : utiliser cette dépendance avec de nombreux fichiers en elle juste pour une raison - avoir une classe - DefaultArtifactVersion

69voto

gizmo Points 8528

Découpez les chaînes avec le point comme délimiteur, puis comparez la traduction en entier côte à côte, en commençant par la gauche.

1 votes

C'est ce que je craignais de devoir recourir à. Cela implique également de boucler sur les jetons dans la chaîne de version la plus courte. Merci de confirmer.

45 votes

Et n'oubliez pas que vous n'avez pas toujours que des numéros. Certaines applications incluront des numéros de version, et pourraient inclure des choses comme 1.0.1b pour la version bêta/ect.

2 votes

Comment fais-tu cela?

52voto

Peter Lawrey Points 229686

Vous devez normaliser les chaînes de version afin de les comparer. Quelque chose comme

import java.util.regex.Pattern;

public class Main {
    public static void main(String... args) {
        compare("1.0", "1.1");
        compare("1.0.1", "1.1");
        compare("1.9", "1.10");
        compare("1.a", "1.9");
    }

    private static void compare(String v1, String v2) {
        String s1 = normalisedVersion(v1);
        String s2 = normalisedVersion(v2);
        int cmp = s1.compareTo(s2);
        String cmpStr = cmp < 0 ? "<" : cmp > 0 ? ">" : "==";
        System.out.printf("'%s' %s '%s'%n", v1, cmpStr, v2);
    }

    public static String normalisedVersion(String version) {
        return normalisedVersion(version, ".", 4);
    }

    public static String normalisedVersion(String version, String sep, int maxWidth) {
        String[] split = Pattern.compile(sep, Pattern.LITERAL).split(version);
        StringBuilder sb = new StringBuilder();
        for (String s : split) {
            sb.append(String.format("%" + maxWidth + 's', s));
        }
        return sb.toString();
    }
}

Imprime

'1.0' < '1.1'
'1.0.1' < '1.1'
'1.9' < '1.10'
'1.a' > '1.9'

2 votes

Attention à la normalisation implique la largeur maximale implicite que vous avez là.

0 votes

@IHeartAndroid Bon point, à moins que vous ne vous attendiez à ce que '4.1' == '4.1.0' Je pense que c'est une commande de sens.

37voto

Markus Jarderot Points 33893
// VersionComparator.java
import java.util.Comparator;

public class VersionComparator implements Comparator {

    public boolean equals(Object o1, Object o2) {
        return compare(o1, o2) == 0;
    }

    public int compare(Object o1, Object o2) {
        String version1 = (String) o1;
        String version2 = (String) o2;

        VersionTokenizer tokenizer1 = new VersionTokenizer(version1);
        VersionTokenizer tokenizer2 = new VersionTokenizer(version2);

        int number1 = 0, number2 = 0;
        String suffix1 = "", suffix2 = "";

        while (tokenizer1.MoveNext()) {
            if (!tokenizer2.MoveNext()) {
                do {
                    number1 = tokenizer1.getNumber();
                    suffix1 = tokenizer1.getSuffix();
                    if (number1 != 0 || suffix1.length() != 0) {
                        // La version un est plus longue que la version deux, et non nulle
                        return 1;
                    }
                }
                while (tokenizer1.MoveNext());

                // La version un est plus longue que la version deux, mais nulle
                return 0;
            }

            number1 = tokenizer1.getNumber();
            suffix1 = tokenizer1.getSuffix();
            number2 = tokenizer2.getNumber();
            suffix2 = tokenizer2.getSuffix();

            if (number1 < number2) {
                // Le nombre un est inférieur au nombre deux
                return -1;
            }
            if (number1 > number2) {
                // Le nombre un est supérieur au nombre deux
                return 1;
            }

            boolean empty1 = suffix1.length() == 0;
            boolean empty2 = suffix2.length() == 0;

            if (empty1 && empty2) continue; // Pas de suffixes
            if (empty1) return 1; // Premier suffixe est vide (1.2 > 1.2b)
            if (empty2) return -1; // Deuxième suffixe est vide (1.2a < 1.2)

            // Comparaison lexicographique des suffixes
            int result = suffix1.compareTo(suffix2);
            if (result != 0) return result;

        }
        if (tokenizer2.MoveNext()) {
            do {
                number2 = tokenizer2.getNumber();
                suffix2 = tokenizer2.getSuffix();
                if (number2 != 0 || suffix2.length() != 0) {
                    // La version un est plus longue que la version deux, et non nulle
                    return -1;
                }
            }
            while (tokenizer2.MoveNext());

            // La version deux est plus longue que la version un, mais nulle
            return 0;
        }
        return 0;
    }
}

// VersionTokenizer.java
public class VersionTokenizer {
    private final String _versionString;
    private final int _length;

    private int _position;
    private int _number;
    private String _suffix;
    private boolean _hasValue;

    public int getNumber() {
        return _number;
    }

    public String getSuffix() {
        return _suffix;
    }

    public boolean hasValue() {
        return _hasValue;
    }

    public VersionTokenizer(String versionString) {
        if (versionString == null)
            throw new IllegalArgumentException("versionString is null");

        _versionString = versionString;
        _length = versionString.length();
    }

    public boolean MoveNext() {
        _number = 0;
        _suffix = "";
        _hasValue = false;

        // Plus de caractères
        if (_position >= _length)
            return false;

        _hasValue = true;

        while (_position < _length) {
            char c = _versionString.charAt(_position);
            if (c < '0' || c > '9') break;
            _number = _number * 10 + (c - '0');
            _position++;
        }

        int suffixStart = _position;

        while (_position < _length) {
            char c = _versionString.charAt(_position);
            if (c == '.') break;
            _position++;
        }

        _suffix = _versionString.substring(suffixStart, _position);

        if (_position < _length) _position++;

        return true;
    }
}

Exemple:

public class Main
{
    private static VersionComparator cmp;

    public static void main (String[] args)
    {
        cmp = new VersionComparator();
        Test(new String[]{"1.1.2", "1.2", "1.2.0", "1.2.1", "1.12"});
        Test(new String[]{"1.3", "1.3a", "1.3b", "1.3-SNAPSHOT"});
    }

    private static void Test(String[] versions) {
        for (int i = 0; i < versions.length; i++) {
            for (int j = i; j < versions.length; j++) {
                Test(versions[i], versions[j]);
            }
        }
    }

    private static void Test(String v1, String v2) {
        int result = cmp.compare(v1, v2);
        String op = "==";
        if (result < 0) op = "<";
        if (result > 0) op = ">";
        System.out.printf("%s %s %s\n", v1, op, v2);
    }
}

Sortie:

1.1.2 == 1.1.2                --->  même longueur et même valeur
1.1.2 < 1.2                   --->  premier nombre (1) inférieur au deuxième nombre (2) => -1
1.1.2 < 1.2.0                 --->  premier nombre (1) inférieur au deuxième nombre (2) => -1
1.1.2 < 1.2.1                 --->  premier nombre (1) inférieur au deuxième nombre (2) => -1
1.1.2 < 1.12                  --->  premier nombre (1) inférieur au deuxième nombre (12) => -1
1.2 == 1.2                    --->  même longueur et même valeur
1.2 == 1.2.0                  --->  premier plus court que deuxième, mais zéro
1.2 < 1.2.1                   --->  premier plus court que deuxième, et non nul
1.2 < 1.12                    --->  premier nombre (2) inférieur au deuxième nombre (12) => -1
1.2.0 == 1.2.0                --->  même longueur et même valeur
1.2.0 < 1.2.1                 --->  premier nombre (0) inférieur au deuxième nombre (1) => -1
1.2.0 < 1.12                  --->  premier nombre (2) inférieur au deuxième nombre (12) => -1
1.2.1 == 1.2.1                --->  même longueur et même valeur
1.2.1 < 1.12                  --->  premier nombre (2) inférieur au deuxième nombre (12) => -1
1.12 == 1.12                  --->  même longueur et même valeur

1.3 == 1.3                    --->  même longueur et même valeur
1.3 > 1.3a                    --->  premier suffixe ('') est vide, mais pas le deuxième ('a') => 1
1.3 > 1.3b                    --->  premier suffixe ('') est vide, mais pas le deuxième ('b') => 1
1.3 > 1.3-SNAPSHOT            --->  premier suffixe ('') est vide, mais pas le deuxième ('-SNAPSHOT') => 1
1.3a == 1.3a                  --->  même longueur et même valeur
1.3a < 1.3b                   --->  premier suffixe ('a') comparé au deuxième suffixe ('b') => -1
1.3a < 1.3-SNAPSHOT           --->  premier suffixe ('a') comparé au deuxième suffixe ('-SNAPSHOT') => -1
1.3b == 1.3b                  --->  même longueur et même valeur
1.3b < 1.3-SNAPSHOT           --->  premier suffixe ('b') comparé au deuxième suffixe ('-SNAPSHOT') => -1
1.3-SNAPSHOT == 1.3-SNAPSHOT  --->  même longueur et même valeur

0 votes

Merci pour cela. J'utilise peut-être une version très ancienne de Java dans mon Eclipse (même s'il est déjà 18.0.1.1 lorsque je le vérifie dans PowerShell), donc la réponse que l'on trouve ici ne fonctionne pas pour moi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X