博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android RxJava使用介绍(二) RxJava的操作符
阅读量:4074 次
发布时间:2019-05-25

本文共 20257 字,大约阅读时间需要 67 分钟。

上一篇文章我们通过一个简单的例子来给大家展示了RxJava的基本用法,相信大家已经对RxJava有了大概的了解,由于上篇文章对RxJava的使用介绍都是点到为止,并没有进行深入展开,也许你对RxJava有种名不副实的感觉。OK,下面我们就进入正题,一步步的揭开RxJava的神秘面纱!

一个例子

RxJava的强大之处,在于它提供了非常丰富且功能强悍的操作符,通过使用和组合这些操作符,你几乎能完成所有你想要完成的任务,举个例子如下:

  1. 现在有一个需求:app启动时显示一张图片(一般是app的logo),也就是我们所说的欢迎页,2-3秒后自动跳转到主页面。这不就是几乎每个app都有的启动页需求吗?几乎不用思考,代码如下:
    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_welcome);        ImageView view = (ImageView) findViewById(R.id.iv_welcome);        view.setImageResource(R.drawable.welcome);        Handler handler = new Handler();        handler.postDelayed(new Runnable() {            @Override            public void run() {                startActivity(new Intent(WelcomeActivity.this, MainActivity.class));                finish();            }        },2000);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

使用RxJava的代码实现如下:

protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_welcome);        ImageView view = (ImageView) findViewById(R.id.iv_welcome);        view.setImageResource(R.drawable.welcome);        Observable.timer(2, TimeUnit.SECONDS, AndroidSchedulers.mainThread()).map(l->{            startActivity(new Intent(this, MainActivity.class));            finish();            return null;        }).subscribe();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里的RxJava使用了两个操作符:一个是timer操作符,它的意思是延迟执行某个操作;一个是map操作符,它的意思是转换某个执行结果。 
恩,好像除了写法不一样,也没看出RxJava有什么优势呢。好吧,继续往下看! 
2. 由于最近产品要做活动,为了在显著的位置把活动的内容显示给用户,经过讨论,就是在欢迎页停留2秒后,要跳转到活动内容的页面(也就是一张广告的图片)上,然后在活动内容的页面停留2-3秒,再跳转到主页面;

protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_welcome);        ImageView view = (ImageView) findViewById(R.id.iv_welcome);        view.setImageResource(R.drawable.welcome);        //开新线程从网络获取图片        AsyncAdImageFromNet();        Handler handler = new Handler();        handler.postDelayed(new Runnable() {            @Override            public void run() {                //mAdBitmapDrawable是从网络获取到的图片,如果获取到图片,则显示出来,否则直接跳转到主页面                if(mAdBitmapDrawable != null){                    view.setImageDrawable(mAdBitmapDrawable);                    handler.postDelayed(new Runnable() {                        @Override                        public void run() {                            startActivity(new Intent(WelcomeActivity.this, MainActivity.class));                            finish();                        }                    }, 2000);                } else {                    startActivity(new Intent(WelcomeActivity.this, MainActivity.class));                    finish();                }            }        }, 2000);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

这段代码没有考虑离线情况,即没有网络的情况下是显示不出活动内容图片的,最好是在有网络的情况下就把图片下载到本地缓存起来,不管有没有网络都装载本地的缓存即可,改进后的代码如下:

protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_welcome);        ImageView view = (ImageView) findViewById(R.id.iv_welcome);        view.setImageResource(R.drawable.welcome);        //本地图片缓存路径        File localBitmapFile = new File(getLocalBitmapPath());        if(localBitmapFile.exists())            mAdBitmapDrawable = BitmapFactory.decodeFile(localBitmapFile);        //开新线程从网络获取图片        AsyncAdImageFromNet();        Handler handler = new Handler();        handler.postDelayed(new Runnable() {            @Override            public void run() {                //mAdBitmapDrawable是本地缓存获取到的图片,如果获取到图片,则显示出来,否则直接跳转到主页面                if(mAdBitmapDrawable != null){                    view.setImageDrawable(mAdBitmapDrawable);                    handler.postDelayed(new Runnable() {                        @Override                        public void run() {                            startActivity(new Intent(WelcomeActivity.this, MainActivity.class));                            finish();                        }                    }, 2000);                } else {                    startActivity(new Intent(WelcomeActivity.this, MainActivity.class));                    finish();                }            }        }, 2000);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

仔细一看,代码还是有问题,加载本地缓存图片,如果图片比较大,就会阻塞主线程,最好是把加载本地缓存图片放在一个线程中执行,代码真是越写越乱,我们看看使用RxJava是怎么做的呢?

protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_welcome);        ImageView view = (ImageView) findViewById(R.id.iv_welcome);        view.setImageResource(R.mipmap.welcome);        Observable.mergeDelayError(                //在新线程中加载本地缓存图片                loadBitmapFromLocal().subscribeOn(Schedulers.io()),                //在新线程中加载网络图片                loadBitmapFromNet().subscribeOn(Schedulers.newThread()),                Observable.timer(3,TimeUnit.SECONDS).map(c->null))                //每隔2秒获取加载数据                .sample(2, TimeUnit.SECONDS, AndroidSchedulers.mainThread())                .flatMap(r->{                    if(r==null)  //如果没有获取到图片,直接跳转到主页面                        return Observable.empty();                    else { //如果获取到图片,则停留2秒再跳转到主页面                        view.setImageDrawable(r);                        return Observable.timer(2, TimeUnit.SECONDS);                    }                }).subscribe(                r->{                },                e->{                    startActivity(new Intent(WelcomeActivity.this, MainActivity.class));                    finish();                },                ()->{                    startActivity(new Intent(WelcomeActivity.this, MainActivity.class));                    finish();                }        );    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

这里使用了几个操作符:首先是mergeDelayError,它的意思是合并几个不同的Observable;sample的意思是每隔一段时间就进行采样,在时间间隔范围内获取最后一个发布的Observable; flatMap的意思是把某一个Observable转换成另一个Observable。

可以看到,使用了RxJava,整个代码思路很清晰,不用考虑底层的线程同步、异步通知等内容,把主要精力都集中在如何实现业务上,这就是响应式函数编程的魅力!

操作符分类

通过上面的例子,大家应该看到了RxJava操作符的威力,下面我按类别把常用操作符分别介绍,其实很多内容都是来自于ReactiveX的官方网站,英文比较好的朋友可以参考()。 
按照官方的分类,操作符大致分为以下几种:

  • Creating Observables(Observable的创建操作符),比如:Observable.create()、Observable.just()、Observable.from()等等;
  • Transforming Observables(Observable的转换操作符),比如:observable.map()、observable.flatMap()、observable.buffer()等等;
  • Filtering Observables(Observable的过滤操作符),比如:observable.filter()、observable.sample()、observable.take()等等;
  • Combining Observables(Observable的组合操作符),比如:observable.join()、observable.merge()、observable.combineLatest()等等;
  • Error Handling Operators(Observable的错误处理操作符),比如:observable.onErrorResumeNext()、observable.retry()等等;
  • Observable Utility Operators(Observable的功能性操作符),比如:observable.subscribeOn()、observable.observeOn()、observable.delay()等等;
  • Conditional and Boolean Operators(Observable的条件操作符),比如:observable.amb()、observable.contains()、observable.skipUntil()等等;
  • Mathematical and Aggregate Operators(Observable数学运算及聚合操作符),比如:observable.count()、observable.reduce()、observable.concat()等等;
  • 其他如observable.toList()、observable.connect()、observable.publish()等等;

创建型操作符

create操作符

create操作符是所有创建型操作符的“根”,也就是说其他创建型操作符最后都是通过create操作符来创建Observable的,其流程图例如下: 
这里写图片描述

调用例子如下:

Observable.create(new Observable.OnSubscribe
() {
@Override
public
void
call(Subscriber
super Integer> observer) {
try {
if (!observer.isUnsubscribed()) {
for (
int i =
1; i <
5; i++) { observer.onNext(i); } observer.onCompleted(); } }
catch (Exception e) { observer.onError(e); } } } ).subscribe(
new Subscriber
() {
@Override
public
void
onNext(Integer item) { System.out.println(
"Next: " + item); }
@Override
public
void
onError(Throwable error) { System.err.println(
"Error: " + error.getMessage()); }
@Override
public
void
onCompleted() { System.out.println(
"Sequence complete."); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

运行结果如下: 
Next: 1 
Next: 2 
Next: 3 
Next: 4 
Sequence complete.

在使用create操作符时,最好要在回调的call函数中增加isUnsubscribed的判断,以便在subscriber在取消订阅时不会再执行call函数中相关代码逻辑,从而避免导致一些意想不到的错误出现;

from操作符

from操作符是把其他类型的对象和数据类型转化成Observable,其流程图例如下: 
这里写图片描述
调用例子如下:

Integer[] items = { 0, 1, 2, 3, 4, 5 };Observable myObservable = Observable.from(items);myObservable.subscribe(    new Action1
() { @Override
public
void
call(Integer item) { System.
out.println(item); } },
new Action1
() { @Override
public
void
call(Throwable error) { System.
out.println(
"Error encountered: " + error.getMessage()); } },
new Action0() { @Override
public
void
call() { System.
out.println(
"Sequence complete"); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

运行结果如下: 






Sequence complete

just操作符

just操作符也是把其他类型的对象和数据类型转化成Observable,它和from操作符很像,只是方法的参数有所差别,其流程图例如下: 
这里写图片描述
调用例子如下:

Observable.just(1, 2, 3)          .subscribe(new Subscriber
() {
@Override
public
void
onNext(Integer item) { System.out.println(
"Next: " + item); }
@Override
public
void
onError(Throwable error) { System.err.println(
"Error: " + error.getMessage()); }
@Override
public
void
onCompleted() { System.out.println(
"Sequence complete."); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

运行结果如下: 
Next: 1 
Next: 2 
Next: 3 
Sequence complete.

defer操作符

defer操作符是直到有订阅者订阅时,才通过Observable的工厂方法创建Observable并执行,defer操作符能够保证Observable的状态是最新的,其流程实例如下: 
这里写图片描述

下面通过比较defer操作符和just操作符的运行结果作比较:

        i=10;        Observable justObservable = Observable.just(i);        i=12;        Observable deferObservable = Observable.defer(new Func0
>() { @Override public Observable call() { return Observable.just(i); } }); i=15; justObservable.subscribe(new Subscriber() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Object o) { System.out.println("just result:" + o.toString()); } }); deferObservable.subscribe(new Subscriber() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Object o) { System.out.println("defer result:" + o.toString()); } }); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

其中i是类的成员变量,运行结果如下: 
just result:10 
defer result:15

可以看到,just操作符是在创建Observable就进行了赋值操作,而defer是在订阅者订阅时才创建Observable,此时才进行真正的赋值操作

timer操作符

timer操作符是创建一串连续的数字,产生这些数字的时间间隔是一定的;这里有两种情况:

  • 一种是隔一段时间产生一个数字,然后就结束,可以理解为延迟产生数字,其流程实例如下: 
    这里写图片描述
  • 一种是每隔一段时间就产生一个数字,没有结束符,也就是是可以产生无限个连续的数字,其流程实例如下: 
    这里写图片描述

timer操作符默认情况下是运行在一个新线程上的,当然你可以通过传入参数来修改其运行的线程。 
下面是调用例子:

        //每隔两秒产生一个数字        Observable.timer(2, 2, TimeUnit.SECONDS).subscribe(new Subscriber
() { @Override
public
void
onCompleted() { System.
out.println(
"Sequence complete."); } @Override
public
void
onError(Throwable e) { System.
out.println(
"error:" + e.getMessage()); } @Override
public
void
onNext(Long aLong) { System.
out.println(
"Next:" + aLong.toString()); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

运行结果如下: 
Next:0 
Next:1 
Next:2 
Next:3 
……

interval操作符

interval操作符是每隔一段时间就产生一个数字,这些数字从0开始,一次递增1直至无穷大;interval操作符的实现效果跟上面的timer操作符的第二种情形一样。以下是流程实例: 
这里写图片描述

interval操作符默认情况下是运行在一个新线程上的,当然你可以通过传入参数来修改其运行的线程。

调用例子就不列出了,基本跟上面timer的调用例子一样。

range操作符

range操作符是创建一组在从n开始,个数为m的连续数字,比如range(3,10),就是创建3、4、5…12的一组数字,其流程实例如下: 
这里写图片描述

调用例子如下:

//产生从3开始,个数为10个的连续数字        Observable.range(3,10).subscribe(new Subscriber
() { @Override
public
void
onCompleted() { System.
out.println(
"Sequence complete."); } @Override
public
void
onError(Throwable e) { System.
out.println(
"error:" + e.getMessage()); } @Override
public
void
onNext(Integer i) { System.
out.println(
"Next:" + i.toString()); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

运行结果如下: 
Next:3 
Next:4 
Next:5 
Next:6 
…. 
Next:12 
Sequence complete.

repeat/repeatWhen操作符

repeat操作符是对某一个Observable,重复产生多次结果,其流程实例如下: 
这里写图片描述

repeatWhen操作符是对某一个Observable,有条件地重新订阅从而产生多次结果,其流程实例如下: 
这里写图片描述
repeat和repeatWhen操作符默认情况下是运行在一个新线程上的,当然你可以通过传入参数来修改其运行的线程。

repeat调用例子如下:

//连续产生两组(3,4,5)的数字        Observable.range(3,3).repeat(2).subscribe(new Subscriber
() { @Override
public
void
onCompleted() { System.
out.println(
"Sequence complete."); } @Override
public
void
onError(Throwable e) { System.
out.println(
"error:" + e.getMessage()); } @Override
public
void
onNext(Integer i) { System.
out.println(
"Next:" + i.toString()); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

运行结果如下: 
Next:3 
Next:4 
Next:5 
Next:3 
Next:4 
Next:5 
Sequence complete.

repeatWhen调用例子如下:

Observable.just(1,2,3).repeatWhen(new Func1
, Observable
>() {
@Override
public Observable
call(Observable
observable) {
//重复3次
return observable.zipWith(Observable.range(
1,
3),
new Func2
() {
@Override
public Integer
call(Void aVoid, Integer integer) {
return integer; } }).flatMap(
new Func1
>() {
@Override
public Observable
call(Integer integer) { System.out.println(
"delay repeat the " + integer +
" count");
//1秒钟重复一次
return Observable.timer(
1, TimeUnit.SECONDS); } }); } }).subscribe(
new Subscriber
() {
@Override
public
void
onCompleted() { System.out.println(
"Sequence complete."); }
@Override
public
void
onError(Throwable e) { System.err.println(
"Error: " + e.getMessage()); }
@Override
public
void
onNext(Integer value) { System.out.println(
"Next:" + value); } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

运行结果如下: 
Next:1 
Next:2 
Next:3 
repeat the 1 count 
Next:1 
Next:2 
Next:3 
repeat the 2 count 
Next:1 
Next:2 
Next:3 
repeat the 3 count 
Next:1 
Next:2 
Next:3 
Sequence complete.

好了,本篇文章到此为止,后面会继续介绍RxJava的其他操作符,敬请期待!

转载地址:http://bquni.baihongyu.com/

你可能感兴趣的文章
React Native(一):搭建开发环境、出Hello World
查看>>
React Native(二):属性、状态
查看>>
JSX使用总结
查看>>
React Native(四):布局(使用Flexbox)
查看>>
React Native(七):Android双击Back键退出应用
查看>>
Android自定义apk名称、版本号自增
查看>>
【剑指offer】q50:树中结点的最近祖先
查看>>
二叉树的非递归遍历
查看>>
【leetcode】Reorder List (python)
查看>>
【leetcode】Linked List Cycle (python)
查看>>
【leetcode】Candy(python)
查看>>
【leetcode】Sum Root to leaf Numbers
查看>>
【leetcode】Pascal's Triangle II (python)
查看>>
如何成为编程高手
查看>>
本科生的编程水平到底有多高
查看>>
Solr及Spring-Data-Solr入门学习
查看>>
python_time模块
查看>>
python_configparser(解析ini)
查看>>
selenium学习资料
查看>>
从mysql中 导出/导入表及数据
查看>>