Android:多CSV文件导入SQLite多张表的代码问题求助
问题排查与修复方案
嘿,你的importaTabelas方法踩了几个Android SQLite和文件操作的常见坑,我帮你拆解并修复了:
核心问题分析
- 数据库连接过早关闭:你在创建表之后直接调用
db.close(),导致后面插入数据时数据库已经关闭,这是最致命的错误——相当于刚开门就锁上,根本没法往里塞数据! - SQL拼接的安全与语法问题:直接把CSV行内容拼进INSERT语句,如果行里有单引号(比如
O'Conner),会直接触发SQL语法错误,还存在SQL注入风险。 - 资源泄漏:
FileReader和BufferedReader用完没关闭,会浪费系统资源。 - 权限授权无后续处理:申请权限后,没有处理授权成功的回调,用户点允许后方法不会自动重新跑起来。
- 路径拼接不规范:用字符串硬拼路径,容易因为系统路径分隔符差异导致找不到文件。
修复后的完整代码
@TargetApi(Build.VERSION_CODES.M) public void importaTabelas() { // 检查读取权限 if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { try { // 初始化目标文件夹 File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File importDir = new File(downloadsDir, "ENEL/IMPORTADOS"); if (!importDir.exists()) { importDir.mkdirs(); } // 获取文件夹下的所有文件 File[] files = importDir.listFiles(); if (files == null) { Toast.makeText(this, "文件夹为空或无法访问", Toast.LENGTH_SHORT).show(); return; } // 获取可写数据库连接(整个循环复用一个连接,不要频繁开关) SQLiteDatabase db = this.banco.getWritableDatabase(); for (File f : files) { if (f.isFile() && f.getName().toLowerCase().endsWith(".csv")) { // 提取表名(去掉.csv后缀) String nomeArq = f.getName(); if (nomeArq.indexOf(".") > 0) { nomeArq = nomeArq.substring(0, nomeArq.lastIndexOf(".")); } // 1. 创建表(如果不存在) String criaTab = "CREATE TABLE IF NOT EXISTS " + nomeArq + " (id integer PRIMARY KEY AUTOINCREMENT, codigo varchar (50))"; db.execSQL(criaTab); // 2. 读取CSV文件内容并插入 BufferedReader buffer = null; try { buffer = new BufferedReader(new FileReader(f)); String line; // 使用事务提升插入效率 db.beginTransaction(); while ((line = buffer.readLine()) != null) { // 跳过空行 if (line.trim().isEmpty()) continue; // 使用参数化查询避免SQL注入和语法错误 String insertSql = "INSERT INTO " + nomeArq + " (codigo) VALUES (?)"; db.execSQL(insertSql, new Object[]{line}); } db.setTransactionSuccessful(); Toast.makeText(this, "表 " + nomeArq + " 导入完成", Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "读取文件 " + f.getName() + " 失败", Toast.LENGTH_SHORT).show(); } finally { // 确保流被关闭 if (buffer != null) { try { buffer.close(); } catch (IOException e) { e.printStackTrace(); } } } } } // 所有操作完成后再关闭数据库 if (db.isOpen()) { db.close(); } } catch (Exception e) { Toast.makeText(this, "导入过程出错: " + e.getMessage(), Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } else { // 请求读取外部存储权限 requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1); } } // 必须重写这个方法处理权限授权结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授权成功后重新执行导入方法 importaTabelas(); } else { Toast.makeText(this, "需要读取存储权限才能导入文件", Toast.LENGTH_SHORT).show(); } } }
关键改动说明
- 复用数据库连接:整个循环只打开一次数据库,所有操作完成后再关闭,避免频繁IO和连接关闭后的操作失败。
- 参数化SQL查询:用
?占位符代替字符串拼接,彻底解决SQL注入和单引号导致的语法错误。 - 添加事务处理:批量插入时用事务包裹,大幅提升插入效率,避免部分插入失败导致的数据不一致。
- 资源自动释放:用
finally块确保流被关闭,避免资源泄漏。 - 完善权限回调:重写
onRequestPermissionsResult,授权成功后自动重新执行导入逻辑。 - 规范路径拼接:用
File构造方法拼接路径,适配不同系统的路径分隔符。 - 增加空值检查:判断
listFiles()的返回值,避免空指针异常。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

