首选项调整存储_使用首选项API存储对象

Preferences API是JDK 1.4中引入的轻量级跨平台持久性API。 它并不是要与传统的数据库引擎接口,而是要使用特定于OS的后端来实现实际的持久性。 该API用于存储少量数据。 实际上,该名称本身就表明了这一点:它的通常用法是存储用户特定的设置或首选项,例如字体大小或窗口布局。 (当然,您可以在其中存储任何内容。)

Preferences API旨在存储字符串,数字,布尔值,简单字节数组等。 在本文中,我们将向您展示如何使用Preferences API存储对象,并提供一个工作库来为您处理细节。 如果您的数据很容易表示为简单对象,而不是诸如字符串和数字之类的单独值,则此功能很有用。

我们将从关于API的简短讨论开始,包括使用它的一些简单示例,然后详细介绍如何使用API​​存储对象,并为我们安排代码。 我们还将演示一些实际使用的API的示例。

为什么要设计首选项API?

如果创建Preferences API主要是为了允许Java程序访问Microsoft Windows注册表,那将是令人惊讶的。 我为什么要提这个? API的设计类似于Windows注册表的设计。 本文前三段中的大多数语句同样适用于注册表。

但是,首选项API与所有Java语言一样,都是跨平台的,因此至少在非Windows系统上也能正常工作。 (当然,本文中的代码是跨平台的。)

首选项API的规范并未指定必须如何实现API,而仅指定了必须执行的操作。 Java运行时环境(JRE)的每种实现都可以不同地实现API。 许多非注册表实现将API数据存储在用户主目录或共享目录中的XML格式的文件中。

像Windows注册表一样,首选项API使用分层树隐喻来存储数据。 起点是根节点 (根节点是树的基础;其他所有节点都是该节点的后代)。 节点可以包含命名值,也可以包含其他节点。 不同的程序将其数据存储在树的不同部分,因此它们不会相互冲突。 正如我们将看到的那样,Preferences API采取了特殊的措施来帮助防止此类冲突。

首先,我们将快速浏览Preferences API的工作方式和使用方式。

使用偏好

了解Preferences API的最佳方法是使用它。 您需要做的第一件事是获得对根节点的访问权:

Preferences root = Preferences.userRoot();

此行代码返回数据树的用户根 。 之前,我们说过系统中的所有数据都存储在树中。 嗯,这不是完全正确的-实际上,有两个数据树-用户树和系统树。 这两棵树的行为相同,但是用途不同。 系统树用于存储旨在供所有用户使用的数据,而每个用户的用户树都不相同。

这两棵树自然有不同的用途。 您将字体首选项存储在用户树中,因为这是特定于用户的问题。 另一方面,您将程序的位置存储在系统树中,因为该位置对于所有用户都是相同的,并且可能对所有用户有用。

较小的程序将使用系统树或用户树,但不会同时使用两者。 较大的应用程序可能会同时使用。 在本文中,我们将只处理用户树,请记住用户树和系统树的行为相同。

现在,让我们看看如何使用Preferences API读取和写入简单值。

获得价值

拥有根节点之后,就可以使用它来读取和写入值。 这是您写字体大小的方法:

root.putInt( "fontsize", 10 );

以下是您稍后阅读的方式:

int fontSize = prefs.getInt( "fontsize", 12 );

请注意, getInt()需要一个默认值-在这种情况下为12。

当然,您不仅可以读写整数。 您可以读写许多原始Java类型。 您还可以将节点存储在其他节点中,如本示例所示:

Preferences child = parent.node( "child" );

这就是Preferences API的全部内容–其余的有用之处在于细节,我们将在下一部分中介绍其中之一。

获取包的节点

不难想象,两个不同的程序员可能想存储不同的字体大小,如果他们每个人都决定以“字体大小”的名称存储它们,那么我们就会遇到问题。 一个程序的首选项会感染另一个程序。

解决方案是将内容存储在特定于程序包的位置,如下所示:

Preferences ourRoot = Preferences.userNodeForPackage( getClass() );

userNodeForPackage()方法采用Class对象,并返回特定于该类的节点。 这样,每个应用程序(大概在自己的程序包中)将具有自己的首选项节点。

现在我们对Preferences API的工作原理有了一个很好的了解,我们需要知道如何扩展它以能够处理对象。

储存物件

理想情况下,这就是我们希望能够将对象写入“首选项”树的方式:

清单1.将对象写入Preferences树的理想方法
Font font = new Font( ... );
Preferences prefs = Preferences.userNodeForPackage( getClass() );
prefs.putObject( "font", font );

但是,可悲的是,Preferences对象没有putObject()getObject()方法。 但是,我们将尽可能地做到这一点。 我们将在名为PrefObj的类中实现这些方法。 运作方式如下:

清单2.实现putObject()和getObject()
Font font = new Font( ... );
Preferences prefs = Preferences.userNodeForPackage( getClass() );
PrefObj.putObject( prefs, "font", font );

它已经接近我们将能够向Preferences类添加方法的程度。

在下一节中,我们将了解如何实现getObject()putObject()

将对象转换为字节数组

我们在这里使用的技术涉及两个技巧。 第一个技巧是将对象转换为字节数组。 这样做的原因很简单:尽管Preferences对象不处理对象,但它确实处理字节数组。

幸运的是,这不是我们从头开始要做的事情-它已内置在Java语言中。 有几种方法可以将对象转换为字节数组。 这是我们在PrefObj类中执行的操作:

清单3.将对象转换为字节数组
static private byte[] object2Bytes( Object o ) throws IOException {
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  ObjectOutputStream oos = new ObjectOutputStream( baos );
  oos.writeObject( o );
  return baos.toByteArray();
}

在这里, ObjectOutputStream类是必不可少的-这就是将对象实际转换为字节流的神奇之处。 通过将ObjectOutputStream包装在ByteArrayOutputStream周围,​​我们可以将字节流转换为字节数组。

还有另一种方法:

清单4.将字节数组转换为对象
static private Object bytes2Object( byte raw[] )
    throws IOException, ClassNotFoundException {
  ByteArrayInputStream bais = new ByteArrayInputStream( raw );
  ObjectInputStream ois = new ObjectInputStream( bais );
  Object o = ois.readObject();
  return o;
}

切记, ObjectOutputStream仅处理实现java.io.Serializable接口的对象,这一点很重要。 幸运的是,这几乎包括核心Java库中的所有对象,以及您声明为实现Serializable的程序中的所有对象。

如前所述,Preferences API确实处理字节数组。 但是,我们在这里构造的字节数组并不十分正确,我们将在下一部分中看到。

分解成碎片

Preferences API对可以存储在其中的数据大小施加了限制。 特别是,字符串限制为MAX_VALUE_LENGTH个字符。 字节数组的长度限制为MAX_VALUE_LENGTH的75%,因为字节数组是通过将它们编码为字符串来存储的。

另一方面,对象可以是任意大小,因此我们需要将其分解成碎片。 当然,最简单的方法是先将其转换为字节数组,然后将字节数组拆分为多个部分。 这是再次从PrefObj分解字节数组的代码:

清单5.将字节数组分解为可消化的块
static private byte[][] breakIntoPieces( byte raw[] ) {
  int numPieces = (raw.length + pieceLength - 1) / pieceLength;
  byte pieces[][] = new byte[numPieces][];
  for (int i=0; i<numPieces; ++i) {
    int startByte = i * pieceLength;
    int endByte = startByte + pieceLength;
    if (endByte > raw.length) endByte = raw.length;
    int length = endByte - startByte;
    pieces[i] = new byte[length];
    System.arraycopy( raw, startByte, pieces[i], 0, length );
  }
  return pieces;
}

这里没有什么复杂的-我们只创建一个数组数组,每个数组的最大长度为pieceLength个字节(pieceLength是MAX_VALUE_LENGTH的四分之三)。 相应地,还有另一种方法可以将各个部分重新组合在一起:

清单6.将片段重组为整个字节数组
static private byte[] combinePieces( byte pieces[][] ) {
  int length = 0;
  for (int i=0; i<pieces.length; ++i) {
    length += pieces[i].length;
  }
  byte raw[] = new byte[length];
  int cursor = 0;
  for (int i=0; i>pieces.length; ++i) {
    System.arraycopy( pieces[i], 0, raw, cursor, pieces[i].length );
    cursor += pieces[i].length;
  }
  return raw;
}

此例程检查片段的总长度,并创建一个新的单个数组,该数组是如此之长。 然后,它一个接一个地复制片段。

读和写作品

在此阶段,我们采用第二个技巧-将值转换为节点。 通常,当我们使用首选项API存储值时,会将其放在首选项数据树中的一个节点中的插槽中。

但是我们不能在这里真正做到这一点。 即使对象是单个值,我们也已将其转换为一组固定长度的字节数组。 如果我们只有一个字节数组,那么将很容易写入数据树中的插槽,因为Preferences API直接支持字节数组。 但这不起作用,因为我们有多个数组。

诀窍是为每个对象分配一个节点。 让我们确保我们清楚这意味着什么。

通常,将值存储在节点中其他插槽之间存在的插槽中。 但是,我们将为每个对象创建一个节点,并将字节数组存储在该节点的插槽中。 让我们将其具体化。 如果可以的话,我们可以将一个对象存储到一个插槽中:

清单7.将对象存储在一个插槽中
Preferences parent = ....;
parent.putObject( "child", object );

但是我们不能这样做,因为“首选项”没有putObject()方法。 相反,我们创建一个节点并将字节数组存储在其中,如下所示:

清单8.相反,将字节数组存储在节点中
Preferences parent = ....;
Preferences child = parent.node( "child" );
for (int i=0; i<pieces.length; ++i) {
  child.putByteArray( ""+i, pieces[i] );
}

因此,我们不是将多个值存储在称为“子”的插槽中,而是将多个值存储在名为“子”的节点中。 这些值使用数字键“ 0”,“ 1”,“ 2”等进行存储。

使用数字键可以使以后的阅读变得很容易:

清单9.阅读易读的文章
Preferences parent = ....;
Preferences child = parent.node( "child" );
for (int i=0; i<numPieces; ++i) {
  pieces[i] = child.getByteArray( ""+i, null );
}

在下一节中,我们将看一下结合了所有这些步骤的例程。

放在一起

PrefObjs有一个称为静态方法putObject()它调用方法中编目前面所述35 ,和8 。 看起来像这样:

清单10.方法putObject()使用其他方法编写代码
static public void putObject( Preferences prefs, String key, Object o )
    throws IOException, BackingStoreException, ClassNotFoundException {
  byte raw[] = object2Bytes( o );
  byte pieces[][] = breakIntoPieces( raw );
  writePieces( prefs, key, pieces );
}

方法putObject()将整个过程分为三个步骤,由我们前面讨论的三个方法实现。 它将对象转换为字节数组( 清单3 ),将数组拆分为较小的数组( 清单5 ),然后将片段写入Preferences API。

有类似的读取方法:

清单11.方法getObject()用于读取片段
static public Object getObject( Preferences prefs, String key )
    throws IOException, BackingStoreException, ClassNotFoundException {
  byte pieces[][] = readPieces( prefs, key );
  byte raw[] = combinePieces( pieces );
  Object o = bytes2Object( raw );
  return o;
}

此方法从Preferences API中读取片段,将它们组合为一个字节数组,然后将其转换回一个对象。

储存信息

如您所见,这是一种使用Preferences API具有的功能,实现其不具有的功能的干净方法。 这是扩展现有库的好方法。 从理论上讲,您可以破解该库或尝试创建一个子类,但是这种方法可能会破坏其他使用Preferences API的程序。 使用这种方法,您可以保持原始API完整无缺,同时以一种干净,有用的方式扩展它。


翻译自: https://www.ibm.com/developerworks/java/library/j-prefapi/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值