Как вы сравниваете две версии строк в Java?
Существует ли стандартная идиома для сравнения номеров версий? Я не могу просто использовать прямое сравнение String, потому что я пока не знаю, какое будет максимальное количество точечных релизов. Мне нужно сравнить версии и придерживаться следующего:
1.0 < 1.1
1.0.1 < 1.1
1.9 < 1.10
36 ответов
Токенизируйте строки с точкой в качестве разделителя, а затем сравнивайте целочисленный перевод рядом, начиная слева.
Другое решение для этого старого поста (для тех, которые могут помочь):
public class Version implements Comparable<Version> {
private String version;
public final String get() {
return this.version;
}
public Version(String version) {
if(version == null)
throw new IllegalArgumentException("Version can not be null");
if(!version.matches("[0-9]+(\\.[0-9]+)*"))
throw new IllegalArgumentException("Invalid version format");
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) // return -1 (a<b)
a.equals(b) // return false
Version a = new Version("2.0");
Version b = new Version("1.9.9");
a.compareTo(b) // return 1 (a>b)
a.equals(b) // return false
Version a = new Version("1.0");
Version b = new Version("1");
a.compareTo(b) // return 0 (a=b)
a.equals(b) // return true
Version a = new Version("1");
Version b = null;
a.compareTo(b) // return 1 (a>b)
a.equals(b) // return false
List<Version> versions = new ArrayList<Version>();
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() // return min version
Collections.max(versions).get() // return max version
// WARNING
Version a = new Version("2.06");
Version b = new Version("2.060");
a.equals(b) // return false
Редактировать:
@daiscog: Спасибо за ваше замечание, этот кусок кода был разработан для платформы Android, и в соответствии с рекомендацией Google, метод "соответствует" проверяет всю строку в отличие от Java, который использует нормативный шаблон. ( Документация Android - документация JAVA)
С 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("Sorry, your version is unsupported");
}
На этой странице вы можете получить правильную строку зависимости для артефакта Maven:
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.0.3</version>
</dependency>
Используйте собственный встроенный Java 9 Version
класс
import java.util.*;
import java.lang.module.ModuleDescriptor.Version;
class Main {
public static void main(String[] args) {
var versions = Arrays.asList(
"1.0.2",
"1.0.0-beta.2",
"1.0.0",
"1.0.0-beta",
"1.0.0-alpha.12",
"1.0.0-beta.11",
"1.0.1",
"1.0.11",
"1.0.0-rc.1",
"1.0.0-alpha.1",
"1.1.0",
"1.0.0-alpha.beta",
"1.11.0",
"1.0.0-alpha.12.ab-c",
"0.0.1",
"1.2.1",
"1.0.0-alpha"
);
versions.stream()
.map(Version::parse)
.sorted()
.forEach(System.out::println);
}
}
Вывод:
0.0.1
1.0.0-alpha
1.0.0-alpha.1
1.0.0-alpha.12
1.0.0-alpha.12.ab-c
1.0.0-alpha.beta
1.0.0-beta
1.0.0-beta.2
1.0.0-beta.11
1.0.0-rc.1
1.0.0
1.0.1
1.0.2
1.0.11
1.1.0
1.2.1
1.11.0
Вам необходимо нормализовать строки версий, чтобы их можно было сравнивать. Что-то вроде
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();
}
}
Печать
'1.0' < '1.1' '1.0.1' < '1.1' '1.9' < '1.10' '1.a' > '1.9'
Лучше всего повторно использовать существующий код, воспользуйтесь классом Maven ComparableVersion
преимущества:
- Лицензия Apache, версия 2.0,
- испытания,
- используется (копируется) в нескольких проектах, таких как spring-security-core, jboss и т. д.
- несколько функций
- это уже java.lang.Comparable
- просто скопируйте и вставьте этот класс, никаких сторонних зависимостей
Не включайте зависимость от maven-артефакта, поскольку он будет тянуть различные переходные зависимости
// 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) {
// Version one is longer than number two, and non-zero
return 1;
}
}
while (tokenizer1.MoveNext());
// Version one is longer than version two, but zero
return 0;
}
number1 = tokenizer1.getNumber();
suffix1 = tokenizer1.getSuffix();
number2 = tokenizer2.getNumber();
suffix2 = tokenizer2.getSuffix();
if (number1 < number2) {
// Number one is less than number two
return -1;
}
if (number1 > number2) {
// Number one is greater than number two
return 1;
}
boolean empty1 = suffix1.length() == 0;
boolean empty2 = suffix2.length() == 0;
if (empty1 && empty2) continue; // No suffixes
if (empty1) return 1; // First suffix is empty (1.2 > 1.2b)
if (empty2) return -1; // Second suffix is empty (1.2a < 1.2)
// Lexical comparison of 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) {
// Version one is longer than version two, and non-zero
return -1;
}
}
while (tokenizer2.MoveNext());
// Version two is longer than version one, but zero
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;
// No more characters
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;
}
}
Пример:
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);
}
}
Выход:
1.1.2 == 1.1.2 ---> same length and value
1.1.2 < 1.2 ---> first number (1) less than second number (2) => -1
1.1.2 < 1.2.0 ---> first number (1) less than second number (2) => -1
1.1.2 < 1.2.1 ---> first number (1) less than second number (2) => -1
1.1.2 < 1.12 ---> first number (1) less than second number (12) => -1
1.2 == 1.2 ---> same length and value
1.2 == 1.2.0 ---> first shorter than second, but zero
1.2 < 1.2.1 ---> first shorter than second, and non-zero
1.2 < 1.12 ---> first number (2) less than second number (12) => -1
1.2.0 == 1.2.0 ---> same length and value
1.2.0 < 1.2.1 ---> first number (0) less than second number (1) => -1
1.2.0 < 1.12 ---> first number (2) less than second number (12) => -1
1.2.1 == 1.2.1 ---> same length and value
1.2.1 < 1.12 ---> first number (2) less than second number (12) => -1
1.12 == 1.12 ---> same length and value
1.3 == 1.3 ---> same length and value
1.3 > 1.3a ---> first suffix ('') is empty, but not second ('a') => 1
1.3 > 1.3b ---> first suffix ('') is empty, but not second ('b') => 1
1.3 > 1.3-SNAPSHOT ---> first suffix ('') is empty, but not second ('-SNAPSHOT') => 1
1.3a == 1.3a ---> same length and value
1.3a < 1.3b ---> first suffix ('a') compared to second suffix ('b') => -1
1.3a < 1.3-SNAPSHOT ---> first suffix ('a') compared to second suffix ('-SNAPSHOT') => -1
1.3b == 1.3b ---> same length and value
1.3b < 1.3-SNAPSHOT ---> first suffix ('b') compared to second suffix ('-SNAPSHOT') => -1
1.3-SNAPSHOT == 1.3-SNAPSHOT ---> same length and value
Интересно, почему все предполагают, что версии состоят только из целых чисел - в моем случае это не так.
Зачем изобретать велосипед (если версия соответствует стандарту Semver)
Сначала установите https://github.com/vdurmont/semver4j через Maven
Тогда используйте эту библиотеку
Semver sem = new Semver("1.2.3");
sem.isGreaterThan("1.2.2"); // true
public static int compareVersions(String version1, String version2){
String[] levels1 = version1.split("\\.");
String[] levels2 = version2.split("\\.");
int length = Math.max(levels1.length, levels2.length);
for (int i = 0; i < length; i++){
Integer v1 = i < levels1.length ? Integer.parseInt(levels1[i]) : 0;
Integer v2 = i < levels2.length ? Integer.parseInt(levels2[i]) : 0;
int compare = v1.compareTo(v2);
if (compare != 0){
return compare;
}
}
return 0;
}
Если у вас уже есть Джексон в вашем проекте, вы можете использовать com.fasterxml.jackson.core.Version
:
import com.fasterxml.jackson.core.Version;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class VersionTest {
@Test
public void shouldCompareVersion() {
Version version1 = new Version(1, 11, 1, null, null, null);
Version version2 = new Version(1, 12, 1, null, null, null);
assertTrue(version1.compareTo(version2) < 0);
}
}
Я сделал это прямо сейчас и спросил себя, правильно ли это? Потому что я никогда не находил более чистого решения, чем мое:
Вам просто нужно разделить версии строки ("1.0.0"), как в этом примере:
userVersion.split("\\.")
Тогда у вас будет: {"1", "0", "0"}
Теперь, используя метод, который я сделал:
isUpdateAvailable(userVersion.split("\\."), latestVersionSplit.split("\\."));
Метод:
/**
* Compare two versions
*
* @param userVersionSplit - User string array with major, minor and patch version from user (exemple: {"5", "2", "70"})
* @param latestVersionSplit - Latest string array with major, minor and patch version from api (example: {"5", "2", "71"})
* @return true if user version is smaller than latest version
*/
public static boolean isUpdateAvailable(String[] userVersionSplit, String[] latestVersionSplit) {
int majorUserVersion = Integer.parseInt(userVersionSplit[0]);
int minorUserVersion = Integer.parseInt(userVersionSplit[1]);
int patchUserVersion = Integer.parseInt(userVersionSplit[2]);
int majorLatestVersion = Integer.parseInt(latestVersionSplit[0]);
int minorLatestVersion = Integer.parseInt(latestVersionSplit[1]);
int patchLatestVersion = Integer.parseInt(latestVersionSplit[2]);
if (majorUserVersion <= majorLatestVersion) {
if (majorUserVersion < majorLatestVersion) {
return true;
} else {
if (minorUserVersion <= minorLatestVersion) {
if (minorUserVersion < minorLatestVersion) {
return true;
} else {
return patchUserVersion < patchLatestVersion;
}
}
}
}
return false;
}
Жду отзывов:)
Пост @alex на Котлине
class Version(inputVersion: String) : Comparable<Version> {
var version: String
private set
override fun compareTo(other: Version) =
(split() to other.split()).let {(thisParts, thatParts)->
val length = max(thisParts.size, thatParts.size)
for (i in 0 until length) {
val thisPart = if (i < thisParts.size) thisParts[i].toInt() else 0
val thatPart = if (i < thatParts.size) thatParts[i].toInt() else 0
if (thisPart < thatPart) return -1
if (thisPart > thatPart) return 1
}
0
}
init {
require(inputVersion.matches("[0-9]+(\\.[0-9]+)*".toRegex())) { "Invalid version format" }
version = inputVersion
}
}
fun Version.split() = version.split(".").toTypedArray()
Применение:
Version("1.2.4").compareTo(Version("0.0.5")) //return 1
Может, кому-то будет интересно мое решение:
class Version private constructor(private val versionString: String) : Comparable<Version> {
private val major: Int by lazy { versionString.split(".")[0].toInt() }
private val minor: Int by lazy { versionString.split(".")[1].toInt() }
private val patch: Int by lazy {
val splitArray = versionString.split(".")
if (splitArray.size == 3)
splitArray[2].toInt()
else
0
}
override fun compareTo(other: Version): Int {
return when {
major > other.major -> 1
major < other.major -> -1
minor > other.minor -> 1
minor < other.minor -> -1
patch > other.patch -> 1
patch < other.patch -> -1
else -> 0
}
}
override fun equals(other: Any?): Boolean {
if (other == null || other !is Version) return false
return compareTo(other) == 0
}
override fun hashCode(): Int {
return major * minor * patch
}
companion object {
private fun doesContainsVersion(string: String): Boolean {
val versionArray = string.split(".")
return versionArray.size in 2..3
&& versionArray[0].toIntOrNull() != null
&& versionArray[1].toIntOrNull() != null
&& (versionArray.size == 2 || versionArray[2].toIntOrNull() != null)
}
fun from(string: String): Version? {
return if (doesContainsVersion(string)) {
Version(string)
} else {
null
}
}
}
}
Применение:
val version1 = Version.from("3.2")
val version2 = Version.from("3.2.1")
version1 <= version2
Я написал библиотеку с открытым исходным кодом под названием MgntUtils, в которой есть утилита, которая работает с версиями String. Он правильно сравнивает их, работает с диапазонами версий и пр. Вот эта библиотека javadoc см методы TextUtils.comapreVersions(...)
, Он был интенсивно использован и хорошо проверен. Вот статья, которая описывает библиотеку и где ее взять. Он доступен как артефакт Maven и на github (с источниками и javadoc)
/**
* written by: Stan Towianski - May 2018
* notes: I make assumption each of 3 version sections a.b.c is not longer then 4 digits: aaaa.bbbb.cccc-MODWORD1(-)modnum2
* 5.10.13-release-1 becomes 0000500100013.501 6.0-snapshot becomes 0000600000000.100
* MODWORD1 = -xyz/NotMatching, -SNAPSHOT, -ALPHA, -BETA, -RC, -RELEASE/nothing return: .0, .1, .2, .3, .4, .5
* modnum2 = up to 2 digit/chars second version
* */
public class VersionCk {
private static boolean isVersionHigher( String baseVersion, String testVersion )
{
System.out.println( "versionToComparable( baseVersion ) =" + versionToComparable( baseVersion ) );
System.out.println( "versionToComparable( testVersion ) =" + versionToComparable( testVersion ) + " is this higher ?" );
return versionToComparable( testVersion ).compareTo( versionToComparable( baseVersion ) ) > 0;
}
//---- not worrying about += for something so small
private static String versionToComparable( String version )
{
// System.out.println("version - " + version);
String versionNum = version;
int at = version.indexOf( '-' );
if ( at >= 0 )
versionNum = version.substring( 0, at );
String[] numAr = versionNum.split( "\\." );
String versionFormatted = "0";
for ( String tmp : numAr )
{
versionFormatted += String.format( "%4s", tmp ).replace(' ', '0');
}
while ( versionFormatted.length() < 12 ) // pad out to aaaa.bbbb.cccc
{
versionFormatted += "0000";
}
// System.out.println( "converted min version =" + versionFormatted + "= : " + versionNum );
return versionFormatted + getVersionModifier( version, at );
}
//---- use order low to high: -xyz, -SNAPSHOT, -ALPHA, -BETA, -RC, -RELEASE/nothing returns: 0, 1, 2, 3, 4, 5
private static String getVersionModifier( String version, int at )
{
// System.out.println("version - " + version );
String[] wordModsAr = { "-SNAPSHOT", "-ALPHA", "-BETA", "-RC", "-RELEASE" };
if ( at < 0 )
return "." + wordModsAr.length + "00"; // make nothing = RELEASE level
int i = 1;
for ( String word : wordModsAr )
{
if ( ( at = version.toUpperCase().indexOf( word ) ) > 0 )
return "." + i + getSecondVersionModifier( version.substring( at + word.length() ) );
i++;
}
return ".000";
}
//---- add 2 chars for any number after first modifier. -rc2 or -rc-2 returns 02
private static String getSecondVersionModifier( String version )
{
System.out.println( "second modifier =" + version + "=" );
Matcher m = Pattern.compile("(.*?)(\\d+).*").matcher( version );
// if ( m.matches() )
// System.out.println( "match ? =" + m.matches() + "= m.group(1) =" + m.group(1) + "= m.group(2) =" + m.group(2) + "= m.group(3) =" + (m.groupCount() >= 3 ? m.group(3) : "x") );
// else
// System.out.println( "No match" );
return m.matches() ? String.format( "%2s", m.group(2) ).replace(' ', '0') : "00";
}
public static void main(String[] args)
{
checkVersion( "3.10.0", "3.4.0");
checkVersion( "5.4.2", "5.4.1");
checkVersion( "5.4.4", "5.4.5");
checkVersion( "5.4.9", "5.4.12");
checkVersion( "5.9.222", "5.10.12");
checkVersion( "5.10.12", "5.10.12");
checkVersion( "5.10.13", "5.10.14");
checkVersion( "6.7.0", "6.8");
checkVersion( "6.7", "2.7.0");
checkVersion( "6", "6.3.1");
checkVersion( "4", "4.0.0");
checkVersion( "6.3.0", "6");
checkVersion( "5.10.12-Alpha", "5.10.12-beTA");
checkVersion( "5.10.13-release", "5.10.14-beta");
checkVersion( "6.7.0", "6.8-snapshot");
checkVersion( "6.7.1", "6.7.0-release");
checkVersion( "6-snapshot", "6.0.0-beta");
checkVersion( "6.0-snapshot", "6.0.0-whatthe");
checkVersion( "5.10.12-Alpha-1", "5.10.12-alpha-2");
checkVersion( "5.10.13-release-1", "5.10.13-release2");
checkVersion( "10-rc42", "10.0.0-rc53");
}
private static void checkVersion(String baseVersion, String testVersion)
{
System.out.println( "baseVersion - " + baseVersion );
System.out.println( "testVersion - " + testVersion );
System.out.println( "isVersionHigher = " + isVersionHigher( baseVersion, testVersion ) );
System.out.println( "---------------");
}
}
некоторый вывод:
---------------
baseVersion - 6.7
testVersion - 2.7.0
versionToComparable( baseVersion ) =0000600070000.500
versionToComparable( testVersion ) =0000200070000.500 is this higher ?
isVersionHigher = false
---------------
baseVersion - 6
testVersion - 6.3.1
versionToComparable( baseVersion ) =0000600000000.500
versionToComparable( testVersion ) =0000600030001.500 is this higher ?
isVersionHigher = true
---------------
baseVersion - 4
testVersion - 4.0.0
versionToComparable( baseVersion ) =0000400000000.500
versionToComparable( testVersion ) =0000400000000.500 is this higher ?
isVersionHigher = false
---------------
baseVersion - 6.3.0
testVersion - 6
versionToComparable( baseVersion ) =0000600030000.500
versionToComparable( testVersion ) =0000600000000.500 is this higher ?
isVersionHigher = false
---------------
baseVersion - 5.10.12-Alpha
testVersion - 5.10.12-beTA
second modifier ==
versionToComparable( baseVersion ) =0000500100012.200
second modifier ==
versionToComparable( testVersion ) =0000500100012.300 is this higher ?
second modifier ==
second modifier ==
isVersionHigher = true
---------------
baseVersion - 5.10.13-release
testVersion - 5.10.14-beta
second modifier ==
versionToComparable( baseVersion ) =0000500100013.500
second modifier ==
versionToComparable( testVersion ) =0000500100014.300 is this higher ?
second modifier ==
second modifier ==
isVersionHigher = true
---------------
baseVersion - 6.7.0
testVersion - 6.8-snapshot
versionToComparable( baseVersion ) =0000600070000.500
second modifier ==
versionToComparable( testVersion ) =0000600080000.100 is this higher ?
second modifier ==
isVersionHigher = true
---------------
baseVersion - 6.7.1
testVersion - 6.7.0-release
versionToComparable( baseVersion ) =0000600070001.500
second modifier ==
versionToComparable( testVersion ) =0000600070000.500 is this higher ?
second modifier ==
isVersionHigher = false
---------------
baseVersion - 6-snapshot
testVersion - 6.0.0-beta
second modifier ==
versionToComparable( baseVersion ) =0000600000000.100
second modifier ==
versionToComparable( testVersion ) =0000600000000.300 is this higher ?
second modifier ==
second modifier ==
isVersionHigher = true
---------------
baseVersion - 6.0-snapshot
testVersion - 6.0.0-whatthe
second modifier ==
versionToComparable( baseVersion ) =0000600000000.100
versionToComparable( testVersion ) =0000600000000.000 is this higher ?
second modifier ==
isVersionHigher = false
---------------
baseVersion - 5.10.12-Alpha-1
testVersion - 5.10.12-alpha-2
second modifier =-1=
versionToComparable( baseVersion ) =0000500100012.201
second modifier =-2=
versionToComparable( testVersion ) =0000500100012.202 is this higher ?
second modifier =-2=
second modifier =-1=
isVersionHigher = true
---------------
baseVersion - 5.10.13-release-1
testVersion - 5.10.13-release2
second modifier =-1=
versionToComparable( baseVersion ) =0000500100013.501
second modifier =2=
versionToComparable( testVersion ) =0000500100013.502 is this higher ?
second modifier =2=
second modifier =-1=
isVersionHigher = true
---------------
baseVersion - 10-rc42
testVersion - 10.0.0-rc53
second modifier =42=
versionToComparable( baseVersion ) =0001000000000.442
second modifier =53=
versionToComparable( testVersion ) =0001000000000.453 is this higher ?
second modifier =53=
second modifier =42=
isVersionHigher = true
---------------
public int compare(String v1, String v2) {
v1 = v1.replaceAll("\\s", "");
v2 = v2.replaceAll("\\s", "");
String[] a1 = v1.split("\\.");
String[] a2 = v2.split("\\.");
List<String> l1 = Arrays.asList(a1);
List<String> l2 = Arrays.asList(a2);
int i=0;
while(true){
Double d1 = null;
Double d2 = null;
try{
d1 = Double.parseDouble(l1.get(i));
}catch(IndexOutOfBoundsException e){
}
try{
d2 = Double.parseDouble(l2.get(i));
}catch(IndexOutOfBoundsException e){
}
if (d1 != null && d2 != null) {
if (d1.doubleValue() > d2.doubleValue()) {
return 1;
} else if (d1.doubleValue() < d2.doubleValue()) {
return -1;
}
} else if (d2 == null && d1 != null) {
if (d1.doubleValue() > 0) {
return 1;
}
} else if (d1 == null && d2 != null) {
if (d2.doubleValue() > 0) {
return -1;
}
} else {
break;
}
i++;
}
return 0;
}
public int CompareVersions(String version1, String version2)
{
String[] string1Vals = version1.split("\\.");
String[] string2Vals = version2.split("\\.");
int length = Math.max(string1Vals.length, string2Vals.length);
for (int i = 0; i < length; i++)
{
Integer v1 = (i < string1Vals.length)?Integer.parseInt(string1Vals[i]):0;
Integer v2 = (i < string2Vals.length)?Integer.parseInt(string2Vals[i]):0;
//Making sure Version1 bigger than version2
if (v1 > v2)
{
return 1;
}
//Making sure Version1 smaller than version2
else if(v1 < v2)
{
return -1;
}
}
//Both are equal
return 0;
}
Я написал небольшую библиотеку Java/Android для сравнения номеров версий: https://github.com/G00fY2/version-compare
В основном он делает следующее:
public int compareVersions(String versionA, String versionB) {
String[] versionTokensA = versionA.split("\\.");
String[] versionTokensB = versionB.split("\\.");
List<Integer> versionNumbersA = new ArrayList<>();
List<Integer> versionNumbersB = new ArrayList<>();
for (String versionToken : versionTokensA) {
versionNumbersA.add(Integer.parseInt(versionToken));
}
for (String versionToken : versionTokensB) {
versionNumbersB.add(Integer.parseInt(versionToken));
}
final int versionASize = versionNumbersA.size();
final int versionBSize = versionNumbersB.size();
int maxSize = Math.max(versionASize, versionBSize);
for (int i = 0; i < maxSize; i++) {
if ((i < versionASize ? versionNumbersA.get(i) : 0) > (i < versionBSize ? versionNumbersB.get(i) : 0)) {
return 1;
} else if ((i < versionASize ? versionNumbersA.get(i) : 0) < (i < versionBSize ? versionNumbersB.get(i) : 0)) {
return -1;
}
}
return 0;
}
Этот фрагмент не предлагает никаких проверок или обработки ошибок. Кроме того, моя библиотека также поддерживает такие суффиксы, как "1.2-rc" > "1.2-beta".
Поскольку ни один ответ на этой странице не обрабатывает смешанный текст, я сделал свою версию:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Main {
static double parseVersion(String v) {
if (v.isEmpty()) {
return 0;
}
Pattern p = Pattern.compile("^(\\D*)(\\d*)(\\D*)$");
Matcher m = p.matcher(v);
m.find();
if (m.group(2).isEmpty()) {
// v1.0.0.[preview]
return -1;
}
double i = Integer.parseInt(m.group(2));
if (!m.group(3).isEmpty()) {
// v1.0.[0b]
i -= 0.1;
}
return i;
}
public static int versionCompare(String str1, String str2) {
String[] v1 = str1.split("\\.");
String[] v2 = str2.split("\\.");
int i = 0;
for (; i < v1.length && i < v2.length; i++) {
double iv1 = parseVersion(v1[i]);
double iv2 = parseVersion(v2[i]);
if (iv1 != iv2) {
return iv1 - iv2 < 0 ? -1 : 1;
}
}
if (i < v1.length) {
// "1.0.1", "1.0"
double iv1 = parseVersion(v1[i]);
return iv1 < 0 ? -1 : (int)Math.ceil(iv1);
}
if (i < v2.length) {
double iv2 = parseVersion(v2[i]);
return - iv2 < 0 ? -1 : (int)Math.ceil(iv2);
}
return 0;
}
public static void main(String[] args) {
System.out.println("versionCompare(v1.0.0, 1.0.0)");
System.out.println(versionCompare("v1.0.0", "1.0.0")); // 0
System.out.println("versionCompare(v1.0.0b, 1.0.0)");
System.out.println(versionCompare("v1.0.0b", "1.0.0")); // -1
System.out.println("versionCompare(v1.0.0.preview, 1.0.0)");
System.out.println(versionCompare("v1.0.0.preview", "1.0.0")); // -1
System.out.println("versionCompare(v1.0, 1.0.0)");
System.out.println(versionCompare("v1.0", "1.0.0")); // 0
System.out.println("versionCompare(ver1.0, 1.0.1)");
System.out.println(versionCompare("ver1.0", "1.0.1")); // -1
}
}
Однако он по-прежнему не подходит для случаев, когда вам нужно сравнить "альфа" с "бета".
Для своих проектов я использую свою библиотеку commons-version https://github.com/raydac/commons-version которая содержит два вспомогательных класса - для анализа версии (проанализированную версию можно сравнить с другим объектом версии, поскольку она сопоставима) и VersionValidator который позволяет проверить версию для некоторого выражения, как !=ide-1.1.1,>idea-1.3.4-SNAPSHOT;<1.2.3
Я создал простую утилиту для сравнения версий на платформе Android с использованием соглашения о семантическом управлении версиями. Поэтому он работает только для строк в формате XYZ (Major.Minor.Patch), где X, Y и Z являются неотрицательными целыми числами. Вы можете найти его на моем GitHub.
Метод Version.compareVersions(String v1, String v2) сравнивает две строки версии. Возвращает 0, если версии равны, 1, если версия v1 предшествует версии v2, -1, если версия v1 более поздняя, чем версия v2, -2, если формат версии недопустим.
На основе /questions/5115104/kak-vyi-sravnivaete-dve-versii-strok-v-java/5115134#5115134
class Version(private val value: String) : Comparable<Version> {
private val splitted by lazy { value.split("-").first().split(".").map { it.toIntOrNull() ?: 0 } }
override fun compareTo(other: Version): Int {
for (i in 0 until maxOf(splitted.size, other.splitted.size)) {
val compare = splitted.getOrElse(i) { 0 }.compareTo(other.splitted.getOrElse(i) { 0 })
if (compare != 0)
return compare
}
return 0
}
}
вы можете использовать как:
System.err.println(Version("1.0").compareTo( Version("1.0")))
System.err.println(Version("1.0") < Version("1.1"))
System.err.println(Version("1.10") > Version("1.9"))
System.err.println(Version("1.10.1") > Version("1.10"))
System.err.println(Version("0.0.1") < Version("1"))
Для тех, кто собирается показывать оповещение о принудительном обновлении на основе номера версии, у меня есть следующая идея. Это можно использовать при сравнении версий между текущей версией приложения Android и версией удаленной конфигурации firebase. Это не совсем ответ на заданный вопрос, но это определенно кому-то поможет.
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
public class Main
{
static String firebaseVersion = "2.1.3"; // or 2.1
static String appVersion = "2.1.4";
static List<String> firebaseVersionArray;
static List<String> appVersionArray;
static boolean isNeedToShowAlert = false;
public static void main (String[]args)
{
System.out.println ("Hello World");
firebaseVersionArray = new ArrayList<String>(Arrays.asList(firebaseVersion.split ("\\.")));
appVersionArray = new ArrayList<String>(Arrays.asList(appVersion.split ("\\.")));
if(appVersionArray.size() < firebaseVersionArray.size()) {
appVersionArray.add("0");
}
if(firebaseVersionArray.size() < appVersionArray.size()) {
firebaseVersionArray.add("0");
}
isNeedToShowAlert = needToShowAlert(); //Returns false
System.out.println (isNeedToShowAlert);
}
static boolean needToShowAlert() {
boolean result = false;
for(int i = 0 ; i < appVersionArray.size() ; i++) {
if (Integer.parseInt(appVersionArray.get(i)) == Integer.parseInt(firebaseVersionArray.get(i))) {
continue;
} else if (Integer.parseInt(appVersionArray.get(i)) > Integer.parseInt(firebaseVersionArray.get(i))){
result = false;
break;
} else if (Integer.parseInt(appVersionArray.get(i)) < Integer.parseInt(firebaseVersionArray.get(i))) {
result = true;
break;
}
}
return result;
}
}
Вы можете запустить этот код, скопировав вставку в https://www.onlinegdb.com/online_java_compiler
Я сам написал небольшую функцию. Упрощенный, используя списки
public static boolean checkVersionUpdate(String olderVerison, String newVersion) {
if (olderVerison.length() == 0 || newVersion.length() == 0) {
return false;
}
List<String> newVerList = Arrays.asList(newVersion.split("\\."));
List<String> oldVerList = Arrays.asList(olderVerison.split("\\."));
int diff = newVerList.size() - oldVerList.size();
List<String> newList = new ArrayList<>();
if (diff > 0) {
newList.addAll(oldVerList);
for (int i = 0; i < diff; i++) {
newList.add("0");
}
return examineArray(newList, newVerList, diff);
} else if (diff < 0) {
newList.addAll(newVerList);
for (int i = 0; i < -diff; i++) {
newList.add("0");
}
return examineArray(oldVerList, newList, diff);
} else {
return examineArray(oldVerList, newVerList, diff);
}
}
public static boolean examineArray(List<String> oldList, List<String> newList, int diff) {
boolean newVersionGreater = false;
for (int i = 0; i < oldList.size(); i++) {
if (Integer.parseInt(newList.get(i)) > Integer.parseInt(oldList.get(i))) {
newVersionGreater = true;
break;
} else if (Integer.parseInt(newList.get(i)) < Integer.parseInt(oldList.get(i))) {
newVersionGreater = false;
break;
} else {
newVersionGreater = diff > 0;
}
}
return newVersionGreater;
}
public static void main(String[] args) {
String version1 = "1.0";
String version2 = "1.0.0";
String[] version1_splits = version1.split("\\.");
String[] version2_splits = version2.split("\\.");
int length = version1_splits.length >= version2_splits.length ? version1_splits.length : version2_splits.length;
int i=0;
for(;i<length;i++){
int version1_int = getValue(version1_splits,i);
int version2_int = getValue(version2_splits,i);
if(version1_int > version2_int){
System.out.println("version1 > version2");
break;
}
else if(version1_int < version2_int){
System.out.println("version2 > version1");
break;
}
else{
if(i == length-1)
System.out.println("version1 = version2");
}
}
}
private static int getValue(String[] version1_splits, int i) {
int temp;
try{
temp = Integer.valueOf(version1_splits[i]);
}
catch(IndexOutOfBoundsException e){
temp=0;
}
return temp;
}
котлин:
@kotlin.jvm.Throws(InvalidParameterException::class)
fun String.versionCompare(remoteVersion: String?): Int {
val remote = remoteVersion?.splitToSequence(".")?.toList() ?: return 1
val local = this.splitToSequence(".").toList()
if(local.filter { it.toIntOrNull() != null }.size != local.size) throw InvalidParameterException("version invalid: $this")
if(remote.filter { it.toIntOrNull() != null }.size != remote.size) throw InvalidParameterException("version invalid: $remoteVersion")
val totalRange = 0 until kotlin.math.max(local.size, remote.size)
for (i in totalRange) {
if (i < remote.size && i < local.size) {
val result = local[i].compareTo(remote[i])
if (result != 0) return result
} else (
return local.size.compareTo(remote.size)
)
}
return 0
}
Использование Java 8 Stream для замены начальных нулей в компонентах. Этот код прошел все тесты на интервью bit.com
public int compareVersion(String A, String B) {
List<String> strList1 = Arrays.stream(A.split("\\."))
.map(s -> s.replaceAll("^0+(?!$)", ""))
.collect(Collectors.toList());
List<String> strList2 = Arrays.stream(B.split("\\."))
.map(s -> s.replaceAll("^0+(?!$)", ""))
.collect(Collectors.toList());
int len1 = strList1.size();
int len2 = strList2.size();
int i = 0;
while(i < len1 && i < len2){
if (strList1.get(i).length() > strList2.get(i).length()) return 1;
if (strList1.get(i).length() < strList2.get(i).length()) return -1;
int result = new Long(strList1.get(i)).compareTo(new Long(strList2.get(i)));
if (result != 0) return result;
i++;
}
while (i < len1){
if (!strList1.get(i++).equals("0")) return 1;
}
while (i < len2){
if (!strList2.get(i++).equals("0")) return -1;
}
return 0;
}
Вы можете использовать библиотеку versionparser . Он может анализировать версии и ограничения версий, выраженные в ряде схем управления версиями (например, Maven, Semantic, NPM и т. д.). Результирующие объекты версий упорядочиваются, а объекты ограничений версий поддерживают операции над множествами (например, пересечение).
Самое простое решение - использовать сравнение строк. Удалите незакрепленные точки или буквы в случае, если версия похожа на v1.0.1Beta с использованием Regex("[^0-9]"), и вы получите для таких версий, как 1.0 и 1.0.1:
String ver1 = "10";
String ver2 = "101";
int res = ver1.compareTo(ver2);
System.out.println("res: "+res);
Если ver1 лексикографически меньше ver2 , будет возвращено отрицательное число, 0, если оно равно, или положительное число, если ver1 больше.
Метод compareTo () сравнивает две строки лексикографически.
Сравнение основано на значении Unicode каждого символа в строках.
Метод возвращает 0, если строка равна другой строке. Значение меньше 0 возвращается, если строка меньше другой строки (меньше символов), и значение больше 0, если строка больше другой строки (больше символов).
Мне понравилась идея от @Peter Lawrey, и я расширил ее до следующих пределов:
/**
* Normalize string array,
* Appends zeros if string from the array
* has length smaller than the maxLen.
**/
private String normalize(String[] split, int maxLen){
StringBuilder sb = new StringBuilder("");
for(String s : split) {
for(int i = 0; i<maxLen-s.length(); i++) sb.append('0');
sb.append(s);
}
return sb.toString();
}
/**
* Removes trailing zeros of the form '.00.0...00'
* (and does not remove zeros from, say, '4.1.100')
**/
public String removeTrailingZeros(String s){
int i = s.length()-1;
int k = s.length()-1;
while(i >= 0 && (s.charAt(i) == '.' || s.charAt(i) == '0')){
if(s.charAt(i) == '.') k = i-1;
i--;
}
return s.substring(0,k+1);
}
/**
* Compares two versions(works for alphabets too),
* Returns 1 if v1 > v2, returns 0 if v1 == v2,
* and returns -1 if v1 < v2.
**/
public int compareVersion(String v1, String v2) {
// Uncomment below two lines if for you, say, 4.1.0 is equal to 4.1
// v1 = removeTrailingZeros(v1);
// v2 = removeTrailingZeros(v2);
String[] splitv1 = v1.split("\\.");
String[] splitv2 = v2.split("\\.");
int maxLen = 0;
for(String str : splitv1) maxLen = Math.max(maxLen, str.length());
for(String str : splitv2) maxLen = Math.max(maxLen, str.length());
int cmp = normalize(splitv1, maxLen).compareTo(normalize(splitv2, maxLen));
return cmp > 0 ? 1 : (cmp < 0 ? -1 : 0);
}
Надеюсь, это поможет кому-то. Он прошел все тестовые случаи в интервью bit и leetcode (необходимо раскомментировать две строки в функции CompareVersion).
Легко проверяется!