从资产中读取文件

211
public class Utils {
    public static List<Message> getMessages() {
        //File file = new File("file:///android_asset/helloworld.txt");
        AssetManager assetManager = getAssets();
        InputStream ims = assetManager.open("helloworld.txt");    
     }
}

我正在使用这段代码尝试从assets文件夹中读取文件。我尝试了两种方法,第一种是使用File,但是我收到了FileNotFoundException的错误提示;第二种是使用AssetManager getAssets()方法,但是没有被识别。 这里有什么解决方案吗?

18个回答

257

下面是我在一个活动中进行缓冲读取时所做的扩展/修改以适应您的需求

BufferedReader reader = null;
try {
    reader = new BufferedReader(
        new InputStreamReader(getAssets().open("filename.txt")));

    // do reading, usually loop until end of file reading  
    String mLine;
    while ((mLine = reader.readLine()) != null) {
       //process line
       ...
    }
} catch (IOException e) {
    //log the exception
} finally {
    if (reader != null) {
         try {
             reader.close();
         } catch (IOException e) {
             //log the exception
         }
    }
}

编辑:如果您的问题是如何在活动之外执行此操作,则我的答案可能没有用处。如果您的问题只是如何从资产中读取文件,则答案在上面。

更新:

要打开指定类型的文件,只需在InputStreamReader调用中添加类型即可,如下所示。

BufferedReader reader = null;
try {
    reader = new BufferedReader(
        new InputStreamReader(getAssets().open("filename.txt"), "UTF-8")); 

    // do reading, usually loop until end of file reading 
    String mLine;
    while ((mLine = reader.readLine()) != null) {
       //process line
       ...
    }
} catch (IOException e) {
    //log the exception
} finally {
    if (reader != null) {
         try {
             reader.close();
         } catch (IOException e) {
             //log the exception
         }
    }
}

编辑

正如评论中@Stan所说,我给出的代码并没有累加每行的内容。每次循环mLine都被替换了。这就是我写//process line的原因。我假设文件包含一些数据(例如联系人列表),每行应该单独处理。

如果你只想加载文件而不进行任何处理,那么你需要在每次循环中累加mLine,可以使用StringBuilder()并将每次循环的结果追加到后面。

另一个编辑

根据@Vincent的评论,我添加了finally块。

还要注意,在Java 7及以上版本中,您可以使用try-with-resources来使用最近Java的AutoCloseableCloseable功能。

上下文

在评论中@LunarWatcher指出getAssets()context中的一个class。因此,如果您在activity之外调用它,您需要引用它并将上下文实例传递给活动。

ContextInstance.getAssets();

这在@Maneesh的答案中有解释。如果对您有用,请点赞他的答案,因为是他指出了这一点。


2
@Stan,在评论中写下你的想法,让作者决定是否更新。修改是为了提高清晰度,而不是改变意义。代码修订应始终首先作为评论发布。 - KyleMit
2
你的代码不能保证及时关闭流和释放资源。我建议你使用 finally {reader.close();} - Vincent Cantin
2
我认为指出上面的代码在ADT中显示错误是有用的 - "reader.close();"行需要放在另一个try-catch块中。请查看此线程:https://dev59.com/lGox5IYBdhLWcg3ww3GV :) - JakeP
1
getAssets是Context中的一个类,因此在活动外部使用时需要调用Context。因此,在活动外部,它将类似于context.getAssets(.....) - Zoe stands with Ukraine
1
根据您的更新(顺便感谢添加),在静态字段中具有上下文会导致内存泄漏。这应该谨慎使用并进行适当清理。否则,您最终会遇到可能对应用程序产生巨大影响的内存泄漏问题。 - Zoe stands with Ukraine
显示剩余7条评论

67
getAssets()

它仅在活动中工作,在其他任何类中,您必须使用Context来进行操作。

为Utils类创建一个构造函数,将活动的引用(丑陋的方式)或应用程序的上下文作为参数传递给它。然后在您的Utils类中使用getAsset()方法。


1
它适用于任何Context的子类,其中Activity只是众多子类之一。 - Jeremy Logan
刚刚注意到我写了 Context - user370305
@user370305 你知道怎么把InputStream转换成FileInputStream吗? - Mike Herasimov
@user370305,这有什么不好看的地方吗? - Sevastyan Savanyuk
1
在不考虑后果的情况下传递 UI 对象通常是一个不好的实践方法。如果你不小心,你可能会导致内存泄漏或尝试使用已经失效的上下文。这是一个关于此主题的好文章:https://android.jlelse.eu/memory-leak-patterns-in-android-4741a7fcb570 - milosmns

62

迟做总比不做好。

在某些情况下,我阅读文件逐行读取时遇到了困难。以下方法是我目前找到的最好方法,并且我推荐使用它。

使用方法:String yourData = LoadData("YourDataFile.txt");

其中YourDataFile.txt被假定为位于assets/中。

 public String LoadData(String inFile) {
        String tContents = "";

    try {
        InputStream stream = getAssets().open(inFile);

        int size = stream.available();
        byte[] buffer = new byte[size];
        stream.read(buffer);
        stream.close();
        tContents = new String(buffer);
    } catch (IOException e) {
        // Handle exceptions here
    }

    return tContents;

 }

我的返回字符串是android.content.res.AssetManager$AssetInputStream@4195dfa0.. - Boldijar Paul
同样的问题,res.AssetManager$AssetInputStream@.... 返回这个值有什么特别的原因吗? - Bigs
你重复分配内存——首先给缓冲区分配内存,然后再给字符串分配内存。这对于更大的文件无效。 - JaakL
1
分配缓冲区大小的完美方法是 stream.available() - Kasim Rangwala

41
public String ReadFromfile(String fileName, Context context) {
    StringBuilder returnString = new StringBuilder();
    InputStream fIn = null;
    InputStreamReader isr = null;
    BufferedReader input = null;
    try {
        fIn = context.getResources().getAssets()
                .open(fileName, Context.MODE_WORLD_READABLE);
        isr = new InputStreamReader(fIn);
        input = new BufferedReader(isr);
        String line = "";
        while ((line = input.readLine()) != null) {
            returnString.append(line);
        }
    } catch (Exception e) {
        e.getMessage();
    } finally {
        try {
            if (isr != null)
                isr.close();
            if (fIn != null)
                fIn.close();
            if (input != null)
                input.close();
        } catch (Exception e2) {
            e2.getMessage();
        }
    }
    return returnString.toString();
}

你可能会认为,如果你关闭 BufferedReader,那么它会自动关闭 InputStreanReader 和 InputStream。因为如果你没有为它们创建一个句柄,比如 input = new BufferedReader(new InputStreamReader(fIn)); - trans
3
我建议在结尾处为关闭所有资源创建单独的try/catch块,而不是将它们全部合并到一个块中——因为如果先前尝试关闭另一个资源时抛出异常,可能会导致其他资源未关闭。 - Reece

39

Kotlin 的一行代码解决方案:

fun readFileText(fileName: String): String {
    return assets.open(fileName).bufferedReader().use { it.readText() }
}

同时您可以将它作为扩展函数在任何地方使用

fun Context.readTextFromAsset(fileName : String) : String{
     return assets.open(fileName).bufferedReader().use { 
     it.readText()}
}

在任何上下文中只需调用 Class

context.readTextFromAsset("my file name")

12
AssetManager assetManager = getAssets();
InputStream inputStream = null;
try {
    inputStream = assetManager.open("helloworld.txt");
}
catch (IOException e){
    Log.e("message: ",e.getMessage());
}

7

getAssets() 方法仅在 Activity 类中调用时有效。

如果你要在非 Activity 类中调用此方法,那么你需要从由 Activity 类传递的 Context 中调用此方法。因此,以下是一行代码,可以让你访问该方法。

ContextInstance.getAssets();

ContextInstance可以作为Activity类的this参数传递。


5

读写文件一直很冗长且容易出错。避免这些问题,只需使用Okio

public void readLines(File file) throws IOException {
  try (BufferedSource source = Okio.buffer(Okio.source(file))) {
    for (String line; (line = source.readUtf8Line()) != null; ) {
      if (line.contains("square")) {
        System.out.println(line);
      }
    }
  }
}

2
你知道为什么这段代码看起来更美观、更简短吗?因为你省略了至少一半的代码。省略的部分包括:1)IOException 的 try/catch 块;2)在出现异常时关闭流;3)这段代码只读取单行,而不是整个文件。就性能而言,这个库绝对是独一无二的,毫无疑问。那么,告诉我,我是否应该避免“这些答案”,并实现 Okio 来读取文件呢?答案是否定的,除非它已经是你的应用程序的一部分。 - Farid

4

这里有一种读取assets文件的方法:

/**
 * Reads the text of an asset. Should not be run on the UI thread.
 * 
 * @param mgr
 *            The {@link AssetManager} obtained via {@link Context#getAssets()}
 * @param path
 *            The path to the asset.
 * @return The plain text of the asset
 */
public static String readAsset(AssetManager mgr, String path) {
    String contents = "";
    InputStream is = null;
    BufferedReader reader = null;
    try {
        is = mgr.open(path);
        reader = new BufferedReader(new InputStreamReader(is));
        contents = reader.readLine();
        String line = null;
        while ((line = reader.readLine()) != null) {
            contents += '\n' + line;
        }
    } catch (final Exception e) {
        e.printStackTrace();
    } finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException ignored) {
            }
        }
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException ignored) {
            }
        }
    }
    return contents;
}

这是一个好的答案,但使用字符串拼接的方法是不好的。考虑使用 StringBuilder。StringBuilder contentBuilder = new StringBuilder(); while((line = reader.readLine()) != null) { builder.append("\n").append(line); }最后,你可以通过以下方式创建新的字符串对象:content = contentBuilder.toString(); - Barterio

4
您可以从文件中加载内容。假设文件存在于资源文件夹中。
public static InputStream loadInputStreamFromAssetFile(Context context, String fileName){
    AssetManager am = context.getAssets();
    try {
        InputStream is = am.open(fileName);
        return is;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

public static String loadContentFromFile(Context context, String path){
    String content = null;
    try {
        InputStream is = loadInputStreamFromAssetFile(context, path);
        int size = is.available();
        byte[] buffer = new byte[size];
        is.read(buffer);
        is.close();
        content = new String(buffer, "UTF-8");
    } catch (IOException ex) {
        ex.printStackTrace();
        return null;
    }
    return content;
}

现在,您可以通过以下方式调用函数来获取内容。
String json= FileUtil.loadContentFromFile(context, "data.json");

假设data.json存储在Application\app\src\main\assets\data.json中。


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