通用汽車組建團隊加速無人駕駛和電動汽車開發

通用汽車本1月21號表示,公司將組建一個團隊加速無人駕駛汽車和電動汽車的開發,通用汽車還會擴充該類產品線。

通用汽車全球產品專案副總裁道格•帕克斯(Doug Parks)將成為無人駕駛技術和汽車業務執行副總裁,他將向全球產品研發主管羅伊斯(Mark Reuss)彙報工作。帕克斯將負責新電動和電池系統、汽車軟體的開發。

在聲明中通用汽車還表示,謝裡•希考克(Sheri Hickok)將擔任無人駕駛合資和車隊實施方面的執行首席工程師,在任命之前希考克是下一代皮卡的首席工程師。此外,通用汽車電動汽車首席設計工程師帕姆•弗雷切(Pam Fletcher)將承擔更多的職責,他將負責無人駕駛和電動汽車的戰略規劃。
 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

常隆客車和AESC公司達成了電動客車錳酸鋰離子電池供應協定

2016年1月14日, 江蘇常隆客車有限公司(江蘇省江陰市)和AESC公司(神奈川縣座間市)達成了電動客車錳酸鋰離子電池供應協定,雙方高層出席了隆重的簽字儀式。本次合作協定初期電池供給規模為30MWh,可以滿足100台12米電動客車的錳酸鋰電池。雙方還預計到2017年可以持續供貨900MWh規模的錳酸鋰電池,滿足3000輛純電動客車生產需求。

通過雙方合作,常隆電動客車將更加安全可靠、經久耐用,努力為市場提供最好的電動客車、最優質的服務、最值得信賴的品牌。同時雙方約定今後持續探討本次合作的動力電池在儲能領域應用的可行性。雙方計畫同步開拓中國市場和國際市場。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

一個excel(20M)就能幹趴你的poi,你信嗎?

  自從上一篇:后,很巧的是這次又發現一個問題,所以有了這篇文章,還是想沿用上篇的”流水帳“的方式查找問題和解決問題。這篇文章主要是因為使用POI導入一個20M的excel發生了OOM(OutOfMemoryError)異常。說來也慚愧,工作了這麼多年,還真沒導入過這種大小的文件,並且還發生了內存溢出。如果你百度下基本上清一色的告訴你:POI導入excel文件有兩種方式,第一種是用戶模式,這種模式用起來很簡單直觀,可以類比xml的dom方式解析(這裏只針對excel2007,因為2003本身就有最大條數限制並且目前基本用的很少,這裏直接忽略),第二種是event模式,這種通常是網上說的解決POI導入大excel的”萬金油“方法,可以類比為xml的sax解析方式。呵呵,我這篇文章首先就是要干趴這種方法(JVM使用-Xms512m -Xmx512m)。不信你隨便寫一個導入接口,導入如下20M大小的execl看看:鏈接: https://pan.baidu.com/s/1DUrS8ctLPp7Z6imOc1aIUQ 提取碼: hd79 。

  首先,既然要導入大點的excel2007,那麼我們應該稍微了解一下這種文件如何存儲數據,我們百度上可以發現,2007其實就是一個壓縮包,可以直接修改後綴成zip然後解壓打開文件看看,如下

 

  上圖可以看到最大的兩個文件就兩個:sharedStrings.xml和sheet1.xml。其中sheet2.xml這個可以不關注,直接從excel刪掉都沒事,這裏沒刪除主要是沒多大關係,這個excel文件也是測試直接提供給我的。由於sheet2比較小,與這個文章說到的內存溢出並無關係,請不要胡思亂想,被分散了注意。

  直接用大文本編輯工具打開上圖兩個大文件,可以發現sharedString.xml里內容其實就是excel中每個單元格里的字符串內容(数字類型除外),sheet.xml就是每個sheet里的結構xml,了解到這裏基本上就了解了本文章說到問題的基本知識,然後下面進入正題。

  先使用百度中查到的提供的event方式導入excel,代碼如下:

package com.example.utils;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.File;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 百度上直接copy過來的
 * XSSF and SAX (Event API)
 */
public abstract class BigDataParseExcelUtil extends DefaultHandler {
    private ReadOnlySharedStringsTable sst;
    private String lastContents;
    private boolean nextIsString;
    private int sheetIndex = -1;
    private List<String> rowlist = new ArrayList<String>();
    private int curRow = 0; // 當前行
    private int curCol = 0; // 當前列索引
    private int preCol = 0; // 上一列列索引
    private int titleRow = 0; // 標題行,一般情況下為0
    private int rowsize = 0; // 列數
    private List excelList = new ArrayList();  //excel全部轉換為list

    // excel記錄行操作方法,以sheet索引,行索引和行元素列表為參數,對sheet的一行元素進行操作,元素為String類型

    public abstract void optRows(int sheetIndex, int curRow,
                                 List<String> rowlist, List excelList) throws SQLException, Exception;

    // 只遍歷一個sheet,其中sheetId為要遍歷的sheet索引,從1開始,1-3

    /**
     * @param filename
     * @param sheetId  sheetId為要遍歷的sheet索引,從1開始,1-3
     * @throws Exception
     */
    public void processOneSheet(String filename, int sheetId) throws Exception {
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader r = new XSSFReader(pkg);
        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);
        XMLReader parser = fetchSheetParser(strings);
        // rId2 found by processing the Workbook
        // 根據 rId# 或 rSheet# 查找sheet
        InputStream sheet2 = r.getSheet("rId" + sheetId);
        sheetIndex++;
        InputSource sheetSource = new InputSource(sheet2);
        parser.parse(sheetSource);
        sheet2.close();
    }

    @Override
    public void characters(char[] ch, int start, int length)
        throws SAXException {
        // 得到單元格內容的值
        lastContents += new String(ch, start, length);
    }

    public void process(InputStream inputStream) throws Exception {
        OPCPackage pkg = OPCPackage.open(inputStream);
        XSSFReader r = new XSSFReader(pkg);
        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);
        XMLReader parser = fetchSheetParser(strings);
        Iterator<InputStream> sheets = r.getSheetsData();
        while (sheets.hasNext()) {
            curRow = 0;
            sheetIndex++;
            InputStream sheet = sheets.next();
            InputSource sheetSource = new InputSource(sheet);
            parser.parse(sheetSource);
            sheet.close();
        }
    }

    /**
     * 遍歷 excel 文件
     */
    public void process(File file) throws Exception {
        OPCPackage pkg = OPCPackage.open(file);
        XSSFReader r = new XSSFReader(pkg);
        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);
        XMLReader parser = fetchSheetParser(strings);
        Iterator<InputStream> sheets = r.getSheetsData();
        while (sheets.hasNext()) {
            curRow = 0;
            sheetIndex++;
            InputStream sheet = sheets.next();
            InputSource sheetSource = new InputSource(sheet);
            parser.parse(sheetSource);
            sheet.close();
        }
    }

    public XMLReader fetchSheetParser(ReadOnlySharedStringsTable sst)
        throws SAXException {
        XMLReader parser = XMLReaderFactory.createXMLReader();
        // .createXMLReader("org.apache.xerces.parsers.SAXParser");
        this.sst = sst;
        parser.setContentHandler(this);
        return parser;
    }

    @Override
    public void startElement(String uri, String localName, String name,
                             Attributes attributes) throws SAXException {
        // c => 單元格
        if (name.equals("c")) {
            // 如果下一個元素是 SST 的索引,則將nextIsString標記為true
            String cellType = attributes.getValue("t");
            String rowStr = attributes.getValue("r");
            curCol = this.getRowIndex(rowStr);
            if (cellType != null && cellType.equals("s")) {
                nextIsString = true;
            } else {
                nextIsString = false;
            }
        }
        // 置空
        lastContents = "";
    }

    @Override
    public void endElement(String uri, String localName, String name)
        throws SAXException {
        // 根據SST的索引值的到單元格的真正要存儲的字符串
        // 這時characters()方法可能會被調用多次
        if (nextIsString) {
            try {
                int idx = Integer.parseInt(lastContents);
                lastContents = new XSSFRichTextString(sst.getEntryAt(idx))
                    .toString();
            } catch (Exception e) {
            }
        }
        // v => 單元格的值,如果單元格是字符串則v標籤的值為該字符串在SST中的索引
        // 將單元格內容加入rowlist中,在這之前先去掉字符串前後的空白符
        if (name.equals("v")) {
            String value = lastContents.trim();
            value = value.equals("") ? " " : value;
            int cols = curCol - preCol;
            if (cols > 1) {
                for (int i = 0; i < cols - 1; i++) {
                    rowlist.add(preCol, "");
                }
            }
            preCol = curCol;
            rowlist.add(curCol - 1, value);
        } else {
            // 如果標籤名稱為 row ,這說明已到行尾,調用 optRows() 方法
            if (name.equals("row")) {
                int tmpCols = rowlist.size();
                if (curRow > this.titleRow && tmpCols < this.rowsize) {
                    for (int i = 0; i < this.rowsize - tmpCols; i++) {
                        rowlist.add(rowlist.size(), "");
                    }
                }
                try {
                    optRows(sheetIndex, curRow, rowlist, excelList);
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                if (curRow == this.titleRow) {
                    this.rowsize = rowlist.size();
                }
                rowlist.clear();
                curRow++;
                curCol = 0;
                preCol = 0;
            }
        }
    }

    // 得到列索引,每一列c元素的r屬性構成為字母加数字的形式,字母組合為列索引,数字組合為行索引,
    // 如AB45,表示為第(A-A+1)*26+(B-A+1)*26列,45行
    public int getRowIndex(String rowStr) {
        rowStr = rowStr.replaceAll("[^A-Z]", "");
        byte[] rowAbc = rowStr.getBytes();
        int len = rowAbc.length;
        float num = 0;
        for (int i = 0; i < len; i++) {
            num += (rowAbc[i] - 'A' + 1) * Math.pow(26, len - i - 1);
        }
        return (int) num;
    }


}
package com.example.service;

import com.example.utils.BigDataParseExcelUtil;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;

/**
 * @author: rongdi
 * @date:
 */
@Service
public class ExcelService {

    public void import1(InputStream inputStream) throws Exception {

        BigDataParseExcelUtil xlx = new BigDataParseExcelUtil() {
            @Override
            public void optRows(int sheetIndex, int curRow, List<String> rowlist, List excelList)
                throws SQLException {
                System.out.println(rowlist);
            }
        };
        xlx.process(inputStream);
    }


}
package com.example.controller;

import com.example.service.ExcelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author: rongdi
 * @date:
 */
@Controller
public class ExcelController {

    @Autowired
    private ExcelService excelService;

    @RequestMapping("/excel/import1")
    @ResponseBody
    public String import1(@RequestParam("file") MultipartFile multipartFile) throws Exception {
        excelService.import1(multipartFile.getInputStream());
        return "ok";
    }

}

  使用postman等工具,導入上面說的20M的文件,報錯如下:

   那我們優化一下不使用inputStream,直接使用一個File傳入看看

    public void import2(File file) throws Exception {
        BigDataParseExcelUtil xlx = new BigDataParseExcelUtil() {
            @Override
            public void optRows(int sheetIndex, int curRow, List<String> rowlist, List excelList)
                throws SQLException {
                System.out.println(rowlist);
            }
        };
        xlx.process(file);
    }

    @RequestMapping("/excel/import2")
    @ResponseBody
    public String import2(@RequestParam("file") MultipartFile multipartFile) throws Exception {
        // 延遲解析比率
        ZipSecureFile.setMinInflateRatio(-1.0d);
        File tmp = Files.createTempFile("tmp-", ".xlsx").toFile();
        Files.copy(multipartFile.getInputStream(), Paths.get(tmp.getPath()), StandardCopyOption.REPLACE_EXISTING);
        excelService.import2(tmp);
        return "ok";
    }

  使用postman上傳文件運行效果如下:

 

   這時候就發現很尷尬了,難道是POI自己代碼里就有bug,我們可以使用斷點調試確認一下這個strings里到底是不是全部放了sharedStrings.xml里的內容。

   由上證明就是這個strings裝多了導致內存溢出了,從這裏可以看出網上說使用event事件解析excel的方案基本都是行不通的,哎,我也不懂為啥百度上都是這種答案,難道他們壓根都沒遇到過大數據導入嗎?當然也有可能我冤枉了他們,因為sharedStrings.xml中存放的是每個單元格的字符串內容,這個存放是排重過的,如果雖然excel里單元格很多,但是大多都是整型或者大多都是重複的,那確實可以跳過這一步一路之後會一路暢通了,因為畢竟sax解析xml確實可以節省很多內存。

  從上分析可以看到POI就兩種方式導入:一種是用戶方式寫代碼簡單,基本按順序數格子就好,但是類比dom方式解析xml,很耗內存。第二種事件方式,類比sax方式解析xml確實很省內存,但是POI提供的類里把解析出的大量字符串放入了集合中,還是會導致內存溢出。那麼我們怎麼解決這個問題,這裏很常規的想法是到底這個strings是用來幹啥的,怎麼用的,如果可以保持strings相同邏輯功能的前提下,修改了ReadOnlySharedStringsTable這個類的邏輯,就可以解決這裏的內存溢出了。那麼我們可以直接搜索ReadOnlySharedStringsTable類里所有用到strings的方法上打上斷點,特別是從strings里取值的方法上,然後調大jvm內存避免內存溢出的情況下斷點調試如下

   我們是不是可以直接往strings里添加字符串和獲取字符串的方法那裡替換掉,不要使用strings這個集合存儲所有字符串。但是既然excel里設計成使用一個sharedStrings.xml存放公共的字符串,而不是像csv格式那樣,每次讀一行取一行數據就好了。那麼這個sharedStrings.xml中的數據總要解析出來,總要有個地方存儲裏面的數據,不然怎麼結合sheet.xml的格式獲取到每一行的數據呢?所以這裏就很尷尬了,不能每次解析sharedStrings.xml時不保存每次需要獲取strings的時候,再去解析一下這個xm吧,如果從本文章的xml上來看,要重複解析25W次,效率極其低。現在問題可以簡化成我們需要把sharedStrings.xml解析出的所有字符串放在一個地方,還能方便解析,由於怕內存溢出,肯定不能放在內存中了。那麼這裏就有一些選擇,比如解析出的字符串按加入strings集合的順序放入數據庫,文件,外部存儲或者緩存(限制內存大小,多餘寫入文件)存儲中。然後使用的時候按照索引位置idx去一一取出。本文章先使用臨時文件來放這些數據,因為不想搞那麼複雜,導入任務不管再多複雜的系統中,最終執行的都會是一個單節點,在單節點中先使用本機資源這種就近資源是最方便的。如下直接先複製源碼,然後修改上述說的兩個地方。

package com.example.utils;

import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.util.Removal;
import org.apache.poi.xssf.model.SharedStrings;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.PushbackInputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.apache.poi.xssf.usermodel.XSSFRelation.NS_SPREADSHEETML;

public class ReadOnlySharedStringsTable extends DefaultHandler implements SharedStrings {

    protected final boolean includePhoneticRuns;

    /**
     * An integer representing the total count of strings in the workbook. This count does not
     * include any numbers, it counts only the total of text strings in the workbook.
     */
    protected int count;

    /**
     * An integer representing the total count of unique strings in the Shared String Table.
     * A string is unique even if it is a copy of another string, but has different formatting applied
     * at the character level.
     */
    protected int uniqueCount;

    /**
     * The shared strings table.
     */
    private List<String> strings;

    private File tmp = null;

    FileOutputStream fos = null;

    private int counts;

    private Map<Integer,String> map = new LinkedHashMap<Integer,String>();

    public ReadOnlySharedStringsTable(OPCPackage pkg)
            throws IOException, SAXException {
        this(pkg, true);
    }

    public ReadOnlySharedStringsTable(OPCPackage pkg, boolean includePhoneticRuns)
            throws IOException, SAXException {
        this.includePhoneticRuns = includePhoneticRuns;
        ArrayList<PackagePart> parts =
                pkg.getPartsByContentType(XSSFRelation.SHARED_STRINGS.getContentType());

        // Some workbooks have no shared strings table.
        if (parts.size() > 0) {
            PackagePart sstPart = parts.get(0);
            readFrom(sstPart.getInputStream());
        }
    }

    /**
     * Like POIXMLDocumentPart constructor
     *
     * Calls {@link #ReadOnlySharedStringsTable(PackagePart, boolean)}, with a
     * value of <code>true</code> to include phonetic runs.
     *
     * @since POI 3.14-Beta1
     */
    public ReadOnlySharedStringsTable(PackagePart part) throws IOException, SAXException {
        this(part, true);
    }

    /**
     * Like POIXMLDocumentPart constructor
     *
     * @since POI 3.14-Beta3
     */
    public ReadOnlySharedStringsTable(PackagePart part, boolean includePhoneticRuns)
        throws IOException, SAXException {
        this.includePhoneticRuns = includePhoneticRuns;
        readFrom(part.getInputStream());
    }
    
    /**
     * Read this shared strings table from an XML file.
     *
     * @param is The input stream containing the XML document.
     * @throws IOException if an error occurs while reading.
     * @throws SAXException if parsing the XML data fails.
     */
    public void readFrom(InputStream is) throws IOException, SAXException {
        // test if the file is empty, otherwise parse it
        PushbackInputStream pis = new PushbackInputStream(is, 1);
        int emptyTest = pis.read();
        if (emptyTest > -1) {
            pis.unread(emptyTest);
            InputSource sheetSource = new InputSource(pis);
            try {
                XMLReader sheetParser = SAXHelper.newXMLReader();
                sheetParser.setContentHandler(this);
                sheetParser.parse(sheetSource);
            } catch(ParserConfigurationException e) {
                throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
            }
        }
    }

    /**
     * Return an integer representing the total count of strings in the workbook. This count does not
     * include any numbers, it counts only the total of text strings in the workbook.
     *
     * @return the total count of strings in the workbook
     */
    @Override
    public int getCount() {
        return this.count;
    }

    /**
     * Returns an integer representing the total count of unique strings in the Shared String Table.
     * A string is unique even if it is a copy of another string, but has different formatting applied
     * at the character level.
     *
     * @return the total count of unique strings in the workbook
     */
    @Override
    public int getUniqueCount() {
        return this.uniqueCount;
    }

    /**
     * Return the string at a given index.
     * Formatting is ignored.
     *
     * @param idx index of item to return.
     * @return the item at the specified position in this Shared String table.
     * @deprecated use <code>getItemAt</code> instead
     */
    @Removal(version = "4.2")
    @Deprecated
    public String getEntryAt(int idx) {
        /**
         * 這裏就是修改部分了,直接從按行存儲的臨時文件讀取需要的字符串
         */
        String value = map.get(idx + 1);
        if(value == null) {

            return readString(idx,1000,this.uniqueCount);
        } else {
            return value;
        }

    }

    /**
     * 從指定位置讀取size個字符串,這裡是使用局部性原理,每次讀取size個字符串,
     * 以免每次需要讀取文件,性能極低
     * @return
     */
    private String readString(int idx,int size,int numbers) {
        map.clear();
        int currNumber = idx + 1;
        if (currNumber < 0 || currNumber > numbers) {
            return null;
        }
        try {
            FileReader in = new FileReader(tmp);
            LineNumberReader reader = new LineNumberReader(in);
            try {
                String line = "";
                for(int i = 1;i <= numbers;i ++) {
                    line = reader.readLine();
                    if(i >= currNumber && i < currNumber + size) {
                        map.put(i, line);
                    }
                }
            } finally {
                reader.close();
                in.close();
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return map.get(idx + 1);
    }


    /**
     * Returns all the strings.
     * Formatting is ignored.
     *
     * @return a list with all the strings
     * @deprecated use <code>getItemAt</code> instead
     */
    @Removal(version = "4.2")
    @Deprecated
    public List<String> getItems() {
        return strings;
    }

    @Override
    public RichTextString getItemAt(int idx) {
        return new XSSFRichTextString(getEntryAt(idx));
    }

    //// ContentHandler methods ////

    private StringBuilder characters;
    private boolean tIsOpen;
    private boolean inRPh;

    @Override
    public void startElement(String uri, String localName, String name,
                             Attributes attributes) throws SAXException {
        if (uri != null && ! uri.equals(NS_SPREADSHEETML)) {
            return;
        }

        if ("sst".equals(localName)) {
            String count = attributes.getValue("count");
            if(count != null) this.count = Integer.parseInt(count);
            String uniqueCount = attributes.getValue("uniqueCount");
            if(uniqueCount != null) this.uniqueCount = Integer.parseInt(uniqueCount);
            try {
                tmp = Files.createTempFile("tmp-", ".xlsx").toFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //    this.strings = new ArrayList<>(this.uniqueCount);
            characters = new StringBuilder(64);
            try {
                fos = new FileOutputStream(tmp,true);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        } else if ("si".equals(localName)) {
            characters.setLength(0);
        } else if ("t".equals(localName)) {
            tIsOpen = true;
        } else if ("rPh".equals(localName)) {
            inRPh = true;
            //append space...this assumes that rPh always comes after regular <t>
            if (includePhoneticRuns && characters.length() > 0) {
                characters.append(" ");
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String name) throws SAXException {
        if (uri != null && ! uri.equals(NS_SPREADSHEETML)) {
            return;
        }

        if ("si".equals(localName)) {
         //   strings.add(characters.toString().intern());
            try {
                /**
                 * 這裏就是修改的一部分,這裏直接把字符串按行存入臨時文件
                 */
                counts ++;
                fos.write((characters.toString() + "\n").getBytes());
                if(counts == this.uniqueCount) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if ("t".equals(localName)) {
            tIsOpen = false;
        } else if ("rPh".equals(localName)) {
            inRPh = false;
        }
    }

    /**
     * Captures characters only if a t(ext) element is open.
     */
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (tIsOpen) {
            if (inRPh && includePhoneticRuns) {
                characters.append(ch, start, length);
            } else if (! inRPh){
                characters.append(ch, start, length);
            }
        }
    }

}

  然後在自己代碼里把包換成自己的包,替換POI里該類的包,運行JVM堆情況如下毫無壓力

  自此內存溢出問題大功告成!針對使用POI導入大Excel遇到的問題總結如下:

  1)網上給出的方案不管是用戶模式還是事件模式,往往都不能支持大excel的導入

  2)excel本質上是一堆excel的壓縮包(這裏只考慮2007忽略2003)改了個後綴名成xlsx

  3)使用事件導入時應先將上傳文件存入文件,再使用文件OPCPackage.open(file),如果直接傳入輸入流,由於裏面邏輯會將輸入流的所有內容先存入ByteArrayOutputStream 中,這個輸出流實際上是一個內存中的字節流,所以也會導致內存溢出。

  4)用戶模式不用考慮,事件模式會先將sharedString.xml這個大xml解析出來放入一個List中,不管通過什麼方式都繞不開需要解析這個類,因為每個單元格的字符串都放在這個xml中,而要解析這個xml最常規的方法就是保存在內存使用list和map之內的容器存放我相信不會有人會想剛解析出一個xml還要存迴文件中把,這裏基本就繞不開ReadOnlySharedStringsTable或者SharedStringsTable,就算你僥倖繞開了,想自己解析,或許還是重複這兩個類的悲劇,這就是另外一種內存溢出的根源。

  回顧一下上述實現直接把sharedStrings.xml中的內容粗略的保存到文件中,然後再從文件中獲取是屬於很低劣的實現,只能說能滿足不內存溢出,性能方面堪憂!下面直接借鑒easyexcel源碼中用到的ReadCache來實現保存sharedStrings.xml中的內容

package com.example.advanceevent;

import com.example.utils.FileUtils;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.Ehcache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.HashMap;
import java.util.UUID;

/**
 * @author: rongdi
 * @date:
 */
public class ReadCache {

    private static final Logger LOGGER = LoggerFactory.getLogger(Ehcache.class);
    private int index = 0;
    private HashMap<Integer, String> dataMap = new HashMap(1334);
    private static CacheManager fileCacheManager;
    private static CacheConfiguration<Integer, HashMap> fileCacheConfiguration;
    private static CacheManager activeCacheManager;
    private CacheConfiguration<Integer, HashMap> activeCacheConfiguration;
    private Cache<Integer, HashMap> fileCache;
    private Cache<Integer, HashMap> activeCache;
    private String cacheAlias;
    private int cacheMiss = 0;

    public ReadCache(int maxCacheActivateSize) {
        this.activeCacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class, HashMap.class, ResourcePoolsBuilder.newResourcePoolsBuilder().heap((long)maxCacheActivateSize, MemoryUnit.MB)).withSizeOfMaxObjectGraph(1000000L).withSizeOfMaxObjectSize((long)maxCacheActivateSize, MemoryUnit.MB).build();
        init();
    }

    private void init() {
        this.cacheAlias = UUID.randomUUID().toString();
        this.fileCache = fileCacheManager.createCache(this.cacheAlias, fileCacheConfiguration);
        this.activeCache = activeCacheManager.createCache(this.cacheAlias, this.activeCacheConfiguration);
    }

    public void put(String value) {
        this.dataMap.put(this.index, value);
        if ((this.index + 1) % 1000 == 0) {
            this.fileCache.put(this.index / 1000, this.dataMap);
            this.dataMap = new HashMap(1334);
        }

        ++this.index;
        if (LOGGER.isDebugEnabled() && this.index % 1000000 == 0) {
            LOGGER.debug("Already put :{}", this.index);
        }

    }

    public String get(Integer key) {
        if (key != null && key >= 0) {
            int route = key / 1000;
            HashMap<Integer, String> dataMap = (HashMap)this.activeCache.get(route);
            if (dataMap == null) {
                dataMap = (HashMap)this.fileCache.get(route);
                this.activeCache.put(route, dataMap);
                if (LOGGER.isDebugEnabled() && this.cacheMiss++ % 1000 == 0) {
                    LOGGER.debug("Cache misses count:{}", this.cacheMiss);
                }
            }

            return (String)dataMap.get(key);
        } else {
            return null;
        }
    }

    public void putFinished() {
        if (this.dataMap != null) {
            this.fileCache.put(this.index / 1000, this.dataMap);
        }
    }

    public void destroy() {
        fileCacheManager.removeCache(this.cacheAlias);
        activeCacheManager.removeCache(this.cacheAlias);
    }

    static {
        File cacheFile = FileUtils.createCacheTmpFile();
        fileCacheManager = CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(cacheFile)).build(true);
        activeCacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
        fileCacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class, HashMap.class, ResourcePoolsBuilder.newResourcePoolsBuilder().disk(10L, MemoryUnit.GB)).withSizeOfMaxObjectGraph(1000000L).withSizeOfMaxObjectSize(10L, MemoryUnit.GB).build();
    }

}
package com.example.advanceevent;

import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.util.Removal;
import org.apache.poi.xssf.model.SharedStrings;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.ArrayList;
import java.util.List;

import static org.apache.poi.xssf.usermodel.XSSFRelation.NS_SPREADSHEETML;

public class ReadOnlySharedStringsTable extends DefaultHandler implements SharedStrings {

    protected final boolean includePhoneticRuns;

    /**
     * An integer representing the total count of strings in the workbook. This count does not
     * include any numbers, it counts only the total of text strings in the workbook.
     */
    protected int count;

    /**
     * An integer representing the total count of unique strings in the Shared String Table.
     * A string is unique even if it is a copy of another string, but has different formatting applied
     * at the character level.
     */
    protected int uniqueCount;

    /**
     * 緩存
     */
    ReadCache readCache = new ReadCache(100);

    private int counts;


    public ReadOnlySharedStringsTable(OPCPackage pkg)
            throws IOException, SAXException {
        this(pkg, true);
    }

    public ReadOnlySharedStringsTable(OPCPackage pkg, boolean includePhoneticRuns)
            throws IOException, SAXException {
        this.includePhoneticRuns = includePhoneticRuns;
        ArrayList<PackagePart> parts =
                pkg.getPartsByContentType(XSSFRelation.SHARED_STRINGS.getContentType());

        // Some workbooks have no shared strings table.
        if (parts.size() > 0) {
            PackagePart sstPart = parts.get(0);
            readFrom(sstPart.getInputStream());
        }
    }

    /**
     * Like POIXMLDocumentPart constructor
     *
     * Calls {@link #ReadOnlySharedStringsTable(PackagePart, boolean)}, with a
     * value of <code>true</code> to include phonetic runs.
     *
     * @since POI 3.14-Beta1
     */
    public ReadOnlySharedStringsTable(PackagePart part) throws IOException, SAXException {
        this(part, true);
    }

    /**
     * Like POIXMLDocumentPart constructor
     *
     * @since POI 3.14-Beta3
     */
    public ReadOnlySharedStringsTable(PackagePart part, boolean includePhoneticRuns)
        throws IOException, SAXException {
        this.includePhoneticRuns = includePhoneticRuns;
        readFrom(part.getInputStream());
    }
    
    /**
     * Read this shared strings table from an XML file.
     *
     * @param is The input stream containing the XML document.
     * @throws IOException if an error occurs while reading.
     * @throws SAXException if parsing the XML data fails.
     */
    public void readFrom(InputStream is) throws IOException, SAXException {
        // test if the file is empty, otherwise parse it
        PushbackInputStream pis = new PushbackInputStream(is, 1);
        int emptyTest = pis.read();
        if (emptyTest > -1) {
            pis.unread(emptyTest);
            InputSource sheetSource = new InputSource(pis);
            try {
                XMLReader sheetParser = SAXHelper.newXMLReader();
                sheetParser.setContentHandler(this);
                sheetParser.parse(sheetSource);
            } catch(ParserConfigurationException e) {
                throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
            }
        }
    }

    /**
     * Return an integer representing the total count of strings in the workbook. This count does not
     * include any numbers, it counts only the total of text strings in the workbook.
     *
     * @return the total count of strings in the workbook
     */
    @Override
    public int getCount() {
        return this.count;
    }

    /**
     * Returns an integer representing the total count of unique strings in the Shared String Table.
     * A string is unique even if it is a copy of another string, but has different formatting applied
     * at the character level.
     *
     * @return the total count of unique strings in the workbook
     */
    @Override
    public int getUniqueCount() {
        return this.uniqueCount;
    }

    /**
     * Return the string at a given index.
     * Formatting is ignored.
     *
     * @param idx index of item to return.
     * @return the item at the specified position in this Shared String table.
     * @deprecated use <code>getItemAt</code> instead
     */
    @Removal(version = "4.2")
    @Deprecated
    public String getEntryAt(int idx) {
        /**
         * 這裏就是修改部分了,直接從按行存儲的臨時文件讀取需要的字符串
         */
        return readCache.get(idx);

    }

    /**
     * Returns all the strings.
     * Formatting is ignored.
     *
     * @return a list with all the strings
     * @deprecated use <code>getItemAt</code> instead
     */
    @Removal(version = "4.2")
    @Deprecated
    public List<String> getItems() {
        return null;
    }

    @Override
    public RichTextString getItemAt(int idx) {
        return new XSSFRichTextString(getEntryAt(idx));
    }

    //// ContentHandler methods ////

    private StringBuilder characters;
    private boolean tIsOpen;
    private boolean inRPh;

    @Override
    public void startElement(String uri, String localName, String name,
                             Attributes attributes) throws SAXException {
        if (uri != null && ! uri.equals(NS_SPREADSHEETML)) {
            return;
        }

        if ("sst".equals(localName)) {
            String count = attributes.getValue("count");
            if(count != null) this.count = Integer.parseInt(count);
            String uniqueCount = attributes.getValue("uniqueCount");
            if(uniqueCount != null) this.uniqueCount = Integer.parseInt(uniqueCount);
            //    this.strings = new ArrayList<>(this.uniqueCount);
            characters = new StringBuilder(64);
        } else if ("si".equals(localName)) {
            characters.setLength(0);
        } else if ("t".equals(localName)) {
            tIsOpen = true;
        } else if ("rPh".equals(localName)) {
            inRPh = true;
            //append space...this assumes that rPh always comes after regular <t>
            if (includePhoneticRuns && characters.length() > 0) {
                characters.append(" ");
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String name) throws SAXException {
        if (uri != null && ! uri.equals(NS_SPREADSHEETML)) {
            return;
        }

        if ("si".equals(localName)) {
         //   strings.add(characters.toString().intern());
            readCache.put(characters.toString());
            /**
             * 這裏就是修改的一部分,這裏直接把字符串按行存入臨時文件
             */
            counts ++;
            if(counts == this.uniqueCount) {
                readCache.putFinished();
            }
        } else if ("t".equals(localName)) {
            tIsOpen = false;
        } else if ("rPh".equals(localName)) {
            inRPh = false;
        }
    }

    /**
     * Captures characters only if a t(ext) element is open.
     */
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (tIsOpen) {
            if (inRPh && includePhoneticRuns) {
                characters.append(ch, start, length);
            } else if (! inRPh){
                characters.append(ch, start, length);
            }
        }
    }

}

  至此代碼效率有了相當大的提高,而且內存溢出問題也得到解決。詳細測試代碼:https://github.com/rongdi/poi-example.git

  

  

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

MySQL InnoDB 事務

MySQL 原理篇

MySQL5.5 及以後版本的默認存儲引擎使用的是 InnoDB,接下來針對事務的講解都是基於 InnoDB 存儲引擎的。

事務的定義

事務:數據庫操作的最小工作單元,是作為單個邏輯工作單元執行的一系列操作; 事務是一組不可再分割的操作集合(工作邏輯單元)。

典型事務使用場景:轉賬

update user_account set balance = balance - 1000 where userID = 3;
update user_account set balance = balance + 1000 where userID = 1;

MySQL 開啟事務

/*
  BEGIN / START TRANSACTION        --手工
  COMMIT / ROLLBACK                --事務提交或回滾
  SET SESSION autocommit = ON/OFF  --設定會話級別事務是否自動開啟
*/

MySQL 默認是開啟事務的,通過 SHOW VARIABLES like 'autocommit'; 可以查看 MySQL 的事務開啟情況。

  • 在 autocommit = ON(自動提交事務)的情況下,可以執行 BEGIN; 或者 START TRANSACTION; 命令,改為手動提交事務,執行完 SQL 語句后,需要通過 COMMIT 命令提交事務,或者通過 ROLLBACK 命令回滾事務。
  • 在 autocommit = OFF(手動提交事務)的情況下,執行完 SQL 語句后,需要通過 COMMIT 命令提交事務,或者通過 ROLLBACK 命令回滾事務。

JDBC 編程

connection.setAutoCommit(boolean);

測試命令

SHOW VARIABLES like 'autocommit';

/* autocommit為ON */
update teacher set name ='seven' where id =1;
insert teacher (name,age) value ('james',22);
delete from teacher where name = 'james';

/* autocommit為ON如何開啟事務 */
BEGIN;
START TRANSACTION;

update teacher set name ='seven' where id =1;
insert teacher (name,age) value ('james',22);
delete from teacher where name = 'james';

COMMIT;
ROLLBACK;

/* 將autocommit改成OFF */
set session autocommit = OFF;
update teacher set name ='seven' where id =1;
insert teacher (name,age) value ('james',22);
delete from teacher where name = 'james';

COMMIT;
ROLLBACK;

事務 ACID 特性

原子性(Atomicity):最小的工作單元,整個工作單元要麼一起提交成功,要麼全部失敗回滾。

一致性(Consistency):事務中操作的數據及狀態改變是一致的,即寫入資料的結果必須完全符合預設的規則,不會因為出現系統意外等原因導致狀態的不一致。

隔離型(Isolation):一個事務所操作的數據在提交之前,對其他事務的可見性設定(一般設定為不可見)。

持久性(Durability):事務所做的修改就會永久保存,不會因為系統意外導致數據的丟失。

參考()這篇博文,講了一下原子性和一致性的區別。

事務併發帶來了哪些問題

臟讀

比如 user 表中有一條用戶數據,執行了如下操作:

  1. 事務B更新 id=1 的數據,age 更新為18,不提交事務
  2. 事務A查詢 id=1 的數據
  3. 事務B回滾剛才的更新操作

這個時候,事務A中查詢出的 id=1 的數據,age 的值是16還是18?

不可重複讀

 

比如 user 表中有一條用戶數據,執行了如下操作:

  1. 事務A查詢 id=1 的數據
  2. 事務B更新 id=1 的數據,age 更新為18,並提交事務
  3. 事務A再次查詢 id=1 的數據

這個時候,事務A兩次查詢出的 id=1 的數據,age 的值是16還是18?

幻讀

比如在 user 表中執行了如下操作:

  1. 事務A查詢 age>15 的數據
  2. 事務B新增一條數據,age=22,並提交事務
  3. 事務A再次查詢 age>15 的數據

這個時候,事務A兩次查詢出的數據,數量是1條還是2條?

事務四種隔離級別

SQL92 ANSI/ISO標準:http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

Read Uncommitted(未提交讀未解決併發問題

事務未提交對其他事務也是可見的,臟讀(dirty read)

Read Committed(提交讀)解決臟讀問題

一個事務開始之後,只能看到自己提交的事務所做的修改,不可重複讀(non repeatable read)

Repeatable Read(可重複讀解決不可重複讀問題

在同一個事務中多次讀取同樣的數據結果是一樣的,這種隔離級別未定義解決幻讀的問題

Serializable(串行化)解決所有問題

最高的隔離級別,通過強制事務的串行執行

InnoDB 引擎對隔離級別的支持程度

事務隔離級別

臟讀

不可重複讀

幻讀

未提交讀(Read Uncommitted)

可能

可能

可能

已提交讀(Read Committed)

不可能

可能

可能

可重複讀(Repeatable Read)

不可能

不可能

對 InnoDB 不可能

串行化(Serializable)

不可能

不可能

不可能

事務隔離級別的併發能力:未提交讀 > 已提交讀 > 可重複讀 > 串行化

InnDB 引擎默認的事務隔離級別是可重複讀(Repeatable Read),在該級別中它把幻讀的問題也解決了。InnDB 中事務隔離級別通過鎖、MVCC 實現。

通過如下語句查看 InnoDB 的默認事務隔離級別:

/* 全局、當前會話的隔離級別 */
SELECT @@global.tx_isolation, @@tx_isolation;

通過如下語句設置 InnoDB 的事務隔離級別:

/* 設置全局隔離級別 */
set global transaction isolation level read committed;
/* 設置當前會話的隔離級別 */
set session transaction isolation level read committed;

接下來我們來測試一下 InnoDB 的默認事務隔離級別(Repeatable Read)是否解決了臟讀、不可重複讀、幻讀的問題。

數據準備:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

insert into `user` (`id`, `name`, `age`) values('1','Ada','16');

是否解決了臟讀的問題?

開啟兩個 MySQL 客戶端,模擬事務A和事務B的操作,執行步驟如下:

  1. 事務B開啟手動事務,更新 id=1 的數據,age 更新為18,不提交事務
  2. 事務A查詢 id=1 的數據
  3. 事務B回滾剛才的更新操作

圖中的数字是執行步驟,通過下圖可以看出事務A的執行結果是16,InnDB 的默認事務隔離級別完美的解決了臟讀的問題。

是否解決了不可重複讀的問題?

開啟兩個 MySQL 客戶端,模擬事務A和事務B的操作,執行步驟如下:

  1. 事務A開啟手動事務,查詢 id=1 的數據
  2. 事務B更新 id=1 的數據,age 更新為18,並提交事務
  3. 事務A再次查詢 id=1 的數據

圖中的数字是執行步驟,通過下圖可以看出事務A兩次的執行結果都是16,沒有受事務B更新操作的影響,InnDB 的默認事務隔離級別完美的解決了不可重複讀的問題。

是否解決了幻讀的問題?

開啟兩個 MySQL 客戶端,模擬事務A和事務B的操作,執行步驟如下:

  1. 事務A開啟手動事務,查詢 age>15 的數據
  2. 事務B新增一條數據,age=22,並提交事務
  3. 事務A再次查詢 age>15 的數據

圖中的数字是執行步驟,通過下圖可以看出事務A兩次的執行結果都是一條數據,沒有受事務B新增操作的影響,InnDB 的默認事務隔離級別完美的解決了幻讀的問題。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

東風雷諾武漢工廠正式開張 將投產多款SUV和電動車

2月1日,雷諾汽車宣佈在華合資企業東風雷諾武漢工廠正式開張,該廠耗資8.7億歐元(約合9.42億美元)。

新工廠首款產品將是雷諾科雷嘉緊湊SUV,2016年內還將增添一款規格更大的SUV,預計是和上一代日產奇駿同平臺的車型,2017年則將投產風朗電動車。

東風雷諾武漢工廠年產能為15萬輛,雷諾高管預計未來可能增加一到兩倍。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

世界地質公園所在 日本再生能源四倍進展 創雙贏局面

文:宋瑞文(加州能源特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

口香糖變身藝術品 英藝術家巧手改造垃圾

摘錄自2020年2月24日公視新聞網報導

英國藝術家和美國設計師用他們的巧思,讓街邊亂吐的口香糖或是廢棄布料大變身。

紡織和時裝業是地球上第二大的淡水污染源,美國環保署統計,全美紡織品垃圾2017年統計有1700萬噸,其中1100萬噸送到垃圾場掩埋,只有約260萬噸被回收。

紐約設計師丹尼爾使用廢棄布料創造流行服飾,希望業界能做出反思。2015年開了自己的服飾店,店名叫零廢棄,他的設計受到年輕人喜愛,也在博物館展出,他甚至希望同業複製模仿他的設計,鼓吹透過處理舊服飾和廢棄碎布,創造出環保永續的時尚理念。

而現年57歲的英國人威爾森,原本是一名木雕師,15年前在街上看到黏在地上的口香糖,決定進行大改造。威爾森解釋:「我使用壓克力瓷漆來作畫,然後將口香糖加熱,等它乾後我再噴上噴漆。」一個作品約花費2小時完成,他的作品目前遍布歐洲各地,估計有數千個,他說這就像是每個城市的隱藏藝術,希望人們在欣賞藝術的同時,也不要再亂丟垃圾了。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

寶馬iPerformance插電式混合動力車將於七月推出

寶馬早就有進軍插電式混合動力汽車市場的計畫,2009年,寶馬正式宣佈了市場開發計畫,之後推出了MINI E、Active E等純電力驅動車型,以及i3、i8等插電式混合動力驅動車型。雖然我們很想知道寶馬插電式混合動力系列車型的具體資訊,但目前寶馬系電動車具體資訊還是很有限的。

新推出的iPerformance插電式混合動力車系統將沿用寶馬旗下的主流車型,同樣使用“i”系如i3、i8車型的插電式混合動力技術 ,該系統包括電動機、電池組以及電子控制系統等,第一款載入“i”系技術的插電式混合動力版新一代寶馬7系車BMW 740e iPerformance將於2016年7月正式推出。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

工信部正式發佈首批鉛蓄電池企業名單 風帆\天能\超微等39家企業入選

2月17日,工信部正式發佈《鉛蓄電池行業規範條件(2015年本)》企業名單(第一批),風帆股份、天能電池、超微電源、億能電源、聖陽電源、雄韜電源、荷貝克電源、普發電源、威盛電源、豐江實業等39家企業入選。

2015年12月10日,為進一步規範鉛蓄電池行業管理,加快行業結構調整和轉型升級,工信部對《鉛蓄電池行業准入條件》及《鉛蓄電池行業准入公告管理暫行辦法》進行了修訂,形成了《鉛蓄電池行業規範條件(2015年本)》和《鉛蓄電池行業規範公告管理暫行辦法(2015年本)》。新規範對電池企業的建設申請、生產能力、工藝水準以及環保等方面做出了嚴格的規定。

符合《鉛蓄電池行業規範條件(2015年本)》企業名單(第一批)

(排名不分先後)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

台灣空運大陸一條龍服務

※評比南投搬家公司費用收費行情懶人包大公開

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

韓國SK創新2017年起將為奔馳電動汽車供應車載動力電池

韓國SK集團旗下的能源公司SK創新2月17日宣佈,將從2017年開始為奔馳電動汽車供應車載電池,目前已經進入談判的尾聲。

SK創新並未透露協議的細節。該公司是車載電池領域的後起之秀,于2008年開始涉足這一領域,正在全球進行業務擴張。SK創新于2012年在韓國瑞山市建立了首家電池工廠,每年可以滿足1.5萬輛電動汽車電池的供應。

目前,SK創新向起亞汽車與北汽集團供應車載電池。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?