14.序列化和文件的输入/输出 保存对象

news2024/12/23 13:31:08

14.1 保存对象状态

你已经奏出完美的乐章,现在会想把它储存起来。你可以抓个文房四宝把它记下来,但也可以按下储存按钮(或按下File菜单上的Save)。然后你帮文件命名,并希望这个文件不会让屏幕变成蓝色的画面。 

储存状态的选择有很多种,这可能要看你会如何使用储存下来的状态而决定。我们会在这一章讨论下面两种选项:

如果只有自己写的Java程序会用到这些数据:

① 用序列化(serialization)。
将被序列化的对象写到文件中。然后就可以让你的程序去文件中读取序列化的对象并把它们展开回到活生生的状态。

如果数据需要被其他程序引用:

② 写一个纯文本文件。用其他程序可以解析的特殊字符写到文件中。例如写成用tab字符来分隔的档案以便让电子表格或数据库应用程序能够应用。

当然还有其他的选择。你可以将数据存进任何格式中。举例来说,你可以把数据用字节而不是字符来写入,或者你也可以将数据写成Java的primitive主数据类型,有一些方法可以提供int、long、boolean等的写入功能。但不管用什么方法,基本所需的输入/输出技巧都一样:把数据写到某处,这可能是个磁盘上的文件,或者是来自网络上的串流。读取数据的方向则刚好相反。当然此处所讨论的部分不涉及使用数据库的情况。

存储状态

假设你有个程序,是个幻想冒险游戏,要过很多关才能完成。在游戏进行的过程中,游戏的人物会累积经验值、宝物、体力等。你不会想让游戏每次重新启动时都得要从头来过—这样根本没人玩。因此你需要一种方法来保存人物的状态,并且在重新开启时能够将状态回复到上次存储时的原状。因为你是程序员,所以你的工作是要让存储与恢复尽可能的简单容易。

① 选项一

把3种序列化的人物对象写入文件中。

创建一个文件,让序列化的3种对象写到此文件中。这文件在你以文本文件形式阅读时是无意义的:
“isr GameCharacter
“oge8iTpowerLjava/lang/
String;[weaponst[Ljava/lang/
String;xp&tlfur[Ljava.lang.String;-“vA应(Gxptbowtswordtdustsq~》tTrolluq~tbare handstbig axsq-xtMagicianuq-tspe llstinvisibility

② 选项二

写入纯文本文件

创建文件,写入3行文字,每个人物一行,以逗点来分开属性:
80,EIf,bow, sword,dust
800,Troll,bare hands,big ax
180,Magician,spells,invisibility

14.2 写入文件的序列化对象

将序列化对象写入文件

下面是将对象序列化(存储)的方法步骤:

1.创建出FileOutputStream 

FileOutputStream fileStream = new FileOutputStream("MyGame.eser");

2.创建ObjectOutputStream

ObjectOutputStream os = new ObjectOutputStream(fileStream);

3.写入对象

os.writeObject(characterOne);
os.writeObject(characterTwo);
os.writeObject(characterThree);

4.关闭ObjectOutputStream

os.close();

14.3 输入/输出串流

Java的输入/输出API带有连接类型的串流,它代表来源与目的地之间的连接,连接串流将串流与其他串流连接起来。

一般来说,串流要两两连接才能作出有意义的事情——其中一个表示连接,另一个则是要被调用方法的。为何要两个?因为连接的串流通常都是很低层的。以FileOutputStream为例,它有可以写入字节的方法。但我们通常不会直接写字节,而是以对象层次的观点来写入,所以需要高层的连接串流。

那又为何不以单一的串流来执行呢?这就要考虑到良好的面向对象设计了。每个类只要做好一件事。FileOutputStream把字节写入文件。ObjectOutputStream把对象转换成可以写入串流的数据。当我们调用ObjectOutputStream的writeObject时,对象会被打成串流送到FileOutputStream来写入文件。

这样就可以通过不同的组合来达到最大的适应性!如果只有一种串流类的话,你只好祈祷API的设计人已经想好所有可能的排列组合。但通过链接的方式,你可以自由地安排串流的组合与去向。

将串流(stream)连接起来代表来源与目的地(文件或网络端口)的连接。串流必须要连接到某处才能算是个串流。

14.4 对象序列化

对象被序列化的时候发生了什么事?

1.在堆上的对象

在堆上的对象有状态——实例变量的值。这些值让同一类的不同实例有不同的意义

2.被序列化的对象

序列化的对象保存了实例变量的值,因此之后可以在堆上带回一模一样的实例

对象的状态是什么?有什么需要保存?

存储primitive主数据类型值37和70是很简单的。但如果对象有引用到其他对象的实例变量时要怎么办?如果这些对象还带有其他对象又该如何?对象基本上有哪些部分是独特的?有哪些东西需要被带回来才能让对象回到和存储时完全相同的状态?当然它会有不同的内存位置,但这无关紧要。我们在乎的是堆上是否有与存储时一模一样的对象状态。

当对象被序列化时,被该对象引用的实例变量也会被序列化。且所有被引用的对象也会被序列化··最棒的是,这些操作都是自动进行的!

序列化程序会将对象版图上的所有东西存储起来。被对象的实例变量所引用的所有对象都会被序列化。

Kennel对象带有对Dog数组对象的引用。Dog[]中有两个Dog对象的引用。每个Dog对象带有String和Collar对象的引用。String对象维护字符的集合,而Collar对象持有一个int。
当保存kennel对象时,所有的对象都会保存! 

14.5 实现Serializable接口

如果要让类能够被序列化,就实现Serializable

Serializable接口又被称为marker或tag类的标记用接口,因为此接口并没有任何方法需要实现的。它的唯一目的就是声明有实现它的类是可以被序列化的。也就是说,此类型的对象可以通过序列化的机制来存储。如果某类是可序列化的,则它的子类也自动地可以序列化(接口的本意就是如此)。

import java.io.*;

public class Box implements Serializable {


    private int width;
    private int height;

    public void setWidth(int w) {
        width = w;
    }

    public void setHeight(int h) {
        height = h;
    }

    public static void main(String[] args) {
        Box myBox = new Box();
        myBox.setWidth(50);
        myBox.setHeight(20);

        try {
            FileOutputStream fs = new FileOutputStream("foo.ser");
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(myBox);
            os.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

序列化时全有或全无的 

整个对象版图都必须正确地序列化,不外就得全部失败如果Duck对象不能序列化,Pond对象就不能被序列化。

14.6 使用瞬时变量

如果某实例变量不能或不应该被序列化,就把它标记为transient(瞬时)的

如果你需要序列化程序能够跳过某个实例变量,就把它标记成transient的变量

import java.io.*;

public class Chat implements Serializable {
    transient  String currentID;
    
    String userName;
    
    //还有更多程序代码……
}

如果你有无法序列化的变量不能被存储,可以用transient这个关键词把它标记出来,序列化程序会把它跳过。

为什么有些变量不能被序列化?可能是设计者忘记实现Serializable。或者动态数据只可以在执行时求出而不能或不必存储。虽然Java函数库中大部分的类可以被序列化,你还是无法将网络联机之类的东西保存下来。它得要在执行期当场创建才有意义。一旦程序关闭之后,联机本身就不再有用,下次执行时需要重新创建出来。 

14.7 对象解序列化

解序列化(Deserialization):还原对象

将对象序列化整件事情的重点在于你可以在事后,在不同的Java虚拟机执行期(甚至不是同一个Java虚拟机),把对象恢复到存储时的状态。解序列化有点像是序列化的反向操作。

1.创建FileInputStream

FileInputStream fileStream = new FileInputStream("Mygame.ser");

2.创建ObjectInputStream

ObjectInputStream os = new ObjectInputStream(fileStream);

3.读取对象

Object one = os.readObject();
Object two = os.readObject();
Object three = os.readObject();

4.转换对象类型

GameCharacter elf = (GameCharacter) one;
GameCharacter troll = (GameCharacter) two;
GameCharacter magician = (GameCharacter) three;

5.关闭ObjectInputStream 

os.close();

解序列化的时候发生了什么事?

当对象被解序列化时,Java虚拟机会通过尝试在堆上创建新的对象,让它维持与被序列化时有相同的状态来恢复对象的原状。但这当然不包括transient的变量,它们不是null(对对象引用而言)不然就是使用primitive主数据类型的默认值。

1.对象从stream中读出来。

2.Java虚拟机通过存储的信息判断出对象的class类型。

3.Java虚拟机尝试寻找和加载对象的类。如果Java虚拟机找不到或无法加载该类,则Java虚拟机会抛出例外。

4.新的对象会被配置在堆上,但构造函数不会执行!很明显的,这样会把对象的状态抹去又变成全新的,而这不是我们想要的结果。我们需要的是对象回到存储时的状态。

5.如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化也一样)就会执行。一旦构造函数连锁启动之后将无法停止。也就是说,从第一个不可序列化的父类开始,全部都会重新初始状态。

6.对象的实例变量会被还原成序列化时点的状态值。transient变量会被赋值null的对象引用或primitive主数据类型的默认为0、false等值。

存储与恢复游戏人物

import java.io.*;

public class GameSaverTest {
    public static void main(String[] args) {
        GameCharacter one = new GameCharacter(50, "Elf", new String[] {"bow", "sword", "dust"});
        GameCharacter two = new GameCharacter(200, "Troll", new String[] {"bare hands", "big ax"});
        GameCharacter three = new GameCharacter(120, "Magician", new String[] {"spell", "invisibility"});

        // 假设此处有改变人物状态的程序代码

        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("Game.ser"));
            os.writeObject(one);
            os.writeObject(two);
            os.writeObject(three);
            os.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        one = null;
        two = null;
        three = null;

        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("Game.ser"));
            GameCharacter oneRestore = (GameCharacter) is.readObject();
            GameCharacter twoRestore = (GameCharacter) is.readObject();
            GameCharacter threeRestore = (GameCharacter) is.readObject();

            System.out.println("One's type: " + oneRestore.getType());
            System.out.println("Two's type: " + twoRestore.getType());
            System.out.println("Three's type: " + threeRestore.getType());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
import java.io.*;

public class GameCharacter implements Serializable {
    int power;
    String type;
    String[] weapons;

    public GameCharacter(int p, String t, String[] w) {
        power = p;
        type = t;
        weapons = w;
    }

    public int getPower() {
        return power;
    }

    public String getType() {
        return type;
    }

    public String getWeapons() {
        String weaponList = "";

        for (int i = 0; i < weapons.length; i++) {
            weaponList += weapons[i] + " ";
        }
        return  weaponList;
    }
}

14.8 写入文本文件

将字符串写入文本文件

通过序列化来存储对象是Java程序在来回执行间存储和恢复数据最简单的方式。但有时你还得把数据存储到单纯的文本文件中。假设你的Java程序必须把数据写到文本文件中以让其他可能是非Java的程序读取。例如你的servlet(在Web服务器上执行的Java程序)会读取用户在网页上输入的数据,并将它写入文本文件以让网站管理人能够用电子表格来分析数据。

写入文本数据(字符串)与写入对象是很类似的,你可以使用FileWrite来代替FileOutputStream(当然不会把它链接到ObjectOutputStream上)。

写序列化的对象:

ObjectOutputStream.writeObject(someObject);

写字符串:

fileWriter.write("My first String to save");
import java.io.*;

public class WriteAFile {
    public static void main(String[] args) {
        
        try {
            FileWriter writer = new FileWriter("Foo.txt");
            
            writer.write("hello foo!");
            
            writer.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

14.9 java.io.File

File这个类代表磁盘上的文件,但并不是文件中的内容。啥?你可以把File对象想象成文件的路径,而不是文件本身。例如File并没有读写文件的方法。关于File有个很有用的功能就是它提供一种比使用字符串文件名来表示文件更安全的方式。举例来说,在构造函数中取用字符串文件名的类也可以用File对象来代替该参数,以便检查路径是否合法等,然后再把对象传给FileWriter或FileInputStream。

File对象代表磁盘上的文件或目录的路径名称,如:
/Users/Kathy/Data/GameFile.txt
但它并不能读取或代表文件中的数据。

你可以对File对象做的事情:

1.创建出代表现存盘文件的File对象。

File f = new File("MyCode.txt");

2.建立新的目录。

File dir = new File("Chapter7");
dir.mkdir();

3.列出目录下的内容。

if (dir.isDirectory()) {
    String[] dirContents = dir.list();
    for (int i = 0; i < dirContents.length; i++) {
        System.out.println(dirContents);
    }
}

4.取得文件或目录的绝对路径。

System.out.println(dir.getAbsolutePath());

5.删除文件或目录(成功会返回true)。

boolean isDeleted = f.delete();

缓冲区的奥妙之处

没有缓冲区,就好像逛超市没有推车一样。你只能一次拿一项东西结账。 

缓冲区的奥妙之处在于使用缓冲区比没有使用缓冲区的效率更好。你也可以直接使用FileWriter,调用它的write()来写文件,但它每次都会直接写下去。
你应该不会喜欢这种方式额外的成本,因为每趟磁盘操作都比内存操作要花费更多时间。通过BufferedWriter和FileWriter的链接,BufferedWriter可以暂存一堆数据,然后到满的时候再实际写入磁盘,这样就可以减少对磁盘操作的次数。
如果你想要强制缓冲区立即写入,只要调用writer.flush()这个方法就可以要求缓冲区马上把内容写下去

14.10 读取文本文件

读取文本文件

从文本文件读数据是很简单的,但是这次我们会使用File对象来表示文件,以FileReader来执行实际的读取,并用BufferedReader来让读取更有效率。

读取是以while循环来逐行进行,一直到readLine()的结果为null为止。这是最常见的读取数据方式(几乎非序列化对象都是这样的):以while循环(实际上应该称为while循环测试)来读取,读到没有东西可以读的时候停止(通过读取结果为null来判断)。

import java.io.*;

public class ReadAFile {
    public static void main(String[] args) {

        try {
            File myFile = new File("MyTest.txt");
            FileReader fileReader = new FileReader(myFile);

            BufferedReader reader = new BufferedReader(fileReader);

            String line = null;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

14.11 拆分字符串

用String的split()解析

要如何分开问题和答案?

当你读取文件时,问题和答案是合并在同一行,以“/”字符来分开的。

String的split()可以把字符串拆开。

split()可以将字符串拆开成String的数组。

Version ID:序列化的识别

版本控制很重要!
如果你将对象序列化,则必须要有该类才能还原和使用该对象。OK,这是废话。但若你同时又修改了类会发生什么事?假设你尝试要把Dog对象带回来,而某个非transient的变量却已经从double被改成String。这样会很严重地违反Java的类型安全性。其实不只是修改会伤害兼容性,想想下列的情况:

会损害解序列化的修改:
删除实例变量。
改变实例变量的类型。
将非瞬时的实例变量改为瞬时的。
改变类的继承层次。
将类从可序列化改成不可序列化。
将实例变量改成静态的。

通常不会有事的修改:

加入新的实例变量(还原时会使用默认值)A
在继承层次中加入新的类。
从继承层次中删除类。
不会影响解序列化程序设定变量值的存取层次修改。
将实例变量从瞬时改成非瞬时(会使用默认值)。

使用serialVersionUID

每当对象被序列化的同时,该对象(以及所有在其版图上的对象)都会被“盖”上一个类的版本识别ID。这个ID被称为serialVersionUID,它是根据类的结构信息计算出来的。在对象被解序列化时,如果在对象被序列化之后类有了不同的serialVersionUID,则还原操作会失败!但你还可以有控制权。

如果你认为类有可能会演化,就把版本识别ID放在类中。

当Java尝试要还原对象时,它会比对对象与Java虚拟机上的类的serialVersionUID。例如,如果Dog实例是以23这个ID来序列化的(实际的ID长得多),当Java虚拟机要还原Dog对象时,它会先比对Dog对象和Dog类的serialVersionUID。如果版本不相符,Java虚拟机就会在还原过程中抛出异常。

因此,解决方案就是把serialVersionUID放在class中,让类在演化的过程中还维持相同的ID。
这只会在你有很小心地维护类的变动时才办得到!也就是说你得要对带回旧对象的任何问题负起全责。

若想知道某个类的serialVersionUID,则可以使用JavaDevelopment Kit里面所带的serialver工具来查询。 

14.12 程序料理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1182159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

批量迁移redis实例的key

我们知道migrate 命令可以迁移redis的多个key&#xff0c;但是如果redis的key有非常多&#xff0c;那用起来就很不方便了。 所以下面分享一个脚本来实现批量key的迁移&#xff0c;主要使用的命令为dump和restore 脚本如下&#xff1a; #!/bin/bash redis-cli -h host1 -p 63…

第19章_体系结构

文章目录 1. 逻辑架构剖析1.1 服务器处理客户端请求1.2 Connectors1.3 第1层&#xff1a;连接层1.4 第2层&#xff1a;服务层1.4.1 SQL Interface: SQL接口1.4.2 Parser: 解析器1.4.3 Optimizer: 查询优化器1.4.4 Caches & Buffers&#xff1a; 查询缓存组件 1.5 第3层&…

详细创建Prism架构wpf项目

方案一&#xff1a; 1.创建一个普通wpf项目 2、安装NuGet包&#xff1a;Prism.DryIoc 3、App.xaml.cs中: 将原本的父类Application改为&#xff1a;PrismApplication&#xff0c;并且实现抽象类 CreateShell方法中写上&#xff1a;”return Container.Resolve<MainWindow>…

YOLO目标检测——交通标志识别数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;交通标志识别数据集在自动驾驶、交通安全监控、智能交通系统、驾驶员辅助系统和城市规划等领域都有广泛应用的潜力数据集说明&#xff1a;交通标志识别数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含有交通标识6类…

电源模块的测试方法都有哪些?如何用ATECLOUD进行测试?

电源模块的测试方法主要包括以下步骤&#xff1a; 连接设备&#xff1a;通过LAN通讯总线、测试夹具以及其它线缆对测试所需的设备和需要测试的电源模块进行连接&#xff0c;同时接入纳米box进入登录ATECLOUD云测试平台。 创建测试项目&#xff1a;根据所以的设备仪器搭建所需…

clumsy 0.3 发布,十年前推出的差网络环境模拟工具

clumsy 0.3 现已发布&#xff0c;距离 v0.1 版本已经过去了十年的时间。clumsy 能在 Windows 平台下人工造成不稳定的网络状况&#xff0c;方便你调试应用程序在极端网络状况下的表现。 0.3 二进制文件与一年半前发布的 0.3 RC4 相同。 将滞后时间上限提高到 15 秒改用 zig 0.…

彻底改变日常生活:面向消费类应用的物联网

在本文中&#xff0c;我们将探讨消费应用的物联网世界&#xff0c;包括其关键用途、优势、挑战以及它为消费者带来的令人兴奋的未来。 物联网 (IoT) 正在重塑我们的生活、工作以及与环境互动的方式。 通过物联网&#xff0c;日常物品和设备连接到互联网&#xff0c;创建一个可以…

jenkins部署job

apt install fontconfig openjdk-11-jre wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/war/2.429/jenkins.wardeb包安装 wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/debian-stable/jenkins_2.414.3_all.debdpkg -i jenkins_2.414.3_all.deb 访问 http://…

波形的哪些事

一.静音波形制造(波形卡顿制造) 二.pop波形制造 三.示波器探头设置 四.示波器的差分输入和单端输入的接法不一样 差分的接法&#xff0c;需要配差分探头(如下图)&#xff0c;差分探头的两个脚分别和功放输出通道的两个脚连接 单端的接法&#xff0c;需要单端的探头&#xff0c…

【算法与数据结构】216、LeetCode组合总和 III

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题可以直接利用77题的代码【算法与数据结构】77、LeetCode组合&#xff0c;稍作修改即可使用。   …

Linux文件系统的功能规划

对于运行的进程来说&#xff0c;内存就像一个纸箱子&#xff0c;仅仅是一个暂存数据的地方&#xff0c;而且空间有限。如果我们想要进程结束之后&#xff0c;数据依然能够保存下来&#xff0c;就不能只保存在内存里&#xff0c;而是应该保存在外部存储中。就像图书馆这种地方&a…

第三章:人工智能深度学习教程-基础神经网络(第二节-ANN 和 BNN 的区别)

在本文中&#xff0c;我们将了解单层感知器及其使用 TensorFlow 库在Python中的实现。神经网络的工作方式与我们的生物神经元的工作方式相同。 生物神经元的结构 生物神经元具有三个基本功能 接收外部信号。 处理信号并增强是否需要发送信息。 将信号传递给目标细胞&#x…

[ Linux Busybox ] flash_eraseall 命令解析

文章目录 相关结构体flash_eraseall 函数实现flash_eraseall 实现流程图 文件路径&#xff1a;busybox-1.20.2/miscutils/flash_eraseall.c 相关结构体 MTD 相关信息结构体 struct mtd_info_user {__u8 type; // MTD 设备类型__u32 flags; // MTD设…

网络安全深入学习第八课——正向代理(工具:ReGeorg)

文章目录 一、环境配置二、开始模拟1、拿下跳板机的Webshell权限&#xff0c;并上传shell文件1.1、查看跳板机网络环境1.2、查看arp表 2、使用ReGeorg来建立连接2.1、生产ReGeorg隧道文件2.2、上传ReGeorg隧道的PHP脚本到跳板机2.3、连接隧道2.4、尝试浏览器连接 3、使用Proxif…

栈,线性dp,P1310 [NOIP2011 普及组] 表达式的值

P1310 [NOIP2011 普及组] 表达式的值 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 这道题很有意义 题目描述 对于1 位二进制变量定义两种运算&#xff1a; 运算的优先级是&#xff1a; 先计算括号内的&#xff0c;再计算括号外的。 “ ”运算优先于“⊕”运算&#xf…

reduxjs/toolkit的使用

用法&#xff1a; 下载包进行安装&#xff0c;store主入口&#xff0c;hooks简化store(复制粘贴进去即可)&#xff0c;slice相当于store中的模块化&#xff0c;最后在页面根入口导入store&#xff0c;并使用即可 1、安装 npm install reduxjs/toolkit react-redux -D2、目录结…

【网络编程】传输层——TCP协议

文章目录 TCP协议TCP协议格式窗口大小六个标志位确认应答机制超时重传机制连接管理机制三次握手四次挥手 流量控制滑动窗口拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况TCP小结基于TCP的应用层协议TCP与UDP的对比 TCP相关实验CLOSE_WAIT状态实验TIME_WAIT状态实验TI…

为你提供5个解决“找不到msvcp120.dll无法继续执行代码”

在使用电脑计算机运行软件时&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“找不到msvcp120.dll无法继续执行代码”。这个问题通常是由于缺少相应的运行库文件或者文件损坏导致的。下面我将为你提供5个解决方法&#xff0c;帮助你解决这个问题。 第1个方法.…

​软考-高级-信息系统项目管理师教程 第四版【第18章-项目绩效域-思维导图】​

软考-高级-信息系统项目管理师教程 第四版【第18章-项目绩效域-思维导图】 课本里章节里所有蓝色字体的思维导图

MATLAB画图由于线段太多导致导出图片模糊的解决办法

Matlab画图如果figure内的线条过多&#xff0c;或者散点过多&#xff0c;导出的图片会模糊&#xff0c;解决方案 解决方法就在于figure的导出设置中。 在设置的渲染选项中&#xff0c;渲染器有两个&#xff0c;分别为painters和OpenGL&#xff0c;分别为矢量格式输出和位图输出…