`

存取程序状态的几种方法--Java I/O应用杂谈

    博客分类:
  • java
阅读更多


主要类与方法和描述 

  1. XMLEncoder.writeObject() //将一个对象序列化到外部字节流 
  2. XMLDecoder.readObject() //从外部字节流读取并重新构造对象 


    如果一个bean是如下格式:

  1. public  class MyBean
  2. {
  3.   int i;
  4.   char[] c;
  5.   String s;
  6.   //...(get和set操作省略)...


那么通过XMLEcoder序列化出来的XML文件具有这样的形式: 

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.4.0" class="java.beans.XMLDecoder">
  <object class="MyBean">
    <void property="i">
      <int>1</int>
    </void>
    <void property="c">
      <array class="char" length="3">
        <void index="0">
          <int>a</int>
        </void>
        <void index="1">
          <int>b</int>
        </void>
        <void index="2">
          <int>c</int>
        </void>
      </array>
    </void>
    <void property="s">
      <string>fox jump!</string> 
    </void>
  </object>
</java> 

    像AWTSwing中很多可视化组件都是bean,当然也是可以用这种方式序列化的,下面就是从JDK文档中摘录的一个JFrame序列化以后的XML文件: 

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.0" class="java.beans.XMLDecoder">
  <object class="javax.swing.JFrame">
    <void property="name">
      <string>frame1</string>
    </void>
    <void property="bounds">
      <object class="java.awt.Rectangle">
        <int>0</int>
        <int>0</int>
        <int>200</int>
        <int>200</int>
      </object>
    </void>
    <void property="contentPane">
      <void method="add">
        <object class="javax.swing.JButton">
          <void property="label">
            <string>Hello</string>
          </void>
        </object>
      </void>
    </void>
    <void property="visible">
      <boolean>true</boolean>
    </void>
  </object>
</java> 

    因此但你想要保存的数据是一些不是太复杂的类型的话,把它做成bean再序列化也不失为一种方便的选择。 

Properties
    在以前我总结的一篇关于集合框架的小文章里提到过,Properties是历史集合类的一个典型的例子,这里主要不是介绍它的集合特性。大家可能都经常接触一些配置文件,如Windows的ini文件,Apache的conf文件,还有Java里的properties文件等,这些文件当中的数据以“关键字-值”对的方式保存。“环境变量”这个概念都知道吧,它也是一种“key-value”对,以前也常常看到版上问“如何取得系统某某信息”之类的问题,其实很多都保存在环境变量里,只要用一条

  1. System .getProperties().list(System.out); 


就能获得全部环境变量的列表: 

-- listing properties --
java.runtime.name=Java(TM) 2 Runtime Environment, Stand...
sun.boot.library.path=C:\Program Files\Java\j2re1.4.2_05\bin
java.vm.version=1.4.2_05-b04
java.vm.vendor=Sun Microsystems Inc.
java.vendor.url=http://java.sun.com/
path.separator=;
java.vm.name=Java HotSpot(TM) Client VM
file.encoding.pkg=sun.io
user.country=CN
sun.os.patch.level=Service Pack 1
java.vm.specification.name=Java Virtual Machine Specification
user.dir=d:\my documents\项目\eclipse\SWTDemo
java.runtime.version=1.4.2_05-b04
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
os.arch=x86
java.io.tmpdir=C:\DOCUME~1\cn2lx0q0\LOCALS~1\Temp\
line.separator=

java.vm.specification.vendor=Sun Microsystems Inc.
user.variant=
os.name=Windows XP
sun.java2d.fontpath=
java.library.path=C:\Program Files\Java\j2re1.4.2_05\bi...
java.specification.name=Java Platform API Specification
java.class.version=48.0
java.util.prefs.PreferencesFactory=java.util.prefs.WindowsPreferencesFac...
os.version=5.1
user.home=D:\Users\cn2lx0q0
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=GBK
java.specification.version=1.4
user.name=cn2lx0q0
java.class.path=d:\my documents\项目\eclipse\SWTDemo\bi...
java.vm.specification.version=1.0
sun.arch.data.model=32
java.home=C:\Program Files\Java\j2re1.4.2_05
java.specification.vendor=Sun Microsystems Inc.
user.language=zh
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=mixed mode
java.version=1.4.2_05
java.ext.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
sun.boot.class.path=C:\Program Files\Java\j2re1.4.2_05\li...
java.vendor=Sun Microsystems Inc.
file.separator=\
java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.isalist=pentium i486 i386
 

主要类与方法和描述 

  1. load() //从一个外部流读取属性 
  2. store() //将属性保存到外部流(特别是文件) 
  3. getProperty() //取得一个指定的属性 
  4. setProperty() //设置一个指定的属性 
  5. list() //列出这个Properties对象包含的全部“key-value”对 
  6. System .getProperties() //取得系统当前的环境变量 



    你可以这样保存一个properties文件: 

  1. Properties prop = new Properties();
  2. prop.setProperty("key1""value1");
  3. ...
  4. FileOutputStream out = new FileOutputStream("config.properties");
  5. prop.store(out, "--这里是文件头,可以加入注释--"); 


Preferences
    如果我说Java里面可以不使用JNI的手段操作Windows的注册表你信不信?很多软件的菜单里都有“Setting”或“Preferences”这样的选项用来设定或修改软件的配置,这些配置信息可以保存到一个像上面所述的配置文件当中,如果是Windows平台下,也可能会保存到系统注册表中。从JDK 1.4开始,Java在java.util下加入了一个专门处理用户和系统配置信息的java.util.prefs包,其中一个类Preferences是一种比较“高级”的玩意。从本质上讲,Preferences本身是一个与平台无关的东西,但不同的OS对它的SPI(Service Provider Interface)的实现却是与平台相关的,因此,在不同的系统中你可能看到首选项保存为本地文件、LDAP目录项、数据库条目等,像在Windows平台下,它就保存到了系统注册表中。不仅如此,你还可以把首选项导出为XML文件或从XML文件导入。 

主要类与方法和描述 

  1. systemNodeForPackage() //根据指定的Class对象得到一个Preferences对象,这个对象的注册表路径是从“HKEY_LOCAL_MACHINE\”开始的 
  2. systemRoot() //得到以注册表路径HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs 为根结点的Preferences对象 
  3. userNodeForPackage() //根据指定的Class对象得到一个Preferences对象,这个对象的注册表路径是从“HKEY_CURRENT_USER\”开始的 
  4. userRoot() //得到以注册表路径HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs 为根结点的Preferences对象 
  5. putXXX() //设置一个属性的值,这里XXX可以为基本数值型类型,如int、long等,但首字母大写,表示参数为相应的类型,也可以不写而直接用put,参数则为字符串 
  6. getXXX() //得到一个属性的值 
  7. exportNode() //将全部首选项导出为一个XML文件 
  8. exportSubtree() //将部分首选项导出为一个XML文件 
  9. importPreferences() //从XML文件导入首选项 


    你可以按如下步骤保存数据:

  1. Preferences myPrefs1 = Preferences.userNodeForPackage(this);// 这种方法是在“HKEY_CURRENT_USER\”下按当前类的路径建立一个注册表项
  2. Preferences myPrefs2 = Preferences.systemNodeForPackage(this);// 这种方法是在“HKEY_LOCAL_MACHINE\”下按当前类的路径建立一个注册表项
  3. Preferences myPrefs3 = Preferences.userRoot().node("com.jungleford.demo");// 这种方法是在“HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路径建立一个注册表项
  4. Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 这种方法是在“HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路径建立一个注册表项
  5. myPrefs1.putInt("key1", 10);
  6. myPrefs1.putDouble("key2", -7.15);
  7. myPrefs1.put("key3""value3");
  8. FileOutputStream out = new FileOutputStream("prefs.xml");
  9. myPrefs1.exportNode(out);


 

网络I/O:Socket→RMI


Socket
    Socket编程可能大家都很熟,所以就不多讨论了,只是说通过socket把数据保存到远端服务器或从网络socket读取数据也不失为一种值得考虑的方式。

RMI
    RMI机制其实就是RPC(远程过程调用)的Java版本,它使用socket作为基本传输手段,同时也是序列化最重要的一个应用。现在网络传输从编程的角度来看基本上都是以流的方式操作,socket就是一个例子,将对象转换成字节流的一个重要目标就是为了方便网络传输。
    想象一下传统的单机环境下的程序设计,对于Java语言的函数(方法)调用(注意与C语言函数调用的区别)的参数传递,会有两种情况:如果是基本数据类型,这种情况下和C语言是一样的,采用值传递方式;如果是对象,则传递的是对象的引用,包括返回值也是引用,而不是一个完整的对象拷贝!试想一下在不同的虚拟机之间进行方法调用,即使是两个完全同名同类型的对象他们也很可能是不同的引用!此外对于方法调用过程,由于被调用过程的压栈,内存“现场”完全被被调用者占有,当被调用方法返回时,才将调用者的地址写回到程序计数器(PC),恢复调用者的状态,如果是两个虚拟机,根本不可能用简单压栈的方式来保存调用者的状态。因为种种原因,我们才需要建立RMI通信实体之间的“代理”对象,譬如“存根”就相当于远程服务器对象在客户机上的代理,stub就是这么来的,当然这是后话了。
    本地对象与远程对象(未必是物理位置上的不同机器,只要不是在同一个虚拟机内皆为“远程”)之间传递参数和返回值,可能有这么几种情形:

  • 值传递:这又包括两种子情形:如果是基本数据类型,那么都是“可序列化”的,统统序列化成可传输的字节流;如果是对象,而且不是“远程对象”(所谓“远程对象”是实现了java.rmi.Remote接口的对象),本来对象传递的应该是引用,但由于上述原因,引用是不足以证明对象身份的,所以传递的仍然是一个序列化的拷贝(当然这个对象也必须满足上述“可序列化”的条件)。 
  • 引用传递:可以引用传递的只能是“远程对象”。这里所谓的“引用”不要理解成了真的只是一个符号,它其实是一个留在(客户机)本地stub中的,和远端服务器上那个真实的对象张得一模一样的镜像而已!只是因为它有点“特权”(不需要经过序列化),在本地内存里已经有了一个实例,真正引用的其实是这个“孪生子”。


    由此可见,序列化在RMI当中占有多么重要的地位。

数据库I/O:CMP、Hibernate


什么是“Persistence”
    用过VMWare的朋友大概都知道当一个guest OS正在运行的时候点击“Suspend”将虚拟OS挂起,它会把整个虚拟内存的内容保存到磁盘上,譬如你为虚拟OS分配了128M的运行内存,那挂起以后你会在虚拟OS所在的目录下找到一个同样是128M的文件,这就是虚拟OS内存的完整镜像!这种内存的镜像手段其实就是“Persistence”(持久化)概念的由来。

CMP和Hibernate
    因为我对J2EE的东西不是太熟悉,随便找了点材料看看,所以担心说的不到位,这次就不作具体总结了,人要学习……真是一件痛苦的事情[cry]

序列化再探讨


    从以上技术的讨论中我们不难体会到,序列化是Java之所以能够出色地实现其鼓吹的两大卖点??分布式(distributed)和跨平台(OS independent)的一个重要基础。TIJ(即“Thinking in Java”)谈到I/O系统时,把序列化称为“lightweight persistence”??“轻量级的持久化”,这确实很有意思。

为什么叫做“序列”化?
    开场白里我说更习惯于把“Serialization”称为“序列化”而不是“串行化”,这是有原因的。介绍这个原因之前先回顾一些计算机基本的知识,我们知道现代计算机的内存空间都是线性编址的(什么是“线性”知道吧,就是一个元素只有一个唯一的“前驱”和唯一的“后继”,当然头尾元素是个例外;对于地址来说,它的下一个地址当然不可能有两个,否则就乱套了),“地址”这个概念推广到数据结构,就相当于“指针”,这个在本科低年级大概就知道了。注意了,既然是线性的,那“地址”就可以看作是内存空间的“序号”,说明它的组织是有顺序的,“序号”或者说“序列号”正是“Serialization”机制的一种体现。为什么这么说呢?譬如我们有两个对象a和b,分别是类A和B的实例,它们都是可序列化的,而A和B都有一个类型为C的属性,根据前面我们说过的原则,C当然也必须是可序列化的。

  1. import  java.io.*;
  2. ...
  3. class  A implements Serializable
  4. {
  5.   C c;
  6.   ...
  7. }
  8. class  B implements Serializable
  9. {
  10.   C c;
  11.   ...
  12. }
  13. class  C implements Serializable
  14. {
  15.   ...
  16. }
  17. A a;
  18. B b;
  19. C c1;
  20. ...


    注意,这里我们在实例化a和b的时候,有意让他们的c属性使用同一个C类型对象的引用,譬如c1,那么请试想一下,但我们序列化a和b的时候,它们的c属性在外部字节流(当然可以不仅仅是文件)里保存的是一份拷贝还是两份拷贝呢?序列化在这里使用的是一种类似于“指针”的方案:它为每个被序列化的对象标上一个“序列号”(serial number),但序列化一个对象的时候,如果其某个属性对象是已经被序列化的,那么这里只向输出流写入该属性的序列号;从字节流恢复被序列化的对象时,也根据序列号找到对应的流来恢复。这就是“序列化”名称的由来!这里我们看到“序列化”和“指针”是极相似的,只不过“指针”是内存空间的地址链,而序列化用的是外部流中的“序列号链”
    使用“序列号”而不是内存地址来标识一个被序列化的对象,是因为从流中恢复对象到内存,其地址可能就未必是原来的地址了??我们需要的只是这些对象之间的引用关系,而不是死板的原始位置,这在RMI中就更是必要,在两台不同的机器之间传递对象(流),根本就不可能指望它们在两台机器上都具有相同的内存地址。 

更灵活的“序列化”:transient属性和Externalizable
    Serializable确实很方便,方便到你几乎不需要做任何额外的工作就可以轻松将内存中的对象保存到外部。但有两个问题使得Serializable的威力收到束缚:
    一个是效率问题,《Core Java 2》中指出,Serializable使用系统默认的序列化机制会影响软件的运行速度,因为需要为每个属性的引用编号和查号,再加上I/O操作的时间(I/O和内存读写差的可是一个数量级的大小),其代价当然是可观的。
    另一个困扰是“裸”的Serializable不可定制,傻乎乎地什么都给你序列化了,不管你是不是想这么做。其实你可以有至少三种定制序列化的选择。其中一种前面已经提到了,就是在implements Serializable的类里面添加私有的writeObject()和readObject()方法(这种Serializable就不裸了,[:E]),在这两个方法里,该序列化什么,不该序列化什么,那就由你说了算了,你当然可以在这两个方法体里面分别调用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然执行默认的序列化动作(那你在代码上不就做无用功了?呵呵),也可以用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()方法对你中意的属性进行序列化。但虚拟机一看到你定义了这两个方法,它就不再用默认的机制了。
    如果仅仅为了跳过某些属性不让它序列化,上面的动作似乎显得麻烦,更简单的方法是对不想序列化的属性加上transient关键字,说明它是个“暂态变量”,默认序列化的时候就不会把这些属性也塞到外部流里了。当然,你如果定义writeObject()和readObject()方法的化,仍然可以把暂态变量进行序列化。题外话,像transientviolatefinally这样的关键字初学者可能会不太重视,而现在有的公司招聘就偏偏喜欢问这样的问题 :(
    再一个方案就是不实现Serializable而改成实现Externalizable接口。我们研究一下这两个接口的源代码,发现它们很类似,甚至容易混淆。我们要记住的是:Externalizable默认并不保存任何对象相关信息!任何保存和恢复对象的动作都是你自己定义的。Externalizable包含两个public的方法:

  1. public  void writeExternal(ObjectOutput out) throws IOException;
  2. public  void readExternal(ObjectInput in) throws IOExceptionClassNotFoundException;


    乍一看这和上面的writeObject()和readObject()几乎差不多,但Serializable和Externalizable走的是两个不同的流程:Serializable在对象不存在的情况下,就可以仅凭外部的字节序列把整个对象重建出来;但Externalizable在重建对象时,先是调用该类的默认构造函数(即不含参数的那个构造函数)使得内存中先有这么一个实例,然后再调用readExternal方法对实例中的属性进行恢复,因此,如果默认构造函数中和readExternal方法中都没有赋值的那些属性,特别他们是非基本类型的话,将会是空(null)。在这里需要注意的是,transient只能用在对Serializable而不是Externalizable的实现里面。 

序列化与克隆
    从“可序列化”的递归定义来看,一个序列化的对象貌似对象内存映象的外部克隆,如果没有共享引用的属性的化,那么应该是一个深度克隆。关于克隆的话题有可以谈很多,这里就不细说了,有兴趣的话可以参考IBM developerWorks上的一篇文章:JAVA中的指针,引用及对象的clone

一点启示


    作为一个实际的应用,我在写那个简易的邮件客户端JExp的时候曾经对比过好几种保存Message对象(主要是几个关键属性和邮件的内容)到本地的方法,譬如XML、Properties等,最后还是选择了用序列化的方式,因为这种方法最简单, 大约可算是“学以致用”罢。这里“存取程序状态”其实只是一个引子话题罢了,我想说的是??就如同前面我们讨论的关于logging的话题一样??在Java面前对同一个问题你可以有很多种solution:熟悉文件操作的,你可能会觉得Properties、XML或Bean比较方便,然后又发现了还有Preferences这么一个东东,大概又会感慨“天外有天”了,等到你接触了很多种新方法以后,结果又会“殊途同归”,重新反省Serialization机制本身。这不仅是Java,科学也是同样的道理。

分享到:
评论

相关推荐

    JAVA C/S架构应用程序

    1. **Java编程基础**:Java是一种跨平台的面向对象的编程语言,适合开发桌面应用程序。这个C/S架构的程序很可能使用了Swing或JavaFX等库来构建用户界面,通过Java的JDBC(Java Database Connectivity)API来连接和...

    计算机汇编中断I/O程序设计

    计算机汇编中断I/O程序设计是计算机系统中的一种重要技术,涉及到I/O设备的数据传送方式、程序控制方式、中断方式、DMA方式和I/O处理机方式等多方面的知识点。 I/O设备的数据传送方式有多种,包括查询方式(程序...

    mysql-connector-java-5.1.47 jar包

    MySQL Connector/J是MySQL数据库与Java应用程序之间的重要桥梁,它是一个实现了Java Database Connectivity (JDBC) API的驱动程序,使得Java开发者能够通过编写Java代码来访问和操作MySQL数据库。`mysql-connector-...

    mysql-connector-java-5.1.40.zip和mysql-connector-java-5.1.10.jar

    MySQL是世界上最受欢迎的开源数据库系统之一,而MySQL Connector/J是MySQL官方提供的用于Java应用程序与MySQL数据库之间连接的驱动程序。本文将深入探讨这两个文件:"mysql-connector-java-5.1.40.zip" 和 "mysql-...

    mysql-connector-java-8.0.21.zip

    MySQL是世界上最受欢迎的开源数据库系统之一,而MySQL Connector/J则是MySQL官方提供的用于Java应用程序的JDBC(Java Database Connectivity)驱动程序。"mysql-connector-java-8.0.21.zip"是一个包含MySQL ...

    Database I/O Systems

    - **异步I/O**:在数据准备完成后才通知应用程序,避免了等待时间。 #### 三、存储技术 不同的存储技术具有不同的特性和适用场景,了解这些技术有助于选择最适合的应用方案。 **3.1 直接连接存储(DAS)** - **...

    mysql java最新驱动包 mysql-connector-java-8.0.16.rar

    总的来说,"mysql-connector-java-8.0.16.rar" 是Java开发者与MySQL数据库交互的重要工具,通过它可以轻松地在Java应用程序中实现数据的存取和管理。正确地集成和使用这个驱动包,可以极大地提升开发效率,并确保...

    mysql-connector-java-5.1.0-bin.jar

    MySQL Connector/J是MySQL数据库系统与Java应用程序之间的重要桥梁,它是一个实现了Java Database Connectivity (JDBC) API的驱动程序,使得Java开发者能够方便地在Java应用中存取MySQL数据。"mysql-connector-java-...

    mysql-connector-java-8.0.18.zip

    MySQL Connector/J是MySQL数据库与Java应用程序之间的桥梁,它是一个实现了Java Database Connectivity (JDBC) API的驱动程序,允许Java开发者在Java应用中访问MySQL数据库。`mysql-connector-java-8.0.18.zip` 是这...

    mysql-connector-java-5.1.32.tar.gz

    MySQL是世界上最受欢迎的关系型数据库管理系统之一,而MySQL Connector/J是MySQL官方提供的用于Java应用程序与MySQL数据库进行连接的驱动程序。本文将深入解析“mysql-connector-java-5.1.32.tar.gz”压缩包及其核心...

    mysql-connector-java-5.1.0 jar包

    MySQL Connector/J是MySQL数据库与Java应用程序之间的重要桥梁,它是一个实现了Java Database Connectivity (JDBC) API的驱动程序,使得Java开发者能够方便地在Java应用中访问MySQL数据库。标题中的"mysql-connector...

    mysql-connector-java-8.0.28 jar包

    MySQL Connector/J 8.0.28 是MySQL数据库与Java应用程序之间的重要桥梁,它是一个实现了JDBC(Java Database Connectivity)接口的驱动程序,允许Java开发者在应用中与MySQL数据库进行交互。MySQL是世界上最受欢迎的...

    mysql-connector-java-5.1.27.jar

    MySQL Connector/J是MySQL官方提供的用于Java应用程序连接MySQL数据库的驱动程序,它实现了JDBC(Java Database Connectivity)接口,使得Java开发者能够方便地在应用中存取MySQL数据库。在本案例中,我们讨论的是...

    mysql-connector-java-8.0.12

    3. 异步连接:支持非阻塞 I/O,允许应用程序在等待数据库响应时执行其他任务,提高并发性能。 4. 高可用性:提供了对 MySQL 集群和复制的支持,确保在主服务器故障时能自动切换到备机。 5. 安全性:增强了加密功能,...

    mysql-connector-java-5.1.45

    MySQL Connector/J是MySQL数据库与Java应用程序之间的重要桥梁,它是一个实现了Java Database Connectivity (JDBC) API的驱动程序,使得Java开发者能够方便地在Java应用中访问MySQL数据库。`mysql-connector-java-...

    mysql-connector-java-5.1.40-bin

    总的来说,mysql-connector-java-5.1.40-bin.jar是Java开发人员与MySQL数据库进行通信的重要工具,它的存在使得Java应用程序能够无缝地集成MySQL数据库,实现数据的存取和管理。在实际开发中,选择合适的驱动版本...

    mysql-connector-java-8.0.19.zip

    MySQL Connector/J是MySQL数据库系统与Java应用程序之间的桥梁,它是一个实现了Java Database Connectivity (JDBC) API的驱动程序,使得Java开发者能够方便地在MySQL数据库上执行SQL语句,进行数据的存取操作。...

    mysql5.x最新版本驱动 mysql-connector-java-5.1.47.rar

    MySQL是世界上最受欢迎的关系型数据库...总之,`mysql-connector-java-5.1.47`是连接Java应用程序和MySQL 5.x数据库的关键组件,它的使用极大地简化了Java开发中的数据库操作,使得数据存取和管理变得更为高效和便捷。

    mysql-connector-java-5.1.38.jar

    MySQL是世界上最受欢迎的关系型数据库管理系统之一,而`mysql-connector-java-5.1.38.jar`是一个关键的组件,它是MySQL官方提供的Java驱动程序,用于在Java应用程序中与MySQL数据库进行通信。这个JAR(Java Archive...

    mysql-connector-java-5.1.40-bin数据库连接工具.rar

    MySQL Connector/J是MySQL官方提供的用于Java应用程序连接MySQL数据库的驱动程序,它实现了JDBC(Java Database Connectivity)接口,使得Java开发者能够方便地在Java应用中访问和操作MySQL数据库。 "mysql-...

Global site tag (gtag.js) - Google Analytics