36

下面是一个简单的 ArrayList 排序程序:

ArrayList<String> list = new ArrayList<String>();

list.add("1_Update");
list.add("11_Add");
list.add("12_Delete");
list.add("2_Create");

Collections.sort(list);
for (String str : list) {
  System.out.println(str.toString());
}

我期待这个程序的输出为:

1_Update
2_Create
11_Add
12_Delete

但是当我运行这个程序时,我得到的输出是:

11_Add
12_Delete
1_Update
2_Create

为什么会这样?如何让 ArrayList 进行排序,如预期输出中所示?

4

12 回答 12

70

您可以编写一个自定义比较器:

Collections.sort(list, new Comparator<String>() {
    public int compare(String a, String b) {
        return Integer.signum(fixString(a) - fixString(b));
    }
    private int fixString(String in) {
        return Integer.parseInt(in.substring(0, in.indexOf('_')));
    }
});
于 2009-05-20T21:57:07.227 回答
8

当您将这种类型的数据排序为字符串时,它会比较字符本身,包括数字。例如,所有以“1”开头的字符串都会一起结束。所以订单最终类似于这个......

1 10 100 2 20 200

排序在任何时候都不会“意识到”您正在为字符串的子集分配含义,例如字符串前面的可变长度数字。将数字排序为字符串时,向左侧填充尽可能多的零以覆盖最大数字可能会有所帮助,但是当您不控制数据时,它并不能真正解决问题,如您的示例所示。在那种情况下,排序将是......

001 002 010 020 100 200

于 2009-05-20T21:14:16.207 回答
6

它被排序为文本(按字母顺序),而不是数字。为了解决这个问题,您可以按照 nsayer 的答案中的建议实现自定义比较器。

于 2009-05-20T21:10:27.790 回答
3

它正在进行字典比较。它比较每个字符串中的第一个字符对它们进行排序。然后它比较具有相同第一个字符的第二个字符串。当它将“_”字符与数字进行比较时,它的值大于任何单个数字字符,例如 8 > 7 和 a > 9。请记住,它是在进行字符比较而不是数字比较。

有一些方法可以实现您自己的自定义排序路由,这可能比重命名脚本名称更好。

如果重命名脚本名称是一个选项,这可能允许使用其他脚本工具。一种格式可能是

01_create_table.sql
02_create_index.sql
11_assign_privileges.sql

通过将前两位数字保留为两个字符,词典比较将起作用。

于 2009-05-20T21:19:42.887 回答
2

Collections.sort() 方法的文档说:

根据其元素的自然顺序,将指定列表按升序排序。

这意味着对于字符串,您将按字母顺序获取列表。字符串 11_assign_privileges.sql 位于字符串 1_create_table.sql 之前,而 12_07_insert_static_data.sql 位于 1_create_table.sql 等之前。因此程序按预期工作。

于 2009-05-20T21:13:33.353 回答
0

因为字符串按字母顺序排序,而下划线字符位于数字字符之后。您必须提供一个实现“自然顺序”的比较器才能达到预期的结果。

于 2009-05-20T21:13:43.553 回答
0

字符串比较算法一次比较每个字符1排序之前21后面跟 a或 a无关紧要2

所以100会先排序2。如果您不想要这种行为,您需要一个比较算法来处理这种情况。

于 2009-05-20T21:15:28.230 回答
0

正如其他人所说,默认情况下元素将按字母顺序排序。解决方案是定义一个具体的 java.util.Comparator 类并将其作为第二个参数传递给 sort 方法。您的比较器需要从字符串中解析出前导整数并进行比较。

于 2009-05-20T21:17:40.240 回答
0

要让 Collection.sort() 任意排序,您可以使用

Collections.sort(List list, Comparator c)  

然后简单地实现一个比较器,它拆分字符串并首先根据数字排序,然后根据其余部分或您希望它排序。

于 2009-05-20T21:36:14.553 回答
0

大家已经指出,解释是你的字符串是按字符串排序的,一个数字已经把你的注意力引向Natural Order字符串比较。我只想补充一点,自己编写比较器是一个很好的练习,也是一个练习测试驱动开发的好机会。我在 Code Camp 上用它来演示 TDD;幻灯片和代码在这里

于 2009-05-20T21:42:04.620 回答
0

如上所述,您正在寻找实现自然排序的 Comparator 实现。Jeff Atwood前段时间写了一篇关于自然排序的优秀文章——非常值得一读。

如果您正在寻找 Java 实现,我发现这个很有用: http ://www.davekoelle.com/alphanum.html

于 2009-05-20T21:42:30.330 回答
0

您可以添加 IComparable 接口,然后按特定属性排序。例如,如果您有商店的商品集合,您可能想按价格或按类别等对它们进行排序。如果您想按名称订购,这是一个示例:

请注意 ArrayList 如何按项目的 name 属性排序。如果你不添加 IComparable 那么当你使用 sort 方法时它会抛出一个错误。

在此处输入图像描述

static void Main(string[] args)
    {
        ArrayList items = new ArrayList();
        items.Add(new Item("book", 12.32));
        items.Add(new Item("cd", 16.32));
        items.Add(new Item("bed", 124.2));
        items.Add(new Item("TV", 12.32));

        items.Sort();

        foreach (Item temp in items)
            Console.WriteLine("Name:{0} Price:{1}", temp.name, temp.price);
        Console.Read();            
    }


    class Item: IComparable
    {
        public string name;
        public double price;

        public Item(string _name, double _price)
        {
            this.name = _name;
            this.price = _price;
        }

        public int CompareTo(object obj)
        {   
            //note that I use the name property I may use a different one
            int temp = this.name.CompareTo(((Item)obj).name);
            return temp;
        }
    }
于 2011-03-12T19:01:53.643 回答