Tệp thành byte [] trong Java


757

Làm cách nào để chuyển đổi a java.io.Filethành a byte[]?


Một cách sử dụng mà tôi có thể nghĩ đến là đọc các đối tượng nối tiếp từ tệp.
Mahm00d

2
Một cách khác là tìm loại tệp bằng tiêu đề.
James P.

Hãy thử byte này [] byte = null; BufferedInputStream fileInputStream = null; thử {Tệp tệp = Tệp mới (filePath); fileInputStream = new BufferedInputStream (new FileInputStream (file)); // fileInputStream = Thread.cienThread (). getContextClassLoader (). getResourceAsStream (this.filePath); byte = new byte [(int) file.length ()]; fileInputStream.read (byte); } Catch (FileNotFoundException ex) {throw ex; }
Rohit Chaurasiya

Câu trả lời:


486

Nó phụ thuộc vào những gì tốt nhất có nghĩa cho bạn. Năng suất khôn ngoan, không phát minh lại bánh xe và sử dụng Apache Commons. Đó là đây IOUtils.toByteArray(InputStream input).


29
@ymajoros: Thật vậy! Tôi muốn có thêm một số dòng mã hơn là một phụ thuộc khác. Phụ thuộc có chi phí ẩn. Bạn cần cập nhật với thư viện đó, bao gồm sự phụ thuộc trong tập lệnh xây dựng của bạn, v.v., giao tiếp với mọi người bằng mã của bạn, v.v. Nếu bạn đang sử dụng một thư viện có mã cho nó hơn là sử dụng, thì tôi sẽ nói tự viết nó
Stijn de Witt

11
Điều này trả lời câu hỏi làm thế nào để đọc một tệp, nhưng không phải là câu hỏi về cách chuyển đổi một đối tượng có kiểu java.IO.File thành byte [].
Ingo

5
Làm thế nào điều này được sử dụng để đọc một Fileđến byte[]? Tôi đang sử dụng Java6 vì vậy tôi không thể sử dụng các phương thức NIO :(
PASTRY

4
@ymajoros bạn có vui lòng chia sẻ bất kỳ "giải pháp 3 dòng tiêu chuẩn" nào với chúng tôi không, vì vậy chúng tôi không phải phụ thuộc vào sự phụ thuộc vào việc phát minh lại bánh xe?
matteo

3
@matteo: nào? Xem các câu trả lời khác, ví dụ Files.read ALLBytes (). Đơn giản, không phụ thuộc.
ymajoros

1292

Từ JDK 7 bạn có thể sử dụng Files.readAllBytes(Path).

Thí dụ:

import java.io.File;
import java.nio.file.Files;

File file;
// ...(file is initialised)...
byte[] fileContent = Files.readAllBytes(file.toPath());

10
Tôi có một đối tượng Tệp, không phải là đường dẫn (từ yêu cầu bài đăng http)
aldo.roman.nurena

81
@ aldo.roman.nurena JDK7 đã giới thiệu một phương thức File.toPath () sẽ cung cấp cho bạn một Object Object.
KevinL

6
Bạn có thể nhận được một đường dẫn từ một tập tin. Thử: Tệp tệp = Tệp mới ("/ path"); Đường dẫn path = Paths.get (file.getAbsolutePath ()); byte [] data = Files.read ALLBytes (đường dẫn);
gfelisberto 28/12/13

2
Việc đóng tệp được xử lý như thế nào trong java.nio - nói cách khác, đoạn mã trên có nên đóng cái gì không?
akauppi

4
@akauppi Xem liên kết trong câu trả lời: "Phương pháp đảm bảo rằng tệp được đóng ..."
Bernhard Barker

226

Kể từ JDK 7 - một lớp lót:

byte[] array = Files.readAllBytes(Paths.get("/path/to/file"));

Không cần phụ thuộc bên ngoài.


13
Đây bây giờ là một lựa chọn tốt hơn câu trả lời được chấp nhận, đòi hỏi Apache Commons.
james.garriss

1
Cảm ơn :) Tôi cũng cần cái này: String text = new String (Files.read ALLBytes (Tệp mới ("/ path / to / file"). ToPath ())); có nguồn gốc từ stackoverflow.com/a/26888713/1257959
cgl

5
Trong Android, nó yêu cầu cấp API tối thiểu là 26.
Ashutosh Chamoli

2
Bạn sẽ cần thêm import java.nio.file.Files;import java.nio.file.Paths;nếu bạn chưa có.
Sam

164
import java.io.RandomAccessFile;
RandomAccessFile f = new RandomAccessFile(fileName, "r");
byte[] b = new byte[(int)f.length()];
f.readFully(b);

Tài liệu cho Java 8: http://docs.oracle.com/javase/8/docs/api/java/io/RandomAccessFile.html


2
Bạn phải kiểm tra giá trị trả về của f.read (). Ở đây đôi khi có thể xảy ra, rằng bạn sẽ không đọc toàn bộ tập tin.
lỗi_

8
Tình huống như vậy chỉ có thể xảy ra nếu tập tin đang thay đổi trong khi bạn đang đọc nó. Trong tất cả các trường hợp khác, IOException bị ném. Để giải quyết vấn đề này, tôi khuyên bạn nên mở tệp ở chế độ đọc-ghi: RandomAccessFile (fileName, "rw")
Dmitry Mitskevich

5
Tôi có thể tưởng tượng các nguồn khác chỉ đọc một phần của tệp (Tệp nằm trên mạng chia sẻ ...) readFully () có hợp đồng bạn đang tìm kiếm.
Nghĩ

3
Hãy nhớ rằng RandomAccessFile không phải là chủ đề an toàn. Vì vậy, đồng bộ hóa có thể cần thiết trong một số trường hợp.
bancer

@DmitryMitskevich Cũng có những trường hợp khác, trên các hệ thống tập tin có thể không phù hợp. ví dụ: đọc "tập tin" trong / Proc / trên linux có thể gây ra các lần đọc ngắn (tức là bạn cần một vòng lặp để đọc tất cả)
nos

78

Về cơ bản bạn phải đọc nó trong bộ nhớ. Mở tệp, phân bổ mảng và đọc nội dung từ tệp vào mảng.

Cách đơn giản nhất là một cái gì đó tương tự như thế này:

public byte[] read(File file) throws IOException, FileTooBigException {
    if (file.length() > MAX_FILE_SIZE) {
        throw new FileTooBigException(file);
    }
    ByteArrayOutputStream ous = null;
    InputStream ios = null;
    try {
        byte[] buffer = new byte[4096];
        ous = new ByteArrayOutputStream();
        ios = new FileInputStream(file);
        int read = 0;
        while ((read = ios.read(buffer)) != -1) {
            ous.write(buffer, 0, read);
        }
    }finally {
        try {
            if (ous != null)
                ous.close();
        } catch (IOException e) {
        }

        try {
            if (ios != null)
                ios.close();
        } catch (IOException e) {
        }
    }
    return ous.toByteArray();
}

Điều này có một số sao chép không cần thiết của nội dung tệp (thực tế dữ liệu được sao chép ba lần: từ tệp đến buffer, từ bufferđến ByteArrayOutputStream, từ ByteArrayOutputStreammảng kết quả thực tế).

Bạn cũng cần đảm bảo rằng bạn chỉ đọc trong bộ nhớ các tệp có kích thước nhất định (điều này thường phụ thuộc vào ứng dụng) :-).

Bạn cũng cần phải điều trị IOExceptioncác chức năng bên ngoài.

Một cách khác là thế này:

public byte[] read(File file) throws IOException, FileTooBigException {
    if (file.length() > MAX_FILE_SIZE) {
        throw new FileTooBigException(file);
    }

    byte[] buffer = new byte[(int) file.length()];
    InputStream ios = null;
    try {
        ios = new FileInputStream(file);
        if (ios.read(buffer) == -1) {
            throw new IOException(
                    "EOF reached while trying to read the whole file");
        }
    } finally {
        try {
            if (ios != null)
                ios.close();
        } catch (IOException e) {
        }
    }
    return buffer;
}

Điều này không có sao chép không cần thiết.

FileTooBigExceptionlà một ngoại lệ ứng dụng tùy chỉnh. Các MAX_FILE_SIZEhằng số là một thông số ứng dụng.

Đối với các tệp lớn, có lẽ bạn nên nghĩ thuật toán xử lý luồng hoặc sử dụng ánh xạ bộ nhớ (xem java.nio).


ios cần phải được tuyên bố ngoài thử
Daryl Spitzer

Câu lệnh "ios.read (bộ đệm)" trong ví dụ thứ hai sẽ chỉ đọc trong 4096 byte đầu tiên của tệp (giả sử bộ đệm 4k giống như được sử dụng trong ví dụ đầu tiên). Để ví dụ thứ hai hoạt động, tôi nghĩ rằng việc đọc phải nằm trong vòng lặp while để kiểm tra kết quả cho -1 (kết thúc tệp đạt được).
Stijn de Witt

Xin lỗi, bỏ qua nhận xét của tôi ở trên, bỏ lỡ bộ đệm thiết lập câu lệnh theo chiều dài của tệp. Tuy nhiên, tôi thích cách ví dụ đầu tiên hơn. Đọc toàn bộ tệp vào một bộ đệm trong một lần là không thể mở rộng. Bạn sẽ có nguy cơ phơi nắng ra khỏi bộ nhớ khi tệp lớn.
Stijn de Witt

Cách "đơn giản nhất" sẽ sử dụng tài nguyên dùng thử.
Sina Madani

Mát mẻ, nhưng một chút để dài dòng.
Sapphire_Brick

77

Như ai đó đã nói, những người sử dụng tệp Apache Commons có thể có những gì bạn đang tìm kiếm

public static byte[] readFileToByteArray(File file) throws IOException

Ví dụ sử dụng ( Program.java):

import org.apache.commons.io.FileUtils;
public class Program {
    public static void main(String[] args) throws IOException {
        File file = new File(args[0]);  // assume args[0] is the path to file
        byte[] data = FileUtils.readFileToByteArray(file);
        ...
    }
}

23

Bạn cũng có thể sử dụng api NIO để làm điều đó. Tôi có thể làm điều này với mã này miễn là tổng kích thước tệp (tính bằng byte) sẽ vừa với một int.

File f = new File("c:\\wscp.script");
FileInputStream fin = null;
FileChannel ch = null;
try {
    fin = new FileInputStream(f);
    ch = fin.getChannel();
    int size = (int) ch.size();
    MappedByteBuffer buf = ch.map(MapMode.READ_ONLY, 0, size);
    byte[] bytes = new byte[size];
    buf.get(bytes);

} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} finally {
    try {
        if (fin != null) {
            fin.close();
        }
        if (ch != null) {
            ch.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Tôi nghĩ rằng nó rất nhanh kể từ khi sử dụng MappedByteBuffer.


2
hoàn toàn không cần sử dụng ánh xạ bộ nhớ nếu bạn chỉ đọc tệp một lần và cuối cùng sẽ sử dụng bộ nhớ gấp đôi so với sử dụng FileInputStream bình thường.
James

1
Thật không may, MappedByteBuffer không tự động phát hành.
Tom Hawtin - tackline

2
tuyệt vời, ví dụ mới bao gồm printStackTrace, xử lý ngoại lệ bị hỏng cổ điển.
James

Tôi đồng ý .. Đó là thứ mặc định mà nhật thực đưa vào. Tôi nghĩ tôi nên suy nghĩ lại ngoại lệ!
Amit

Ive đã được điểm chuẩn nio để tạo byte [] từ Tệp. Khác với việc sử dụng bộ đệm trực tiếp, nó thực sự chiếm gấp đôi bộ nhớ. Mặc dù nó nhanh hơn đối với các tệp rất lớn (nhanh gấp khoảng hai lần so với IO được đệm trong 200M) nhưng dường như mất đi 5 lần cho các tệp khoảng 5M.
Chaffers

22

Nếu bạn không có Java 8 và đồng ý với tôi rằng bao gồm một thư viện đồ sộ để tránh viết một vài dòng mã là một ý tưởng tồi:

public static byte[] readBytes(InputStream inputStream) throws IOException {
    byte[] b = new byte[1024];
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    int c;
    while ((c = inputStream.read(b)) != -1) {
        os.write(b, 0, c);
    }
    return os.toByteArray();
}

Người gọi có trách nhiệm đóng luồng.


21
// Returns the contents of the file in a byte array.
    public static byte[] getBytesFromFile(File file) throws IOException {        
        // Get the size of the file
        long length = file.length();

        // You cannot create an array using a long type.
        // It needs to be an int type.
        // Before converting to an int type, check
        // to ensure that file is not larger than Integer.MAX_VALUE.
        if (length > Integer.MAX_VALUE) {
            // File is too large
            throw new IOException("File is too large!");
        }

        // Create the byte array to hold the data
        byte[] bytes = new byte[(int)length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;

        InputStream is = new FileInputStream(file);
        try {
            while (offset < bytes.length
                   && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
                offset += numRead;
            }
        } finally {
            is.close();
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file "+file.getName());
        }
        return bytes;
    }

Ngoài ra, đặt numRead bên trong vòng lặp. Khai báo các biến trong phạm vi hợp lệ nhỏ nhất bạn có thể. Đặt nó bên ngoài vòng lặp while chỉ cần thiết để cho phép kiểm tra "while" phức tạp đó; sẽ tốt hơn để thực hiện kiểm tra EOF bên trong vòng lặp (và ném EOFException nếu nó xảy ra).
erickson

throw new IOException("File is too large!");Chúng ta nên làm gì khi tệp quá lớn? Có bất kỳ ví dụ về nó?
Fer

21

Cách đơn giản để làm điều đó:

File fff = new File("/path/to/file");
FileInputStream fileInputStream = new FileInputStream(fff);

// int byteLength = fff.length(); 

// In android the result of file.length() is long
long byteLength = fff.length(); // byte count of the file-content

byte[] filecontent = new byte[(int) byteLength];
fileInputStream.read(filecontent, 0, (int) byteLength);

Có những cách đơn giản hơn, chẳng hạn như các lớp lót đã được đề cập.
Sapphire_Brick

@Sap túi_Brick Cách đơn giản hơn có - nhưng một lớp lót không phù hợp với mọi tình huống. Chẳng hạn như Android.
Behr

17

Cách đơn giản nhất để đọc byte từ tệp

import java.io.*;

class ReadBytesFromFile {
    public static void main(String args[]) throws Exception {
        // getBytes from anyWhere
        // I'm getting byte array from File
        File file = null;
        FileInputStream fileStream = new FileInputStream(file = new File("ByteArrayInputStreamClass.java"));

        // Instantiate array
        byte[] arr = new byte[(int) file.length()];

        // read All bytes of File stream
        fileStream.read(arr, 0, arr.length);

        for (int X : arr) {
            System.out.print((char) X);
        }
    }
}

1
Tôi tranh luận về việc là "Cách đơn giản nhất" :)
BlondCode

Bạn có thể giải thích ở đây? Tại sao bạn có một cuộc tranh cãi?
Muhammad Sadiq

3
Không có gì đặc biệt, nhưng bạn nói đơn giản nhất và tôi thấy các giải pháp đơn giản hơn -> theo tôi đó không phải là đơn giản nhất. Có thể đó là vài năm trước, nhưng thế giới đang thay đổi. Tôi sẽ không dán nhãn giải pháp của riêng tôi với một tuyên bố như vậy. ;) Nếu chỉ có bạn viết "Theo tôi thì đơn giản nhất là .." hoặc "đơn giản nhất tôi tìm thấy .." Đừng muốn làm phiền bạn, chỉ cần nghĩ tốt để truyền đạt điều này.
BlondCode

@MuhammadSadiq: không nhập bất cứ thứ gì .*, nó được coi là thông lệ xấu.
Sapphire_Brick

13

Quả ổi có Files.toByteArray () để cung cấp cho bạn. Nó có một số lợi thế:

  1. Nó bao gồm trường hợp góc trong đó các tệp báo cáo độ dài bằng 0 nhưng vẫn có nội dung
  2. Nó được tối ưu hóa cao, bạn nhận được OutOfMemoryException nếu cố đọc trong một tệp lớn trước khi thử tải tệp. (Thông qua việc sử dụng thông minh file.length ())
  3. Bạn không phải phát minh lại bánh xe.

12
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;

File file = getYourFile();
Path path = file.toPath();
byte[] data = Files.readAllBytes(path);

Cấp độ JDK này là gì?
Jonathan S. Fisher

11

Sử dụng cách tiếp cận tương tự như câu trả lời wiki cộng đồng, nhưng dọn dẹp và biên dịch ra khỏi hộp (cách tiếp cận ưa thích nếu bạn không muốn nhập libs Commons của Apache, ví dụ như trên Android):

public static byte[] getFileBytes(File file) throws IOException {
    ByteArrayOutputStream ous = null;
    InputStream ios = null;
    try {
        byte[] buffer = new byte[4096];
        ous = new ByteArrayOutputStream();
        ios = new FileInputStream(file);
        int read = 0;
        while ((read = ios.read(buffer)) != -1)
            ous.write(buffer, 0, read);
    } finally {
        try {
            if (ous != null)
                ous.close();
        } catch (IOException e) {
            // swallow, since not that important
        }
        try {
            if (ios != null)
                ios.close();
        } catch (IOException e) {
            // swallow, since not that important
        }
    }
    return ous.toByteArray();
}

8

Tôi tin rằng đây là cách dễ nhất:

org.apache.commons.io.FileUtils.readFileToByteArray(file);

7
đã có câu trả lời với đề nghị này từ Tom vào năm 2009
Knut Herrmann

7

ReadFully Đọc các byte b.length từ tệp này vào mảng byte, bắt đầu từ con trỏ tệp hiện tại. Phương pháp này đọc lặp lại từ tệp cho đến khi số byte được yêu cầu được đọc. Phương thức này chặn cho đến khi số byte được yêu cầu được đọc, kết thúc luồng được phát hiện hoặc ném ngoại lệ.

RandomAccessFile f = new RandomAccessFile(fileName, "r");
byte[] b = new byte[(int)f.length()];
f.readFully(b);

5

Nếu bạn muốn đọc byte vào bộ đệm byte được phân bổ trước, câu trả lời này có thể hữu ích.

Dự đoán đầu tiên của bạn có thể sẽ được sử dụng InputStream read(byte[]). Tuy nhiên, phương pháp này có một lỗ hổng khiến nó khó sử dụng một cách vô lý: không có gì đảm bảo rằng mảng sẽ thực sự được lấp đầy hoàn toàn, ngay cả khi không gặp phải EOF.

Thay vào đó, hãy xem DataInputStream readFully(byte[]). Đây là một trình bao bọc cho các luồng đầu vào và không có vấn đề được đề cập ở trên. Ngoài ra, phương pháp này ném khi gặp EOF. Đẹp hơn nhiều.


4

Cách sau đây không chỉ chuyển đổi java.io.File thành byte [], tôi cũng thấy đó là cách nhanh nhất để đọc trong tệp, khi kiểm tra nhiều phương thức đọc tệp Java khác nhau :

java.nio.file.Files.read ALLBytes ()

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class ReadFile_Files_ReadAllBytes {
  public static void main(String [] pArgs) throws IOException {
    String fileName = "c:\\temp\\sample-10KB.txt";
    File file = new File(fileName);

    byte [] fileBytes = Files.readAllBytes(file.toPath());
    char singleChar;
    for(byte b : fileBytes) {
      singleChar = (char) b;
      System.out.print(singleChar);
    }
  }
}

3

Hãy để tôi thêm một giải pháp khác mà không cần sử dụng thư viện của bên thứ ba. Nó sử dụng lại một mẫu xử lý ngoại lệ được đề xuất bởi Scott ( link ). Và tôi đã chuyển phần xấu xí thành một tin nhắn riêng biệt (tôi sẽ ẩn trong một số lớp FileUtils;))

public void someMethod() {
    final byte[] buffer = read(new File("test.txt"));
}

private byte[] read(final File file) {
    if (file.isDirectory())
        throw new RuntimeException("Unsupported operation, file "
                + file.getAbsolutePath() + " is a directory");
    if (file.length() > Integer.MAX_VALUE)
        throw new RuntimeException("Unsupported operation, file "
                + file.getAbsolutePath() + " is too big");

    Throwable pending = null;
    FileInputStream in = null;
    final byte buffer[] = new byte[(int) file.length()];
    try {
        in = new FileInputStream(file);
        in.read(buffer);
    } catch (Exception e) {
        pending = new RuntimeException("Exception occured on reading file "
                + file.getAbsolutePath(), e);
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (Exception e) {
                if (pending == null) {
                    pending = new RuntimeException(
                        "Exception occured on closing file" 
                             + file.getAbsolutePath(), e);
                }
            }
        }
        if (pending != null) {
            throw new RuntimeException(pending);
        }
    }
    return buffer;
}

3
public static byte[] readBytes(InputStream inputStream) throws IOException {
    byte[] buffer = new byte[32 * 1024];
    int bufferSize = 0;
    for (;;) {
        int read = inputStream.read(buffer, bufferSize, buffer.length - bufferSize);
        if (read == -1) {
            return Arrays.copyOf(buffer, bufferSize);
        }
        bufferSize += read;
        if (bufferSize == buffer.length) {
            buffer = Arrays.copyOf(buffer, bufferSize * 2);
        }
    }
}

1

Một cách khác để đọc byte từ tệp

Reader reader = null;
    try {
        reader = new FileReader(file);
        char buf[] = new char[8192];
        int len;
        StringBuilder s = new StringBuilder();
        while ((len = reader.read(buf)) >= 0) {
            s.append(buf, 0, len);
            byte[] byteArray = s.toString().getBytes();
        }
    } catch(FileNotFoundException ex) {
    } catch(IOException e) {
    }
    finally {
        if (reader != null) {
            reader.close();
        }
    }

không sử dụng các khối bắt rỗng. nó làm cho việc sửa lỗi khó khăn
Sapphire_Brick

1
//The file that you wanna convert into byte[]
File file=new File("/storage/0CE2-EA3D/DCIM/Camera/VID_20190822_205931.mp4"); 

FileInputStream fileInputStream=new FileInputStream(file);
byte[] data=new byte[(int) file.length()];
BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream);
bufferedInputStream.read(data,0,data.length);

//Now the bytes of the file are contain in the "byte[] data"

1
Mặc dù mã này có thể cung cấp một giải pháp cho câu hỏi, tốt hơn là thêm ngữ cảnh vào lý do tại sao / cách thức hoạt động của nó. Điều này có thể giúp người dùng trong tương lai tìm hiểu và áp dụng kiến ​​thức đó vào mã của riêng họ. Bạn cũng có thể có phản hồi tích cực từ người dùng dưới dạng upvote, khi mã được giải thích.
borchvm

Chà, đó là phần quan trọng mà tôi sẽ ghi nhớ trong các bài viết trong tương lai. Cảm ơn những hiểu biết hữu ích của bạn.
Usama Mehmood

0

Thử cái này :

import sun.misc.IOUtils;
import java.io.IOException;

try {
    String path="";
    InputStream inputStream=new FileInputStream(path);
    byte[] data=IOUtils.readFully(inputStream,-1,false);
}
catch (IOException e) {
    System.out.println(e);
}

Điều đó đòi hỏi một triển khai JRE cụ thể sẽ phá vỡ ứng dụng nếu chạy trên một JRE khác.
rattaman

2
một lỗi nhỏ: là IOException và không phải IOexception, nhưng cảm ơn :)
Matan Marciano

1
@MatanMarciano: xấu của tôi
Sapphire_Brick

-7

Trong JDK8

Stream<String> lines = Files.lines(path);
String data = lines.collect(Collectors.joining("\n"));
lines.close();

2
Đọc câu hỏi người bạn nói tiếng Pháp của tôi, nó hỏi về việc chuyển đổi thành "byte []" và câu trả lời của bạn không cung cấp điều đó.
Kaiser Keister

2
Điều này không cung cấp tùy chọn từ xa thậm chí để trả lời cho việc chuyển đổi sang byte []!
Anddo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.