android 开发 解码gif图片,获取每帧bitmap
环境:android 4.3 (注意对于android4.4版本解码出来不正确,除了第一帧正确外,其余的都是显示不同的地方) 通用版本见: android 开发对gif解码(适配android 4.2、4.3、4.4版本)
使用方法:
void showGif2()
{
gifDecoder = new GifImageDecoder();
try {
gifDecoder.read(this.getResources().openRawResource(R.drawable.b17)); //这是Gif图片资源
int size =gifDecoder.getFrameCount();
for(int i=0;i<size;i++)
{ ImageView iv_image = new ImageView(CustomActivity.this);
iv_image.setPadding(5, 5, 5, 5);
LayoutParams lparams = new LayoutParams(100,100);
iv_image.setLayoutParams(lparams);
iv_image.setImageBitmap(gifDecoder.getFrame(i));
ll_decodeimages.addView(iv_image);
// gifFrame.nextFrame();
}
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
见效果:
全部代码:
package com.xlm.testgif; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream; import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.view.Menu;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams; public class CustomActivity extends Activity { LinearLayout ll_decodeimages;
// GifFrame gifFrame = null;
GifImageDecoder gifDecoder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom);
ll_decodeimages = (LinearLayout) findViewById(R.id.ll_decodeimages);
showGif2();
}
// void showGif()
// {
// // 解析gif动画
// gifFrame = GifFrame.createGifImage(fileConnect(this.getResources().openRawResource(R.drawable.b10)));
// int size = gifFrame.size();
// for(int i=0;i<size;i++)
// {
//
// ImageView iv_image = new ImageView(CustomActivity.this);
// iv_image.setPadding(5, 5, 5, 5);
// LayoutParams lparams = new LayoutParams(100,100);
// iv_image.setLayoutParams(lparams);
// iv_image.setImageBitmap(gifFrame.getImage());
// ll_decodeimages.addView(iv_image);
// gifFrame.nextFrame();
// }
// }
void showGif2()
{
gifDecoder = new GifImageDecoder();
try {
gifDecoder.read(this.getResources().openRawResource(R.drawable.b17));
int size =gifDecoder.getFrameCount();
for(int i=0;i<size;i++)
{ ImageView iv_image = new ImageView(CustomActivity.this);
iv_image.setPadding(5, 5, 5, 5);
LayoutParams lparams = new LayoutParams(100,100);
iv_image.setLayoutParams(lparams);
iv_image.setImageBitmap(gifDecoder.getFrame(i));
ll_decodeimages.addView(iv_image);
// gifFrame.nextFrame();
}
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Handler handler = new Handler()
// {
//
// @Override
// public void handleMessage(Message msg) {
// switch(msg.what)
// {
// case 1:
// Bitmap bmp = (Bitmap) msg.obj;
// if(bmp!=null)
// {
// ImageView iv_image = new ImageView(CustomActivity.this);
// iv_image.setImageBitmap(bmp);
// ll_decodeimages.addView(iv_image);
// }
// break;
// }
// }
//
// };
/**
* 独立线程解码gif图片
* @author huqiang
*
*/
// class DecodeGif implements Runnable
// {
// Context mContext ;
// Handler mHandler;
// public DecodeGif(Context context,Handler handler)
// {
// this.mContext = context;
// this.mHandler = handler;
// }
// @Override
// public void run() {
// //开始解析gif
// // 解析gif动画
//// gifFrame = GifFrame.createGifImage(fileConnect(this.getResources().openRawResource(R.drawable.test)));
// }
// public void start()
// {
// new Thread(this).start();
// }
// }
// 读取文件
public byte[] fileConnect(InputStream is)
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = is.read()) != -1)
{
baos.write(ch);
}
byte[] b = baos.toByteArray();
baos.close();
baos = null;
is.close();
is = null;
return b;
} catch (Exception e)
{
return null;
}
} }
package com.xlm.testgif; import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; public class GifImageDecoder {
private static final String TAG = GifImageDecoder.class.getSimpleName();
private final GifImageDecoder self = this; // File read status: No errors.
public static final int STATUS_OK = 0;
// File read status: Error decoding file (may be partially decoded)
public static final int STATUS_FORMAT_ERROR = 1;
// File read status: Unable to open source.
public static final int STATUS_OPEN_ERROR = 2;
// Trailer
private static final byte TRR_CODE = (byte) 0x3B;
// Image Block
private static final byte IMG_CODE = (byte) 0x2C;
// Extension
private static final byte EXT_CODE = (byte) 0x21;
// Graphic Control Extension
private static final byte GC_EXT = (byte) 0xF9;
// Application Extension
private static final byte APP_EXT = (byte) 0xFF;
// Comment Extension
private static final byte CMT_EXT = (byte) 0xFE;
// Plain Text Extension
private static final byte TXT_EXT = (byte) 0x01; private static final int MIN_DELAY = 100;
private static final int MIN_DELAY_ENFORCE_THRESHOLD = 20; protected int mStatus;
protected int mWidth; // full mCurrentImage mWidth
protected int mHeight; // full mCurrentImage mHeight
protected Bitmap mCurrentImage; // current frame
protected Bitmap mLastImage; // previous frame
protected int mDispose = 0; // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
protected int mLastDispose = 0;
protected int mDelay = 0; // mDelay in milliseconds
protected ArrayList<GifFrame> mGifFrames; // mGifFrames read from current file
protected int mFrameCount; private int mOffset = 0; private GifHeader mGifHeader;
private GraphicControlExtension mGcExt;
private ImageBlock mImageBlock; private static class GifFrame {
public GifFrame(Bitmap im, int del) {
image = im;
delay = del;
} public Bitmap image;
public int delay;
} /**
* Gets display duration for specified frame.
*
* @param n int index of frame
* @return delay in milliseconds
*/
public int getDelay(int n) {
mDelay = -1;
if ((n >= 0) && (n < mFrameCount)) {
mDelay = mGifFrames.get(n).delay;
if (mDelay < MIN_DELAY_ENFORCE_THRESHOLD) {
mDelay = MIN_DELAY;
}
}
return mDelay;
}
/**
* Gets the number of GifFrames read from file.
*
* @return frame count
*/
public int getFrameCount() {
return mFrameCount;
} /**
* Gets the first (or only) image read.
*
* @return BufferedBitmap containing first frame, or null if none.
*/
public Bitmap getBitmap() {
return getFrame(0);
} /**
* Gets the image contents of frame n.
*
* @return BufferedBitmap representation of frame, or null if n is invalid.
*/
public Bitmap getFrame(int n) {
if (mFrameCount <= 0)
return null;
n = n % mFrameCount;
return (mGifFrames.get(n)).image;
} /**
* Reads GIF image from stream
*
* @param is containing GIF file.
* @return read status code (0 = no errors)
*/ public int read(InputStream is) throws IOException {
init();
if (is != null) {
byte[] buffer = Utils.streamToBytes(is);
mGifHeader = new GifHeader(buffer, mOffset);
mOffset += mGifHeader.size;
mWidth = mGifHeader.getWidth();
mHeight = mGifHeader.getHeight();
if (!mGifHeader.getSignature().equals("GIF")) {
return STATUS_FORMAT_ERROR;
}
while (buffer[mOffset] != TRR_CODE) {
if (buffer[mOffset] == IMG_CODE) {
// ImageBlock
mImageBlock = new ImageBlock(buffer, mOffset);
mOffset += mImageBlock.size; mFrameCount++;
// create new image to receive frame data
mCurrentImage = extractImage();
if (mLastDispose > 0) {
if (mLastDispose == 3) {
// use image before last
int n = mFrameCount - 2;
if (n > 0) {
mLastImage = getFrame(n - 1);
} else {
mLastImage = null;
}
}
}
mGifFrames.add(new GifFrame(mCurrentImage, mDelay)); // add image to frame
resetFrame();
} else if (buffer[mOffset] == EXT_CODE) {
if (buffer[mOffset + 1] == GC_EXT) {
//GraphicControlExtension
mGcExt = new GraphicControlExtension(buffer, mOffset);
mOffset += mGcExt.size;
mDispose = mGcExt.getDisposalMothod(); // disposal method
if (mDispose == 0) {
mDispose = 1; // elect to keep old image if discretionary
}
mDelay = mGcExt.getDelayTime() * 10; // delay in milliseconds
} else if (buffer[mOffset + 1] == APP_EXT) {
//ApplicationExtension
ApplicationExtension appExt = new ApplicationExtension(buffer, mOffset);
mOffset += appExt.size;
} else if (buffer[mOffset + 1] == CMT_EXT) {
//CommentExtension
CommentExtension cmtExt = new CommentExtension(buffer, mOffset);
mOffset += cmtExt.size;
} else if (buffer[mOffset + 1] == TXT_EXT) {
//PlainTextExtension
PlainTextExtension txtExt = new PlainTextExtension(buffer, mOffset);
mOffset += txtExt.size;
} else {
throw new IOException();
}
} else {
throw new IOException();
}
}
} else {
mStatus = STATUS_OPEN_ERROR;
}
return mStatus;
} /**
* Initializes or re-initializes reader
*/
protected void init() {
mStatus = STATUS_OK;
mFrameCount = 0;
mGifFrames = new ArrayList<GifFrame>();
} /**
* Resets frame state for reading next image.
*/
protected void resetFrame() {
mLastDispose = mDispose;
mLastImage = mCurrentImage;
mDispose = 0;
mDelay = 0;
} /**
* Extract new image
*
* @return image
*/
@SuppressLint("NewApi") private Bitmap extractImage() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
outputStream.write(mGifHeader.bytes);
if (mGcExt != null) {
if ((mWidth != mImageBlock.getImageWidth() || mHeight != mImageBlock.getImageHeight()) &&
mGcExt.getTransparentColorFlag() == 0) {
mGcExt.setTransparentColorFlagTrue();
}
outputStream.write(mGcExt.bytes);
}
outputStream.write(mImageBlock.bytes);
outputStream.write((byte) 0x3B);
outputStream.flush(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap newBitmap = BitmapFactory.decodeStream(new BufferedInputStream(new ByteArrayInputStream(outputStream.toByteArray())));
if (newBitmap != null) {
if (mLastImage == null) {
return newBitmap;
} else {
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(mLastImage, 0, 0, null);
canvas.drawBitmap(newBitmap, 0, 0, null);
return bitmap;
}
} else {
if (mLastImage != null) {
return mLastImage;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
} }
return null;
} private class GifHeader {
public byte[] bytes;
public int size; public GifHeader(byte[] bytes, int offset) {
boolean globalColorTableFlag = (bytes[offset + 0x0A] & 0x80) != 0x00;
int globalColorTableSize = (bytes[offset + 0x0A] & 0x07); // get size
size = 0x0D;
if (globalColorTableFlag) {
size += Math.pow(2, (globalColorTableSize + 1)) * 3;
} this.bytes = new byte[size];
System.arraycopy(bytes, offset, this.bytes, 0, size);
} public String getSignature() {
return new String(bytes, 0, 3);
} public String getVersion() {
return new String(bytes, 3, 3);
} public int getWidth() {
return (bytes[6] & 0xFF) + ((bytes[7] & 0xFF) << 8);
} public int getHeight() {
return (bytes[8] & 0xFF) + ((bytes[9] & 0xFF) << 8);
} public int getGlobalColorTableFlag() {
return (bytes[10] & 0x80) >> 7;
} public int getColorResolution() {
return (bytes[10] & 0x70) >> 4;
} public int getSortFlag() {
return (bytes[10] & 0x08) >> 3;
} public int getSizeOfGlobalColorTable() {
return (bytes[10] & 0x07);
} public int getBackgroundColorIndex() {
return bytes[11] & 0xFF;
} public int getPixelAspectRatio() {
return bytes[12];
} public int[] getGlobalColorTable() {
if (getGlobalColorTableFlag() == 0) {
return new int[0];
}
int[] colors = new int[(int) Math.pow(2, getSizeOfGlobalColorTable() + 1)];
for (int i = 0; i < colors.length; i++) {
colors[i] = ((bytes[13 + (i * 3)] & 0xFF) << 16) + ((bytes[13 + (i * 3) + 1] & 0xFF) << 8) + (bytes[13 + (i * 3) + 2] & 0xFF);
}
return colors;
}
} private class ImageBlock {
public byte[] bytes;
public int size; public ImageBlock(byte[] bytes, int offset) {
int blockSize;
boolean localColorTableFlag = (bytes[offset + 0x09] & 0x80) != 0x00;
int localColorTableSize = (bytes[offset + 0x09] & 0x07); //get size
size = 0x0A;
if (localColorTableFlag) {
size += Math.pow(2, (localColorTableSize + 1)) * 3;
}
size += 1; //LZW Minimum Code Size //ImageData
blockSize = bytes[offset + size] & 0xFF;
size += 1;
while (blockSize != 0x00) {
size += blockSize;
blockSize = bytes[offset + size] & 0xFF;
size += 1;
} this.bytes = new byte[size];
System.arraycopy(bytes, offset, this.bytes, 0, size);
} public int getImageSeparator() {
return bytes[0] & 0xFF;
} public int ImageLeftPosition() {
return (bytes[1] & 0xFF) + ((bytes[2] & 0xFF) << 8);
} public int getImageTopPosition() {
return (bytes[3] & 0xFF) + ((bytes[4] & 0xFF) << 8);
} public int getImageWidth() {
return (bytes[5] & 0xFF) + ((bytes[6] & 0xFF) << 8);
} public int getImageHeight() {
return (bytes[7] & 0xFF) + ((bytes[8] & 0xFF) << 8);
} public int getLocalColorTableFlag() {
return (bytes[9] & 0x80) >> 7;
} public int getInterlaceFlag() {
return (bytes[9] & 0x40) >> 6;
} public int getSortFlag() {
return (bytes[9] & 0x20) >> 5;
} public int getReserved() {
return (bytes[9] & 0x18) >> 2;
} public int getSizeOfLocalColorTable() {
return bytes[9] & 0x03;
} public int[] getLocalColorTable() {
if (getLocalColorTableFlag() == 0) {
return new int[0];
}
int[] colors = new int[(int) Math.pow(2, getSizeOfLocalColorTable() + 1)];
for (int i = 0; i < colors.length; i++) {
colors[i] = ((bytes[10 + (i * 3)] & 0xFF) << 16) + ((bytes[10 + (i * 3) + 1] & 0xFF) << 8) + (bytes[10 + (i * 3) + 2] & 0xFF);
}
return colors;
} public int getLZWMinimumCodeSize() {
if (getLocalColorTableFlag() == 0) {
return bytes[10] & 0xFF;
} else {
return bytes[10 + (int) Math.pow(2, getSizeOfLocalColorTable() + 1) * 3] & 0xFF;
}
}
} private class ApplicationExtension {
public byte[] bytes;
public int size; public ApplicationExtension(byte[] bytes, int offset) {
int blockSize;
// get size
size = 0x0E; blockSize = bytes[offset + size] & 0xFF;
size += 1;
while (blockSize != 0x00) {
size += blockSize;
blockSize = bytes[offset + size] & 0xFF;
size += 1;
} this.bytes = new byte[size];
System.arraycopy(bytes, offset, this.bytes, 0, size);
} public int getExtensionIntroducer() {
return bytes[0] & 0xFF;
} public int getExtensionLabel() {
return bytes[1] & 0xFF;
} public int getBlockSize1() {
return bytes[2] & 0xFF;
} public String getApplicationIdentifier() {
return new String(bytes, 3, 8);
} public String getApplicationAuthenticationCode() {
return new String(bytes, 11, 3);
}
} private class GraphicControlExtension {
public byte[] bytes;
public int size; public GraphicControlExtension(byte[] bytes, int offset) {
size = 8;
this.bytes = new byte[size];
System.arraycopy(bytes, offset, this.bytes, 0, size);
} public int getExtensionIntroducer() {
return bytes[0] & 0xFF;
} public int getGraphicControlLabel() {
return bytes[1] & 0xFF;
} public int getBlockSize() {
return bytes[2] & 0xFF;
} public int getReserved() {
return (bytes[3] & 0xE0) >> 5;
} public int getDisposalMothod() {
return (bytes[3] & 0x1C) >> 2;
} public int getUserInputFlag() {
return (bytes[3] & 0x02) >> 1;
} public int getTransparentColorFlag() {
return (bytes[3] & 0x01);
} public int getDelayTime() {
return (bytes[4] & 0xFF) + ((bytes[5] & 0xFF) << 8);
} public int getTransparentColorIndex() {
return bytes[6];
} public void setTransparentColorFlagTrue() {
int value = getReserved() | getDisposalMothod() | getUserInputFlag() | 0x01;
bytes[3] = (byte) Integer.parseInt(Utils.toHex(value, 2), 16);
}
} private class CommentExtension {
public byte[] bytes;
public int size; public CommentExtension(byte[] bytes, int offset) {
int blockSize;
// get size
size = 0x02; blockSize = bytes[offset + size] & 0xFF;
size += 1;
while (blockSize != 0x00) {
size += blockSize;
blockSize = bytes[offset + size] & 0xFF;
size += 1;
} this.bytes = new byte[size];
System.arraycopy(bytes, offset, this.bytes, 0, size);
}
} private class PlainTextExtension {
public byte[] bytes;
public int size; public PlainTextExtension(byte[] bytes, int offset) {
int blockSize;
// get size
size = 0x0F; blockSize = bytes[offset + size] & 0xFF;
size += 1;
while (blockSize != 0x00) {
size += blockSize;
blockSize = bytes[offset + size] & 0xFF;
size += 1;
} this.bytes = new byte[size];
System.arraycopy(bytes, offset, this.bytes, 0, size);
}
}
}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream; public class Utils {
public static String toHex(int value, int length) {
String hex = Integer.toHexString(value);
hex = hex.toUpperCase(); if (hex.length() < length) {
while (hex.length() < length)
hex = "0" + hex;
} else if (hex.length() > length) {
hex = hex.substring(hex.length() - length);
}
return hex;
} public static byte[] streamToBytes(InputStream stream) throws IOException,
OutOfMemoryError {
byte[] buff = new byte[1024];
int read;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
while ((read = stream.read(buff)) != -1) {
bao.write(buff, 0, read);
}
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
return bao.toByteArray();
}
}
android 开发 解码gif图片,获取每帧bitmap的更多相关文章
- Android开发之通过反射获取到挂断电话的API
Android开发黑名单工具类,需要用到挂断电话的API,但是该API处于隐藏状态,需要通过反射得到该方法.. 步骤: 1.通过当前类获取到ServiceManager的字节码 Class< ? ...
- (腾讯视频)iOS开发之视频根据url获取第一帧图片,获取任一帧图片
#import <AVFoundation/AVFoundation.h> + (UIImage*) thumbnailImageForVideo:(NSURL *)videoURL at ...
- Android开发——内存优化 图片处理
8. 用缓存避免内存泄漏 很常见的一个例子就是图片的三级缓存结构,分别为网络缓存,本地缓存以及内存缓存.在内存缓存逻辑类中,通常会定义这样的集合类. private HashMap<Strin ...
- android开发 socket接收图片并保存
逻辑:接收到socket之后需要将socket发送的图片数据保存下来并通知handler更新界面 关键代码: public void readImage(Socket socket) { try { ...
- Android开发:实时处理摄像头预览帧视频------浅析PreviewCallback,onPreviewFrame,AsyncTask的综合应用(转)
原文地址:http://blog.csdn.net/yanzi1225627/article/details/8605061# 很多时候,android摄像头模块不仅预览,拍照这么简单,而是需要在预览 ...
- Android 开发工具类 31_WebService 获取手机号码归属地
AndroidInteractWithWebService.xml <?xml version="1.0" encoding="utf-8"?> & ...
- android开发 实现同时显示png/jpg 等bitmap图片还可以显示gif图片,有效防止OOM
本来使用第三方jar包 GifView.jar 发现使用的时候不能显示png图片,而且多次setgifimage的时候还会OOM: 现在使用了一个新的第三方,demo是别人的, 下载链接:http: ...
- Android开发之通过反射获取到Android隐藏的方法
在PackageManger中,有些方法被隐藏了,无法直接调用,需要使用反射来获取到该方法. 比如方法:getPackageSizeInfo(),通过这个方法可以获取到apk的CacheSize,Co ...
- Android开发之将图片文件转化为字节数组字符串,并对其进行Base64编码处理
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985, 转载请说明出处. /** * @将图片文件转化为字节数组字符串,并对其进行Base64编码处理 * ...
随机推荐
- 通过Migration在EF6中用多个DbContext
通过Migration在EF6中用多个DbContext EF EF6 C# Migration 通过Migration在EF6中用多个DbContext 前言 实现目标 设置多数据上下文 更新数据脚 ...
- 基于CSS+dIV的网页层,点击后隐藏或显示
一个基于CSS+dIV的网页层,用JavaScript结合Input按钮进行控制,点击后显示或隐藏,网页上常用到的特效之一,实用性较强,相信对大家的前端设计有帮助. <!DOCTYPE html ...
- ref和out的区别?
ref 和out的区别在面试中会常问到: 首先:两者都是按地址传递的,使用后都将改变原来参数的数值. 其次:ref可以把参数的数值传递进函数,但是out是要把参数清空,就是说你无法把一个数值从out传 ...
- 使用虚拟机在ubuntu下搭建mongoDB开发环境和简单增删改查操作
最近在折腾mongodb和nodejs,在imooc上找了一个mongodb的入门教程,跟着里面一步一步的走,下面记录下我操作的步骤和遇到的问题. 课程地址:http://www.imooc.com/ ...
- (二)OJ的主要文件
OJ搭建好了后,我们要熟悉一下OJ项目下的文件及文件夹. 首先,安装好的OJ是在目录var/www/html下. html下的php文件 这些php文件都是些主要跳转页面. admin文件夹 登录管理 ...
- mac brew install redis
在mac 下安装redis 执行brew install redis ==> Downloading http://download.redis.io/releases/redis-2.8.19 ...
- paypal api 相关资料
https://developer.paypal.com/ https://developer.paypal.com/docs/classic/api/merchant/GetBalance_API_ ...
- iOS反射机制
iOS属性反射:说白了,就是将两个对象的所有属性,用动态的方式取出来,并根据属性名,自动绑值.(注意:对象的类,如果是派生类,就得靠其他方式来实现了,因为得到不该基类的属性.) 本人常用的反射方式,有 ...
- 【转载/修改】ScrollLayout代码修正,追加模仿viewpager滚动速度
组件作用为类似ViewPager但直接插视图的横向滚动容器. 修改自:http://blog.csdn.net/yaoyeyzq/article/details/7571940 在该组件基础上修正了滚 ...
- Hive深入浅出
1. Hive是什么 1) Hive是什么? 这里引用 Hive wiki 上的介绍: Hive is a data warehouse infrastructure built on top of ...