在Java中删除文件名扩展名

24

(不包括任何外部库。)

在Java中,最有效的方法是什么来删除文件名的扩展名,而不假设任何文件名吗?

以下是一些示例和预期结果:

  • folder > folder
  • hello.txt > hello
  • read.me > read
  • hello.bkp.txt > hello.bkp
  • weird..name > weird.
  • .hidden > .hidden

(或者最后一个应该只是hidden?)

编辑: 原问题假设输入是一个文件名(而不是文件路径)。由于一些答案谈论了文件路径,因此这样的函数也应该能够处理类似于:

  • rare.folder/hello > rare.folder/hello

Sylvain M的回答非常好地处理了这种特殊情况。


你可以根据“.”对字符串进行标记化。 - Albinoswordfish
2
".hidden" 不应该变成 "hidden"。 - ninesided
1
更重要的是,".hidden.txt" 文件怎么处理? - ninesided
1
@ninesided 那应该是“.hidden”。 - hpique
Sylvain M的ID改变了吗?我没有看到用那个名字签署的答案。另外,就我个人而言,我发现Python的os.path.splitext函数总是表现出我想要的方式,因此可以参考它来获取一组经过深思熟虑的用例。 - Michael Scheper
显示剩余2条评论
10个回答

28

使用Apache Common IO库http://commons.apache.org/io/

public static String removeExtension(String filename)

顺便提一下,源代码在这里:

http://commons.apache.org/proper/commons-io/javadocs/api-release/src-html/org/apache/commons/io/FilenameUtils.html#line.1025

唉,我刚刚尝试了一些东西...

System.out.println(FilenameUtils.getExtension(".polop")); // polop
System.out.println(FilenameUtils.removeExtension(".polop")); // empty string

这个解决方案似乎不太好... 即使使用常见的IO操作,你仍然需要处理removeExtension()、getExtension()和indexOfExtension()等函数...


我应该提到我不能包含外部库,但我会查看Commons IO源代码。 - hpique
这个Commons库是我最终采取的方法。我编辑了OP的问题,以包含这个限制。 - Joshua Pinter
我喜欢你的做法! :) 它完美地为我工作!谢谢你! - zBaoAnhLe

14

我将尝试使用两个参数版本的 lastIndexOf 来移除一些特殊情况检查代码,希望能使意图更加可读。感谢 Justin 'jinguy' Nelson 提供了这种方法的基础:https://dev59.com/anA75IYBdhLWcg3wJFiY#3449259

public static String removeExtention(String filePath) {
    // These first few lines the same as Justin's
    File f = new File(filePath);

    // if it's a directory, don't remove the extention
    if (f.isDirectory()) return filePath;

    String name = f.getName();

    // Now we know it's a file - don't need to do any special hidden
    // checking or contains() checking because of:
    final int lastPeriodPos = name.lastIndexOf('.');
    if (lastPeriodPos <= 0)
    {
        // No period after first character - return name as it was passed in
        return filePath;
    }
    else
    {
        // Remove the last period and everything after it
        File renamed = new File(f.getParent(), name.substring(0, lastPeriodPos));
        return renamed.getPath();
    }
}

对我来说,这比特殊处理隐藏文件和不包含点的文件更清晰。它也更符合我理解你的规范的要求;类似于“删除最后一个点及其后面的所有内容,假设它存在且不是文件名的第一个字符”。

请注意,此示例还意味着输入和输出为字符串。由于大多数抽象需要File对象,因此如果这些也是输入和输出,那么会稍微更清晰一些。


一个小问题:如果你的输入是文件路径(正如变量名所述),这个解决方案在某些情况下会失败。请参见修订后的问题。 - hpique
啊,说得对。我已经修改了答案,以便在检测不需要更改的情况下始终返回输入字符串;如果我们确实返回了一个修改过的字符串,则该字符串会带有输入路径中的目录(如果有的话)作为前缀。 - Andrzej Doyle
我认为这确实是一个更好看的版本。 - jjnguy
这段代码行不通,因为final int lastPeriodPos = name.lastIndexOf('.', 1);将从索引1开始向后搜索,所以它只会检查前两个字符。 - pistolPanties
@pistol 这是一个非常好的观点,我很惊讶之前没有人发现。我已经修复了逻辑,现在只需取 . 的最后一个索引而不需要任何边界 - 并且接受结果为 0(即第一个字符是句号)而不需要修改。感谢您指出这一点。 - Andrzej Doyle
这段代码适用于实际文件路径,但不适用于像“http://i.imgur.com/57CFv.jpg”这样的URL。它将返回“http:/i.imgur.com/57CFv”,因此会删除一个斜杠。 - sudoExclaimationExclaimation

12
这将获取文件路径并返回不带扩展名的新文件名。
public static String removeExtention(String filePath) {
    File f = new File(filePath);
    // if it's a directory, don't remove the extention
    if (fisDirectory()) return f.getName();
    String name = f.getName();
    // if it is a hidden file
    if (name.startsWith(".")) {
        // if there is no extn, do not rmove one...
        if (name.lastIndexOf('.') == name.indexOf('.')) return name;
    }
    // if there is no extention, don't do anything
    if (!name.contains(".") return name;
    // Otherwise, remove the last 'extension type thing'
    return name.substring(0, name.lastIndexOf('.'))
}

需要注意的是,此文档是在我的小型笔记本电脑上,在SO编辑器框中编写的。这段代码不适用于生产环境,只是为了作为第一次尝试从文件名中删除扩展名的好例子。


2
你需要检查 name.lastIndexOf ('。')!= -1,否则最后的 substring 调用将抛出异常。正如hgpc所说,不要假设文件名的任何内容,这似乎是应该正确处理的情况。 - Andrzej Doyle
那么,在这种情况下,我会认为扩展名是.name',而weird.`是文件的名称。 - jjnguy
filePath变量的名称具有误导性,因为如果filePath确实是路径,则此解决方案可能无法正常工作。例如:“rare.folder/hello” - hpique
@And 谢谢。通常我觉得这是隐含的,但当人们开始提出晦涩的测试案例时,我觉得有必要发布免责声明。 - jjnguy
@hgpc,我在所有情况下只返回名称,因为您只想要文件/目录的名称而不带扩展名。 - jjnguy
显示剩余6条评论

2
int p=name.lastIndexOf('.');
if (p>0)
  name=name.substring(0,p);

我之前说的是“p>0”,而不是“p>=0”,因为如果第一个字符是句点,我们可能不想像你的“.hidden”例子那样抹掉整个名称。
您是想实际更新磁盘上的文件名,还是只在内部进行操作?

我假设您仅拥有文件名,即没有路径。我看到其他人提出了这个问题。要处理完整的路径需要另外几行代码。但这并不是什么大问题。 - Jay

2
假设您有一个有效的文件名,那么这其实非常容易。
在Windows文件名中,点字符仅用于指定扩展名。因此,请去掉点及其后面的任何内容。
在类Unix的文件名中,如果最后一个分隔符(“/”)后面跟着至少一个字符,并且不是第一个字符,则点表示扩展名。找到最后一个点,查看它是否满足条件,如果满足则去掉它和任何后续字符。
在执行此操作之前,重要的是验证文件名,因为对于无效的文件名,此算法可能会执行意外操作并生成有效的文件名。因此,在Windows中,您可能需要检查点后是否有反斜杠或冒号。
如果您不知道正在处理哪种类型的文件名,则将它们全部视为类Unix格式即可完成大部分工作。

你应该展示代码,而不是试图用语言描述条件。 - Pacerier

1
使用新的Remover().remove(String)。
jdb@Vigor14:/tmp/stackoverflow> javac Remover.java && java Remover
folder > folder
hello.txt > hello
read.me > read
hello.bkp.txt > hello.bkp
weird..name > weird.
.hidden > .hidden

Remover.java,

import java.util.*;

public class Remover {

    public static void main(String [] args){
        Map<String, String> tests = new LinkedHashMap<String, String>();
        tests.put("folder", "folder");
        tests.put("hello.txt", "hello");
        tests.put("read.me", "read");
        tests.put("hello.bkp.txt", "hello.bkp");
        tests.put("weird..name", "weird.");
        tests.put(".hidden", ".hidden");

        Remover r = new Remover();
        for(String in: tests.keySet()){
            String actual = r.remove(in);
            log(in+" > " +actual);
            String expected = tests.get(in);
            if(!expected.equals(actual)){
                throw new RuntimeException();
            }
        }
    }

    private static void log(String s){
        System.out.println(s);
    }

    public String remove(String in){
        if(in == null) {
            return null;
        }
        int p = in.lastIndexOf(".");
        if(p <= 0){
            return in;
        }
        return in.substring(0, p);
    }
}

在这个新案例中失败了:rare.folder/hello > rare.folder/hello。 - Janek Bogucki

1

对于这些内容的正则表达式来说,它们足够“快”,但与可以想到的最简单方法相比并不高效:从末尾扫描字符串,并在第一个点处截断它(不包括该点)。在Java中,您可以使用lastIndexOf和substring仅获取您感兴趣的部分。初始点应视为特殊情况,如果最后一个出现的“。”在开头,则应返回整个字符串。


1

我知道一个正则表达式可以做到这一点,但在Java中必须写大约10行代码才能进行简单的正则表达式替换吗?

包括和不包括隐藏文件:

^(.*)\..*$
^(..*)\..*$

0

上面的remove()函数应该被重写以支持像LOST.DIR/myfile.txt这样的测试用例。

    public static String removeExtension( String in )
{
    int p = in.lastIndexOf(".");
    if ( p < 0 )
        return in;

    int d = in.lastIndexOf( File.separator );

    if ( d < 0 && p == 0 )
        return in;

    if ( d >= 0 && d > p )
        return in;

    return in.substring( 0, p );
}

0
filename.replace("$(.+)\.\\w+", "\1");

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