最近需要编写一个将一系列图片合并为一个webp动图的程序,遇到了一个文件排序的问题。
文件排序问题:
1.按照文件大小排序
2.按照文件修改时间排序
3.按照文件名称排序
这些排序操作都很简单,编程实现也很容易。
但是对于第3种排序,即按照文件名称排序,理解上其实存在一些歧义。
例如对于以下文件序列:
a10.txt
file9.txt
a9.txt
file8.txt
a8.txt
file11.txt
file7.txt
file10.txt
a11.txt
可以给出以下两种文件名称排序结果:
a8.txt
a9.txt
a10.txt
a11.txt
file7.txt
file8.txt
file9.txt
file10.txt
file11.txt
a10.txt
a11.txt
a8.txt
a9.txt
file10.txt
file11.txt
file7.txt
file8.txt
file9.txt
第二种结果很好理解,文件名按照字符的字典顺序进行排序,编程实现起来很简单。而第一种结果中,则提取了文件名中的数字,对数字部分按照数字大小来排序,其他字符部分按照字典序排序。
当文件名中的数字位数一致,即短位数用0补齐的情况下,两种排序方式是一致的,但是当文件名中的数字位数不一致时,两种排序的结果则有差异,在实际场景中,我们往往需要的是第一种结果。
第一种结果的排序方式如何编程实现呢?仔细一想,还有些麻烦,上例中,文件名只存在一个数字,如果文件名中存在多组数字呢,例如:
file9_9.txt
file9_10.txt
file9_11.txt
file10_9.txt
file10_10.txt
file10_11.txt
实现方式:
将文件名称按照普通字符、数字串拆分为一个字符串数组,在对两个文件名进行排序时,依次对比它们的字符串数组中的每一项,如果两者都是普通字符,则按照普通字符比较,如果两者都是数字串,则按照数字大小比较,否则认为数字串小于普通字符串,空字符串小于数组字符串。
编码实现:
两个类:
FileNameEntry.class:文件名中的一个字串,要么是一个不包含数字的普通字符串,要么是一个纯数字字符串。
FileName.class:FileNameEntry的集合。
package com.dancen.util.filesort;
import java.util.ArrayList;
import java.util.List;
import com.dancen.util.MyStringUtil;
/**
*
* @author dancen
*
*/
public class FileNameEntry implements Comparable<FileNameEntry>
{
private String text;
private long number;
public static void main(String[] args)
{
List<FileNameEntry> entries = createFileNameEntries("a1");
for(FileNameEntry entry : entries)
{
System.out.println(entry);
}
}
public static List<FileNameEntry> createFileNameEntries(String fileName)
{
List<FileNameEntry> rs = null;
if(null != fileName)
{
rs = new ArrayList<FileNameEntry>();
StringBuilder textStringBuilder = new StringBuilder();
StringBuilder numberStringBuilder = new StringBuilder();
for(char c : fileName.toCharArray())
{
if(Character.isDigit(c))
{
if(0 != textStringBuilder.length())
{
String text = textStringBuilder.toString();
textStringBuilder.delete(0, textStringBuilder.length());
FileNameEntry fileNameEntry = new FileNameEntry(text, -1);
rs.add(fileNameEntry);
}
numberStringBuilder.append(c);
}
else
{
if(0 != numberStringBuilder.length())
{
String text = numberStringBuilder.toString();
numberStringBuilder.delete(0, numberStringBuilder.length());
long number = MyStringUtil.parseLong(text, (long)-1);
FileNameEntry fileNameEntry = new FileNameEntry(text, number);
rs.add(fileNameEntry);
}
textStringBuilder.append(c);
}
}
if(0 != textStringBuilder.length())
{
String text = textStringBuilder.toString();
textStringBuilder.delete(0, textStringBuilder.length());
FileNameEntry fileNameEntry = new FileNameEntry(text, -1);
rs.add(fileNameEntry);
}
if(0 != numberStringBuilder.length())
{
String text = numberStringBuilder.toString();
numberStringBuilder.delete(0, numberStringBuilder.length());
long number = MyStringUtil.parseLong(text, (long)-1);
FileNameEntry fileNameEntry = new FileNameEntry(text, number);
rs.add(fileNameEntry);
}
}
return rs;
}
private FileNameEntry(String text, long number)
{
this.setText(text);
this.setNumber(number);
}
public String getText()
{
return this.text;
}
public long getNumber()
{
return this.number;
}
@Override
public String toString()
{
return String.format("text=%s,number=%d", this.text, this.number);
}
@Override
public int compareTo(FileNameEntry entry)
{
int rs = 0;
if(null != entry)
{
String text = entry.getText();
long number = entry.getNumber();
if(0 <= this.number)
{
if(0 <= number)
{
rs = (int)(this.number - number);
}
else
{
rs = -1;
}
}
else
{
if(0 <= number)
{
rs = 1;
}
else
{
rs = this.text.compareTo(text);
}
}
}
return rs;
}
private void setText(String text)
{
if(null == text)
{
throw new IllegalArgumentException("the text is null");
}
this.text = text;
}
private void setNumber(long number)
{
this.number = number;
}
}
package com.dancen.util.filesort;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 文件名称:支持智能排序
* @author dancen
*
*/
public class FileName implements Comparable<FileName>
{
private String name;
private List<FileNameEntry> entries;
public static void main(String[] args)
{
List<FileName> fileNames = new ArrayList<FileName>();
fileNames.add(new FileName("a"));
fileNames.add(new FileName("005c"));
fileNames.add(new FileName("c"));
fileNames.add(new FileName("b100"));
fileNames.add(new FileName("b9"));
fileNames.add(new FileName("05c"));
fileNames.add(new FileName("004c"));
fileNames.add(new FileName("a002"));
fileNames.add(new FileName("b10"));
fileNames.add(new FileName("a001"));
fileNames.add(new FileName("a9_1"));
fileNames.add(new FileName("a008_5"));
fileNames.add(new FileName("a000"));
fileNames.add(new FileName("b"));
fileNames.add(new FileName("d"));
fileNames.add(new FileName("b1000"));
fileNames.add(new FileName("a008_4"));
fileNames.add(new FileName("a008_10"));
fileNames.add(new FileName("a008_10_1"));
fileNames.add(new FileName("a008_10_10"));
fileNames.add(new FileName("a9_11"));
fileNames.add(new FileName("2e3"));
fileNames.add(new FileName("2e4"));
fileNames.add(new FileName("3e1"));
fileNames.add(new FileName("f"));
fileNames.add(new FileName("3e5"));
fileNames.add(new FileName("3e3"));
fileNames.add(new FileName("3e20"));
Collections.sort(fileNames);
for(FileName fileName : fileNames)
{
System.out.println(fileName.getName());
}
}
public FileName(String name)
{
this.setName(name);
this.entries = FileNameEntry.createFileNameEntries(this.name);
}
public String getName()
{
return this.name;
}
public List<FileNameEntry> getEntries()
{
return new ArrayList<FileNameEntry>(this.entries);
}
@Override
public int compareTo(FileName fileName)
{
int rs = 0;
if(null != fileName)
{
List<FileNameEntry> entries = fileName.getEntries();
int size = Math.min(this.entries.size(), entries.size());
for(int i = 0; i < size; i++)
{
rs = this.entries.get(i).compareTo(entries.get(i));
if(0 != rs)
{
break;
}
}
if(0 == rs)
{
rs = this.entries.size() - entries.size();
}
}
return rs;
}
private void setName(String name)
{
if(null == name)
{
throw new IllegalArgumentException("the name is null");
}
this.name = name.trim();
}
}
代码中的输出结果:
2e3
2e4
3e1
3e3
3e5
3e20
004c
005c
05c
a
a000
a001
a002
a008_4
a008_5
a008_10
a008_10_1
a008_10_10
a9_1
a9_11
b
b9
b10
b100
b1000
c
d
f