Java'da geçici bir dizin / klasör nasıl oluşturulur?


Yanıtlar:


390

JDK 7 kullanıyorsanız , geçici dizini oluşturmak için yeni Files.createTempDirectory sınıfını kullanın.

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

JDK 7'den önce bunu yapmalısınız:

public static File createTempDirectory()
    throws IOException
{
    final File temp;

    temp = File.createTempFile("temp", Long.toString(System.nanoTime()));

    if(!(temp.delete()))
    {
        throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
    }

    if(!(temp.mkdir()))
    {
        throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
    }

    return (temp);
}

İsterseniz daha iyi istisnalar (alt sınıf IOException) yapabilirsiniz.


12
Bu tehlikeli. Java'nın dosyaları hemen silmediği biliniyor, bu nedenle mkdir bazen başarısız olabilir
Demiurg

4
@Demiurg Bir dosyanın hemen silinmediği tek dosya, dosya zaten açıkken Windows'tadır (örneğin bir virüs tarayıcısı tarafından açılabilir). Başka türlü göstermek için başka belgeleriniz var mı (Böyle şeyleri merak ediyorum :-)? Düzenli olarak gerçekleşirse, yukarıdaki kod çalışmaz, nadirse, silme gerçekleşene kadar (veya bazı maksimum denemelere ulaşılana kadar) yukarıdaki koda çağrı yapmak işe yarayacaktır.
TofuBeer

6
@Demiurg Java'nın dosyaları hemen silmediği biliniyor. Açmasanız bile bu doğrudur. Yani, daha güvenli bir yol temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();.
Xiè Jìléi

102
Bu kod delete()ve arasında bir yarış durumu vardır mkdir(): Kötü amaçlı bir işlem bu arada hedef dizini oluşturabilir (son oluşturulan dosyanın adını alarak). Files.createTempDir()Alternatif için bakınız .
Joachim Sauer

11
Sevdim! öne çıkmak, kaçırmak çok kolay. Öğrenciler tarafından yazılmış bir sürü kod okudum ... eğer (! İ) sinir bozucu olacak kadar
yaygınsa

182

Google Guava kütüphanesinde çok sayıda yardımcı program bulunmaktadır. Buradaki notlardan biri Files sınıfıdır . Aşağıdakiler de dahil olmak üzere bir dizi yararlı yöntem vardır:

File myTempDir = Files.createTempDir();

Bu, tam olarak istediğiniz şeyi tek bir satırda yapar. Buradaki belgeleri okursanız, önerilen uyarlamanın File.createTempFile("install", "dir")genellikle güvenlik açıkları getirdiğini görürsünüz .


Hangi güvenlik açığından bahsettiğinizi merak ediyorum. Bu yaklaşım, bir dizin koşulu zaten varsa (bir saldırgan tarafından oluşturulur) File.mkdir () işlevinin başarısız olması beklendiği için bir yarış koşulu oluşturuyor gibi görünmüyor. Bu çağrının da kötü niyetli bir sembolik bağlantıdan kaynaklanacağını düşünmüyorum. Ne demek istediğini açıklığa kavuşturabilir misin?
abb

3
@abb: Guava belgelerinde belirtilen yarış koşulunun ayrıntılarını bilmiyorum. Sorunun özellikle belirtilmesi nedeniyle belgelerin doğru olduğundan şüpheleniyorum.
Spina

1
@abb Haklısın. Mkdir () iadesi kontrol edildiği sürece güvenli olacaktır. Spina kodu bu mkdir () yöntemini kullanır. grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/… . Yapışkan bit etkin olduğundan, / tmp dizini kullanılırken bu yalnızca Unix sistemlerinde olası bir sorundur.
Sarel Botha

@SarelBotha buradaki boşluğu doldurduğunuz için teşekkürler. Bir süredir bunun hakkında tembellik etmeyi merak ediyordum.
Spina

168

Eğer test için geçici bir dizin gerekiyor ve, JUnit kullanıyorsanız @Ruleile birlikte TemporaryFolderçözer senin sorunun:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

Gönderen belgeler :

TemporaryFolder Kuralı, sınama yöntemi tamamlandığında (geçip geçmediği) silinmesi garanti edilen dosya ve klasörlerin oluşturulmasına izin verir


Güncelleme:

JUnit Jupiter (sürüm 5.1.1 veya üstü) kullanıyorsanız, JUnit 5 Extension Pack olan JUnit Pioneer'ı kullanma seçeneğiniz vardır.

Proje belgelerinden kopyalandı :

Örneğin, aşağıdaki sınama, tek bir sınama yöntemi için uzantıyı kaydeder, bir dosya oluşturur ve geçici dizine yazar ve içeriğini denetler.

@Test
@ExtendWith(TempDirectory.class)
void test(@TempDir Path tempDir) {
    Path file = tempDir.resolve("test.txt");
    writeFile(file);
    assertExpectedFileContent(file);
}

JavaDoc ve TempDirectory JavaDoc'unda daha fazla bilgi

Gradle:

dependencies {
    testImplementation 'org.junit-pioneer:junit-pioneer:0.1.2'
}

Uzman:

<dependency>
   <groupId>org.junit-pioneer</groupId>
   <artifactId>junit-pioneer</artifactId>
   <version>0.1.2</version>
   <scope>test</scope>
</dependency>

Güncelleme 2:

@TempDir açıklama deneysel bir özellik olarak JUnit Jüpiter 5.4.0 serbest bırakmak için ilave edildi. JUnit 5 Kullanıcı Kılavuzundan kopyalanan örnek :

@Test
void writeItemsToFile(@TempDir Path tempDir) throws IOException {
    Path file = tempDir.resolve("test.txt");

    new ListWriter(file).write("a", "b", "c");

    assertEquals(singletonList("a,b,c"), Files.readAllLines(file));
}

8
JUnit
4.7'den

Windows 7'de JUnit 4.8.2'de çalışmaz! (İzinler sorunu)
istisna

2
@CraigRinger: Buna güvenmek neden mantıksız?
Adam Parkin

2
@AdamParkin Dürüst olmak gerekirse, artık hatırlamıyorum. Açıklama başarısız!
Craig Ringer

1
Bu yaklaşımın ana yararı, dizinin JUnit tarafından yönetilmesidir (testten önce oluşturulur ve testten sonra tekrar tekrar silinir). Ve işe yarıyor. Eğer "temp dir henüz oluşturulmadı" alırsanız, bunun nedeni @ Kuralı veya alanın herkese açık olmadığını unutmuş olmanız olabilir.
Bogdan Calmac

42

Bu sorunu çözmek için naif olarak yazılmış kod, buradaki cevapların bir kısmı da dahil olmak üzere yarış koşullarından muzdariptir. Tarihsel olarak yarış koşullarını dikkatlice düşünebilir ve kendiniz yazabilir veya Google'ın Guava'sı gibi bir üçüncü taraf kütüphanesi kullanabilirsiniz (Spina'nın cevabının önerdiği gibi) Veya buggy kodu yazabilirsiniz.

Ancak JDK 7'den itibaren iyi haberler var! Java standart kitaplığının kendisi artık bu soruna düzgün çalışan (açık olmayan) bir çözüm sunmaktadır. Java.nio.file.Files # createTempDirectory () öğesini istiyorsunuz . Gönderen belgeler :

public static Path createTempDirectory(Path dir,
                       String prefix,
                       FileAttribute<?>... attrs)
                                throws IOException

Adını oluşturmak için verilen öneki kullanarak belirtilen dizinde yeni bir dizin oluşturur. Ortaya çıkan Yol, verilen dizine ait FileSystem ile ilişkilendirilir.

Dizin adının nasıl oluşturulduğuna ilişkin ayrıntılar uygulamaya bağlıdır ve bu nedenle belirtilmez. Mümkün olduğunda önek, aday adları oluşturmak için kullanılır.

Bu, böyle bir işlev isteyen Sun hata izleyicisindeki utanç verici eski hata raporunu etkili bir şekilde çözer .


35

Bu, Guava kütüphanesinin Files.createTempDir () kaynağının kaynak kodudur. Düşündüğünüz kadar karmaşık değil:

public static File createTempDir() {
  File baseDir = new File(System.getProperty("java.io.tmpdir"));
  String baseName = System.currentTimeMillis() + "-";

  for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
    File tempDir = new File(baseDir, baseName + counter);
    if (tempDir.mkdir()) {
      return tempDir;
    }
  }
  throw new IllegalStateException("Failed to create directory within "
      + TEMP_DIR_ATTEMPTS + " attempts (tried "
      + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
}

Varsayılan olarak:

private static final int TEMP_DIR_ATTEMPTS = 10000;

Buraya bakın


28

Daha deleteOnExit()sonra açıkça silseniz bile kullanmayın .

Google daha fazla bilgi için 'deleteonexit kötüdür' , ancak sorunun özü şudur:

  1. deleteOnExit() sadece normal JVM kapatmaları için siler, çökme veya JVM işlemini öldürmez.

  2. deleteOnExit() sadece JVM kapatıldığında silinir - uzun süre çalışan sunucu işlemleri için iyi değildir, çünkü

  3. En kötüsü - deleteOnExit()her geçici dosya girişi için bellek tüketir. İşleminiz aylarca çalışıyorsa veya kısa sürede çok sayıda geçici dosya oluşturuyorsa, belleği tüketirsiniz ve JVM kapanana kadar asla serbest bırakmazsınız.


1
Sınıf ve jar dosyalarının altında JVM tarafından oluşturulan gizli dosyaları bulduğu bir JVM var ve bu ek bilgilerin silinmesi biraz zaman alıyor. Savaşları patlayan web kaplarında sıcak tekrarlar yaparken, JVM, bitirdikten sonra ancak birkaç saat koştuktan sonra çıkmadan önce temizlemek için birkaç dakika sürebilir.
Thorbjørn Ravn Andersen

20

Java 1.7 itibariyle createTempDirectory(prefix, attrs)ve createTempDirectory(dir, prefix, attrs)içerdiğijava.nio.file.Files

Misal: File tempDir = Files.createTempDirectory("foobar").toFile();


14

Kendi kodum için yapmaya karar verdim:

/**
 * Create a new temporary directory. Use something like
 * {@link #recursiveDelete(File)} to clean this directory up since it isn't
 * deleted automatically
 * @return  the new directory
 * @throws IOException if there is an error creating the temporary directory
 */
public static File createTempDir() throws IOException
{
    final File sysTempDir = new File(System.getProperty("java.io.tmpdir"));
    File newTempDir;
    final int maxAttempts = 9;
    int attemptCount = 0;
    do
    {
        attemptCount++;
        if(attemptCount > maxAttempts)
        {
            throw new IOException(
                    "The highly improbable has occurred! Failed to " +
                    "create a unique temporary directory after " +
                    maxAttempts + " attempts.");
        }
        String dirName = UUID.randomUUID().toString();
        newTempDir = new File(sysTempDir, dirName);
    } while(newTempDir.exists());

    if(newTempDir.mkdirs())
    {
        return newTempDir;
    }
    else
    {
        throw new IOException(
                "Failed to create temp dir named " +
                newTempDir.getAbsolutePath());
    }
}

/**
 * Recursively delete file or directory
 * @param fileOrDir
 *          the file or dir to delete
 * @return
 *          true iff all files are successfully deleted
 */
public static boolean recursiveDelete(File fileOrDir)
{
    if(fileOrDir.isDirectory())
    {
        // recursively delete contents
        for(File innerFile: fileOrDir.listFiles())
        {
            if(!FileUtilities.recursiveDelete(innerFile))
            {
                return false;
            }
        }
    }

    return fileOrDir.delete();
}

2
Bu güvensiz. İlk (eşit derecede güvensiz) seçenekte Joachim Sauer tarafından yapılan yoruma bakın. Bir dosyanın veya dizinin varlığını kontrol etmenin ve dosya adını atomik olarak almanın doğru yolu, dosyayı veya dizini oluşturmaktır.
zbyszek

1
@zbyszek javadocs "UUID, kriptografik olarak güçlü bir sahte rasgele sayı üreteci kullanılarak üretildi" diyor. Kötü niyetli bir işlem, exist () ve mkdirs () arasında aynı ada sahip bir diz nasıl oluşturulursa. Aslında buna baktığımda, var olan () testimin biraz aptalca olabileceğini düşünüyorum.
Keith

Keith: Bu durumda UUID'nin güvenli olup olmaması çok önemli değil. Bir şekilde "sızıntı" olarak sorguladığınız isim hakkındaki bilgiler için yeterlidir. Örneğin, oluşturulan dosyanın bir NFS dosya sisteminde olduğunu ve saldırganın paketleri (pasif olarak) dinleyebileceğini varsayalım. Veya rastgele jeneratör durumu sızdırılmış. Benim yorumumda, çözümünüzün kabul edilen cevap kadar eşit derecede güvensiz olduğunu söyledim, ancak bu adil değil: kabul edilen, inotify ile yenmek için önemsizdir ve bunu yenmek çok daha zordur. Bununla birlikte, bazı senaryolarda kesinlikle mümkündür.
zbyszek

2
Aynı düşünceye sahiptim ve bunun gibi rastgele UUID'ler kullanarak bir çözüm uyguladım. Hiçbir kontrol yok, sadece bir girişim yaratma - randomUUID yöntemi tarafından kullanılan güçlü RNG hemen hemen hiçbir çarpışmayı garanti etmez (DB tablolarında birincil anahtarlar oluşturmak için kullanılabilir, bunu kendim yaptım ve hiçbir zaman bir çarpışma bilemedim), çok emin. Eğer kimse emin değilse stackoverflow.com/questions/2513573/…
brabster

Java'nın uygulamasına bakarsanız, çarpışma gerçekleşmeyene kadar rastgele adlar oluştururlar. Maksimum girişimleri sonsuzdur. Kötü niyetli biri dosya / dizin adınızı tahmin etmeye devam ederse, sonsuza kadar döngü yapardı. İşte kaynağa bir bağlantı: hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share/… Bir şekilde atomik olarak benzersiz bir ad oluşturabilmesi için dosya sistemini kilitleyebileceğini düşünüyordum ve dizin oluşturmak, ama sanırım kaynak koduna göre bunu yapmaz.
dosentmatter

5

Eh, "createTempFile" aslında dosyayı oluşturur. Öyleyse neden sadece onu silmiyorsunuz, sonra mkdir'i yapıyorsunuz?


1
Her zaman mkdir () için dönüş değerini kontrol etmelisiniz. Bu yanlışsa, dizinin zaten var olduğu anlamına gelir. Bu, güvenlik sorunlarına neden olabilir, bu nedenle bunun uygulamanızda bir hata oluşturup yaratmayacağını düşünün.
Sarel Botha

1
Diğer cevaptaki yarış durumu hakkındaki nota bakınız.
Volker Stolz

Sevdiğim, yarışı yasaklayan
Martin Wickman

4

Bu kod oldukça iyi çalışmalıdır:

public static File createTempDir() {
    final String baseTempPath = System.getProperty("java.io.tmpdir");

    Random rand = new Random();
    int randomInt = 1 + rand.nextInt();

    File tempDir = new File(baseTempPath + File.separator + "tempDir" + randomInt);
    if (tempDir.exists() == false) {
        tempDir.mkdir();
    }

    tempDir.deleteOnExit();

    return tempDir;
}

3
Dizin zaten mevcutsa ve dizine okuma / yazma erişiminiz yoksa veya normal bir dosyaysa ne olur? Orada bir yarış durumunuz da var.
Jeremy Huiskamp

2
Ayrıca deleteOnExit, boş olmayan dizinleri silmez.
Trenton

3

Bu RFE ve yorumlarında tartışıldığı gibi , tempDir.delete()önce arayabilirsiniz . Ya da System.getProperty("java.io.tmpdir")orada bir dizin kullanabilir ve oluşturabilirsiniz. Her iki durumda da aramayı unutmamalısınız tempDir.deleteOnExit(), yoksa dosya işiniz bittikten sonra silinmez.


Bu özellik "... temp" değil, "java.io.tmpdir" değil mi? Bkz. Java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
Andrew Swan

Oldukça öyle. Okuduğumu tekrar etmeden önce doğrulamam gerekirdi.
Michael Myers

Java.io.tmpdir paylaşılır, böylece başkalarının ayak parmaklarına basmaktan kaçınmak için her zamanki voodoo'yu yapmanız gerekir.
Thorbjørn Ravn Andersen

3

Sadece tamamlamak için, bu google guava kütüphanesinden kod. Benim kodum değil, ama burada bu iş parçacığında göstermek için değerli olduğunu düşünüyorum.

  /** Maximum loop count when creating temp directories. */
  private static final int TEMP_DIR_ATTEMPTS = 10000;

  /**
   * Atomically creates a new directory somewhere beneath the system's temporary directory (as
   * defined by the {@code java.io.tmpdir} system property), and returns its name.
   *
   * <p>Use this method instead of {@link File#createTempFile(String, String)} when you wish to
   * create a directory, not a regular file. A common pitfall is to call {@code createTempFile},
   * delete the file and create a directory in its place, but this leads a race condition which can
   * be exploited to create security vulnerabilities, especially when executable files are to be
   * written into the directory.
   *
   * <p>This method assumes that the temporary volume is writable, has free inodes and free blocks,
   * and that it will not be called thousands of times per second.
   *
   * @return the newly-created directory
   * @throws IllegalStateException if the directory could not be created
   */
  public static File createTempDir() {
    File baseDir = new File(System.getProperty("java.io.tmpdir"));
    String baseName = System.currentTimeMillis() + "-";

    for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
      File tempDir = new File(baseDir, baseName + counter);
      if (tempDir.mkdir()) {
        return tempDir;
      }
    }
    throw new IllegalStateException(
        "Failed to create directory within "
            + TEMP_DIR_ATTEMPTS
            + " attempts (tried "
            + baseName
            + "0 to "
            + baseName
            + (TEMP_DIR_ATTEMPTS - 1)
            + ')');
  }

2

Aynı sorunu aldım, bu yüzden ilgilenenler için başka bir cevap ve yukarıdakilerden birine benzer:

public static final String tempDir = System.getProperty("java.io.tmpdir")+"tmp"+System.nanoTime();
static {
    File f = new File(tempDir);
    if(!f.exists())
        f.mkdir();
}

Ve uygulamam için, çıkıştaki sıcaklığı temizlemek için bir seçenek eklemeye karar verdim, böylece bir kapatma kancasına ekledim:

Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {
            //stackless deletion
            String root = MainWindow.tempDir;
            Stack<String> dirStack = new Stack<String>();
            dirStack.push(root);
            while(!dirStack.empty()) {
                String dir = dirStack.pop();
                File f = new File(dir);
                if(f.listFiles().length==0)
                    f.delete();
                else {
                    dirStack.push(dir);
                    for(File ff: f.listFiles()) {
                        if(ff.isFile())
                            ff.delete();
                        else if(ff.isDirectory())
                            dirStack.push(ff.getPath());
                    }
                }
            }
        }
    });

Yöntem , çağrı silmeden (ki tamamen isteğe bağlıdır ve bu noktada özyineleme ile yapabilirsiniz) temp silmeden önce tüm alt dizinleri ve dosyaları silmek , ancak güvenli tarafta olmak istiyorum.


2

Diğer cevaplarda da görebileceğiniz gibi, standart bir yaklaşım ortaya çıkmamıştır. Dolayısıyla zaten Apache Commons i? Fileutils kullanarak aşağıdaki yaklaşım öneriyoruz söz Apache Commons IO :

/**
 * Creates a temporary subdirectory in the standard temporary directory.
 * This will be automatically deleted upon exit.
 * 
 * @param prefix
 *            the prefix used to create the directory, completed by a
 *            current timestamp. Use for instance your application's name
 * @return the directory
 */
public static File createTempDirectory(String prefix) {

    final File tmp = new File(FileUtils.getTempDirectory().getAbsolutePath()
            + "/" + prefix + System.currentTimeMillis());
    tmp.mkdir();
    Runtime.getRuntime().addShutdownHook(new Thread() {

        @Override
        public void run() {

            try {
                FileUtils.deleteDirectory(tmp);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    return tmp;

}

Apache, istenen "standart" a en yakın gelen ve hem JDK 7 hem de daha eski sürümlerle çalışan kütüphaneyi paylaştığı için bu tercih edilir. Bu ayrıca "yeni" bir Yol örneği değil (arabellek tabanlı ve JDK7'nin getTemporaryDirectory () yönteminin sonucu olacaktır) değil, "eski" bir Dosya örneği (akış tabanlı) döndürür -> Bu nedenle çoğu insanın ne zaman ihtiyaç duyduğunu döndürür geçici bir dizin oluşturmak istiyorlar.


1

Benzersiz bir isim yaratma girişimlerini seviyorum, ancak bu çözüm bile bir yarış koşulunu dışlamaz. Test exists()ve if(newTempDir.mkdirs())yöntem çağırma işleminden sonra başka bir işlem kayabilir . İçinde gömülü olduğunu düşündüğüm yerel koda başvurmadan tamamen güvenli hale nasıl bir fikrim yok File.createTempFile().


1

Java 7'den önce şunları da yapabilirsiniz:

File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();

1
Güzel kod. Ancak talihsiz "deleteOnExit ()" çalışmaz, çünkü Java tüm klasörü aynı anda silemez. Tüm dosyaları özyinelemeli olarak silmeniz gerekir: /
Adam Taras

1

Bu küçük örneği deneyin:

Kod:

try {
    Path tmpDir = Files.createTempDirectory("tmpDir");
    System.out.println(tmpDir.toString());
    Files.delete(tmpDir);
} catch (IOException e) {
    e.printStackTrace();
}


İthalat:
java.io.IOException
java.nio.file.Files
java.nio.file.Path

Windows makinesinde konsol çıkışı:
C: \ Users \ kullanıcıAdı \ AppData \ Local \ Temp \ tmpDir2908538301081367877

Yorum:
Files.createTempDirectory otomatik olarak benzersiz kimlik oluşturur - 2908538301081367877.

Not:
Dizinleri tekrar tekrar silmek için aşağıdakileri okuyun:
Java'da dizinleri tekrar tekrar silin


0

Dizini benzersiz bir ad oluşturmak için File#createTempFileve kullanarak deleteTamam görünüyor. ShutdownHookJVM kapatıldığında dizini (yinelemeli olarak) silmek için bir eklemeniz gerekir .


Bir kapatma kancası hantaldır. File # deleteOnExit de çalışmaz mı?
Daniel Hiller

2
#deleteOnExit benim için çalışmadı - boş olmayan dizinleri silmeyeceğine inanıyorum.
muriloq

Java 8 ile çalışan hızlı bir test uyguladım, ancak geçici klasör silinmedi, bkz. Pastebin.com/mjgG70KG
geri
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.