Android 基础之文件和数据库

Java 文件流

3403199-e7d6ffadb302200e.png
java 文件流整理

1. 字节流文件操作(读写)的代码

public static void readFileByByte(String filePath) {
        
        File file = new File(filePath);

        InputStream inputStream = null; 
        OutputStream outputStream = null;  
        
        try {
                     
            inputStream = new FileInputStream(file); 
            outputStream = new FileOutputStream("d:/work/readFileByByte.txt");

            int temp;
            while ((temp = inputStream.read()) != -1) {
                outputStream.write(temp);
            }
        } catch (Exception e) {
            e.getStackTrace();
        } finally {
            if (inputStream != null && outputStream != null) {
                try {
                    inputStream.close();
                    outputStream.close();
                } catch (IOException e) {
                    e.getStackTrace();
                }
            }
        }
    }

2. 字符流文件操作(读写)的代码

public static void readFileByCharacter(String filePath) {
        
        File file = new File(filePath);
        
        FileReader reader = null;
        FileWriter writer = null;
        try {
            reader = new FileReader(file);
            writer = new FileWriter("d:/work/readFileByCharacter.txt");

            int temp;
            while ((temp = reader.read()) != -1) {
                writer.write((char)temp);
            }
        } catch (IOException e) {
            e.getStackTrace();
        } finally {
            if (reader != null && writer != null) {
                try {
                    reader.close();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3. 按行(读写)的代码

public static void readFileByLine(String filePath) {
        
        File file = new File(filePath);
       
        BufferedReader bufReader = null;
        BufferedWriter bufWriter = null;
        try {
          
            bufReader = new BufferedReader(new FileReader(file));
            bufWriter = new BufferedWriter(new FileWriter("d:/work/readFileByLine.txt"));
             
            String temp = null;
            while ((temp = bufReader.readLine()) != null) {
                bufWriter.write(temp+"\n");
            }
        } catch (Exception e) {
            e.getStackTrace();
        } finally {
            if (bufReader != null && bufWriter != null) {
                try {
                    bufReader.close();
                    bufWriter.close();
                } catch (IOException e) {
                    e.getStackTrace();
                }
            }
        }
    }

4. 将字节流要转换成字符流的代码

private static String getOuterIp() throws IOException {  
        InputStream inputStream = null;  
        BufferedReader bufferedReader = null;
        try {  
            URL url = new URL("http://1212.ip138.com/ic.asp");  
            URLConnection urlconnnection = url.openConnection();  
            inputStream = urlconnnection.getInputStream();  
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "GB2312");  //字节流转字符流,并且设置编码格式
            bufferedReader = new BufferedReader(inputStreamReader);  
            StringBuffer webContent = new StringBuffer();  
            String str = null;  
            while ((str = bufferedReader.readLine()) != null) {  
                webContent.append(str);  
            }  
            int ipStart = webContent.indexOf("[") + 1;  
            int ipEnd = webContent.indexOf("]");  
            return webContent.substring(ipStart, ipEnd);  
        } finally {  
            if (inputStream != null && bufferedReader != null) {  
                inputStream.close();  
                bufferedReader.close();
            }  
        }  
    }  

Java 随机访问文件

使用随机访问文件,我们可以从文件读取以及写入文件。
使用文件输入和输出流的读取和写入是顺序过程。
使用随机访问文件,我们可以在文件中的任何位置读取或写入。
RandomAccessFile 类的一个对象可以进行随机文件访问。我们可以读/写字节和所有原始类型的值到一个文件。
RandomAccessFile 可以使用其 readUTF() 和 writeUTF() 方法处理字符串。
RandomAccessFile 类不在 InputStream 和 OutputStream 类的类层次结构中。

模式

可以在四种不同的访问模式中创建随机访问文件。访问模式值是一个字符串。

模式 含义
"r" 文件以只读模式打开。
"rw" 该文件以读写模式打开。 如果文件不存在,则创建该文件。
"rws" 该文件以读写模式打开。 对文件的内容及其元数据的任何修改立即被写入存储设备。
"rwd" 该文件以读写模式打开。 对文件内容的任何修改立即写入存储设备。

读和写

我们通过指定文件名和访问模式来创建RandomAccessFile类的实例。

RandomAccessFile  raf = new RandomAccessFile("randomtest.txt", "rw");

随机访问文件具有文件指针,当我们从其读取数据或向其写入数据时,该文件指针向前移动。
文件指针是我们下一次读取或写入将开始的光标。
其值指示光标与文件开头的距离(以字节为单位)。
我们可以通过使用其getFilePointer()方法来获取文件指针的值。
当我们创建一个RandomAccessFile类的对象时,文件指针被设置为零。
我们可以使用seek()方法将文件指针设置在文件中的特定位置。
RandomAccessFile的length()方法返回文件的当前长度。我们可以通过使用其setLength()方法来扩展或截断文件。

实例

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class Main {
  public static void main(String[] args) throws IOException {
    String fileName = "randomaccessfile.txt";
    File fileObject = new File(fileName);

    if (!fileObject.exists()) {
      initialWrite(fileName);
    }
    readFile(fileName);
    readFile(fileName);
  }

  public static void readFile(String fileName) throws IOException {
    RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
    int counter = raf.readInt();
    String msg = raf.readUTF();

    System.out.println(counter);
    System.out.println(msg);
    incrementReadCounter(raf);
    raf.close();
  }

  public static void incrementReadCounter(RandomAccessFile raf)
      throws IOException {
    long currentPosition = raf.getFilePointer();
    raf.seek(0);
    int counter = raf.readInt();
    counter++;
    raf.seek(0);
    raf.writeInt(counter);
    raf.seek(currentPosition);
  }

  public static void initialWrite(String fileName) throws IOException {
    RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
    raf.writeInt(0);
    raf.writeUTF("Hello world!");
    raf.close();
  }
}

Java 序列化的高级认识

序列化 ID 问题

情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。

问题:C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。

解决:虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。

静态变量序列化

public class Test implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    public static int staticVar = 5;
 
    public static void main(String[] args) {
        try {
            //初始时staticVar为5
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            out.writeObject(new Test());
            out.close();
 
            //序列化后修改为10
            Test.staticVar = 10;
 
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                    "result.obj"));
            Test t = (Test) oin.readObject();
            oin.close();
             
            //再读取,通过t.staticVar打印新的值
            System.out.println(t.staticVar);
             
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

代码中的 main 方法,将对象序列化后,修改静态变量的数值,再将序列化对象读取出来,然后通过读取出来的对象获得静态变量的数值并打印出来。依照代码,这个 System.out.println(t.staticVar) 语句输出的是 10 还是 5 呢?

最后的输出是 10,对于无法理解的读者认为,打印的 staticVar 是从读取的对象里获得的,应该是保存时的状态才对。之所以打印 10 的原因在于序列化时,并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。

父类的序列化与 Transient 关键字

情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。

解决:要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就 需要有默认的无参的构造函数。在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

对敏感字段加密

情境:服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

解决:在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

序列化存储规则

ObjectOutputStream out = new ObjectOutputStream(
                   new FileOutputStream("result.obj"));
   Test test = new Test();
   //试图将对象两次写入文件
   out.writeObject(test);
   out.flush();
   System.out.println(new File("result.obj").length());
   out.writeObject(test);
   out.close();
   System.out.println(new File("result.obj").length());
 
   ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
           "result.obj"));
   //从文件依次读出两个文件
   Test t1 = (Test) oin.readObject();
   Test t2 = (Test) oin.readObject();
   oin.close();
            
   //判断两个引用是否指向同一个对象
   System.out.println(t1 == t2);

解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得代码中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。

SQLite详解

首先创建一个继承在 SQLiteOpenHelper 的类,并重写onCreate()和onUpgrade()方法。这个类主要用于建数据库和建表用。

public class OrderDBHelper extends SQLiteOpenHelper{
    private static final int DB_VERSION = 1;
    private static final String DB_NAME = "myTest.db";
    public static final String TABLE_NAME = "Orders";

    public OrderDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // create table Orders(Id integer primary key, CustomName text, OrderPrice integer, Country text);
        String sql = "create table if not exists " + TABLE_NAME + " (Id integer primary key, CustomName text, OrderPrice integer, Country text)";
        sqLiteDatabase.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
        sqLiteDatabase.execSQL(sql);
        onCreate(sqLiteDatabase);
    }
}

再创建一个 OrderDao 类用于处理所有的数据操作方法。在 OrderDao 中实例化
OrderDBHelper:

public OrderDao(Context context) {
    this.context = context;
    ordersDBHelper = new OrderDBHelper(context);
}

数据库操作无外乎:“增删查改”。
对于“增删改”这类对表内容变换的操作,我们需先调用getWritableDatabase(),在执行的时候可以调用通用的execSQL(String sql)方法或对应的操作API:insert()、delete()、update()。而对“查”,需要调用getReadableDatabase(),这时就不能使用execSQL方法了,得使用query()或rawQuery()方法。

db = ordersDBHelper.getWritableDatabase();
db.beginTransaction();

// insert into Orders(Id, CustomName, OrderPrice, Country) values (7, "Jne", 700, "China");
ContentValues contentValues = new ContentValues();
contentValues.put("Id", 7);
contentValues.put("CustomName", "Jne");
contentValues.put("OrderPrice", 700);
contentValues.put("Country", "China");
db.insertOrThrow(OrderDBHelper.TABLE_NAME, null, contentValues);

db.setTransactionSuccessful();

public int delete(String table, String whereClause, String[] whereArgs) {
    acquireReference();
    try {
        SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
                (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
        try {
            return statement.executeUpdateDelete();
        } finally {
            statement.close();
        }
    } finally {
        releaseReference();
    }
}

db = ordersDBHelper.getWritableDatabase();
db.beginTransaction();

// update Orders set OrderPrice = 800 where Id = 6
ContentValues cv = new ContentValues();
cv.put("OrderPrice", 800);
db.update(OrderDBHelper.TABLE_NAME,
        cv,
        "Id = ?",
        new String[]{String.valueOf(6)});
db.setTransactionSuccessful();

db = ordersDBHelper.getReadableDatabase();
// select count(Id) from Orders where Country = 'China'
cursor = db.query(OrderDBHelper.TABLE_NAME,
        new String[]{"COUNT(Id)"},
        "Country = ?",
        new String[] {"China"},
        null, null, null);

if (cursor.moveToFirst()) {
    count = cursor.getInt(0);
}

猜你喜欢

转载自blog.csdn.net/weixin_34326429/article/details/88142384