c2 Streams - Filter Streams

本文深入探讨Java中的IO流,包括FilterStream的链式使用、BufferedStream的工作原理、PrintStream在网络编程中的注意事项以及DataStreams的基本操作。通过实例解析,帮助读者理解如何高效地进行数据读写。

Filter Streams被组织在“链”(chain)中,每一个Filter流从上一个流中获取数据,然后传给下一个Filter 流。


Chain Filter Stream Together

一般的使用Filter Stream的方式:

FileInputStream     fin = new FileInputStream("data.txt");
BufferedInputStream bin = new BufferedInputStream(fin);

这种方式可能会导致的问题是从fin、bin都调用了read()方法,这会导致问题,解决办法是可以这么写:

InputStream in = new FileInputStream("data.txt");
in = new BufferedInputStream(in);

利用了多态,面向接口,所以使用的只会是Filter Stream,而不会是FileInputStream。但如果想使用在superclass中没有的方法呢,可以这么写:

DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(
    new FileOutputStream("data.txt")));


BufferedStream


BufferedOutputStream内部有一个缓冲区buff,里面临时存放着要写入的数据,当buff已满或者flush时,一次性写入。这要比多次写入每次一点数据要快。尤其在网络连接中,TCP segment 和 UDP package 都有一个头,大概40字节,假入要写入1k的数据,如果每次1个字节,那光头就要发送40k字节,而如果一次性发送,只需要1k多一点。虽然实际中没有这么夸张,因为网卡和TCP实现者都有不同层次的缓存,但网络写入时用缓冲流会提高很大效率。

BufferedIutputStream内部又有一个缓冲区buf,当调用read()方法时,先从缓冲区中取数据,如果缓冲区中没数据了,就从underlying source中读数据,这时,从BufferedIutputStream中读出的数据和从underlying stream中的读出的数据一样多,不管客户端是否要立即读取完所有数据,暂时不用的保存在buf中。目前在网络连接中BufferedIutputStream的作用不是那么明显,因为网络中的瓶颈在于网络传输速度,而不在网卡向程序传输数据的速度。


public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int bufferSize)
public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int bufferSize)

默认的input stream的buf size是2048字节,output stream 是512字节。理想的buf的大小要看要缓冲的是什么流,对应网络连接,buf一般要比package稍大些,但是,package大小比较难预测,会因网络状况和所使用协议不同而又不同,一般情况下,一个TCP segment 的大小不超过1 kilobyte 。


BufferedIutputStream只重写了InputStream的方法,没有新方法加入,大部分InputStream在返回前只读一次underlying stream 或者 source,但BufferedIutputStream与他们不同,在他的可读mutilbyte的方法中,它会一直读直到他的buf完全读满,或者stream读完了,或者underlying stream被block了。

BufferedIutputStream也没有新的方法,每次write数据都是写在了buf中,记住send时要flush。


PrintStream

最常用的的PrintStream是System.out,out就是个PrintStream。

print()将arg转换成string,以默认编码写入到underlying stream中,println()则是会在行末尾添加一个与平台相关的line-separator,在Unix(Mac Os X)是linefeed  \n, 在Mac Os 9上是 return carrige \r,在Window上是 \r\n .


网络程序员要像避免瘟疫一样避免使用PrintStream!

原因一:println()与平台有关,不同平台有不同的行分隔符。在本地输出到控制台没什么问题,但在网络程序中,想HTTP协议要求换行符是 “\r\n” ,如果在Windows上向client、server发送信息没问题,但在Mac,Unix上就麻烦了,因为那些系统的换行符不是“\r\n”.尽管HTTP协议也接受非法的line teminator,但偶尔还是有异常出现的。.

原因二:PrintStream使用当前平台默认的编码,但这并非是client或者server期望的。PrintStream没有提供任何改变字符集的方法。尽管可以用PrintWriter代替使用,但这个问题仍然存在。

原因三:Printstream吃掉了所有的异常。这适合写些HelloWorld的程序教初学者,让刚开始学习程序的初学者不用为异常处理麻烦。但网络程序比本地控制台要不稳定得多,必须准备好处理意想不到的异常,而这些处理需要异常抛出。

Printstream在内部有异常是设置了一个内部标志,通过checkError()来查看。但简而言之,Printstream提供的错误信息对写网络程序来时完全不够。


Data Streams

DataInputStream、DataOutputStream提供了写和读java基础类型和以二进制表示的String的方法。使用二进制的首要目的是为了在两个使用网络、datafile、管道和其他中间媒介连接起来的java程序间传递数据,output stream写什么,input stream就读什么。

public final void writeChars(String s) throws IOException
public final void writeBytes(String s) throws IOException
public final void writeUTF(String s) throws IOException

writeChars()遍历String,将每个character按顺序,2个字节写入。

writeBytes()遍历String,只写每个character最低位有效字节。对于某些字符集比如Latin-1会丢失某些string的信息。大部分时候要避免使用该方法。

上面2个方法都不包含string的长度,writeUTF()包含string的长度,该方法只能用于和另外一个java程序交换text,另外的那个java程序使用 DataInputstream 读取。要和其他任意程序交换UTF-8的text,应该用适当的编码的InputStreamReader。


DataInputStream提供了著名的readLine()方法,但任何情况下都别使用这个方法。已经被废弃且充满了bug。废弃的原因是在大部分情况下不把non-ASCII转换成bytes,该任务现在由BufferedReader的readLine()代替。但这两个方法都有一个隐蔽的bug:都不把carriage return 当作一行的结尾,只认为一个linefeed(/n)或者 carriage return and linefeed ( /r/n )是行的结尾。当在流中读到一个carriage return时,readLine()继续读看下个是否是linefeed,如果是,/r/n 被thrown away,以string返回该行。如果不是,则throw away /r,返回该行,读到的那个字符作为下一行。可是,如果/r是该流的最后一个字符,那就完了,一直在那等吧!

该问题在读文件时还不明显,因为stream的末尾会有-1在等着。但在网络中,一个持续的connection,当client、server发送外最后一个character后没有关闭连接,等着对方回应,如果幸运,最终会因连接超时而得到一个IOException,尽管会多花一些时间和丢掉流中的最后一行数据,否则就无限期的等待下去吧!




import os import subprocess import json def get_video_info(video_path): """获取视频的时长(秒)和分辨率(宽,高)""" cmd = [ "ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=duration,width,height", "-of", "json", video_path ] result = subprocess.run(cmd, capture_output=True, text=True) data = json.loads(result.stdout) duration = float(data["streams"][0]["duration"]) width = int(data["streams"][0]["width"]) height = int(data["streams"][0]["height"]) return duration, (width, height) def extract_first_frame(input_video, output_image): """提取视频的第一帧为图片""" cmd = [ "ffmpeg", "-i", input_video, "-vf", "select=eq(n\\,0)", "-vframes", "1", output_image, "-y", "-loglevel", "error" ] subprocess.run(cmd) def create_zoom_effect(input_image, output_video, duration, resolution): """生成缩放动画视频(基于提取的第一帧图片)""" width, height = resolution cmd = [ "ffmpeg", "-y", "-loop", "1", "-i", input_image, "-vf", f"scale={width}:{height}:force_original_aspect_ratio=increase," f"crop={width}:{height}," "zoompan=z='min(zoom+0.001,1.5)':d=750:" f"x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s={width}x{height}," "fps=60,tpad=stop=1:stop_mode=clone", "-t", str(duration), "-r", "60", "-c:v", "libx264", "-pix_fmt", "yuv420p", "-preset", "fast", "-crf", "18", output_video, "-loglevel", "error" ] subprocess.run(cmd) def merge_videos(original_video, loop_video, output_path): """合成原始视频和缩放动画""" cmd = [ "ffmpeg", "-y", "-i", original_video, "-i", loop_video, "-filter_complex", "[1:v]scale=720:1280,setsar=1,fps=60[orig];" "[0:v]scale=720:1280,setsar=1,fps=60[loop];" "[orig][loop]overlay=enable='gte(n,4)*eq(mod(n-4,2),0)'[v]", "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-preset", "fast", "-crf", "20", "-pix_fmt", "yuv420p", "-r", "60", "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart", output_path, "-loglevel", "error" ] subprocess.run(cmd) def main(): # 输入文件 original_video = "1.MP4" # 原始视频(用于合成) frame_source_video = "2.MP4" # 提取第一帧的图片来源 # 临时文件 first_frame_img = "temp_first_frame.jpg" zoom_effect_video = "temp_zoom_effect.mp4" # 输出文件 output_video = "1_processed_8164.mp4" try: # 1. 获取原始视频的时长和分辨率(用于缩放动画的时长) duration, (width, height) = get_video_info(original_video) print(f"视频信息: 时长={duration:.2f}秒, 分辨率={width}x{height}") # 2. 从 2.MP4 提取第一帧(修正点!) extract_first_frame(frame_source_video, first_frame_img) print(f"已从 {frame_source_video} 提取第一帧: {first_frame_img}") # 3. 生成缩放动画(基于提取的图片) create_zoom_effect(first_frame_img, zoom_effect_video, duration, (width, height)) print("已生成缩放动画: temp_zoom_effect.mp4") # 4. 合成最终视频(原始视频 + 缩放动画) merge_videos(original_video, zoom_effect_video, output_video) print(f"合成完成: {output_video}") finally: # 清理临时文件 for file in [first_frame_img, zoom_effect_video]: if os.path.exists(file): os.remove(file) print(f"已删除临时文件: {file}") if __name__ == "__main__": main() 这样生成的视频会被平台检测到如何检测不到呢
09-20
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值