android开发游记:使用sharepreference存储复杂对象的解决方案

sharepreference是android系统中的一种数据持久化的解决方案,我们经常用它来存储APP的配置信息和一些简单数据类型,但是我们可不可以用它来存放复杂数据类型呢?

sharepreference保存数据是按照健-值的形式的,不支持复杂数据类型的存储,但是我们可以把复杂数据类型转换成简单数据类型进行存储,最实际的就是转换为具有特殊意义的字符串,然后使用sharepreference形式来进行持久化,提前数据的时候就把字符串提取出来再转换成原来的类型就可以。

先写一个方法用于把byte转为十六进制字符串:

/** * desc:将数组转为16进制 * * @param bArray * @return modified: */
    private static String bytesToHexString(byte[] bArray) {
        if (bArray == null) {
            return null;
        }
        if (bArray.length == 0) {
            return "";
        }
        StringBuffer sb = new StringBuffer(bArray.length);
        String sTemp;
        for (int i = 0; i < bArray.length; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2)
                sb.append(0);
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }

上面的代码首先进行判空后将byte进行遍历逐位取出,通过Integer.toHexString方法转换为16进制的字符串再进行拼接,仔细看可能注意到我把byte的每一位与0xFF进行了与运算。
可以看出bArray[i] & 0xFF运算后得出的仍然是个int,那么为何要和 0xFF进行与运算呢?直接 Integer.toHexString(b[i]),将byte强转为int不行吗?答案是不行的。
原因是为什么呢?

  1. byte所占大小为8位而int型占32位
  2. java的二进制数据存储采用的是补码

如果不进行&0xff,那么当一个byte会转换成int时,由于int是32位,而byte只有8位这时会进行补位,
例如补码11111111的十进制数为-1转换为int时变为11111111111111111111111111111111好多1啊,呵呵!即0xffffffff但是这个数是不对的,这种补位就会造成误差。
和0xff相与后,高24比特就会被清0了,结果就对了。

如果不清楚补码的话,就该复习下计算机基础理论了,下面贴上一些讲解,不行了解底层原理的可以跳过了:

byte是一个字节保存的,有8个位,即8个0、1。
8位的第一个位是符号位,
也就是说0000 0001代表的是数字1
1000 0000代表的就是-1
所以正数最大位0111 1111,也就是数字127
负数最大为1111 1111,也就是数字-128
上面说的是二进制原码,但是在java中采用的是补码的形式,下面介绍下什么是补码
1、反码:
一个数如果是正,则它的反码与原码相同;
一个数如果是负,则符号位为1,其余各位是对原码取反;
2、补码:利用溢出,我们可以将减法变成加法
对于十进制数,从9得到5可用减法:
9-4=5 因为4+6=10,我们可以将6作为4的补数
改写为加法:
9+6=15(去掉高位1,也就是减10)得到5.
对于十六进制数,从c到5可用减法:
c-7=5 因为7+9=16 将9作为7的补数
改写为加法:
c+9=15(去掉高位1,也就是减16)得到5.
在计算机中,如果我们用1个字节表示一个数,一个字节有8位,超过8位就进1,在内存中情况为(100000000),进位1被丢弃。
⑴一个数为正,则它的原码、反码、补码相同
⑵一个数为负,刚符号位为1,其余各位是对原码取反,然后整个数加1

  • 1的原码为 10000001
  • 1的反码为 11111110
    + 1
  • 1的补码为 11111111

    0的原码为 00000000
    0的反码为 11111111(正零和负零的反码相同)
    +1
    0的补码为 100000000(舍掉打头的1,正零和负零的补码相同)

接着是保存复杂数据类型的方法,必须要说明的是,该类型必须实现Serializable可序列化接口:

    /** * desc:保存对象 * * @param context * @param key * @param obj * 要保存的对象,只能保存实现了serializable的对象 modified: */
    public static void saveObject(Context context, String key, Object obj) {
        try {
            // 保存对象
            SharedPreferences.Editor sharedata = context.getSharedPreferences("user", Context.MODE_PRIVATE).edit();
            // 先将序列化结果写到byte缓存中,其实就分配一个内存空间
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream os = new ObjectOutputStream(bos);
            // 将对象序列化写入byte缓存
            os.writeObject(obj);
            // 将序列化的数据转为16进制保存
            String bytesToHexString = bytesToHexString(bos.toByteArray());
            // 保存该16进制数组
            sharedata.putString(key, bytesToHexString);
            sharedata.commit();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("", "保存obj失败");
        }
    }

上面第方法首先把传入的对象转换为字节流,再调用上面实现的字节转16进制字符串的方法将字节流转为字符串后进行保存。

现在保存实现了,那么接下来就是读取了
首先,先实现一个方法把16进制字符串转换为byte数组:

/** * desc:将16进制的数据转为数组 * * @param data * @return modified: */
    private static byte[] StringToBytes(String data) {
        String hexString = data.toUpperCase().trim();
        if (hexString.length() % 2 != 0) {
            return null;
        }
        byte[] retData = new byte[hexString.length() / 2];
        for (int i = 0; i < hexString.length(); i++) {
            int int_ch; // 两位16进制数转化后的10进制数
            char hex_char1 = hexString.charAt(i); // 两位16进制数中的第一位(高位*16)
            int int_ch1;
            if (hex_char1 >= '0' && hex_char1 <= '9')
                int_ch1 = (hex_char1 - 48) * 16; // // 0 的Ascll - 48
            else if (hex_char1 >= 'A' && hex_char1 <= 'F')
                int_ch1 = (hex_char1 - 55) * 16; // // A 的Ascll - 65
            else
                return null;
            i++;
            char hex_char2 = hexString.charAt(i); // /两位16进制数中的第二位(低位)
            int int_ch2;
            if (hex_char2 >= '0' && hex_char2 <= '9')
                int_ch2 = (hex_char2 - 48); // // 0 的Ascll - 48
            else if (hex_char2 >= 'A' && hex_char2 <= 'F')
                int_ch2 = hex_char2 - 55; // // A 的Ascll - 65
            else
                return null;
            int_ch = int_ch1 + int_ch2;
            retData[i / 2] = (byte) int_ch;// 将转化后的数放入Byte里
        }
        return retData;
    }

这里要先将16进制的字符逐一取出转换为16进制数,再转为10进制最后转为byte,存入数组,这里byte转int就不用进行与运算了,因为低精度转高精度不会丢失精度。

接下来,是通过sharepreference方式获取存入的复杂对象的方法:

/** * desc:获取保存的Object对象 * * @param context * @param key * @return modified: */
    public static Object readObject(Context context, String key) {
        try {
            SharedPreferences sharedata = context.getSharedPreferences("user", Context.MODE_PRIVATE);
            if (sharedata.contains(key)) {
                String string = sharedata.getString(key, "");
                if (TextUtils.isEmpty(string)) {
                    return null;
                } else {
                    // 将16进制的数据转为数组,准备反序列化
                    byte[] stringToBytes = StringToBytes(string);
                    ByteArrayInputStream bis = new ByteArrayInputStream(stringToBytes);
                    ObjectInputStream is = new ObjectInputStream(bis);
                    // 返回反序列化得到的对象
                    Object readObject = is.readObject();
                    return readObject;
                }
            }
        } catch (StreamCorruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // 所有异常返回null
        return null;
    }

将存入的16进制字符串取出调用上面的字符串转byte[]的方法得到byte数组,再通过字节流生成一个对象,对象是Object类型,直接返回,在调用方法后自行进行强制转换,转换为原来的类型即可。

到这里,我们就实现了使用sharepreference存储复杂对象。

你可能感兴趣的:(android,shareprefe,复杂对象存储)