在CSV文件中传输二进制数据(图像等)

时间:2018-08-08 16:11:10

标签: file csv integration binary-data data-migration

除了存储为字符串,数字等的其他一些元数据信息外,我还需要传输一些二进制数据。我拥有的二进制数据是存储为数据库中blob列的图像文件,我想在数据库中包含blob列。 csv文件并将 csv 文件存储在文件系统或sftp服务器中,我想它没有导入到存储位置。

如何将二进制数据另存为 csv 文件中的另一列?以这种方式传输二进制数据是一个好习惯吗?

2 个答案:

答案 0 :(得分:1)

通常(也是正确的)方法是将二进制数据编码为base64。这样会将数据放大4:3。

虽然通常将csv文件视为文本文件,但是您可以 将原始二进制数据写入文件。

然后应将该数据括在双引号中,并且数据中所有现有的双引号都必须用另一个双引号进行转义。引用这些字段还将处理二进制数据中的任何换行符,但是读者必须对此予以支持。如果读者知道它正在读取二进制数据(即,如果您自己提供读取器),则其中也可能存在空字节。

但是,如果您的数据必须采用某种unicode格式,则可能会出现问题。

因此,通常将原始二进制数据写入csv文件不是一个好习惯,最好使用base64编码。

答案 1 :(得分:1)

正如correct Answer by Danny_ds所讨论的,是的,确实可以在CSV中包含二进制数据,但是必须以文本和CSV友好的方式对该数据进行编码。如上所述,这种文本和CSV友好的显而易见的选择是Base64

示例应用

这是示例应用程序的完整源代码。

此应用程序可以通过Internet下载一些图像。即,来自姐妹站点StackOverflow.com和StackExchange.com的徽标。下载后,这些图像被编码为Base64字符串。

然后写入2列CSV文件。列是名称和图像数据。在此示例中,我们有2行,上面列出的每个网站都有一行。请注意,Base64编码不包含逗号或引号,因此无需将Base64包含标准CSV格式的引号。

为证明这是可行的,然后读取CSV文件。图像数据从Base64解码回二进制数据。二进制数据被写入存储器。您可以自己打开PNG图像文件。它们应该看起来像thisthis

使用Apache Commons CSV库完成CSV的编写和读取。

package work.basil.example;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class CsvImager
{
    // Write a CSV file, of two columns: name of the image, image in Base64 data.
    private void writeCsv ( final Map < String, BufferedImage > map , final Path path )
    {
        Objects.requireNonNull( map );

        CSVFormat format = CSVFormat.RFC4180.withHeader( "Name" , "Image" );
        try (
                BufferedWriter writer = Files.newBufferedWriter( path , StandardCharsets.UTF_8 ) ;
                CSVPrinter printer = new CSVPrinter( writer , format ) ;
        )
        {
            // Print rows.
            for ( String key : map.keySet() )
            {
                printer.print( key );
                BufferedImage image = Objects.requireNonNull( map.get( key ) );
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                ImageIO.write( image , "PNG" , stream );
                String imageData = Base64.getEncoder().encodeToString( stream.toByteArray() );
                printer.print( imageData );
                printer.println();
            }
        } catch ( IOException e )
        {
            e.printStackTrace();
        }
    }

    // Read images from a CSV file in storage.
    public Map < String, BufferedImage > readCsv ( final Path path )
    {
        Objects.requireNonNull( path );
        Map < String, BufferedImage > map = Map.of();
        try ( BufferedReader reader = Files.newBufferedReader( path ) )
        {
            map = new HashMap <>();
            CSVFormat format = CSVFormat.RFC4180.withHeader( "Name" , "Image" ).withFirstRecordAsHeader();
            Iterable < CSVRecord > records = format.parse( reader );
            for ( CSVRecord record : records )
            {
                String name = record.get( "Name" );
                String imageBase64 = record.get( "Image" );
//                System.out.println("imageBase64:\n" + imageBase64 + "\n");
                byte[] bytes = Base64.getDecoder().decode( imageBase64 );
                ByteArrayInputStream stream = new ByteArrayInputStream( bytes );
                BufferedImage image = ImageIO.read( stream );
                map.put( name , image );
            }

        } catch ( IOException e )
        {
            e.printStackTrace();
        }
        return map;
    }

    // Download images from the Internet.
    private Map < String, BufferedImage > fetchImages ()
    {
        Map < String, BufferedImage > map = Map.of(); // Initialize to empty map.
        try
        {
            URL urlStackOverflow = null, urlStackExchange = null;
            urlStackOverflow = new URL( "https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png" );
            urlStackExchange = new URL( "https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/se/se-logo.png" );

            BufferedImage imageStackOverflow = ImageIO.read( urlStackOverflow );
            BufferedImage imageStackExchange = ImageIO.read( urlStackExchange );

            System.out.println( "imageStackOverflow: " + imageStackOverflow );
            System.out.println( "imageStackExchange: " + imageStackExchange );

            map = Map.of( "logoStackOverflow.png" , imageStackOverflow , "logoStackExchange.png" , imageStackExchange );
        } catch ( MalformedURLException e ) // `new URL` fail.
        {
            e.printStackTrace();
        } catch ( IOException e ) // `ImageIO.read` fail.
        {
            e.printStackTrace();
        }
        ;
        return map;
    }

    // Produce individual image files on disk, to manually verify that the downloaded images were successfully Base64 endcoded, written to CSV, read from CSV, and decoded back to images.
    public void writeImages ( final Map < String, BufferedImage > map , final Path pathToFolder )
    {
        Objects.requireNonNull( map );
        Objects.requireNonNull( pathToFolder );
        if ( map.isEmpty() )
        {
            throw new IllegalArgumentException( "The Map should have elements but is empty. Message # 77063b5a-4398-49f0-b1a4-442255a13b77." );
        }
        if ( ! Files.isDirectory( pathToFolder ) )
        {
            throw new IllegalArgumentException( "The specified path must lead to an existing folder. Message # 6a19313d-b8a9-4a53-9b82-7672172923f9." );
        }
        for ( String key : map.keySet() )
        {
            Path pathToFile = pathToFolder.resolve( key );
            try (
                    OutputStream stream = Files.newOutputStream( pathToFile ) ;
            )
            {
                BufferedImage image = Objects.requireNonNull( map.get( key ) );
                ImageIO.write( image , "PNG" , stream );
            } catch ( IOException e )
            {
                e.printStackTrace();
            }
        }
    }

    // --------| Demo  |-----------------------

    public void demo ()
    {
        Map < String, BufferedImage > map = this.fetchImages(); // Pairs of name & image.
        Path path = Paths.get( "/Users/basilbourque/images.csv" );
        this.writeCsv( map , path );
        Map < String, BufferedImage > mapOut = this.readCsv( path );
        Path pathOut = path.getParent();
        this.writeImages( mapOut , pathOut );
    }

    public static void main ( String[] args )
    {
        CsvImager app = new CsvImager();
        app.demo();

        System.out.println( "Done." );
    }
}

提示:CSV中用于列名的魔术字符串NameImage的分散使用应替换为Apache Commons CSV支持的枚举。我把它留给读者练习。