数据库和 MIDP,第二部分:数据映射

作者:Eric Giguere

正如 第一部分 所述,移动信息设备描述 (Mobile Information Device Profile,MIDP) 通过记录管理系统 (Record Management System,RMS) 提供数据持久存储。MIDP 对持久存储的支持局限于简单的字节数组 (byte array),且记录的读写是按整体进行,而不是按字段进行的。因此,RMS 应用程序编程接口 (API) 非常简单,但它要求应用程序使用一个非常简单的二进制格式进行数据存储。

本文将描述封装低级存储操作的数据映射策略,目的是提高应用程序存储和检索持久数据的效率。

数据操作的核心类

实际上,将数据写到一个记录库 (record store) 和通过网络向服务器发送数据包并没有什么区别。MIDP 基于其上的有限连接设备配置 (Connected Limited Device Configuration,CLDC) 包括源自 J2SE 核心库的标准数据操作类,这些类对于 RMS 操作十分有用。使用通用接口的一个很大的好处是,MIDlet 可以更容易地和运行在标准和企业 Java 平台上的应用程序进行互操作。

字节数组流

ByteArrayInputStream 对象可以将字节数组转换成输入流 (input stream),举例如下:

...
byte[] data = new byte[]{ 1, 2, 3 };
ByteArrayInputStream bin = new ByteArrayInputStream( data );int b;while( ( b = bin.read() ) != -1 ){System.out.println( b );
}try {bin.close();
} catch( IOException e ){// never thrown in this case
}
...

输入流将顺序返回该数组中的每个字节,直到数组的尾部。利用 mark()reset(),可以在任何时候重新定位字节数组中的流 (stream)。

对象 ByteArrayOutputStream 可以捕获内存缓冲区中的数据,以便以后转换成字节数组:

...
ByteArrayOutputStream bout = new ByteArrayOutputStream();bout.write( 1 );
bout.write( 2 );
bout.write( 3 );byte[] data = bout.toByteArray();for( int i = 0; i < data.length; ++i ){System.out.println( data[i] );
}try {bout.close();
} catch( IOException e ){// never thrown in this case
}
...

随着数据不断地写入流中,ByteArrayOutputStream 的缓冲区的大小会自动增长。toByteArray() 方法将捕获到的数据拷贝到字节数组中。通过调用 reset(),我们就可以重用内部缓冲区,以便进行更多的数据捕获。

数据流

DataInputStream 可以将一个原始输入流转换成为原始数据类型和字符串:

...
InputStream in = ... // an input stream
DataInputStream din = new DataInputStream( in );    try {int custID = din.readInt();String lastName = din.readUTF();String firstName = din.readUTF();long timestamp = din.readLong();din.close();
}
catch( IOException e ){// handle the error here
}
...

只有以 DataInputStream 所期望的与机器无关的格式写入 stream 中的数据,数据才可以读取。该类的方法可以读取大多数简单 Java 数据类型,这些方法包括 readBoolean()、readByte()、readChar()、readShort()、readInt()readLong()。CLDC 1.1 实现额外支持 readFloat()readDouble()。也有方法用来读取字节数组和无符号值,这些方法是 readFully()、readUnsignedByte()readUnsignedShort()

readUTF() 方法可读取 UTF-8 格式的字符串,字符串含有的字符数可达 65,535。如果要读取一个两字节字符值的序列的字符串,应用程序必须多次调用 readChar(),它既可将一个分隔符看做字符串的结尾,也可认为字符串的长度已知。长度可以是一个固定的值,也可以写入字符串前部的 stream 中。

用来读取数据的应用程序必须知道原始数据的写入顺序,以便调用正确的方法。

DataOutputStream 可将字符串和原始数据类型写入一个输出流 (output stream):

...
OutputStream out = ... // an output stream
DataOutputStream dout = new DataOutputStream( out );try {dout.writeInt( 100 );dout.writeUTF( "Smith" );dout.writeUTF( "John" );dout.writeLong( System.currentTimeMillis() );dout.close();
}
catch( IOException e ){// handle the error here
}
...

这些数据用 DataInputStream 所期望的相同的与机器无关的格式写入。该类的方法可以写入大多数简单的 Java 数据类型,这些方法是:writeBoolean()、writeByte()、writeChar()、writeShort()、writeInt()writeLong()。 CLDC 1.1 实现额外支持 writeFloat()writeDouble()。

可调用两个方法来写字符串。您可以用 writeUTF() 写一个用 UTF-8 格式编码的最多含有 65,535 个字符的字符串,或者调用 writeChars() 来写一个两字节字符的序列。

基本数据映射

标准数据操作类可使基本数据映射变得容易..将数据写入记录库只需要将 DataOutputStreamByteArrayOutputStream 合并,并存储得到的字节数组:

...
RecordStore rs = ... // a record store
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream( bout );try {dout.writeInt( 100 );dout.writeUTF( "Smith" );dout.writeUTF( "John" );dout.writeLong( System.currentTimeMillis() );dout.close();byte[] data = bout.toByteArray();rs.addRecord( data, 0, data.length );
}
catch( RecordStoreException e ){// handle RMS error here
}
catch( IOException e ){// handle IO error here
}
...

数据的读取是上述的步骤逆过程,它使用一个 DataInputStream 和一个 ByteArrayInputStream:

...
RecordStore rs = ... // a record store
int recordID = ... // the record to read
ByteArrayInputStream bin;
DataInputStream din;try {byte[] data = rs.getRecord( recordID );bin = new ByteArrayInputStream( data );din = new DataInputStream( bin );int id = din.readInt();String lastName = din.readUTF();String firstName = din.readUTF();long timestamp = din.readLong();din.close();... // process data here
}
catch( RecordStoreException e ){// handle RMS error here
}
catch( IOException e ){// handle IO error here
}
...

当写字符串时,需要注意 null 值。在大多数情况下,您会反而写一个空字符串。否则,您将需要用标记字节 (marker byte) 将 null 字符串和空字符串分别开来。

简单对象映射

在许多情况下,您要保存的数据被封装到一个对象实例中。例如,Contact 类含有某个人的联系信息:

package j2me.example;// The contact information for a personpublic class Contact {private String _firstName;private String _lastName;private String _phoneNumber;public Contact(){}public Contact( String firstName, String lastName,String phoneNumber ){_firstName = firstName;_lastName = lastName;_phoneNumber = phoneNumber;}public String getFirstName(){return _firstName != null ? _firstName : "";}public String getLastName(){return _lastName != null ? _lastName : "";}public String getPhoneNumber(){return _phoneNumber != null ? _phoneNumber : "";}public void setFirstName( String name ){_firstName = name;}public void setLastName( String name ){_lastName = name;}public void setPhoneNumber( String number ){_phoneNumber = number;}
}

如果类可以修改,那么引入持久存储的最简单的方法是添加方法,以实现对象和字节数组之间的相互映射:

public void fromByteArray( byte[] data ) throws IOException {ByteArrayInputStream bin = new ByteArrayInputStream(data);DataInputStream din = new DataInputStream( bin );_firstName = din.readUTF();_lastName = din.readUTF();_phoneNumber = din.readUTF();din.close();
}public byte[] toByteArray() throws IOException {ByteArrayOutputStream bout = new ByteArrayOutputStream();DataOutputStream dout = new DataOutputStream( bout );dout.writeUTF( getFirstName() );dout.writeUTF( getLastName() );dout.writeUTF( getPhoneNumber() );dout.close();return bout.toByteArray();
}

那么,存储一个对象就成了一个简单的操作:

...
Contact contact = ... // the contact to store
RecordStore rs = ... // the record store to usetry {byte[] data = contact.toByteArray();rs.addRecord( data, 0, data.length );
}
catch( RecordStoreException e ){// handle the RMS error here
}
catch( IOException e ){// handle the IO error here
}
...

对象的检索也同样容易:

...
RecordStore rs = ... // the record store to use
int recordID = ... // the record ID to read from
Contact contact = new Contact();try {byte[] data = rs.getRecord( recordID );contact.fromByteArray( data );
}
catch( RecordStoreException e ){// handle the RMS error here
}
catch( IOException e ){// handle the IO error here
}
...

如果类不可修改,如标准 VectorHashtable 类,您就需要写一个帮助器类。例如,这里有一个帮助器类,它可将一组非空字符串映射到一个字节数组:

package j2me.example;import java.io.*;
import java.util.*;// A helper class that converts transforms string
// vectors (vectors whose elements are instances of
// String or StringBuffer ) into byte arrays and vice-versa.public class StringVectorHelper {// Reconstitutes a vector from a byte array.public Vector fromByteArray( byte[] data )throws IOException {ByteArrayInputStream bin =new ByteArrayInputStream( data );DataInputStream din = new DataInputStream( bin );int count = din.readInt();Vector v = new Vector( count );while( count-- > 0 ){v.addElement( din.readUTF() );}din.close();return v;}// Transforms a vector into a byte array.public byte[] toByteArray( Vector v )throws IOException {ByteArrayOutputStream bout =new ByteArrayOutputStream();DataOutputStream dout = new DataOutputStream( bout );dout.writeInt( v.size() );Enumeration e = v.elements();while( e.hasMoreElements() ){Object o = e.nextElement();dout.writeUTF( o != null ? o.toString() : "" );}dout.close();return bout.toByteArray();}
}

当然,您这里所做的一切都是为了开发一个对象串行化框架。一个完整的框架超出了本文的讨论范围,然而当您进行对象持久存储时,以下的问题还是值得仔细研究考虑的:

  • 对象创建。CLDC 并不含有反射 API (reflection API),所以您需要重建持久对象。它可以是一个接收 stream 或者字节数组的构造器,也可以是一个更加复杂的 factory 类。
  • 版本化。如果一个对象的数据布局可以变化,那么您将需要在字节数组的开始处存储一个数字,该数用来标识所存储的对象的版本。
  • 对象引用。如果一个对象引用了另一个对象,那么它们的关系必须保持。在有些情况下,可以用一个索引或者一个键来替代对象引用,索引和键可以在反串行化后对被引用的对象进行定位。否则,您就必须存储一个完整的对象图,而不仅仅是一个单一的对象。

您可以避免这些问题,或者将它们的影响降低到最低,方法就是只保存和恢复原始 Java 数据类型和简单的、独立的对象。fromByteArray()toByteArray() 方法简单,但它们却是进行对象持久存储的简单有效的方法。您也可以使用这些技术通过网络进行对象的拷贝:一旦对象以字节数组的形式存在,那么,利用网络连接将数组发送到另一个设备或者一个外部服务器,并在另一端重建对象,就成为一个简单的事情。例如,您为 J2ME 应用程序所开发的数据类可以很容易的用于为 J2EE 所开发的 servlet 中。

使用数据流

以上的示例直接处理字节数组。尽管它们在保存和恢复一组数据方面很方便,然而,当需要存储多组数据(例如,一组对象)时,对字节数组的处理就成为任务繁重的工作了。一个更好的方法是将字节数组的管理和数据的读写分开。例如,可以向 Contact 类添加如下的方法:

public void fromDataStream( DataInputStream din ) throws IOException {_firstName = din.readUTF();_lastName = din.readUTF();_phoneNumber = din.readUTF();
}public void toDataStream( DataOutputStream dout ) throws IOException {dout.writeUTF( getFirstName() );dout.writeUTF( getLastName() );dout.writeUTF( getPhoneNumber() );
}

为方便起见,我们可以保持现有的 fromByteArraytoByteArray 方法,但是我们应该对它们进行重写以便使用新的面向 stream 的方法:

public void fromByteArray( byte[] data ) throws IOException {ByteArrayInputStream bin = new ByteArrayInputStream(data);DataInputStream din = new DataInputStream( bin );fromDataStream( din );din.close();
}public byte[] toByteArray() throws IOException {ByteArrayOutputStream bout = new ByteArrayOutputStream();DataOutputStream dout = new DataOutputStream( bout );toDataStream( dout );dout.close();return bout.toByteArray();
}

集中数据的读写以确保其一致性。

下一部分提要

在本部分中,您已经学习了一些基本数据映射技术。在 第三部分 中,您将了解到一个更成熟的方法来管理持久存储数据,该方法可使您的应用程序存储和检索由多个包含不同数据类型的字段所组成的对象。

关于作者

Eric Giguere 是来自 Sybase 子公司 iAnywhere Solutions 的一个软件开发人员,主要从事用于手持设备和无线计算的 Java 技术。他拥有 Waterloo 大学的 BMath 和 MMath 学位,并广泛撰写有关计算主题的文章。

转载于:https://www.cnblogs.com/canpigfly/archive/2005/05/13/154440.html

数据库和 MIDP,第二部分:数据映射相关推荐

  1. mysql做kv数据库_如何将SQL数据映射到KV数据库

    日常吐槽 国外文章也不是都是好文章啊,不要见到英文就觉得高大上了-- 前言 越来越多的关系型数据库底层选择基于KV构建,例如TiDB的TiKV(RocksDB),cockroach的levelDB,M ...

  2. 帆软FCP第二题:数据库中有一张地区数据统计表,但是并不规则

    [题目要求] 数据库中有一张地区数据统计表,但是并不规则 ,记录类似于,225100:02:3:20160725是一串代码,以:分割,第1位为地区代码,第2位为分类代码,第3位为数量,第4位为日期 地 ...

  3. 使用FoundationDB高效地将SQL数据映射到NoSQL存储系统中

    NoSQL数据库 --FoundationDB的键-值存储系统 FoundationDB是一个分布式的键-值存储系统,支持全局ACID事务操作,并且性能出众.在安装系统时,可以指定数据分发的级别.数据 ...

  4. ASP.NET3.5 企业级项目开发 -- 第二章 数据访问层(DAL)的开发

    为什么80%的码农都做不了架构师?>>>    ASP.NET3.5 企业级项目开发 -- 第二章 数据访问层(DAL)的开发          前言:本篇主要讲述数据访问层的开发, ...

  5. 第二章 数据查询语言DQL

    目录 1 mysql登录与退出 2  常用基础语句 查询所有数据库 使用某一个数据库 显示某数据库中的所有表 显示表的属性结构 3 MySQL语法规范 关键字格式 注释 4 基础查询 语法 着重号`字 ...

  6. python爬虫豆瓣读书top250+数据清洗+数据库+Java后端开发+Echarts数据可视化(四)

    之前的博客已经写了python爬取豆瓣读书top250的相关信息和清洗数据.将数据导入数据库并创建相应的数据表,以及进行项目准备工作,接下来开始正式编写后台代码. 如果有没看懂的或是不了解上一部分说的 ...

  7. 各种数据库查询前几条数据的方法

    sql在不同数据库查询前几条数据 关键字: sql 前几条结果  sql在不同数据库查询前几条数据  1. ORACLE    SELECT * FROM TABLE1 WHERE ROWNUM< ...

  8. 映射到此登录名的用户_小课堂:什么是数据映射以及如何进行数据映射

    全文共1500字,预计学习时长5分钟 数据映射是数据处理的重要组成部分. 数据映射中的一个错误可以在组织中引起连锁反应,并由于重复的错误和不准确的分析对组织造成破坏. 因此,如果你不了解数据映射的重要 ...

  9. (转)分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)...

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

最新文章

  1. Windows Mobile 6.0 SDK和中文模拟器下载
  2. java框架知识_java框架知识点总结
  3. 学习笔记Spark(四)—— Spark编程基础(创建RDD、RDD算子、文件读取与存储)
  4. 【并查集】并查集的基本操作总结
  5. AppDelegate 处理iOS应用的生命周期事件
  6. matlab 柱面投影,matlab练习程序(圆柱投影)
  7. 手动安装boost库
  8. docker快速入门_Docker标签快速入门
  9. linux-vim操作-查找与替换
  10. 容器大小_C++复习篇(7)序列式容器vector
  11. jupyter notebook 修改主题、字体、字号等
  12. Python五子棋游戏源代码源程序
  13. 饥荒如何解锁机器人_《饥荒》全部人物怎样解锁 全人物解锁条件及方法一览...
  14. 使用Jimi处理图像
  15. 斑马Zebra 170Xi4 打印机驱动
  16. 今日头条街拍图片爬取
  17. 如何控制工业设计公司的设计效果?
  18. 机器人Ameca挣脱“灵魂”枷锁觉醒 邪魅一笑瞬间令人恐怖
  19. 天源石化举行国庆前消防应急-装车台液态烃泄漏演练
  20. 1 赫斯曼网管软件industrial hivision下载步骤

热门文章

  1. 要想完全放弃Windows使用Linux需要多少勇气?
  2. 什么叫做微内核?与安卓系统有什么区别?
  3. 如何看待夸克,酷狗概念版等简洁型软件?
  4. 为什么有人执着于只买黑色的手机?
  5. JS基础--组合继承,寄生组合式继承
  6. Java中的properties文件中的key不能使用项目中的接口名和Java文件名
  7. 谷歌浏览器怎么查找和改变编码格式
  8. Java定义字符串(2种方式)
  9. c语言程序设计小学生测验,c语言程序设计(1) 小学生计算机辅助教学系统
  10. sql server死锁_了解SQL Server中的死锁定义