如何按升序排序文件名?

20

我有一个文件夹里面存放了一组文件,它们的命名都以相似的名称开头,只有一个文件不同。以下是一个例子:

Coordinate.txt
Spectrum_1.txt
Spectrum_2.txt
Spectrum_3.txt
.
.
.
Spectrum_11235

我能够列出指定文件夹中的所有文件,但列表不按光谱号的升序排列。例如:执行程序时,我会得到以下结果:

Spectrum_999.txt
Spectrum_9990.txt
Spectrum_9991.txt
Spectrum_9992.txt
Spectrum_9993.txt
Spectrum_9994.txt
Spectrum_9995.txt
Spectrum_9996.txt
Spectrum_9997.txt
Spectrum_9998.txt
Spectrum_9999.txt

但是这个顺序是不正确的。在Spectrum_999.txt文件之后应该有Spectrum_1000.txt文件。有人可以帮忙吗?以下是代码:

import java.io.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

    public class FileInput {

        public void userInput()
        {
            Scanner scanner = new Scanner( System.in );
            System.out.println("Enter the file path: ");
            String dirPath = scanner.nextLine(); // Takes the directory path as the user input

            File folder = new File(dirPath);
            if(folder.isDirectory())
            {
                File[] fileList = folder.listFiles();

                Arrays.sort(fileList);

                System.out.println("\nTotal number of items present in the directory: " + fileList.length );


                // Lists only files since we have applied file filter
                for(File file:fileList)
                {
                    System.out.println(file.getName());
                }

                // Creating a filter to return only files.
                FileFilter fileFilter = new FileFilter()
                {
                    @Override
                    public boolean accept(File file) {
                        return !file.isDirectory();
                    }
                };

                fileList = folder.listFiles(fileFilter);

                // Sort files by name
                Arrays.sort(fileList, new Comparator()
                {
                    @Override
                    public int compare(Object f1, Object f2) {
                        return ((File) f1).getName().compareTo(((File) f2).getName());
                    }
                });

                //Prints the files in file name ascending order
                for(File file:fileList)
                {
                    System.out.println(file.getName());
                }

            }   
        }
    }
11个回答

34
你所要求的是数字排序。你需要实现一个Comparator并将其传递给Arrays#sort方法。在比较方法中,你需要从每个文件名中提取数字,然后进行比较。
你现在得到输出的原因是排序是按字母数字方式进行的。
这里是一种非常基本的做法。这段代码使用简单的String操作来提取数字。如果你知道文件名的格式,例如Spectrum_<number>.txt,那么这种方法就行之有效。更好的提取方式是使用正则表达式
public class FileNameNumericSort {

    private final static File[] files = {
        new File("Spectrum_1.txt"),
        new File("Spectrum_14.txt"),
        new File("Spectrum_2.txt"),
        new File("Spectrum_7.txt"),     
        new File("Spectrum_1000.txt"), 
        new File("Spectrum_999.txt"), 
        new File("Spectrum_9990.txt"), 
        new File("Spectrum_9991.txt"), 
    };

    @Test
    public void sortByNumber() {
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                int n1 = extractNumber(o1.getName());
                int n2 = extractNumber(o2.getName());
                return n1 - n2;
            }

            private int extractNumber(String name) {
                int i = 0;
                try {
                    int s = name.indexOf('_')+1;
                    int e = name.lastIndexOf('.');
                    String number = name.substring(s, e);
                    i = Integer.parseInt(number);
                } catch(Exception e) {
                    i = 0; // if filename does not match the format
                           // then default to 0
                }
                return i;
            }
        });

        for(File f : files) {
            System.out.println(f.getName());
        }
    }
}

输出

Spectrum_1.txt
Spectrum_2.txt
Spectrum_7.txt
Spectrum_14.txt
Spectrum_999.txt
Spectrum_1000.txt
Spectrum_9990.txt
Spectrum_9991.txt

1
@LukasEder 这里有一种非常基本的方法。此代码使用简单的字符串操作来提取数字。如果您知道文件名的格式,例如Spectrum_<number>.txt,则可以使用此方法。更好的提取方式是使用正则表达式。这里的答案并不意味着提供完全可工作的生产代码,而是提供解决问题的提示和技巧,以及一个非常基本的代码示例。希望我能回答您的问题。 - A4L

8

目前被接受的答案仅对总是以相同名称(即忽略前缀)命名的文件的数字后缀执行此操作。

一种更通用的解决方案,在这里我写了博客文章, 适用于任何文件名,将名称分成段并按数字(如果两个段都是数字)或词典顺序排序,否则。 灵感来自于这个答案:

public final class FilenameComparator implements Comparator<String> {
    private static final Pattern NUMBERS = 
        Pattern.compile("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
    @Override public final int compare(String o1, String o2) {
        // Optional "NULLS LAST" semantics:
        if (o1 == null || o2 == null)
            return o1 == null ? o2 == null ? 0 : -1 : 1;

        // Splitting both input strings by the above patterns
        String[] split1 = NUMBERS.split(o1);
        String[] split2 = NUMBERS.split(o2);
        for (int i = 0; i < Math.min(split1.length, split2.length); i++) {
            char c1 = split1[i].charAt(0);
            char c2 = split2[i].charAt(0);
            int cmp = 0;

            // If both segments start with a digit, sort them numerically using 
            // BigInteger to stay safe
            if (c1 >= '0' && c1 <= '9' && c2 >= '0' && c2 <= '9')
                cmp = new BigInteger(split1[i]).compareTo(new BigInteger(split2[i]));

            // If we haven't sorted numerically before, or if numeric sorting yielded 
            // equality (e.g 007 and 7) then sort lexicographically
            if (cmp == 0)
                cmp = split1[i].compareTo(split2[i]);

            // Abort once some prefix has unequal ordering
            if (cmp != 0)
                return cmp;
        }

        // If we reach this, then both strings have equally ordered prefixes, but 
        // maybe one string is longer than the other (i.e. has more segments)
        return split1.length - split2.length;
    }
}

这也可以处理带有子版本的版本,例如像version-1.2.3.txt这样的内容。

3
我看到您正在使用正则表达式,不知道您是从哪里得到灵感的...我猜您应该得到一颗小饼干;-) - A4L
@A4L:你说得对,我已经标注了我的来源,就像我应该做的一样! - Lukas Eder

7

Commons IO库中提供了一个NameFileComparator类,它具有按名称、最后修改日期、大小等特征排序文件数组的功能。可以按升序或降序排列文件,并区分大小写或不区分大小写。

引入:

org.apache.commons.io.comparator.NameFileComparator

代码:

File directory = new File(".");
File[] files = directory.listFiles();
Arrays.sort(files, NameFileComparator.NAME_COMPARATOR)

2
那不是按照原始问题想要的方式排序。它忽略了数字。 - c4k
正确,这种排序会将a10.txt放在a9.txt之前。 - undefined

3

您可以在上面的评论中找到解决问题的方法,但考虑到只发布了链接,我会提供来自该网站的代码。效果很好。

  1. You need to create your own AlphanumericalComparator.

     import java.io.File;
     import java.util.Comparator;
    
    public class AlphanumFileComparator implements Comparator
    {
    
       private final boolean isDigit(char ch)
       {
        return ch >= 48 && ch <= 57;
       }
    
    
    private final String getChunk(String s, int slength, int marker)
    {
        StringBuilder chunk = new StringBuilder();
        char c = s.charAt(marker);
        chunk.append(c);
        marker++;
        if (isDigit(c))
        {
            while (marker < slength)
            {
                c = s.charAt(marker);
                if (!isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        } else
        {
            while (marker < slength)
            {
                c = s.charAt(marker);
                if (isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        }
        return chunk.toString();
    }
    
    public int compare(Object o1, Object o2)
    {
        if (!(o1 instanceof File) || !(o2 instanceof File))
        {
            return 0;
        }
        File f1 = (File)o1;
        File f2 = (File)o2;
        String s1 = f1.getName();
        String s2 = f2.getName();
    
        int thisMarker = 0;
        int thatMarker = 0;
        int s1Length = s1.length();
        int s2Length = s2.length();
    
        while (thisMarker < s1Length && thatMarker < s2Length)
        {
            String thisChunk = getChunk(s1, s1Length, thisMarker);
            thisMarker += thisChunk.length();
    
            String thatChunk = getChunk(s2, s2Length, thatMarker);
            thatMarker += thatChunk.length();
    
            /** If both chunks contain numeric characters, sort them numerically **/
    
            int result = 0;
            if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0)))
            {
                // Simple chunk comparison by length.
                int thisChunkLength = thisChunk.length();
                result = thisChunkLength - thatChunk.length();
                // If equal, the first different number counts
                if (result == 0)
                {
                    for (int i = 0; i < thisChunkLength; i++)
                    {
                        result = thisChunk.charAt(i) - thatChunk.charAt(i);
                        if (result != 0)
                        {
                            return result;
                        }
                    }
                }
            } else
            {
                result = thisChunk.compareTo(thatChunk);
            }
    
            if (result != 0)
                return result;
        }
    
        return s1Length - s2Length;
    }
    }
    

2. 按照这个类别来整理你的文件。

     File[] listOfFiles = rootFolder.listFiles();
     Arrays.sort(listOfFiles, new AlphanumFileComparator() );
     ...to sth with your files.

希望对你有所帮助。这个方法对我非常有效。
解决方案来自:http://www.davekoelle.com/files/AlphanumComparator.java,可以在这里找到。

2
Arrays.sort(fileList, new Comparator()
{
    @Override
    public int compare(Object f1, Object f2) {
        String fileName1 = ((File) f1).getName();
        String fileName2 = ((File) f1).getName();

        int fileId1 = Integer.parseInt(fileName1.split("_")[1]);
        int fileId2 = Integer.parseInt(fileName2.split("_")[1]);

        return fileId1 - fileId2;
    }
});

请确保处理文件名中不含下划线的文件。


2

只需使用以下代码:

  1. 升序排列:Collections.sort(List)

  2. 降序排列:Collections.sort(List,Collections.reverseOrder())


它必须是类型为 List<File> 的。 - Somil Aseeja
Collections.sort使用自然排序,这会将"a_10"放在"a_9"之前。这不符合问题的要求。 - stormdb

1
在此找到由Ivan Gerasimiv实现的智能AlphaDecimal比较器最佳实现(此处)。它被提议作为标准JDK比较器(此处)的扩展,并在一个线程(此处)中进行了讨论。
不幸的是,据我所知,这个更改仍然没有进入JDK。但是,您可以使用第一个链接中的代码作为解决方案。对我而言,它非常有效。

0

我的 Kotlin 版本算法。该算法用于按名称对文件和目录进行排序。

import kotlin.Comparator

const val DIFF_OF_CASES = 'a' - 'A'

/** File name comparator. */
class FileNameComparator(private val caseSensitive: Boolean = false) : Comparator<String> {

    override fun compare(left: String, right: String): Int {
        val csLeft = left.toCharArray()
        val csRight = right.toCharArray()
        var isNumberRegion = false
        var diff = 0; var i = 0; var j = 0
        val lenLeft = csLeft.size; val lenRight = csRight.size
        while (i < lenLeft && j < lenRight) {
            val cLeft = getCharByCaseSensitive(csLeft[i])
            val cRight = getCharByCaseSensitive(csRight[j])
            val isNumericLeft = cLeft in '0'..'9'
            val isNumericRight = cRight in '0'..'9'
            if (isNumericLeft && isNumericRight) {
                // Number start!
                if (!isNumberRegion) {
                    isNumberRegion = true
                    // Remove prefix '0'
                    while (i < lenLeft && cLeft == '0') i++
                    while (j < lenRight && cRight == '0') j++
                    if (i == lenLeft || j == lenRight) break
                }
                // Diff start: calculate the diff value.
                if (cLeft != cRight && diff == 0) diff = cLeft - cRight
            } else {
                if (isNumericLeft != isNumericRight) {
                    // One numeric and one char: the longer one is backwards.
                    return if (isNumberRegion) { if (isNumericLeft) 1 else -1 } else cLeft - cRight
                } else {
                    // Two chars: if (number) diff don't equal 0, return it (two number have same length).
                    if (diff != 0) return diff
                    // Calculate chars diff.
                    diff = cLeft - cRight
                    if (diff != 0) return diff
                    // Reset!
                    isNumberRegion = false
                    diff = 0
                }
            }
            i++; j++
        }
        if (i == lenLeft) i--
        if (j == lenRight) j--
        return csLeft[i] - csRight[j]
    }

    private fun getCharByCaseSensitive(c: Char): Char
            = if (caseSensitive) c else if (c in 'A'..'Z') (c + DIFF_OF_CASES) else c
}


例如,对于输入:
          "A__01__02",
            "A__2__02",
            "A__1__23",
            "A__11__23",
            "A__3++++",
            "B__1__02",
            "B__22_13",
            "1_22_2222",
            "12_222_222",
            "2222222222",
            "1.sadasdsadsa",
            "11.asdasdasdasdasd",
            "2.sadsadasdsad",
            "22.sadasdasdsadsa",
            "3.asdasdsadsadsa",
            "adsadsadsasd1",
            "adsadsadsasd10",
            "adsadsadsasd3",
            "adsadsadsasd02"

它将按以下方式对它们进行排序:

1.sadasdsadsa 1_22_2222 2.sadsadasdsad 3.asdasdsadsadsa 11.asdasdasdasdasd 12_222_222 22.sadasdasdsadsa 2222222222 A__01__02 A__1__23 A__2__02 A__3++++ A__11__23 adsadsadsasd02 adsadsadsasd1 adsadsadsasd3 adsadsadsasd10 B__1__02 B__22_13 

0

最简单的方法是使用NameFileComparator.NAME_COMPARATOR。以下是代码格式:

File[] fileNamesArr = filePath.listFiles();
if(fileNamesArr != null && fileNamesArr.length > 0) {
    Arrays.sort(fileNamesArr, NameFileComparator.NAME_COMPARATOR);
}

为了实现 NameFileComparator,您需要添加以下 Maven 库: implementation 'commons-io:commons-io:2.6' 还需导入以下内容: import org.apache.commons.io.comparator.NameFileComparator; 就这些...

-1

这只是另一种方法,但利用Java8的强大功能来实现。

List<Path> x = Files.list(Paths.get("C:\\myPath\\Tools"))
            .filter(p -> Files.exists(p))
            .map(s -> s.getFileName())
            .sorted()
            .collect(Collectors.toList());

x.forEach(System.out::println);

如果您使用映射,那么 sorted 以及其后的部分将会使用文件名(即 String 对象)而不是实际路径或文件对象。在收集结果时,toList 方法实际上会生成一个 List<String> 而不是一个 List<Path> 对象。不过我已经根据我的需求调整了您的代码:File[] sortedFiles = Arrays.stream(files).filter(f -> Files.exists(f.toPath())).sorted(Comparator.comparing(File::getName)).toArray(File[]::new); - Roman Vottner
sorted()使用自然排序,这将把"a_10"放在"a_9"之前。这不是问题所要求的。 - stormdb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接