Câu trả lời:
Một luồng đại diện cho một chuỗi các đối tượng (thường là byte, nhưng không nhất thiết phải như vậy), có thể được truy cập theo thứ tự tuần tự. Các hoạt động tiêu biểu trên một luồng:
Một luồng cụ thể có thể hỗ trợ đọc (trong trường hợp đó là "luồng đầu vào"), ghi ("luồng đầu ra") hoặc cả hai. Không phải tất cả các luồng đều có thể tìm kiếm.
Đẩy lùi là khá hiếm, nhưng bạn luôn có thể thêm nó vào luồng bằng cách gói luồng đầu vào thực trong luồng đầu vào khác chứa bộ đệm bên trong. Đọc đến từ bộ đệm và nếu bạn đẩy lùi thì dữ liệu sẽ được đặt vào bộ đệm. Nếu không có gì trong bộ đệm thì luồng đẩy ngược sẽ đọc từ luồng thực. Đây là một ví dụ đơn giản về "bộ điều hợp luồng": nó nằm ở "phần cuối" của luồng đầu vào, nó là luồng đầu vào và nó làm thêm một điều gì đó mà luồng ban đầu không có.
Luồng là một sự trừu tượng hữu ích vì nó có thể mô tả các tệp (thực sự là mảng, do đó tìm kiếm rất đơn giản) nhưng cũng có thể nhập / xuất đầu cuối (không thể tìm kiếm trừ khi được đệm), ổ cắm, cổng nối tiếp, v.v. hoặc "Tôi muốn có một số dữ liệu và tôi không quan tâm nó đến từ đâu hoặc làm thế nào nó đến được", hoặc "Tôi sẽ tạo ra một số dữ liệu, và nó hoàn toàn phụ thuộc vào người gọi của tôi những gì xảy ra với nó". Cái trước lấy tham số luồng đầu vào, cái sau lấy tham số luồng đầu ra.
Sự tương tự tốt nhất tôi có thể nghĩ là một luồng là một băng chuyền đi về phía bạn hoặc dẫn ra khỏi bạn (hoặc đôi khi cả hai). Bạn lấy thứ ra khỏi luồng đầu vào, bạn đặt thứ vào luồng đầu ra. Một số băng tải bạn có thể nghĩ là phát ra từ một lỗ trên tường - chúng không thể tìm kiếm được, đọc hoặc viết là một giao dịch một lần duy nhất. Một số băng tải được đặt trước mặt bạn và bạn có thể di chuyển dọc theo việc chọn nơi ở trong luồng bạn muốn đọc / viết - đó là tìm kiếm.
Tuy nhiên, như IRBMe nói, tốt nhất bạn nên nghĩ về một luồng về các hoạt động mà nó cung cấp (thay đổi từ triển khai đến thực hiện, nhưng có nhiều điểm chung) hơn là tương tự vật lý. Luồng là "những thứ bạn có thể đọc hoặc viết". Khi bạn bắt đầu kết nối các bộ điều hợp luồng, bạn có thể nghĩ về chúng như một hộp có băng tải và băng tải ra, bạn kết nối với các luồng khác và sau đó hộp thực hiện một số chuyển đổi trên dữ liệu (nén nó hoặc thay đổi tốc độ dòng UNIX cho những người DOS, hoặc bất cứ điều gì). Các đường ống là một phép thử kỹ lưỡng khác của phép ẩn dụ: đó là nơi bạn tạo ra một cặp luồng sao cho bất kỳ thứ gì bạn viết vào cái này đều có thể được đọc từ cái kia. Hãy nghĩ về lỗ sâu đục :-)
Một luồng đã là một phép ẩn dụ, một sự tương tự, vì vậy thực sự không cần phải đặt một luồng khác. Về cơ bản, bạn có thể nghĩ về nó như một đường ống với dòng nước chảy trong đó nước thực sự là dữ liệu và đường ống là dòng chảy. Tôi cho rằng đó là loại ống 2 chiều nếu luồng là hai chiều. Về cơ bản, đó là một sự trừu tượng phổ biến được đặt trên những thứ có luồng hoặc chuỗi dữ liệu theo một hoặc cả hai hướng.
Trong các ngôn ngữ như C #, VB.Net, C ++, Java, v.v., ẩn dụ luồng được sử dụng cho nhiều thứ. Có các luồng tệp, trong đó bạn mở một tệp và có thể đọc từ luồng hoặc ghi vào đó liên tục; Có các luồng mạng trong đó đọc từ và ghi vào luồng đọc và ghi vào kết nối mạng được thiết lập bên dưới. Các luồng chỉ để viết thường được gọi là các luồng đầu ra, như trong ví dụ này và tương tự, các luồng chỉ để đọc được gọi là các luồng đầu vào, như trong ví dụ này .
Luồng có thể thực hiện chuyển đổi hoặc mã hóa dữ liệu ( ví dụ: SslStream trong .Net, sẽ ăn hết dữ liệu đàm phán SSL và ẩn nó khỏi bạn; TelnetStream có thể ẩn các cuộc đàm phán Telnet khỏi bạn, nhưng cung cấp quyền truy cập vào dữ liệu; A ZipOutputStream trong Java cho phép bạn ghi vào các tệp trong kho lưu trữ zip mà không phải lo lắng về các phần bên trong của định dạng tệp zip.
Một điều phổ biến khác bạn có thể tìm thấy là các luồng văn bản cho phép bạn viết các chuỗi thay vì byte hoặc một số ngôn ngữ cung cấp các luồng nhị phân cho phép bạn viết các kiểu nguyên thủy. Một điều phổ biến bạn sẽ tìm thấy trong các luồng văn bản là mã hóa ký tự mà bạn nên biết.
Một số luồng cũng hỗ trợ truy cập ngẫu nhiên, như trong ví dụ này . Mặt khác, một luồng mạng, vì những lý do rõ ràng, sẽ không.
UNIX giống như các hệ điều hành cũng hỗ trợ mô hình truyền phát với đầu vào và đầu ra chương trình, như được mô tả ở đây .
Các câu trả lời cho đến nay là tuyệt vời. Tôi chỉ cung cấp một thứ khác để làm nổi bật rằng một luồng không phải là một chuỗi byte hoặc cụ thể cho ngôn ngữ lập trình vì khái niệm này là phổ biến (trong khi việc triển khai của nó có thể là duy nhất). Tôi thường thấy rất nhiều giải thích trực tuyến về SQL, hoặc C hoặc Java, có nghĩa là một đoạn phim xử lý các vị trí bộ nhớ và các hoạt động cấp thấp. Nhưng họ thường giải quyết làm thế nào để tạo một filestream và vận hành trên tệp tiềm năng bằng ngôn ngữ đã cho của họ thay vì thảo luận về khái niệm của một luồng.
Như đã đề cập a stream
là một phép ẩn dụ, một sự trừu tượng của một cái gì đó phức tạp hơn. Để trí tưởng tượng của bạn hoạt động, tôi đưa ra một số phép ẩn dụ khác:
vòi là dòng
vòi, vòi và các cơ chế liên quan để cho phép khí chảy vào bể của bạn là dòng
đường cao tốc là dòng suối
tai và mắt của bạn là dòng
Hy vọng rằng bạn nhận thấy trong các ví dụ này rằng các ẩn dụ dòng chỉ tồn tại để cho phép một cái gì đó đi qua nó (hoặc trên nó trong trường hợp của đường cao tốc) và không phải lúc nào chúng cũng đặt ra thứ mà chúng đang chuyển. Một sự phân biệt quan trọng. Chúng tôi không đề cập đến tai của chúng tôi như một chuỗi các từ. Một vòi vẫn là một vòi nếu không có nước chảy qua, nhưng chúng ta phải kết nối nó với một cái vòi để nó thực hiện công việc của mình một cách chính xác. Một chiếc xe hơi không phải là 'loại' phương tiện duy nhất có thể đi qua đường cao tốc.
Do đó, một luồng có thể tồn tại mà không có dữ liệu truyền qua nó miễn là nó được kết nối với một tệp .
Tiếp theo, chúng ta cần trả lời một vài câu hỏi. Tôi sẽ sử dụng các tệp để mô tả các luồng vì vậy ... Tệp là gì? Và làm thế nào để chúng ta đọc một tập tin? Tôi sẽ cố gắng trả lời điều này trong khi duy trì một mức độ trừu tượng nhất định để tránh sự phức tạp không cần thiết và sẽ sử dụng khái niệm tệp liên quan đến hệ điều hành linux vì tính đơn giản và khả năng truy cập của nó.
Một tập tin là một sự trừu tượng :)
Hoặc, đơn giản như tôi có thể giải thích, một tệp là một cấu trúc dữ liệu mô tả tệp và một phần dữ liệu là nội dung thực tế.
Phần cấu trúc dữ liệu (được gọi là inode trong các hệ thống UNIX / linux) xác định các phần thông tin quan trọng về nội dung, nhưng không bao gồm chính nội dung (hoặc tên của tệp cho vấn đề đó). Một trong những thông tin mà nó lưu giữ là một địa chỉ bộ nhớ đến nơi nội dung bắt đầu. Vì vậy, với tên tệp (hoặc liên kết cứng trong linux), bộ mô tả tệp (tên tệp số mà hệ điều hành quan tâm) và vị trí bắt đầu trong bộ nhớ, chúng ta có thể gọi một tệp.
(khóa lấy là một 'tệp' được xác định bởi hệ điều hành vì đó là HĐH cuối cùng phải xử lý. Và vâng, các tệp phức tạp hơn nhiều).
Càng xa càng tốt. Nhưng làm thế nào để chúng tôi có được nội dung của tập tin, nói một bức thư tình cho bạn, để chúng tôi có thể in nó?
Nếu chúng ta bắt đầu từ kết quả và di chuyển ngược lại, khi chúng ta mở một tệp trên máy tính, toàn bộ nội dung của nó sẽ bị văng trên màn hình để chúng ta đọc. Nhưng bằng cách nào? Rất phương pháp là câu trả lời. Nội dung của tập tin là một cấu trúc dữ liệu khác. Giả sử một mảng các ký tự. Chúng ta cũng có thể nghĩ về điều này như một chuỗi.
Vậy làm thế nào để chúng ta 'đọc' chuỗi này? Bằng cách tìm vị trí của nó trong bộ nhớ và lặp qua mảng ký tự của chúng tôi, mỗi lần một ký tự cho đến khi kết thúc ký tự tệp. Nói cách khác, một chương trình.
Một luồng được 'tạo' khi chương trình của nó được gọi và nó có một vị trí bộ nhớ để gắn vào hoặc kết nối . Giống như ví dụ về vòi nước của chúng tôi, vòi không hiệu quả nếu nó không được kết nối với một ống nối. Trong trường hợp của luồng, nó phải được kết nối với một tệp để nó tồn tại.
Các luồng có thể được tinh chỉnh thêm, ví dụ: luồng để nhận đầu vào hoặc luồng để gửi nội dung tệp đến đầu ra tiêu chuẩn. UNIX / linux kết nối và giữ mở 3 filestream cho chúng tôi ngay lập tức bat, stdin (đầu vào tiêu chuẩn), thiết bị xuất chuẩn (đầu ra tiêu chuẩn) và stderr (lỗi tiêu chuẩn). Các luồng có thể được xây dựng dưới dạng cấu trúc dữ liệu hoặc các đối tượng cho phép chúng tôi thực hiện các hoạt động phức tạp hơn của luồng dữ liệu qua chúng, như mở luồng, đóng luồng hoặc kiểm tra lỗi mà tệp được kết nối. C ++ cin
là một ví dụ về đối tượng truyền phát.
Chắc chắn, nếu bạn chọn như vậy, bạn có thể viết luồng của riêng bạn.
Luồng là một đoạn mã có thể tái sử dụng, trừu tượng hóa sự phức tạp của việc xử lý dữ liệu trong khi cung cấp các hoạt động hữu ích để thực hiện trên dữ liệu.
Một sự tương tự khác: Bạn không thể bơi ngược lại một luồng, đó là lý do tại sao bạn chỉ có thể lấy bit, byte, chuỗi hoặc đối tượng tiếp theo từ luồng, trong khi dữ liệu đã đọc bị xóa. Một vé một chiều ... hoặc về cơ bản chỉ là một hàng đợi mà không lưu trữ kiên trì.
Vì vậy, chúng ta cần hàng đợi? Bạn quyết định.
Từ "stream" đã được chọn vì nó đại diện cho (trong cuộc sống thực) một ý nghĩa rất giống với những gì chúng ta muốn truyền tải khi chúng ta sử dụng nó.
Bắt đầu suy nghĩ về sự tương tự với một dòng nước. Bạn nhận được một luồng dữ liệu liên tục, giống như nước liên tục chảy trong một dòng sông. Bạn không nhất thiết phải biết dữ liệu đến từ đâu và thường thì bạn không cần phải đến; có thể là từ một tập tin, một ổ cắm hoặc bất kỳ nguồn nào khác, nó không (không nên) thực sự quan trọng. Điều này rất giống với việc nhận một dòng nước, theo đó bạn không cần biết nó đến từ đâu; có thể là từ hồ, đài phun nước hoặc bất kỳ nguồn nào khác, điều đó không (không nên) thực sự quan trọng. nguồn